TypeScript + Bun な環境にて、SQLiteを操作したいことがありました。
Bunにはネイティブの SQLite driver があることから、そのまま bun:sqlite
を使うこともできそうでした。
SQLite – API | Bun Docs
ただ、日頃ORMでDBまわりを書いていることから、ORM的な何かを使いたくなりました。
BunのExamplesを見ていたところ、 Drizzle ORM
が紹介されていました。
また、Drizzleでは
Drizzle Kit — is a CLI companion for automatic SQL migrations generation and rapid prototyping.
と、 Drizzle Kit
を使ってマイグレーションなどを行うことができそうでした。
他にも、Drizzleを使っている日本語の記事を読んだところ、自分が欲しいものとしてちょうど良さそうと感じました。
そこで、まずは Drizzle Kit の各コマンドをためしてみたときのメモを残します。
目次
- 環境
- Bun 上で Drizzle Kit の各コマンドを実行するための準備
- drizzle-kit generate でマイグレーションファイルを作成
- migtate.ts を作成し、SQLiteへマイグレーションファイルを適用
- 0.30.9 時点ではマイグレーションのロールバック機能がない模様
- drizzle-kit drop の挙動を確認
- drizzle-kit studio により、ブラウザでSQLiteの中身を確認する
- ソースコード
環境
- Windows11 WSL2
- TypeScript 5.4.5
tsc -v
より
- Bun 1.1.6
- DBは SQLite
bun:sqlite
モジュールを使う
- Drizzle ORM 0.30.9
- Drizzle Kit 0.20.17
- WebStorm 2024.1.1
なお、WebStorm 2024.1.1
では、まだ Bun
を公式サポートしていないようです。以下のissueによると、デバッグまわりが厳しそうな印象です。
ただ、今回の記事ではデバッグは不要なので、WebStormで実装を進めることにします。
Bun 上で Drizzle Kit の各コマンドを実行するための準備
Bunのセットアップ
まだ何も Bun の環境ができていないことから、最初に Bun をセットアップします。
Bunの公式ドキュメントに従い、Bunをインストールします。
Installing | Installation | Bun Docs
今回は nodenv を使ってリポジトリごとに Node.js を使い分けている環境だったので、 npm
でインストールします。
$ npm install -g bun
次に bun --version
したところ、bun コマンドがないと言われてしまいました。
そこで、Bunの公式ドキュメントのHow to add to your PATH
) に従い、追加で設定作業を行います。
まずは shell を確認します。
$ echo $SHELL /bin/bash
bashだったので、 ~/.bashrc
にの末尾に以下を追記します。
export BUN_INSTALL="$HOME/.bun" export PATH="$BUN_INSTALL/bin:$PATH"
新しくターミナルを開いたところ、 bun
コマンドを実行できました。
$ bun --version 1.1.6
Bunの設定が完了したため、 bun init
を実行し、projectの設定をしておきます。
bun init – Templating | Bun Docs
$ bun init bun init helps you get started with a minimal project and tries to guess sensible defaults. Press ^C anytime to quit package name (drizzle_with_bun): entry point (index.ts): Done! A package.json file was saved in the current directory. + index.ts + .gitignore + tsconfig.json (for editor auto-complete) + README.md To get started, run: bun run index.ts
Drizzleのセットアップ
Bunの環境ができたので、次は Drizzle ORM や Drizzle Kit のセットアップを行います。
必要なパッケージのインストール
Drizzle の公式ドキュメントに従い、Bun SQLite を使うために必要なパッケージをインストールします。
Bun SQLite | Drizzle ORM - SQLite
まずは Drizzle ORMをインストールします。
$ bun add drizzle-orm bun add v1.1.6 (e58d67b4) installed drizzle-orm@0.30.9 1 package installed [734.00ms]
続いて Drizzle Kitを -D
オプション付きでインストールします。
$ bun add -D drizzle-kit bun add v1.1.6 (e58d67b4) installed drizzle-kit@0.20.17 with binaries: - drizzle-kit 62 packages installed [3.02s]
drizzle.config.ts の作成
Drizzle ORMでは drizzle.config.ts
ファイルにて設定を変更できます。
Drizzle ORM - Configuration
今回のDrizzle ORM の設定は
- テーブルごとに、Drizzle ORMの shema ファイルは別にする
- schema ファイルは
src/schema
ディレクトリの下に置く - Drizzle ORM のマイグレーションファイルは
drizzle
ディレクトリの下に置く
とするため、以下の内容で drizzle.config.ts
を作成します。
import type { Config } from "drizzle-kit" export default { schema: "./src/schema/*", out: "./drizzle", } satisfies Config
以上で準備は終わりです。
drizzle-kit generate でマイグレーションファイルを作成
まずは drizzle-kit generate
でマイグレーションファイルを生成してみます。
Drizzle ORM - Migrations
ドキュメントによると、PythonのDjangoのように、スキーマを元にマイグレーションのSQLを自動で生成するようです。
実際にためしてみます。
スキーマファイルを作成
マイグレーションファイルの元となるスキーマファイルを作成します。
今回用意するスキーマは
- テーブル名
authors
- 列は以下の2つ
id
- integer型
- 主キーで、自動インクリメント
name
- string型
とし、 src/schema/authors.ts
へ定義します。
ちなみに、今回の定義では以下の点を考慮しています。
- Drizzle ORM における SQLite の列定義の詳細については、以下に記載あり
- DjangoやRailsと異なり、
id
列は明示的に定義する - 「SQLiteのinteger型で自動インクリメント & 採番した番号は再利用しない」とするために、
autoIncrement
を指定する
実際の定義はこちら。
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core' export const authors = sqliteTable("authors", { id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }), name: text("name"), })
package.json に Drizzle Kit の generate コマンドを追加
コマンドは何度も使うことになりそうなので、 package.json
へコマンドを追加しておきます。
"scripts": { "generate": "drizzle-kit generate:sqlite" },
マイグレーションファイルを自動生成
続いて、 Drizzle Kit によるマイグレーションファイルの生成を行います。
Bunでは bun run
により package.json の scripts
を実行できます。
Run a package.json
script | bun run – Runtime | Bun Docs
そこで、先ほど追加した generate
を実行してみます。
# 実行 $ bun run generate # 以下はログ $ drizzle-kit generate:sqlite drizzle-kit: v0.20.17 drizzle-orm: v0.30.9 No config path provided, using default 'drizzle.config.ts' Reading config file 'path/to/drizzle.config.ts' 1 tables authors 2 columns 0 indexes 0 fks [✓] Your SQL migration file ➜ drizzle/0000_yielding_the_spike.sql 🚀
生成されたファイル 0000_yielding_the_spike.sql
を確認すると、以下のSQLが定義されていました。
CREATE TABLE `authors` ( `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` text );
また、 meta
ディレクトリの中にもファイルが生成されていました。
drizzle_with_bun/drizzle$ tree . ├── 0000_yielding_the_spike.sql └── meta ├── 0000_snapshot.json └── _journal.json
もう1つマイグレーションファイルを追加
次に、Drizzle Kit がスキーマの差分をうまく検知できるか、確認してみます。
先ほど作成したスキーマ authors.ts
に age
列を追加します。
export const authors = sqliteTable("authors", { id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }), name: text("name"), age: integer("age"), // 追加 })
再度 bun run generate
したところ、新しくファイルが生成されました。
$ bun run generate $ drizzle-kit generate:sqlite drizzle-kit: v0.20.17 drizzle-orm: v0.30.9 No config path provided, using default 'drizzle.config.ts' Reading config file '/home/thinkami/dev/projects/typescript/drizzle/drizzle_with_bun/drizzle.config.ts' 1 tables authors 3 columns 0 indexes 0 fks [✓] Your SQL migration file ➜ drizzle/0001_flawless_hitman.sql 🚀
生成された 0001_flawless_hitman.sql
を確認すると、以下のSQLが生成されていました。差分が検知できているようです。
ALTER TABLE authors ADD `age` integer;
また、 metaディレクトリの中にも変化があり、
0001_snapshot.json
ファイルが追加_journal.json
ファイルのentries
に要素が追加
となっていました。
migtate.ts を作成し、SQLiteへマイグレーションファイルを適用
ここまででマイグレーションファイルを2つ作成しました。ただ、まだこれらのファイルはSQLiteへ適用していません。
そこで、これらマイグレーションファイルを適用していきます。
ただ、Drizzle ORMの公式ドキュメントには
Drizzle ORM is designed to be an opt-in solution at any point of your development flow. You can either run the generated migrations via Drizzle, or treat them as generic SQL migrations and run them with any other tool.
とありました。
Drizzle ORMやKitには、マイグレーションファイルを適用するコマンドが無いようです。
そこで、Bunのドキュメントに従い、 migrate.ts
ファイルを作成する方針で対応します。
Use Drizzle ORM with Bun | Bun Examples
migrate.ts を作成
Bunのドキュメントにある migrate.ts
をそのまま流用し、 migrate.ts
を作成します。
import { migrate } from "drizzle-orm/bun-sqlite/migrator" import { drizzle } from "drizzle-orm/bun-sqlite" import { Database } from "bun:sqlite" const sqlite = new Database("sqlite.db") const db = drizzle(sqlite) // [Ignore] TS80007: await has no effect on the type of this expression. await migrate(db, { migrationsFolder: "./drizzle" })
package.json にマイグレーション適用コマンドを追加
migrate
という名前で scripts にエントリを追加します。
"scripts": { "generate": "drizzle-kit generate:sqlite", "migrate": "bun run migrate.ts", },
マイグレーションを適用
bun run migrate
を実行すると、マイグレーションが適用され、 sqlite.db
が追加されました。
WebStorm のプラグイン Database tools and SQL for WebStorm
で sqlite.db
を開くと、 authors
テーブルができていました。
age
列も追加されていることから、2つのマイグレーションファイルが一気に適用されたようです。
また、SQLiteには __drizzle_migrations
というテーブルもできていました。
公式ドキュメントによると
By default, all information about executed migrations will be stored in the database inside the __drizzle_migrations table, and for PostgreSQL, inside the drizzle schema. However, you can configure where to store those records.
とのことです。
__drizzle_migrations
テーブルの中身はこんな感じでした。
0.30.9 時点ではマイグレーションのロールバック機能がない模様
migrateによるマイグレーション適用があるならば、その逆方向であるロールバックもあるのでは...と思い探してみたところ、Github の discussion がありました。
Migrations Rollback · drizzle-team/drizzle-orm · Discussion #1339
上記を読む限り、現時点ではロールバック機能がないようです。
drizzle-kit drop の挙動を確認
上記の通り、Drizzle にはマイグレーションのロールバック機能はありません。
一方、Drizzle Kitのドキュメントには Drop migration のコマンドがありました。
Drop migration | Drizzle ORM - List of commands
このコマンドを使うと何が起こるのか気になったことから、試してみます。
Drop migrationの実行
drizzle-kit drop
を実行してみます。
$ drizzle-kit drop drizzle-kit: v0.20.17 drizzle-orm: v0.30.9 No config path provided, using default 'drizzle.config.ts' Reading config file '/path/to/drizzle.config.ts' No config path provided, using default 'drizzle.config.ts' Reading config file '/path/to/drizzle.config.ts' Please select migration to drop: 0000_yielding_the_spike ❯ 0001_flawless_hitman # 選択後 [✓] 0001_flawless_hitman migration successfully dropped
実行後に git status を見たところ、以下が差分として出ていました。
マイグレーションファイルの削除の他に、metaディレクトリの中も変更しているようです。
deleted: drizzle/0001_flawless_hitman.sql deleted: drizzle/meta/0001_snapshot.json modified: drizzle/meta/_journal.json
一方、SQLiteには変更を加えていないようで、
__drizzle_migrations
テーブルのレコードに変化なしauthors
テーブルにもage
列が残ったまま
という状態でした。
以上より、drizzle-kit drop
は「不要なマイグレーションファイルを削除しつつ、自動生成した meta ディレクトリの中身を変更する」と理解しました。
Drop migration後に、差分を検知したり元に戻せるか確認
Drop migrationではマイグレーションファイルと実際のDBで差分が生まれてしまいました。
この差分を検知したり、元に戻せるかを確認してみます。
drizzle-kit up では差分を検知できない
ドキュメントには
We’re rapidly evolving Drizzle Kit APIs and from time to time there’s a need to upgrade underlying metadata structure.
drizzle-kit up:{dialect} is a utility command to keep all metadata up to date.
https://orm.drizzle.team/kit-docs/commands#maintain-stale-metadata
とあり、Drizzle Kit APIの変更によりメタデータが壊れないようにする目的のコマンドのようでした。そのため、今回の目的とは異なりそうです。
それでも、 drop 直後の状態(マイグレーションファイルとテーブルの状態が不一致)で、挙動を確認してみます。
$ drizzle-kit up:sqlite drizzle-kit: v0.20.17 drizzle-orm: v0.30.9 No config path provided, using default 'drizzle.config.ts' Reading config file '/path/to/drizzle.config.ts' Everything's fine 🐶🔥
Drizzle Kit のバージョンアップは行っていないこともあり、 Everything's fine でした。
マイグレーションファイルとテーブルの状態が不一致なことは関係ないようです。
drizzle-kit check でも差分を検知できない
drizzle-kit check
について、公式ドキュメントには
drizzle-kit check:{dialect} is a very powerful tool for you to check consistency of your migrations.
That’s extremely useful when you have multiple people on the project, altering database schema on different branches.
Drizzle Kit will check for all collisions and inconsistencies.
とありました。
そこで、ここまでの「マイグレーションファイルとテーブルの状態が不一致」という状態でためしてみます。
$ drizzle-kit check:sqlite drizzle-kit: v0.20.17 drizzle-orm: v0.30.9 No config path provided, using default 'drizzle.config.ts' Reading config file '/path/to/drizzle.config.ts' Everything's fine 🐶🔥
Everything's fine と表示されました。Checkコマンド的には問題ないようです。
元に戻す方法を探す
ここまでで差分は検知できないようでした。
そこで、差分は検知できなくても元に戻す方法を探してみます。
【NG】再度 Generate migrations する
「マイグレーションファイルを消してしまったのなら再度作り直せば良いのでは」ということで、再度マイグレーションファイルを生成してみます。
$ bun run generate $ drizzle-kit generate:sqlite drizzle-kit: v0.20.17 drizzle-orm: v0.30.9 No config path provided, using default 'drizzle.config.ts' Reading config file '/path/to/drizzle.config.ts' 1 tables authors 3 columns 0 indexes 0 fks [✓] Your SQL migration file ➜ drizzle/0001_real_raider.sql 🚀
再度マイグレーションファイルが生成されました。ファイル名は異なるものの、Drop migration した内容と一致しています。
ALTER TABLE authors ADD `age` integer;
マイグレーションファイルが生成できたので、 migrate してみます。
# 実行 $ bun run migrate # 以降はログ $ bun run migrate.ts 1 | import { entityKind } from "./entity.js"; 2 | class DrizzleError extends Error { 3 | static [entityKind] = "DrizzleError"; 4 | constructor({ message, cause }) { 5 | super(message); ^ DrizzleError: Failed to run the query 'ALTER TABLE authors ADD `age` integer;' at new DrizzleError (/path/to/node_modules/drizzle-orm/errors.js:5:5) at run (/path/to/node_modules/drizzle-orm/sqlite-core/session.js:72:13) at migrate (/path/to/node_modules/drizzle-orm/sqlite-core/dialect.js:546:13) at migrate (/path/to/node_modules/drizzle-orm/bun-sqlite/migrator.js:4:3) at /path/to/migrate.ts:10:7 15 | logger; 16 | exec(query) { 17 | this.client.exec(query); 18 | } 19 | prepareQuery(query, fields, executeMethod, isResponseInArrayMode, customResultMapper) { 20 | const stmt = this.client.prepare(query.sql); ^ SQLiteError: duplicate column name: age errno: 1 at prepare (bun:sqlite:249:19) at prepareQuery (/path/to/node_modules/drizzle-orm/bun-sqlite/session.js:20:18) at run (/path/to/node_modules/drizzle-orm/sqlite-core/session.js:70:14) at migrate (/path/to/node_modules/drizzle-orm/sqlite-core/dialect.js:546:13) at migrate (/path/to/node_modules/drizzle-orm/bun-sqlite/migrator.js:4:3) at /path/to/migrate.ts:10:7 error: script "migrate" exited with code 1
エラーになってしまいました。 SQLiteError: duplicate column name: age
とある通り、すでに age
列が存在しているために発生したようです。
この方法ではうまくいかないことがわかったので、 Drop migration しておきます。
$ drizzle-kit drop ... [✓] 0001_real_raider migration successfully dropped
【NG】drizzle-kit introspect する
Drizzle Kit には、既存のDBからスキーマを生成する方法もあるようです。
https://orm.drizzle.team/kit-docs/commands#introspect--pull
そこで、 drizzle-kit introspect:sqlite
を実行したところ、エラーになりました。
$ drizzle-kit introspect:sqlite No config path provided, using default path Reading config file '/path/to/drizzle.config.ts' Invalid input Either "turso", "libsql", "better-sqlite" are available options for "--driver"
公式ドキュメントを見たところ、bun:sqlite
だけでは introspect
コマンドは使えないようで、別途 driver
の指定が必要そうでした。
https://orm.drizzle.team/kit-docs/config-reference#driver
introspect できるようセットアップする
設定ファイルの変更が必要そうなことから、 drizzle.config.ts
に
- driver
- dbCredentials
の設定を追加します。
export default { schema: "./src/schema/*", out: "./drizzle", // ここから下を追加 driver: "better-sqlite", dbCredentials: { url: "./sqlite.db" } } satisfies Config
また、公式ドキュメントによると、driverとして better-sqlite
を使う場合は better-sqlite3
も必要そうなので追加でインストールします。
https://orm.drizzle.team/docs/get-started-sqlite#better-sqlite3
$ bun add better-sqlite3
introspect する
再度 introspect
を実行してみたところ、ディレクトリ drizzle
の下にスキーマファイル schema.ts
が生成されました。
中身を見ると、すべてのスキーマが1つのファイルに含まれていました。
import { sqliteTable, AnySQLiteColumn, numeric, text, integer } from "drizzle-orm/sqlite-core" import { sql } from "drizzle-orm" export const drizzleMigrations = sqliteTable("__drizzle_migrations", { id: numeric("id").primaryKey(), hash: text("hash").notNull(), createdAt: numeric("created_at"), }); export const authors = sqliteTable("authors", { id: integer("id").primaryKey({ autoIncrement: true }).notNull(), name: text("name"), age: integer("age"), });
ただ、
から、この方法で元に戻すのは難しそうでした。
【OK】git reset --hard HEAD で戻す
仕方ないので
git reset --hard HEAD
の実行- instrospect 実行時に生成されたファイルを削除
を行い、変更を元に戻しました。
見た目場は元に戻りましたが、この後の別の mirgate が成功するか気になりました。
そこで、authors.ts にスキーマの変更を追加し、ためしてみます。
export const authors = sqliteTable("authors", { id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }), name: text("name"), age: integer("age"), note: text("note"), // 追加 })
マイグレーションファイルを生成します。
$ bun run generate # ... No config path provided, using default 'drizzle.config.ts' Reading config file '/path/to/drizzle.config.ts' 1 tables authors 4 columns 0 indexes 0 fks [✓] Your SQL migration file ➜ drizzle/0002_slow_baron_zemo.sql 🚀
migrate します。
$ bun run migrate
SQLiteの構造を見ると、 note
が追加されていました。問題なさそうです。
drizzle-kit studio により、ブラウザでSQLiteの中身を確認する
Drizzle Kit のドキュメントを見たところ、ブラウザでDBの中身を確認する Drizzle Studio
向けのコマンドがありました。
ためしてみたところ、エラーになりました。エラーメッセージから、
- driver
- dbCredentials
の設定が必要そうと分かりました。
$ drizzle-kit studio drizzle-kit: v0.20.17 drizzle-orm: v0.30.9 No config path provided, using default path Reading config file '/path/to/drizzle.config.ts' Invalid input You need to specify a "driver" param in you config. It will help drizzle to know how to query you database. You can read more about drizzle.config: https://orm.drizzle.team/kit-docs/config-reference Invalid input You need to specify a "dbCredentials" param in you config. It will help drizzle to know how to query you database. You can read more about drizzle.config: https://orm.drizzle.team/kit-docs/config-reference
そこで、introspect 向けでセットアップしたときと同じ内容を drizzle.config.ts
に追加します。
export default { schema: "./src/schema/*", out: "./drizzle", // ここから下を追加 driver: "better-sqlite", dbCredentials: { url: "./sqlite.db" } } satisfies Config
再度実行したところ、Drizzle Studio が起動しました。
$ drizzle-kit studio drizzle-kit: v0.20.17 drizzle-orm: v0.30.9 No config path provided, using default path Reading config file '/path/to/drizzle.config.ts' [Warning] Drizzle Studio is currently in Beta. If you find anything that is not working as expected or should be improved, feel free to create an issue on GitHub: https://github.com/drizzle-team/drizzle-kit-mirror/issues/new or write to us on Discord: https://discord.gg/WcRKz2FFxN Drizzle Studio is up and running on https://local.drizzle.studio
https://local.drizzle.studio
にブラウザでアクセスしたところ、DBの中身が表示されました。
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/drizzle_with_bun-example
今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/drizzle_with_bun-example/pull/1