DrizzleがTiDB Serverlessに対応していた件
僕がちょうど検証していた2週間後くらいにリリースされていたみたいです…。
ということで試してみます。
というか公式のチュートリアルあるので詳しくはそっちを見てね。
接続情報を取得する
TiDBの管理画面のconnectを押下します。
Serverless Driverを選択してmysqlから始まる文字列をゲットします。またパスワードも必要なので生成してください。
mysql://<USER_ID>.root:<PASSWORD>@gateway01.ap-northeast-1.prod.aws.tidbcloud.com/<DB_NAME>
※DBをまだつくってない場合は
CREATE DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
をSQL Editorから実行してください。 test_db
は適当に変えてください。
既存プロジェクトを書き換える
ちょうどDrizzleをD1で使っているStackを作っていたので、それをTiDBに切り替えます。
pnpm up drizzle-orm @tidbcloud/serverless
環境変数に TIDB_HTTP_URL
を追加
TIDB_HTTP_URL=mysql://<USER_ID>.root:<PASSWORD>@gateway01.ap-northeast-1.prod.aws.tidbcloud.com/<DB_NAME>
スキーマを書き換えます。
schema.ts ※長いので折りたたみ
import { sql } from "drizzle-orm";
import {
mysqlTable,
varchar,
int,
timestamp,
boolean,
primaryKey,
} from "drizzle-orm/mysql-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const users = mysqlTable("user", {
id: varchar("id", { length: 255 }).notNull().primaryKey(),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(),
emailVerified: timestamp("emailVerified"),
image: varchar("image", { length: 255 }),
});
export const accounts = mysqlTable(
"account",
{
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type<"oauth" | "oidc" | "email">()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
refresh_token: varchar("refresh_token", { length: 255 }),
access_token: varchar("access_token", { length: 255 }),
expires_at: int("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
id_token: varchar("id_token", { length: 255 }),
session_state: varchar("session_state", { length: 255 }),
},
(table) => ({
pk: primaryKey(table.userId, table.provider),
}),
);
export const sessions = mysqlTable("session", {
sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires").notNull(),
});
export const verificationTokens = mysqlTable(
"verificationToken",
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires").notNull(),
},
(table) => ({
pk: primaryKey(table.identifier, table.token),
}),
);
export const todos = mysqlTable("todos", {
id: int("id").primaryKey().autoincrement(),
description: varchar("description", { length: 255 }).notNull(),
userId: varchar("user_id", { length: 255 })
.notNull()
.references(() => users.id),
completed: boolean("completed").default(false),
createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`),
});
export const insertTodoSchema = createInsertSchema(todos).extend({
description: z
.string()
.min(1, "Description is required")
.min(3, "Description must be at least 3 characters")
.max(100, "Description must be 100 characters or less"),
});
export const selectTodoSchema = createSelectSchema(todos);
export const createTodoSchema = z.object({
description: z
.string()
.min(1, "Description is required")
.min(3, "Description must be at least 3 characters")
.max(100, "Description must be 100 characters or less"),
});
export const updateToggleTodoSchema = z.object({
id: z.coerce.number(),
completed: z.boolean(),
});
export const updateTodoParamSchema = z.object({
id: z.coerce.number(),
});
export const updateTodoJsonSchema = z.object({
description: z.string().min(1),
});
export const deleteTodoSchema = z.object({
id: z.coerce.number(),
});
drizzle.config.tsを設定します。リモートのTiDBに直接接続する設定です。
import { defineConfig } from "drizzle-kit";
if (!process.env.TIDB_HTTP_URL) {
throw new Error("TIDB_HTTP_URL is not defined");
}
export default defineConfig({
schema: "./app/schema.ts",
out: "./drizzle/migrations",
dialect: "mysql",
dbCredentials: {
url: process.env.TIDB_HTTP_URL,
},
verbose: true,
strict: true,
});
そして以下を実行します。
# drizzle-kitがinstallされていない場合は以下
# pnpm add -D drizzle-kit mysql2
npx drizzle-kit generate
or
pnpm drizzle-kit generate
続いてそのままリモートのDBに対してmigration用のSQLを流します。
SQL Editorで確認すると作成されていることがわかります。
ではこの状態であとはアプリケーションから使うだけになりました。
今回のアプリケーションはNext.js on Cloudflare Page(@cloudflare/next-on-pages)上で動くHono経由で動いているので、
import { getRequestContext } from "@cloudflare/next-on-pages";
import { connect } from "@tidbcloud/serverless";
import { drizzle } from "drizzle-orm/tidb-serverless";
export const runtime = "edge";
export const getDb = () => {
const client = connect({
url: getRequestContext().env.TIDB_HTTP_URL,
});
return drizzle(client);
};
という様に書くことでContextを取得してenvを呼び出せます(honoならmiddlewareとして書くのです…という声が聞こえましたが一旦スルーします、そのうち)。
さてこれで開発サーバを動かしてみます
pnpm dev
え、エラーだと…。
[auth][cause]: Error: Execute SQL fail: Error 1406 (22001): Data too long for column 'id_token' at row 1
お、追加でマイグレーションするのにちょうどいいエラーですね。
修正します。
diff --git a/app/schema.ts b/app/schema.ts
index 44284f5..0a2e352 100644
--- a/app/schema.ts
+++ b/app/schema.ts
@@ -4,6 +4,7 @@ import {
varchar,
int,
timestamp,
+ text,
boolean,
primaryKey,
} from "drizzle-orm/mysql-core";
@@ -34,7 +35,7 @@ export const accounts = mysqlTable(
expires_at: int("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
- id_token: varchar("id_token", { length: 255 }),
+ id_token: text("id_token"),
session_state: varchar("session_state", { length: 255 }),
},
(table) => ({
SQLを生成します。
npx drizzle-kit generate
こういうSQLが生成されました。
ALTER TABLE `account` MODIFY COLUMN `id_token` text;
流します。
npx drizzle-kit migrate
無事に成功したみたいなので、再度アプリケーションで確認します。
レコードも確認できました!
最後に
DrizzleさんTiDB Serverlessに対応したなら言ってよ!!!!!(見てないだけ)
おわり
Discussion
ローカルでTiDBを立ち上げて作業する方法を書きます。
TiDBのインストール
これで一発で入ります。
TiDBの起動
この状態で別ターミナルからmysqlコマンドでアクセスできるようになります。
今回はDrizzleを使っているので、configを修正します。
※testというDBが初期状態でCREATEされているみたいです。
そして以下を実行するとスキーマが流せます。
studioで確認してみます。
tableが生成されていれば成功です。
最後に私のpackage.jsonを書いておきます。
以上です。
追加です。アプリケーションからもローカルのtidbにアクセスする方法です。
結論的にはアプリケーションからはmysql2を使ってのローカルのmysqlへのアクセスができません。なので
drizzle-orm/mysql-proxy
を使うように修正します。ここで
http://localhost:8000
を用意する必要があります。これは本家の ドキュメント…は間違っているので githubの exampleの方のコードを拝借します。をして
そして
として実行します。
あとはちゃんとgenerate & migrateができている状態で
pnpm dev
を行ってもらえればローカルのみmysql-proxy経由でDBにアクセスできていることが確認できると思います。