LoginSignup
0
0

チーム開発参加の記録【2023-10~2024-03】(2) Go言語からTursoを使ってみた

Last updated at Posted at 2023-12-14

本シリーズのリンク

本記事で行うこと

前回の記事ではSQLiteでORM「Bun」を使ってみました。
本記事では、前回作ったGoプログラムをTursoで動かしてみます。

Tursoチュートリアルに沿ってDBを用意する

チュートリアルを見ながら、CLIをインストールするところから始めて、テーブル作成&テストデータINSERTまで行います。

CLIインストール

私はLinux環境ですので、以下のコマンドを実行しました。

curl -sSfL https://get.tur.so/install.sh | bash

バージョン確認

$ turso --version
turso version v0.87.6

サインアップ

以下を実行後、ブラウザでサインアップします。

turso auth signup

論理データベース作成

以下を実行してDBを作成しました。
DB名は何でも良いですが、本記事ではチュートリアルのまま「my-db」にしました。

turso db create my-db

DB作成後、以下を実行してDBの情報を取得します。

turso db show my-db

表示された情報:

image.png

この情報のうち、URLはGo言語のプログラムに埋め込んで使います。

DBのコマンドラインツールでテーブルを作成する

以下を実行してDBのコマンドラインツールを立ち上げます。

turso db shell my-db

前回の記事と同じテーブルを作成します。

CREATE TABLE foo (
    col_text           TEXT    NOT NULL,
    col_text_null      TEXT,
    col_int            INTEGER NOT NULL,
    col_int_null       INTEGER,
    col_real           REAL    NOT NULL,
    col_real_null      REAL,
    col_timestamp      REAL    NOT NULL,
    col_timestamp_null REAL,
    col_json           TEXT    NOT NULL,
    col_json_null      TEXT,
    col_array          TEXT    NOT NULL,
    col_array_null     TEXT
) STRICT;

スクリーンショット:

image.png

引き続き、前回の記事と同じデータをINSERTします。

INSERT INTO foo
(
    col_text,
    col_text_null,
    col_int,
    col_int_null,
    col_real,
    col_real_null,
    col_timestamp,
    col_timestamp_null,
    col_json,
    col_json_null,
    col_array,
    col_array_null
)
VALUES
(
    'text_data',
    NULL,
    1234567890123456789,
    NULL,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    NULL,
    JULIANDAY('now'),
    NULL,
    '{"a":100,"b":"bbb"}',
    NULL,
    '[10,null]',
    NULL
),
(
    'text_data',
    'text_data',
    1234567890123456789,
    1234567890123456789,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    JULIANDAY('now'),
    JULIANDAY('now'),
    '{"a":200,"b":null}',
    '{"a":300,"z":"zzz"}',
    '[30,null,50]',
    '[]'
);

INSERTしたら、fooテーブルにSELECT文を発行してみます。

SELECT
    col_text,
    col_text_null,
    col_int,
    col_int_null,
    col_real,
    col_real_null,
    STRFTIME('%Y-%m-%dT%H:%M:%fZ', col_timestamp) AS col_timestamp,
    STRFTIME('%Y-%m-%dT%H:%M:%fZ', col_timestamp_null) AS col_timestamp_null,
    col_json,
    col_json_null,
    col_array,
    col_array_null
FROM
    foo;

スクリーンショット:

image.png

結果が見づらいですが、データがINSERTされたことが確認できました。

DBのコマンドラインツールから抜けるには、Ctrl+dを押すか、以下のようにコマンドを入力します。

image.png

Go言語からTursoのDBを操作する

それではGo言語に行きます。
前回の記事とほぼ同じプログラムですが、Turso向けに変更を加えてあります。

app.go

app.go
package main

import (
	"context"
	"database/sql"
	"net/http"
	"os"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	_ "github.com/tursodatabase/libsql-client-go/libsql"
	"github.com/uptrace/bun"
	"github.com/uptrace/bun/dialect/sqlitedialect"
	"github.com/uptrace/bun/extra/bundebug"
)

type (
	httpError struct {
		Error string `json:"error"`
	}

	httpMessage struct {
		Message string `json:"message"`
	}
)

var (
	db *bun.DB
)

