Drizzle ORM から TiDB Serverless に接続する
過去こちらの記事で Prisma ORM を用いてTiDB Serverlessへの接続を行いました。実行基盤としてはCloudflare Workersを用いています。
今日はもう一つのORMであるDrizzleを使ってみます。
Drizzle ORM とは?
Prismaと同様に、TypeScriptの型や構造と、データベースのテーブル(行・列)を橋渡しする仕組みを提供します。TytpeScriptと連携させることで型安全なRDBMS呼び出しが可能となり、SQLに近い構文で、安全かつ型補完を得ながら記述できるため、ビジネスロジックと統一的な記述が可能となり、コード全体の可読性が高まります。また、SQLインジェクションなど不正なSQLを外部から実行させるという攻撃にも強くなることがメリットです。
Prismaは独自のPrisma Schema Languageによりデータ操作を行い、スキーマを .prisma ファイルで定義してからコード生成が必要ですが、DrizzleはTypeScriptファイル内に直接スキーマを記述でき、再コンパイル不要で反映されます。Prismaより軽量であることから、FaaSやエッジコンピューティング基盤の場合に、Prismaより優先的に採用されるケースがあります。
const result = await db.select().from(users).where(eq(users.id, 1));
今日はこちらを試していきます。
さっそくやってみる
0. TiDB Serverlessの起動
Connect
から取得しておきます。
はじめて起動した場合はパスワードが存在していませんので、Generate Password
をクリックすると生成されます。
1.Project の初期化と Drizzle のインストール
mkdir drizzle-tidb-example
cd drizzle-tidb-example
npm init -y
npm install drizzle-orm mysql2 dotenv
2.ts ファイルの作成とテスト実行
// db.ts
import { createPool } from 'mysql2/promise';
import { drizzle } from 'drizzle-orm/mysql2';
import { mysqlTable, int, varchar } from 'drizzle-orm/mysql-core';
// 接続設定(TiDB Serverless)
const pool = createPool({
host: 'gateway01.us-west-2.prod.aws.tidbcloud.com',
port: 4000,
user: 'xxxxx.root',
password: 'yyyyy',
database: 'test',
ssl: {
rejectUnauthorized: true,
},
});
// DrizzleのDBインスタンス生成
const db = drizzle(pool);
// スキーマ定義(usersテーブル)
const users = mysqlTable('users', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', { length: 255 }),
});
async function main() {
// テーブル作成
await db.execute(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
`);
// データ挿入
await db.insert(users).values({ name: 'Alice' });
// データ取得
const allUsers = await db.select().from(users);
console.log(allUsers);
}
main().catch(console.error);
user
とpassword
は皆さんの環境用に書き換えておきます。
これで準備は完了です。
npx tsx db.ts
を実行すればテーブル作成
,データ挿入
,データ取得
が行われ以下が表示されます。
[ { id: 1, name: 'Alice' } ]
TiDB Serverless 側でもデータが入っています。
eq , where の実行
次にwhere
句を実行してみます。これには追加でeq
が必要となります。
以下を追記します。
import { eq } from 'drizzle-orm';
const result = await db.select().from(users).where(eq(users.name, 'Alice'));
先ほどと同じ出力が出てくると思います。(実行のたびにInsertも走るのでAlice行がどんどん増えていきますが・・・)ちなみにDrizzleはCreate Table時は自動でCREATE TABLE IF NOT EXISTS
を発行していますので、その部分はエラーとはならず処理がスキップされています。
order by
やlimit
句もこのように記載して実行できます。
const result = await db.select().from(users).orderBy(users.id).limit(5);
Like や Count の実行
こちらも同様にまずはコンポーネントが必要となります。
import { like } from 'drizzle-orm';
import { count } from 'drizzle-orm';
const result = await db
.select()
.from(users)
.where(like(users.name, '%Ali%'));
const result = await db
.select({ count: count(users.id) })
.from(users);
生SQLを実行
実はSQLをそのまま実行させることもできます。
const [rows] = await db.execute(`
SELECT name, LENGTH(name) as length FROM users;
`);
console.log(rows);
ただこの方法はコード全体の可読性の観点からは、必要な場合を除いて定常的に用いるのはあまりお勧めできないです。特に複数人で作業を行う場合などは必ず統一された方法でデータ操作を行う方がいいかと思います。
例えば以下のようなケースは生SQLの発行が必要です。
1. TiDB Serverless 専用コマンドの実行
DrizzleはMySQLとしてTiDB Serverlessを呼び出しています。ここには方言差異が存在しており、TiDB独自コマンドを発行したい時
2. Drizzleが未サポートのSQL構文
WITH ranked_users AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id DESC) AS rank
FROM users
)
SELECT * FROM ranked_users WHERE rank = 1;
Windows関数がまだ未サポートなので生SQLが必要になります。
3. 発行したSQLの最適化を行いたい場合
基本Drizzleは最適化されたSQLを発行してくれます。TiDB Serverless側でも以下の様に発行されたSQLを`確認できます。
Drizzle側でもtoSQL
関数で確認できます。
import { sql } from 'drizzle-orm';
const stmt = db.select().from(users).where(eq(users.name, 'Alice'));
console.log('Generated SQL:', stmt.toSQL()); // ⬅ SQL構造出力
Generated SQL: {
sql: 'select `id`, `name` from `users` where `users`.`name` = ?',
params: [ 'Alice' ]
}
Discussion