はじめに
最近Goの学習を始めました!!
超絶初心者ですので誤ってる点などがあったら教えていただけると幸いです!
Cloudflare Workers + D1でアプリケーションを開発を試みており、
syumai/workersを使用することになりました。
その際に、GORM + Cloudflare D1を組み合わせて開発しようとしてハマったポイントがあったので書き記します。
同じところで詰まった人の助けになれば幸いです!
結論(TL;DR)
GORM→D1へ接続するときは、以下のどちらかの対応を取ればいいです
-
gorm-driver-d1(リポジトリ)を用いてREST APIを経由して接続 -
sqliteのDialectorを自作してworkersの内部からSQLiteっぽく直接接続
(厳密にはSQLiteとして実行するわけじゃないのでっぽくと表現しています)
前提: Cloudflare D1の仕組み
Cloudflare D1はCloudflareが提供するサーバーレスとSQLiteのデータベースサービスです。
呼び出し方法は2種類あります。
| 呼び出し元 | 方法 |
|---|---|
| Workers内部から | SQLiteとして直接実行 または REST API経由 |
| 外部から | REST API経由のみ(参照) |
外部からREST API経由で使う場合、通信のレイテンシや同時リクエスト数の制限が発生します。
せっかくWorkers上で動かしているのに、わざわざREST APIを挟むのはもったいないと感じたため、Workers内部から呼ぶ方法を使用したいと考えました。
妥協択: gorm-driver-d1 を使ってREST API経由で接続する
既存のGORM向けD1ドライバーである kofj/gorm-driver-d1 を使うと、REST API経由でD1にアクセスできます。
外部から接続する場合や、Workers内部にこだわらない場合はこちらが手軽です。
しかし、先述した通り、わざわざREST APIを挟みたくないので私は使用しませんでした。
本命択: syumai/workers の cloudflare/d1 を使ってSQLiteとして直接接続する
syumai/workers が alpha版として提供している cloudflare/d1 パッケージを使うと、WorkersランタイムからD1をSQLiteとして直接呼び出せます。
以下のように記述するだけで通常のSQLとして操作できるようになります。
import (
"database/sql"
workerd1 "github.com/syumai/workers/cloudflare/d1"
)
func openDB() (*sql.DB, error) {
connector, err := workerd1.OpenConnector("DB")
if err != nil {
return nil, err
}
sqlDB := sql.OpenDB(connector)
}
直面した問題
cloudflare/d1 で取得した *sql.DB をGORMに渡して初期化しようとしたところ、エラーが発生しました。
発生したエラー(追記: 20260221)
[wrangler:info] GET /favicon.ico 500 Internal Server Error (11ms)
2026/02/15 00:34:30 failed to initialize db: failed on promise: Error: D1_ERROR: not authorized to use function: sqlite_version at offset 7:
SQLITE_ERROR
▲ [WARNING] exit code: 1
原因を調査したところ以下の2つの問題が複合的に発生していました
1. D1では select sqlite_version() が実行できない
D1はSQLiteをベースにしていますが、sqlite_version() の実行が制限されています。(参考)
2. GORMのSQLite dialectorは初期化時に select sqlite_version() を実行する
GORMのSQLite dialectorは Initialize の中で sqlite_version() を呼び出す実装になっています。(該当のdialectorのコード)
この2つが組み合わさった結果、D1が sqlite_version() を拒否 → GORMの初期化が失敗するという流れになっていました。
問題への対応: dialectorのメソッドの一部を自作する
GORMのSQLite DialectorのInitializeメソッドがselect sqlite_version()するため、
その部分を取り除いたInitializeメソッドを自作することにしました!
(ソースと文法が違うのはWASM用だからです)
package d1dialector
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/callbacks"
)
type Dialector struct {
sqlite.Dialector
UseReturning bool // sqlite 3.35.0 以上ならこれをtrueにする
}
// Initialize を上書き。sqlite_version() を呼ばないようにする
func (d Dialector) Initialize(db *gorm.DB) error {
if d.Conn == nil {
return fmt.Errorf("d1 dialector: Conn is required (use sqlite.New(sqlite.Config{Conn: ...}))")
}
db.ConnPool = d.Conn
cfg := &callbacks.Config{
LastInsertIDReversed: true,
}
if d.UseReturning {
cfg.CreateClauses = []string{"INSERT", "VALUES", "ON CONFLICT", "RETURNING"}
cfg.UpdateClauses = []string{"UPDATE", "SET", "FROM", "WHERE", "RETURNING"}
cfg.DeleteClauses = []string{"DELETE", "FROM", "WHERE", "RETURNING"}
}
callbacks.RegisterDefaultCallbacks(db, cfg)
for k, v := range d.ClauseBuilders() {
if _, ok := db.ClauseBuilders[k]; !ok {
db.ClauseBuilders[k] = v
}
}
return nil
}
標準の sqlite.Dialector を埋め込みつつ、Initialize だけをオーバーライドしています。
sqlite_version() の実行を除いた以外は、元の実装と同等の処理をしています。
これにより、GORMをD1のSQLiteとして正常に初期化できるようになりました。![]()
まとめ
cloudflareのworkers環境からd1をsqliteを使おうとする際に、gormと相性が悪く純正の方法だと難しく、dialectorを自作する必要がありました。
REST API経由で素直にgormのドライバーを使うか、ちょいムズなdialectorの道をいくかは作成するアプリケーションによって変わってくると思います!
dialectorの道を選んだ方への助けとなれば幸いです!