func selectFoo(c echo.Context) error {
	type (
		jsonType struct {
			A *int    `json:"a"`
			B *string `json:"b"`
		}

		resultSetType struct {
			ColText          string       `json:"colText"`
			ColTextNull      *string      `json:"colTextNull"`
			ColInt           int64        `json:"colInt"`
			ColIntNull       *int64       `json:"colIntNull"`
			ColReal          float64      `json:"colReal"`
			ColRealNull      *float64     `json:"colRealNull"`
			ColTimestamp     time.Time    `json:"colTimestamp"`
			ColTimestampNull bun.NullTime `json:"colTimestampNull"`
			ColJson          jsonType     `json:"colJson"`
			ColJsonNull      *jsonType    `json:"colJsonNull"`
			ColArray         []*int64     `json:"colArray"`
			ColArrayNull     *[]*int64    `json:"colArrayNull"`
		}
	)

	ctx := context.TODO()

	resultSetFoo := make([]resultSetType, 0)
	if err := db.NewSelect().
		Column("col_text").
		Column("col_text_null").
		Column("col_int").
		Column("col_int_null").
		Column("col_real").
		Column("col_real_null").
		ColumnExpr("STRFTIME('%Y-%m-%dT%H:%M:%fZ', col_timestamp) AS col_timestamp").
		ColumnExpr("STRFTIME('%Y-%m-%dT%H:%M:%fZ', col_timestamp_null) AS col_timestamp_null").
		Column("col_json").
		Column("col_json_null").
		Column("col_array").
		Column("col_array_null").
		Table("foo").
		Scan(ctx, &resultSetFoo); err != nil {

		return c.JSON(http.StatusInternalServerError, httpError{Error: err.Error()})
	}

	return c.JSON(http.StatusOK, resultSetFoo)
}

func updateFoo(c echo.Context) error {
	ctx := context.TODO()

	if _, err := db.NewUpdate().
		Table("foo").
		SetColumn("col_text_null", "?", "ABC").
		Where("col_text_null IS NULL").
		Exec(ctx); err != nil {

		return c.JSON(http.StatusInternalServerError, httpError{Error: err.Error()})
	}

	return c.JSON(http.StatusOK, httpMessage{Message: "ok"})
}

func insertFoo(c echo.Context) error {
	type dummyModel struct{}

	ctx := context.TODO()

	if _, err := db.NewInsert().
		Model((*dummyModel)(nil)).
		ModelTableExpr("foo").
		Value("col_text", "?", "XYZ").
		Value("col_text_null", "?", nil).
		Value("col_int", "?", 123).
		Value("col_int_null", "?", nil).
		Value("col_real", "?", 3.14).
		Value("col_real_null", "?", nil).
		Value("col_timestamp", "JULIANDAY('now')").
		Value("col_timestamp_null", "?", nil).
		Value("col_json", "?", "{}").
		Value("col_json_null", "?", nil).
		Value("col_array", "?", "[]").
		Value("col_array_null", "?", nil).
		Exec(ctx); err != nil {

		return c.JSON(http.StatusInternalServerError, httpError{Error: err.Error()})
	}

	return c.JSON(http.StatusOK, httpMessage{Message: "ok"})
}

func deleteFoo(c echo.Context) error {
	ctx := context.TODO()

	if _, err := db.NewDelete().
		Table("foo").
		Where("col_text = ?", "XYZ").
		Exec(ctx); err != nil {

		return c.JSON(http.StatusInternalServerError, httpError{Error: err.Error()})
	}

	return c.JSON(http.StatusOK, httpMessage{Message: "ok"})
}

func main() {
	tursoAuthToken := os.Getenv("TURSO_AUTH_TOKEN")
	dbUrl := "libsql://my-db-kanedaq.turso.io?authToken=" + tursoAuthToken
	sqlite, err := sql.Open("libsql", dbUrl)
	if err != nil {
		panic(err)
	}
	sqlite.SetMaxOpenConns(1)

	db = bun.NewDB(sqlite, sqlitedialect.New())
	db.AddQueryHook(bundebug.NewQueryHook(
		bundebug.WithVerbose(true),
		bundebug.FromEnv("BUNDEBUG"),
	))

	// EchoでAPIサーバー
	e := echo.New()
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Routing
	api := e.Group("/api")
	api.GET("/selectFoo", selectFoo)
	api.PUT("/updateFoo", updateFoo)
	api.POST("/insertFoo", insertFoo)
	api.DELETE("/deleteFoo", deleteFoo)

	e.Logger.Fatal(e.Start(":1323"))
}

前回の記事のapp.goとの差分は以下の通りです。
Tursoは、SQLiteをフォークしたlibSQLを使っていますので、そのGo SDKを使うように変更しました。

@@ -4,13 +4,14 @@
        "context"
        "database/sql"
        "net/http"
+       "os"
        "time"

        "github.com/labstack/echo/v4"
        "github.com/labstack/echo/v4/middleware"
