TypeScript用ORM「Drizzle ORM」をDBファーストで使う試み（PostgreSQL編）

# 本記事で行うこと

TypeScript用のORM「[Drizzle ORM](https://orm.drizzle.team/)」をデータベースファーストで使う試みのPostgreSQL編です。
Webフレームワークは[Hono](https://hono.dev/)を使用します。

関連記事

- [TypeScript用ORM「Drizzle ORM」をDBファーストで使う試み（SQLite編）](https://qiita.com/kanedaq/items/2e5b8e2911ea8211761b)
- [チーム開発参加の記録【2023-10～2024-03】(4) Go言語用ORM「Bun」をDBファーストで使う試み（PostgreSQL使用）](https://qiita.com/kanedaq/items/ec9bd42fefc755559df6)」

# テーブルを作る

では作業に移ります。
DBファーストですので、まずDDLを書きます。

```sql
CREATE TABLE foo
(
    col_serial                 SERIAL                   NOT NULL,
    col_bigserial              BIGSERIAL                NOT NULL,
    col_boolean                BOOLEAN                  NOT NULL,
    col_boolean_null           BOOLEAN,
    col_smallint               SMALLINT                 NOT NULL,
    col_smallint_null          SMALLINT,
    col_int                    INTEGER                  NOT NULL,
    col_int_null               INTEGER,
    col_bigint                 BIGINT                   NOT NULL,
    col_bigint_null            BIGINT,
    col_numeric                NUMERIC                  NOT NULL,
    col_numeric_null           NUMERIC,
    col_real                   REAL                     NOT NULL,
    col_real_null              REAL,
    col_double                 DOUBLE PRECISION         NOT NULL,
    col_double_null            DOUBLE PRECISION,
    col_text                   TEXT                     NOT NULL,
    col_text_null              TEXT,
    col_char                   CHAR(1)                  NOT NULL,
    col_char_null              CHAR(1),
    col_date                   DATE                     NOT NULL,
    col_date_null              DATE,
    col_time_with              TIME WITH TIME ZONE      NOT NULL,
    col_time_with_null         TIME WITH TIME ZONE,
    col_time                   TIME                     NOT NULL,
    col_time_null              TIME,
    col_timestamp_with         TIMESTAMP WITH TIME ZONE NOT NULL,
    col_timestamp_with_null    TIMESTAMP WITH TIME ZONE,
    col_timestamp              TIMESTAMP                NOT NULL,
    col_timestamp_null         TIMESTAMP,
    col_uuid                   UUID                     NOT NULL,
    col_uuid_null              UUID,
    col_json                   JSONB                    NOT NULL,
    col_json_null              JSONB,
    col_array                  INTEGER[]                NOT NULL,
    col_array_null             INTEGER[]
);
```

## 上記DDLの意図

列について、私がプロジェクトで使うかもしれない型を広めに試します。
JSON型と配列型も試します。
複合型も試しましたが、SELECT文ではBun同様にjsonを経由することで使えたものの、更新系コマンドが未サポートの様子だったので、本記事では省きました。

# テスト用データをINSERTする

作成したfooテーブルに、以下のSQLでテスト用データを2件INSERTしました。

```sql
INSERT INTO foo
(
    col_boolean,
    col_boolean_null,
    col_smallint,
    col_smallint_null,
    col_int,
    col_int_null,
    col_bigint,
    col_bigint_null,
    col_numeric,
    col_numeric_null,
    col_real,
    col_real_null,
    col_double,
    col_double_null,
    col_text,
    col_text_null,
    col_char,
    col_char_null,
    col_date,
    col_date_null,
    col_time_with,
    col_time_with_null,
    col_time,
    col_time_null,
    col_timestamp_with,
    col_timestamp_with_null,
    col_timestamp,
    col_timestamp_null,
    col_uuid,
    col_uuid_null,
    col_json,
    col_json_null,
    col_array,
    col_array_null
)
VALUES
(
    TRUE,
    NULL,
    12345,
    NULL,
    123456789,
    NULL,
    1234567890123456789,
    NULL,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    NULL,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    NULL,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    NULL,
    'text_data',
    NULL,
    'c',
    NULL,
    '2023-01-01',
    NULL,
    '12:00:00',
    NULL,
    '12:00:00',
    NULL,
    '2023-12-31 23:59:59',
    NULL,
    '2023-12-31 23:59:59',
    NULL,
    GEN_RANDOM_UUID(),
    NULL,
    '{"a": 10, "b": "Json"}'::JSONB,
    NULL,
    ARRAY[10, NULL, 30]::INTEGER[],
    NULL
),
(
    FALSE,
    FALSE,
    32767,
    32767,
    987654321,
    987654321,
    876543210987654321,
    876543210987654321,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117,
    'text_data',
    'text_data',
    'c',
    'c',
    '2023-01-01',
    '2023-01-01',
    '12:00:00',
    '12:00:00',
    '12:00:00',
    '12:00:00',
    '2023-12-31 23:59:59',
    '2023-12-31 23:59:59',
    '2023-12-31 23:59:59',
    '2023-12-31 23:59:59',
    GEN_RANDOM_UUID(),
    GEN_RANDOM_UUID(),
    '{"a": 20, "b": null}'::JSONB,
    '{"a": null, "b": "Json"}'::JSONB,
    ARRAY[10, NULL, 30]::INTEGER[],
    ARRAY[10, NULL, 30]::INTEGER[]
);
```

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

```sql
SELECT
    *
FROM
    foo;
```

SELECT文の結果です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/ebc6e149-30b6-4532-f9f1-a730f20316c6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/eff5f276-3287-e5eb-1262-a6fdac0ee119.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/4e4ab04b-c44b-02a9-7474-8b31a4f11745.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/711502b4-8e71-9ca4-60ac-33ef85762e94.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/91b0a072-c9b9-0d18-0943-1268dfaf1723.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/08ad0147-7def-0b6c-8775-3d0f443b1d23.png)

# HonoとDrizzle ORMを使って、fooテーブルにCRUD操作するWeb APIサーバーを作ってみる

それでは、TypeScriptからDrizzle ORMを使っていきます。

## プロジェクト作成

プロジェクト名は「exercise_drizzle_postgres」にしました。
どのテンプレートを使うか聞かれたので、「bun」を選択しました。
参考ページ： https://hono.dev/getting-started/basic

```sh
bunx create-hono exercise_drizzle_postgres
```

## パッケージインストール

プロジェクトディレクトリ直下で、以下を実行しました。
参考ページ： https://orm.drizzle.team/docs/get-started-postgresql#supabase

```sh
bun add drizzle-orm postgres
bun add -D drizzle-kit
```

## DBからTypeScriptのスキーマを自動生成

DBファーストなので、コードからDBスキーマを生成するのではなく、DBスキーマからコードを生成する順になりました。
プロジェクトディレクトリ直下で、以下のdrizzle-kitコマンドを実行しました。
connectionStringオプションには、PostgreSQLの接続URLを指定します。

```sh
drizzle-kit introspect:pg --driver=pg --connectionString=postgresql://postgres:secret@localhost:5432/postgres
```

## ディレクトリ構造とファイル

drizzle/ディレクトリ配下のファイルは、drizzle-kitコマンドを実行したときに自動生成されたものです。

```
Project Root
  ├── drizzle/
  │     ├── meta/
  │     │     ├── 0000_snapshot.json
  │     │     └── journal.json
  │     ├── 0000_fixed_toxin.sql
  │     └── schema.ts
  ├── src/
  │     └── index.ts
  ├── package.json
  ├── README.md
  └── tsconfig.json
```

## drizzle/schema.ts

自動生成されたコードです。
src/index.tsからimportして使用します。

```drizzle/schema.ts
import { pgTable, serial, bigserial, boolean, smallint, integer, bigint, numeric, real, doublePrecision, text, char, date, time, timestamp, uuid, jsonb } from "drizzle-orm/pg-core"
  import { sql } from "drizzle-orm"



export const foo = pgTable("foo", {
	colSerial: serial("col_serial").notNull(),
	colBigserial: bigserial("col_bigserial", { mode: "bigint" }).notNull(),
	colBoolean: boolean("col_boolean").notNull(),
	colBooleanNull: boolean("col_boolean_null"),
	colSmallint: smallint("col_smallint").notNull(),
	colSmallintNull: smallint("col_smallint_null"),
	colInt: integer("col_int").notNull(),
	colIntNull: integer("col_int_null"),
	// You can use { mode: "bigint" } if numbers are exceeding js number limitations
	colBigint: bigint("col_bigint", { mode: "number" }).notNull(),
	// You can use { mode: "bigint" } if numbers are exceeding js number limitations
	colBigintNull: bigint("col_bigint_null", { mode: "number" }),
	colNumeric: numeric("col_numeric").notNull(),
	colNumericNull: numeric("col_numeric_null"),
	colReal: real("col_real").notNull(),
	colRealNull: real("col_real_null"),
	colDouble: doublePrecision("col_double").notNull(),
	colDoubleNull: doublePrecision("col_double_null"),
	colText: text("col_text").notNull(),
	colTextNull: text("col_text_null"),
	colChar: char("col_char", { length: 1 }).notNull(),
	colCharNull: char("col_char_null", { length: 1 }),
	colDate: date("col_date").notNull(),
	colDateNull: date("col_date_null"),
	colTimeWith: time("col_time_with", { withTimezone: true }).notNull(),
	colTimeWithNull: time("col_time_with_null", { withTimezone: true }),
	colTime: time("col_time").notNull(),
	colTimeNull: time("col_time_null"),
	colTimestampWith: timestamp("col_timestamp_with", { withTimezone: true, mode: 'string' }).notNull(),
	colTimestampWithNull: timestamp("col_timestamp_with_null", { withTimezone: true, mode: 'string' }),
	colTimestamp: timestamp("col_timestamp", { mode: 'string' }).notNull(),
	colTimestampNull: timestamp("col_timestamp_null", { mode: 'string' }),
	colUuid: uuid("col_uuid").notNull(),
	colUuidNull: uuid("col_uuid_null"),
	colJson: jsonb("col_json").notNull(),
	colJsonNull: jsonb("col_json_null"),
	colArray: integer("col_array").array().notNull(),
	colArrayNull: integer("col_array_null").array(),
});
```

## src/index.ts

メインのコードです。

```src/index.ts
import { Hono } from "hono"
import postgres from "postgres"
import { drizzle } from "drizzle-orm/postgres-js"
import { sql, eq } from "drizzle-orm"
import { foo } from "../drizzle/schema"
import {execute} from "drizzle-kit/orm-extenstions/d1-driver/wrangler-client";

const client = postgres("postgresql://postgres:secret@localhost:5432/postgres")
const db = drizzle(client, { logger: true })

const app = new Hono()
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404))

app.get("/api/selectFoo", async (c) => {
    const result = await db.select(
    {
        colSerial: foo.colSerial,
        colBigserial: sql`CAST(col_bigserial AS TEXT)`,
        colBoolean: foo.colBoolean,
        colBooleanNull: foo.colBooleanNull,
        colSmallint: foo.colSmallint,
        colSmallintNull: foo.colSmallintNull,
        colInt: foo.colInt,
        colIntNull: foo.colIntNull,
        colBigint: sql`CAST(col_bigint AS TEXT)`,
        colBigintNull: sql`CAST(col_bigint_null AS TEXT)`,
        colNumeric: sql`CAST(col_numeric AS TEXT)`,
        colNumericNull: sql`CAST(col_numeric_null AS TEXT)`,
        colReal: foo.colReal,
        colRealNull: foo.colRealNull,
        colDouble: foo.colDouble,
        colDoubleNull: foo.colDoubleNull,
        colText: foo.colText,
        colTextNull: foo.colTextNull,
        colChar: foo.colChar,
        colCharNull: foo.colCharNull,
        colDate: sql`CAST(col_date AS TEXT)`,
        colDateNull: sql`CAST(col_date_null AS TEXT)`,
        colTimeWith: foo.colTimeWith,
        colTimeWithNull: foo.colTimeWithNull,
        colTime: foo.colTime,
        colTimeNull: foo.colTimeNull,
        colTimestampWith: foo.colTimestampWith,
        colTimestampWithNull: foo.colTimestampWithNull,
        colTimestamp: sql`CAST(col_timestamp AS TEXT)`,
        colTimestampNull: sql`CAST(col_timestamp_null AS TEXT)`,
        colUuid: foo.colUuid,
        colUuidNull: foo.colUuidNull,
        colJson: foo.colJson,
        colJsonNull: foo.colJsonNull,
        colArray: foo.colArray,
        colArrayNull: foo.colArrayNull
    }
    ).from(foo)
    .execute()

    return c.json(result)
})

app.put("/api/updateFoo", async (c) => {
    await db.update(foo)
        .set({
            colTextNull: "ABC",
            colArray: sql`ARRAY[111, NULL, 333]::INTEGER[]`,
            colArrayNull: [444, 555, 666],
            colJson: sql`${{a: 777, b: null, c: "Hello"}}::JSONB`,
            colJsonNull: sql`'{}'::JSONB`,
        })
        .where(eq(foo.colSerial, 1))

    return c.json({ message: "ok", ok: true })
})

app.post("/api/insertFoo", async (c) => {
    await db.insert(foo)
        .values({
            colBoolean: false,
            colBooleanNull: null,
            colSmallint: 9999,
            colSmallintNull: null,
            colInt: 999999999,
            colIntNull: null,
            colBigint: 999999999999999999n,
            colBigintNull: null,
            colNumeric: sql`${"2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178"}::NUMERIC`,
            colNumericNull: null,
            colReal: 2.71828182845904523536028747135266,
            colRealNull: null,
            colDouble: 2.71828182845904523536028747135266,
            colDoubleNull: null,
            colText: "Hello",
            colTextNull: null,
            colChar: "z",
            colCharNull: null,
            colDate: "2024-01-21",
            colDateNull: null,
            colTimeWith: "13:00",
            colTimeWithNull: null,
            colTime: "13:00",
            colTimeNull: null,
            colTimestampWith: "2024-01-21 13:00:00.000000",
            colTimestampWithNull: null,
            colTimestamp: "2024-01-21 13:00:00.000000",
            colTimestampNull: null,
            colUuid: sql`GEN_RANDOM_UUID()`,
            colUuidNull: null,
            colJson: sql`${{a: 777, b: null, c: "Hello"}}::JSONB`,
            colJsonNull: sql`${{}}::JSONB`,
            colArray: sql`ARRAY[111, NULL, 333]::INTEGER[]`,
            colArrayNull: [444, 555, 666]
        })

    return c.json({ message: "ok", ok: true })
})

app.delete("/api/deleteFoo", async (c) => {
    const subQuery = db.select(
        {
            _: sql`max(${foo.colSerial})`
        }
        ).from(foo)
    await db.delete(foo)
        .where(eq(foo.colSerial, subQuery))

    return c.json({ message: "ok", ok: true })
})

export default app
```

## package.json

```package.json
{
  "scripts": {
    "dev": "bun run --hot src/index.ts"
  },
  "dependencies": {
    "drizzle-orm": "^0.29.3",
    "hono": "^3.12.6",
    "postgres": "^3.4.3"
  },
  "devDependencies": {
    "@types/bun": "^1.0.0",
    "drizzle-kit": "^0.20.13"
  }
}
```

## tsconfig.json

```tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true,
    "strict": true,
    "jsx": "react-jsx",
    "jsxImportSource": "hono/jsx"
  }
}
```

# CRUD操作

それでは、CRUD操作について一つ一つ見ていきます。

## SELECT文

まずはSELECT文です。

### SELECT用ハンドラの中身

ハンドラの中身を見ていきます。
SQLが透けて見えますね。
私みたいにSQLを書きたい人には、嬉しいORMだと思いました。

「sql``」で囲った部分は、Drizzle ORMがSQLを生成するときにそのまま使われるので、DB側の構文やファンクションを使用できます。

- DBでBIGSERIAL型、BIGINT型、NUMERIC型で定義された列は、TypeScriptのnumber型で受け取ると誤差が生じることがあるので、TEXT型にキャストしました。
- DBでタイムゾーンなしの日付時間型で定義された列は、TypeScriptで受け取ったらタイムゾーン付きになる現象が起きたので、TEXT型にキャストしてそれを防ぎました。

```typescript
    const result = await db.select(
    {
        colSerial: foo.colSerial,
        colBigserial: sql`CAST(col_bigserial AS TEXT)`,
        colBoolean: foo.colBoolean,
        colBooleanNull: foo.colBooleanNull,
        colSmallint: foo.colSmallint,
        colSmallintNull: foo.colSmallintNull,
        colInt: foo.colInt,
        colIntNull: foo.colIntNull,
        colBigint: sql`CAST(col_bigint AS TEXT)`,
        colBigintNull: sql`CAST(col_bigint_null AS TEXT)`,
        colNumeric: sql`CAST(col_numeric AS TEXT)`,
        colNumericNull: sql`CAST(col_numeric_null AS TEXT)`,
        colReal: foo.colReal,
        colRealNull: foo.colRealNull,
        colDouble: foo.colDouble,
        colDoubleNull: foo.colDoubleNull,
        colText: foo.colText,
        colTextNull: foo.colTextNull,
        colChar: foo.colChar,
        colCharNull: foo.colCharNull,
        colDate: sql`CAST(col_date AS TEXT)`,
        colDateNull: sql`CAST(col_date_null AS TEXT)`,
        colTimeWith: foo.colTimeWith,
        colTimeWithNull: foo.colTimeWithNull,
        colTime: foo.colTime,
        colTimeNull: foo.colTimeNull,
        colTimestampWith: foo.colTimestampWith,
        colTimestampWithNull: foo.colTimestampWithNull,
        colTimestamp: sql`CAST(col_timestamp AS TEXT)`,
        colTimestampNull: sql`CAST(col_timestamp_null AS TEXT)`,
        colUuid: foo.colUuid,
        colUuidNull: foo.colUuidNull,
        colJson: foo.colJson,
        colJsonNull: foo.colJsonNull,
        colArray: foo.colArray,
        colArrayNull: foo.colArrayNull
    }
    ).from(foo)
    .execute()

    return c.json(result)
```

### Drizzle ORMが生成したSQL

上記コードからDrizzle ORMが生成したSQLを、整形して見やすくしました。

```sql
select
    "col_serial",
    CAST(col_bigserial AS TEXT),
    "col_boolean",
    "col_boolean_null",
    "col_smallint",
    "col_smallint_null",
    "col_int",
    "col_int_null",
    CAST(col_bigint AS TEXT),
    CAST(col_bigint_null AS TEXT),
    CAST(col_numeric AS TEXT),
    CAST(col_numeric_null AS TEXT),
    "col_real",
    "col_real_null",
    "col_double",
    "col_double_null",
    "col_text",
    "col_text_null",
    "col_char",
    "col_char_null",
    CAST(col_date AS TEXT),
    CAST(col_date_null AS TEXT),
    "col_time_with",
    "col_time_with_null",
    "col_time",
    "col_time_null",
    "col_timestamp_with",
    "col_timestamp_with_null",
    CAST(col_timestamp AS TEXT),
    CAST(col_timestamp_null AS TEXT),
    "col_uuid",
    "col_uuid_null",
    "col_json",
    "col_json_null",
    "col_array",
    "col_array_null"
from
    "foo"
```

### selectFoo APIの実行結果

selectFoo APIを呼び出した結果です。
レスポンスは、ハンドラの最後の一行で、結果表をそのままjsonにして返しているだけですが、きれいなレスポンスが生成できていますね。
シンプルなコードできれいなレスポンスを返せているのはポイントが高いです。

```json
[
    {
        "colSerial": 1,
        "colBigserial": "1",
        "colBoolean": true,
        "colBooleanNull": null,
        "colSmallint": 12345,
        "colSmallintNull": null,
        "colInt": 123456789,
        "colIntNull": null,
        "colBigint": "1234567890123456789",
        "colBigintNull": null,
        "colNumeric": "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117",
        "colNumericNull": null,
        "colReal": 3.1415927,
        "colRealNull": null,
        "colDouble": 3.141592653589793,
        "colDoubleNull": null,
        "colText": "text_data",
        "colTextNull": null,
        "colChar": "c",
        "colCharNull": null,
        "colDate": "2023-01-01",
        "colDateNull": null,
        "colTimeWith": "12:00:00+00",
        "colTimeWithNull": null,
        "colTime": "12:00:00",
        "colTimeNull": null,
        "colTimestampWith": "2023-12-31T23:59:59.000Z",
        "colTimestampWithNull": null,
        "colTimestamp": "2023-12-31 23:59:59",
        "colTimestampNull": null,
        "colUuid": "aa4c082e-b82c-4b8f-a7a6-411a92d3c75a",
        "colUuidNull": null,
        "colJson": {
            "a": 10,
            "b": "Json"
        },
        "colJsonNull": null,
        "colArray": [
            10,
            null,
            30
        ],
        "colArrayNull": null
    },
    {
        "colSerial": 2,
        "colBigserial": "2",
        "colBoolean": false,
        "colBooleanNull": false,
        "colSmallint": 32767,
        "colSmallintNull": 32767,
        "colInt": 987654321,
        "colIntNull": 987654321,
        "colBigint": "876543210987654321",
        "colBigintNull": "876543210987654321",
        "colNumeric": "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117",
        "colNumericNull": "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117",
        "colReal": 3.1415927,
        "colRealNull": 3.1415927,
        "colDouble": 3.141592653589793,
        "colDoubleNull": 3.141592653589793,
        "colText": "text_data",
        "colTextNull": "text_data",
        "colChar": "c",
        "colCharNull": "c",
        "colDate": "2023-01-01",
        "colDateNull": "2023-01-01",
        "colTimeWith": "12:00:00+00",
        "colTimeWithNull": "12:00:00+00",
        "colTime": "12:00:00",
        "colTimeNull": "12:00:00",
        "colTimestampWith": "2023-12-31T23:59:59.000Z",
        "colTimestampWithNull": "2023-12-31T23:59:59.000Z",
        "colTimestamp": "2023-12-31 23:59:59",
        "colTimestampNull": "2023-12-31 23:59:59",
        "colUuid": "8a4fa849-1c61-440f-bc6e-7c780f3afda6",
        "colUuidNull": "f014beb1-3bb3-4dbd-956c-6126d41f3692",
        "colJson": {
            "a": 20,
            "b": null
        },
        "colJsonNull": {
            "a": null,
            "b": "Json"
        },
        "colArray": [
            10,
            null,
            30
        ],
        "colArrayNull": [
            10,
            null,
            30
        ]
    }
]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/1ccc4489-a865-70a8-fdb5-197f80af4de2.png)

## UPDATE文

次はUPDATE文を見ていきます。

### UPDATE用ハンドラの中身

ハンドラのクエリー部分を見ます。
col_serial列が1の行について、TEXT型、配列型、JSON型の列をそれぞれ更新する内容です。

```typescript
    await db.update(foo)
        .set({
            colTextNull: "ABC",
            colArray: sql`ARRAY[111, NULL, 333]::INTEGER[]`,
            colArrayNull: [444, 555, 666],
            colJson: sql`${{a: 777, b: null, c: "Hello"}}::JSONB`,
            colJsonNull: sql`'{}'::JSONB`,
        })
        .where(eq(foo.colSerial, 1))
```

### Drizzle ORMが生成したSQL

上記コードからDrizzle ORMが生成したSQLを、整形して見やすくしました。

```sql
update
    "foo"
set
    "col_text_null" = $1,
    "col_array" = ARRAY[111, NULL, 333]::INTEGER[],
    "col_array_null" = $2,
    "col_json" = $3::JSONB,
    "col_json_null" = '{}'::JSONB
where
    "foo"."col_serial" = $4

-- params: [
    "ABC",
    "{444,555,666}",
    {"a":777,"b":null,"c":"Hello"},
    1
]
```

### updateFoo APIの実行結果

updateFoo APIを呼び出した結果を見てみます。

```sql
SELECT
    col_text_null,
    col_array,
    col_array_null,
    col_json,
    col_json_null
FROM
    foo
WHERE
    col_serial = 1;
```

5つの列が意図した値に更新されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/18b0c32e-83c1-6863-fae8-fe1090bcafb7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/2bb7cf2c-fa18-9cfb-f8d2-ef6b12307d04.png)

## INSERT文

次はINSERT文を見ていきます。
1行挿入する内容です。

### INSERT用ハンドラの中身

ハンドラのクエリー部分を見ます。

```typescript
    await db.insert(foo)
        .values({
            colBoolean: false,
            colBooleanNull: null,
            colSmallint: 9999,
            colSmallintNull: null,
            colInt: 999999999,
            colIntNull: null,
            colBigint: 999999999999999999n,
            colBigintNull: null,
            colNumeric: sql`${"2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178"}::NUMERIC`,
            colNumericNull: null,
            colReal: 2.71828182845904523536028747135266,
            colRealNull: null,
            colDouble: 2.71828182845904523536028747135266,
            colDoubleNull: null,
            colText: "Hello",
            colTextNull: null,
            colChar: "z",
            colCharNull: null,
            colDate: "2024-01-21",
            colDateNull: null,
            colTimeWith: "13:00",
            colTimeWithNull: null,
            colTime: "13:00",
            colTimeNull: null,
            colTimestampWith: "2024-01-21 13:00:00.000000",
            colTimestampWithNull: null,
            colTimestamp: "2024-01-21 13:00:00.000000",
            colTimestampNull: null,
            colUuid: sql`GEN_RANDOM_UUID()`,
            colUuidNull: null,
            colJson: sql`${{a: 777, b: null, c: "Hello"}}::JSONB`,
            colJsonNull: sql`${{}}::JSONB`,
            colArray: sql`ARRAY[111, NULL, 333]::INTEGER[]`,
            colArrayNull: [444, 555, 666]
        })
```

### Drizzle ORMが生成したSQL

上記コードからDrizzle ORMが生成したSQLを、整形して見やすくしました。

```sql
insert into "foo" (
    "col_serial",
    "col_bigserial",
    "col_boolean",
    "col_boolean_null",
    "col_smallint",
    "col_smallint_null",
    "col_int",
    "col_int_null",
    "col_bigint",
    "col_bigint_null",
    "col_numeric",
    "col_numeric_null",
    "col_real",
    "col_real_null",
    "col_double",
    "col_double_null",
    "col_text",
    "col_text_null",
    "col_char",
    "col_char_null",
    "col_date",
    "col_date_null",
    "col_time_with",
    "col_time_with_null",
    "col_time",
    "col_time_null",
    "col_timestamp_with",
    "col_timestamp_with_null",
    "col_timestamp",
    "col_timestamp_null",
    "col_uuid",
    "col_uuid_null",
    "col_json",
    "col_json_null",
    "col_array",
    "col_array_null"
)
values (
    default,
    default,
    $1,
    $2,
    $3,
    $4,
    $5,
    $6,
    $7,
    $8,
    $9::NUMERIC,
    $10,
    $11,
    $12,
    $13,
    $14,
    $15,
    $16,
    $17,
    $18,
    $19,
    $20,
    $21,
    $22,
    $23,
    $24,
    $25,
    $26,
    $27,
    $28,
    GEN_RANDOM_UUID(),
    $29,
    $30::JSONB,
    $31::JSONB,
    ARRAY[111, NULL, 333]::INTEGER[],
    $32
)

-- params: [
    false,
    null,
    9999,
    null,
    999999999,
    null,
    999999999999999999,
    null,
    "2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178",
    null,
    2.718281828459045,
    null,
    2.718281828459045,
    null,
    "Hello",
    null,
    "z",
    null,
    "2024-01-21",
    null,
    "13:00",
    null,
    "13:00",
    null,
    "2024-01-21 13:00:00.000000",
    null,
    "2024-01-21 13:00:00.000000",
    null,
    null,
    {"a":777,"b":null,"c":"Hello"},
    {},
    "{444,555,666}"
]
```

### insertFoo APIの実行結果

insertFoo APIを呼び出した結果です。
3行目に、意図した行がINSERTされたことを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/293badbc-4795-2420-4508-88dd3270841c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/4f66c2f1-d244-27f3-c7db-76a4bea94a31.png)

## DELETE文

次はDELETE文を見ていきます。

### DELETE用ハンドラの中身

ハンドラのクエリー部分を見ます。
col_serial列==最大値の行を削除する内容です。

```typescript
    const subQuery = db.select(
        {
            _: sql`max(${foo.colSerial})`
        }
        ).from(foo)
    await db.delete(foo)
        .where(eq(foo.colSerial, subQuery))
```

### Drizzle ORMが生成したSQL

上記コードからDrizzle ORMが生成したSQLを、整形して見やすくしました。

```sql
delete from
    "foo"
where
    "foo"."col_serial" = (
        select
            max("col_serial")
        from
            "foo"
    )
```

### deleteFoo APIの実行結果

deleteFoo APIを呼び出した結果です。
意図した行（3行目）が正常に削除されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/77757ece-5075-6d19-cce4-1926500afe92.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/84e68d03-0b57-19a0-74dd-959731ec3ea5.png)

# まとめ

Drizzle ORMをDBファーストで使ってみたところ、PostgreSQLでも問題なく動いてくれました。
ただし本記事執筆時点では、PostgreSQLの複合型（composite type）は、Drizzle ORMでサポートされていないようでした。