+       _ "github.com/tursodatabase/libsql-client-go/libsql"
        "github.com/uptrace/bun"
        "github.com/uptrace/bun/dialect/sqlitedialect"
-       "github.com/uptrace/bun/driver/sqliteshim"
        "github.com/uptrace/bun/extra/bundebug"
 )

@@ -134,7 +135,9 @@
 }

 func main() {
-       sqlite, err := sql.Open(sqliteshim.ShimName, "/home/user/DataGripProjects/sqlite/exercise.sqlite3")
+       tursoAuthToken := os.Getenv("TURSO_AUTH_TOKEN")
+       dbUrl := "libsql://my-db-kanedaq.turso.io?authToken=" + tursoAuthToken
+       sqlite, err := sql.Open("libsql", dbUrl)
        if err != nil {
                panic(err)
        }

go.mod

go.mod
module exercise_turso

go 1.21

require (
	github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
	github.com/fatih/color v1.15.0 // indirect
	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
	github.com/jinzhu/inflection v1.0.0 // indirect
	github.com/klauspost/compress v1.15.15 // indirect
	github.com/labstack/echo/v4 v4.11.3 // indirect
	github.com/labstack/gommon v0.4.0 // indirect
	github.com/tursodatabase/libsql-client-go v0.0.0-20231216154754-8383a53d618f // indirect
	github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.19 // indirect
	github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
	github.com/uptrace/bun v1.1.16 // indirect
	github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 // indirect
	github.com/uptrace/bun/extra/bundebug v1.1.16 // indirect
	github.com/valyala/bytebufferpool v1.0.0 // indirect
	github.com/valyala/fasttemplate v1.2.2 // indirect
	github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
	golang.org/x/crypto v0.14.0 // indirect
	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
	golang.org/x/net v0.17.0 // indirect
	golang.org/x/sys v0.13.0 // indirect
	golang.org/x/text v0.13.0 // indirect
	golang.org/x/time v0.3.0 // indirect
	nhooyr.io/websocket v1.8.7 // indirect
)

実行準備

上記プログラムからTursoのDBに接続するために、認証トークンが必要です。
以下を実行して認証トークンを作成します。

turso db tokens create my-db

app.go用に、このトークンを環境変数TURSO_AUTH_TOKENに設定すれば、実行準備完了です。

実行

4つのAPIを動かしたところ、前回の記事と同じ結果になりました。
以下、selectFoo APIの結果のみ載せておきます。

image.png

以上、Tursoで前回の記事と同じ処理ができることが確認できました。

libSQL serverをローカルで動かす

Tursoに接続しなくても、libSQL serverをローカルで動かすこともできます。
開発中はこちらを使うと良いでしょう。

libSQL serverのインストールと起動

このページから「sqld-installer.sh」をダウンロードして起動し、指示に従ってバイナリをインストール後、sqldコマンドでlibSQL serverを起動しました。

image.png

ホームディレクトリでsqldを起動したら、~/data.sqld/dbs/default というディレクトリが作成され、そこに以下のファイルが作られていました。

image.png

このうち、dataというファイルがデータベースファイルでした。
以下のように、sqlite3コマンドでこのdataファイルに接続して、DBを操作できました。

sqlite3 ~/data.sqld/dbs/default/data

image.png

Go言語からローカルのlibSQL DBを操作する

それではGo言語に行きます。
Turso向けとの違いは、

  • URLがローカルホスト
  • 認証トークンが不要
app.go
package main

import (
	"context"
	"database/sql"
	"net/http"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	_ "github.com/tursodatabase/libsql-client-go/libsql"
	"github.com/uptrace/bun"
	"github.com/uptrace/bun/dialect/sqlitedialect"
	"github.com/uptrace/bun/extra/bundebug"
)

type (
	httpError struct {
		Error string `json:"error"`
	}

	httpMessage struct {
		Message string `json:"message"`
	}
)

var (
	db *bun.DB
)

func selectFoo(c echo.Context) error {
	type (
		jsonType struct {
			A *int    `json:"a"`
			B *string `json:"b"`
		}

		resultSetType struct {
			ColText          string       `json:"colText"`
			ColTextNull      *string      `json:"colTextNull"`
			ColInt           int64        `json:"colInt"`
			ColIntNull       *int64       `json:"colIntNull"`
			ColReal          float64      `json:"colReal"`
			ColRealNull      *float64     `json:"colRealNull"`
			ColTimestamp     time.Time    `json:"colTimestamp"`
			ColTimestampNull bun.NullTime `json:"colTimestampNull"`
			ColJson          jsonType     `json:"colJson"`
			ColJsonNull      *jsonType    `json:"colJsonNull"`
			ColArray         []*int64     `json:"colArray"`
			ColArrayNull     *[]*int64    `json:"colArrayNull"`
		}
	)

	ctx := context.TODO()

	resultSetFoo := make([]resultSetType, 0)
	if err := db.NewSelect().
		Column("col_text").
		Column("col_text_null").
		Column("col_int").
		Column("col_int_null").
		Column("col_real").
		Column("col_real_null").
		ColumnExpr("STRFTIME('%Y-%m-%dT%H:%M:%fZ', col_timestamp) AS col_timestamp").
		ColumnExpr("STRFTIME('%Y-%m-%dT%H:%M:%fZ', col_timestamp_null) AS col_timestamp_null").
		Column("col_json").
		Column("col_json_null").
		Column("col_array").
		Column("col_array_null").
		Table("foo").
		Scan(ctx, &resultSetFoo); err != nil {

		return c.JSON(http.StatusInternalServerError, httpError{Error: err.Error()})
	}

	return c.JSON(http.StatusOK, resultSetFoo)
}

func updateFoo(c echo.Context) error {
	ctx := context.TODO()

	if _, err := db.NewUpdate().
		Table("foo").
		SetColumn("col_text_null", "?", "ABC").
		Where("col_text_null IS NULL").
		Exec(ctx); err != nil {

		return c.JSON(http.StatusInternalServerError, httpError{Error: err.Error()})
	}

	return c.JSON(http.StatusOK, httpMessage{Message: "ok"})
}

func insertFoo(c echo.Context) error {
	type dummyModel struct{}

	ctx := context.TODO()

	if _, err := db.NewInsert().
		Model((*dummyModel)(nil)).
		ModelTableExpr("foo").
		Value("col_text", "?", "XYZ").
		Value("col_text_null", "?", nil).
		Value("col_int", "?", 123).
		Value("col_int_null", "?", nil).
		Value("col_real", "?", 3.14).
		Value("col_real_null", "?", nil).
		Value("col_timestamp", "JULIANDAY('now')").
		Value("col_timestamp_null", "?", nil).
		Value("col_json", "?", "{}").
		Value("col_json_null", "?", nil).
		Value("col_array", "?", "[]").
		Value("col_array_null", "?", nil).
		Exec(ctx); err != nil {

		return c.JSON(http.StatusInternalServerError, httpError{Error: err.Error()})
	}

	return c.JSON(http.StatusOK, httpMessage{Message: "ok"})
}

func deleteFoo(c echo.Context) error {
	ctx := context.TODO()

	if _, err := db.NewDelete().
		Table("foo").
		Where("col_text = ?", "XYZ").
		Exec(ctx); err != nil {

		return c.JSON(http.StatusInternalServerError, httpError{Error: err.Error()})
	}

	return c.JSON(http.StatusOK, httpMessage{Message: "ok"})
}

func main() {
	dbUrl := "http://127.0.0.1:8080"
	sqlite, err := sql.Open("libsql", dbUrl)
	if err != nil {
		panic(err)
	}
	sqlite.SetMaxOpenConns(1)

	db = bun.NewDB(sqlite, sqlitedialect.New())
	db.AddQueryHook(bundebug.NewQueryHook(
		bundebug.WithVerbose(true),
		bundebug.FromEnv("BUNDEBUG"),
	))

	// EchoでAPIサーバー
	e := echo.New()
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Routing
	api := e.Group("/api")
	api.GET("/selectFoo", selectFoo)
	api.PUT("/updateFoo", updateFoo)
	api.POST("/insertFoo", insertFoo)
	api.DELETE("/deleteFoo", deleteFoo)

	e.Logger.Fatal(e.Start(":1323"))
}

Turso版app.goとの差分は以下の通りです。

@@ -4,7 +4,6 @@
        "context"
        "database/sql"
        "net/http"
-       "os"
        "time"

        "github.com/labstack/echo/v4"
@@ -135,8 +134,7 @@
 }

 func main() {
-       tursoAuthToken := os.Getenv("TURSO_AUTH_TOKEN")
-       dbUrl := "libsql://my-db-kanedaq.turso.io?authToken=" + tursoAuthToken
+       dbUrl := "http://127.0.0.1:8080"
        sqlite, err := sql.Open("libsql", dbUrl)
        if err != nil {
                panic(err)

動作確認したところ、Turso版と同じ結果が得られました。

まとめ

初めてTursoを使いましたが、SQLite向けのプログラムを少し変更するだけで動いてくれました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0