Zenn
Closed74

読む:SQLに対するバックエンドのアプローチ比較、そしてSafeQLの紹介

Yug (やぐ)Yug (やぐ)

へー

ORM やクエリビルダを利用したり、逆に SQL を記述してコード生成を行ったりと、様々な方法があります。

まず、SQL に対するアプローチには大きく分けて 2 つの方法があります。

  1. SQL を覆い隠す方法
  2. 記述した SQL に型を付ける方法
Yug (やぐ)Yug (やぐ)

なるほど。裏で生のsqlが書かれるみたいな感じ?

これらのライブラリは、提供されるメソッドを呼び出すことで、SQL の記述をライブラリが担ってくれるため、SQL を直接記述する必要がありません。

Yug (やぐ)Yug (やぐ)

ほー

使い方のフローとしては以下のとおりです。

  • スキーマファイルを記述する
  • メソッドを呼び出してクエリを実行する
Yug (やぐ)Yug (やぐ)

まずはormから

スキーマファイルってやつを作る

Prisma では、独自のスキーマファイルを記述します。

model User {
 id        Int      @id @default(autoincrement())
 email     String   @unique
 name      String
}
...

自動で作ってくれたりもするよってことかな?

なお、Prisma は DB にアクセスしてスキーマファイルを生成する introspect 機能も提供しています。

Yug (やぐ)Yug (やぐ)

drizzle ormはprismaと違ってスキーマファイルが独自の書き方ではなく普通にtsで書くってことか

Drizzle ORM では、TypeScript のオブジェクトとしてスキーマを記述します。

import { integer, pgTable, serial, text } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
 id: serial("id").primaryKey(),
 name: text("name"),
});
Yug (やぐ)Yug (やぐ)

へぇ、drizzle ormは書き方2つあんのか。

スキーマを記述したら、以下のようにクエリを実行します。
クエリの方法としては、Prisma 風にクエリを記述する方法と、SQL 風にクエリを記述する方法があります。

const result = await db.query.users.findMany({
 with: {
   posts: true,
 },
});
const result = await db.select().from(users);
Yug (やぐ)Yug (やぐ)

次はormではなくクエリビルダのKyselyについて

ふむ、スキーマファイルが不要ってことかな

Kysely では、スキーマ生成について特に記述はありません。
kysely の場合は自分で型を記述する必要があります。こちらも、型を生成するアプローチがいくつか存在しています。

そういう訳でもなさそう?よくわからん

kysely-introspect というライブラリを利用することで、スキーマから型を生成することができます。

Yug (やぐ)Yug (やぐ)

(スキーマの記述方法がわからんが)これ一番直感的で好きかも

スキーマを記述したら、以下のようにクエリを実行します。

const persons = await db
 .selectFrom("person")
 .select("id")
 .where("first_name", "=", "Arnold")
 .execute();

supabaseもこんな感じだったから慣れてるのが理由かもしれない

Yug (やぐ)Yug (やぐ)

なるほど、生のsqlに近いのかな?

SQL ファイルとして別途クエリを記述し、これに対してコードジェネレータなどを利用して型を付ける方法です。

そうっぽい

有名どころのライブラリを提示します。

  • sqlc
  • TypedSQL

これらのライブラリでは、記述された SQL ファイルを外部・DB のクエリパーサで解析し、TypeScript の型定義を生成します。

sqlを生で書きたい人はこれが良いんだろうなぁ

Yug (やぐ)Yug (やぐ)

ふむ

使い方のフローとしては以下のとおりです。

  • SQL ファイルを記述する
  • 設定ファイルを記述する
  • コード生成を行い、型定義を生成する
  • 生成された型定義を利用してクエリを実行する
Yug (やぐ)Yug (やぐ)

追記:「SQLファイルを記述する」の部分は、スキーマ定義のファイルとアプリケーション側(使う側)のファイルの2つが必要っぽい

Yug (やぐ)Yug (やぐ)

まずsqlcから

スキーマ定義側。やっぱりsql感がすごい

sqlc

まず、以下のような SQL ファイルを記述します。
schema.sql というファイルに、スキーマ定義を記述します。

CREATE TABLE authors (
 id   BIGSERIAL PRIMARY KEY,
 name text      NOT NULL,
 bio  text
);

これが使う側か

また、query.sql というファイルに、アプリケーション内で呼び出すクエリを記述します。

-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;
Yug (やぐ)Yug (やぐ)

ぬ、スキーマ定義とは別に設定ファイルというものが必要なのか

次に、以下のような sqlc.yaml ファイルに設定を記述します。

version: "2"
plugins:
  - name: ts
    wasm:
      url: (sqlcのwasmのURL)
      sha256: (対応するハッシュ値)
sql:
  - engine: "postgresql"
    queries: "query.sql"
    schema: "schema.sql"
    database:
      managed: true
    codegen:
      - out: db
        plugin: ts
        options:
          runtime: node
          driver: pg
Yug (やぐ)Yug (やぐ)

コード生成と型定義の生成?よくわからん

  • コード生成を行い、型定義を生成する

設定ファイルを記述したら、以下のコマンドを実行してコード生成を行います。

sqlc generate
Yug (やぐ)Yug (やぐ)

だめだ全然わからん

生成されたクエリは、以下のように利用します。

import postgres from "postgres";

import {
  getAuthor,
} from "./db/query_sql";

...

// Get that author
const seal = await getAuthor(sql, { id: author.id });
if (seal === null) {
  throw new Error("seal not found");
}
console.log(seal);
Yug (やぐ)Yug (やぐ)

次、TypedSQL

このsql文の意味を読み取りたいなぁ

TypedSQL

TypedSQL は、Prisma と連携して提供されるライブラリです。
スキーマは Prisma のスキーマを利用するため、クエリのみを記述します。

この SQL ファイルはprisma/sql/ディレクトリに配置します。

SELECT u.id, u.name, COUNT(p.id) as "postCount"
FROM "User" u
LEFT JOIN "Post" p ON u.id = p."authorId"
GROUP BY u.id, u.name

GROUP BY はグループ分け
https://techmania.jp/blog/sql-group-by/

LEFT JOIN / ON は左外部結合

左の行は強制的に全て表示し、右テーブルには全ての値がNULLである行を生成して結合

https://qiita.com/ngron/items/db4947fb0551f21321c0

これかなり図示がわかりやすいし総復習になる
https://wa3.i-3-i.info/word15311.html

多分uはUserテーブルの略でpはPostテーブルの略かと思われる

なるほど、なんとなくこのsql文わかった

「ユーザは全部出すけど、それに合致するpostのデータもあればくっつける、なかったらnullになる」
かな

Yug (やぐ)Yug (やぐ)

さっきのprismaの独自のスキーマファイルとはだいぶ違うなぁ

次に、設定ファイルを記述します。
TypedSQL の場合、ORM で紹介した Prisma と連携して提供されるため、Prisma に設定を記述します。

generator client {
 provider = "prisma-client-js"
 previewFeatures = ["typedSql"]
}
Yug (やぐ)Yug (やぐ)

やはりコード生成&型生成のコマンド実行は必須なのか

設定ファイルを記述したら、以下のコマンドを実行してコード生成を行います。

prisma generate --sql
Yug (やぐ)Yug (やぐ)

ほう

生成されたクエリは、以下のように利用します。

import { PrismaClient } from "@prisma/client";
import { getUsersWithPosts } from "@prisma/client/sql";

const prisma = new PrismaClient();

const usersWithPostCounts = await prisma.$queryRawTyped(getUsersWithPosts());
console.log(usersWithPostCounts);
  • import { getUsersWithPosts } from "@prisma/client/sql";とは?
    • さっきのsqlファイルのこと?であればそのsql実行結果がgetUsersWithPosts()の実行結果として取得できる感じか?
  • $queryRawTyped()とは?
    • 受け取ったsqlの結果を良い感じに取得するメソッドかな?
Yug (やぐ)Yug (やぐ)

たしかに

前者のアプローチは、SQL を覆い隠すことで便利に扱うというものです。
これに対し後者のアプローチは、SQL をそのまま記述し、そこから便利な機能を後付けするというものです。

なるほどなぁ
https://x.com/dmikurube/status/1789160173757677742

ふむふむ

それぞれのアプローチには、メリットとデメリットがあります。

Yug (やぐ)Yug (やぐ)

なるほど

SQL を覆い隠す方法

メリット

  • SQL を直接記述する必要がない
  • メソッドで隠蔽されているため、型安全性やエラーチェックが行いやすい
  • 厚いラッパーが提供され、便利な機能も多く提供されることがある

デメリット

  • SQL の記述をそのまま行えないため、複雑なクエリを記述する際に不便
  • ORM・クエリビルダの使い方自体を覚える必要がある
  • したがって別言語・別環境への移行コストが高く、コードを使い回すことが難しい

用意されてるから楽だし型安全だけど、複雑なクエリは書きづらいし独自性高くて面倒だったりするよね~という感じ

Yug (やぐ)Yug (やぐ)

なるほどー

記述した SQL に機能を後付けする方法

メリット

  • SQL をそのまま記述できるため、複雑なクエリも記述しやすい
  • SQL ファイルを利用するため、既存の SQL をそのまま利用できる
  • コード生成により、型安全性が担保される

また余談ですが、ORM を記述している頃は生の SQL が書けないという後ろめたさがありました。このような感情への対処法として、SQL をそのまま記述する方法は有用であると考えられます。

デメリット

  • コード生成の手間がかかる
  • sql 記述時にはエラーが発生しないため、別の手段でエラーチェックを行う必要がある
  • sql ファイルが分離されるため、命名が面倒

別にこっちもコード生成によって型安全性は担保されるのか。んで複雑なクエリも書きやすい
ただ面倒だったりするよね~あとエラー検知しづらいよね~みたいな

Yug (やぐ)Yug (やぐ)

ふむ

3 つ目のデメリットについては賛否両論が存在します。

SQL のクエリを分離することで、SQL の再利用性が高まることは確かです。
一方で、クエリを分離することで関心事が分散し、コードの可読性が下がるという意見もあります。

クエリというものは性質上アプリケーションと密結合であり、どちらが好ましいかは人により様々な意見があると思われます。

Yug (やぐ)Yug (やぐ)

ほー、良いとこ取り的な?構成ファイルも不要なのか

SafeQL は、コード内部で SQL を記述し、これに対して型を付けることができるライブラリです。
分類的には後者のアプローチ(記述した SQL に機能を後付ける方法)に該当します。
しかし、書き心地としては前者のアプローチのように型を付けられ、構成ファイルを記述する必要がなく、使い勝手が良いと考えられます。

そうなのか、safeql使うならpostgresqlの勉強もせねばだな

なお、対応している DB は PostgreSQL のみです。

safeqlはeslintルールなのか

また、SafeQL は eslint ルールとして実装されるため、eslint の導入が必須となっています。
クエリの解析は実際に DB にアクセスして行われます。

Yug (やぐ)Yug (やぐ)

これ見たらpostgresqlインストールされてなかったのでインストールするか

psql --version

windows版をインストール
https://www.postgresql.org/download/

なのにpsql --versionがエラーになったのでGPTの言う通りシステム環境変数のPATHにpostgresqlのbinフォルダのpathを設定すると、

出てくるようになった!すばらし

Yug (やぐ)Yug (やぐ)

よし実行してみるか

SafeQL は単に SQL クエリに型が付くだけなので、マイグレーション周りは別途のライブラリを利用する必要があります。
ここは、SQL ファイルを記述してコード生成する方法と同じです。
今回はマイグレーションの手間を省くため、そのまま SQL を実行します。
ローカルで Postgres を起動します。なお、接続 URL はpostgresql://localhost:5432/postgresです。

psql -h localhost -p 5432 -U postgres

ちゃんと接続してるっぽい

Claudeに聞いた

Yug (やぐ)Yug (やぐ)

んで以下sqlをターミナルで実行か

CREATE DATABASE "safeql-test";
\c safeql-test
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL
);

claudeの説明

よし、無事完了

Yug (やぐ)Yug (やぐ)

これはプロジェクトでインストールしちゃうか、そのまま使うかもだし

次に、SafeQL をインストールします。今回は pnpm を利用しています。

pnpm install -D @ts-safeql/eslint-plugin libpg-query

pnpmは入れてないのでnpmに変えて実行

ん、大量のエラーが出るな

Claudeに聞いた

「node gyp」で検索したら良さげな記事発見
https://qiita.com/AkihiroTakamura/items/25ba516f8ec624e66ee7

Visual StudionC++がインストールされていないことが原因っぽい...?
Pythonはinstallしてある

たしかにちょこちょこ「C++が見当たらないぞ」ってエラーコードがある

とりあえずnpm install --global windows-build-tools実行してみるか?

しかしClaude曰くこれ

ただし、2024年現在では windows-build-tools パッケージは非推奨となっており、代わりに直接 Visual Studio のインストーラーから「C++ によるデスクトップ開発」ワークロードをインストールする方法が推奨されています。この方が確実で新しい方法です。

たしかに記事コメント欄にもエラーが出たとある。たしかに古い記事だからなぁ
https://qiita.com/AkihiroTakamura/items/25ba516f8ec624e66ee7#comment-62fa02d70c9d8316e5eb

んでgithubも2021年にアーカイブされてしまってるので開発止まってるということか。確かにやめといたほうがよさそうかも
https://github.com/felixrieseberg/windows-build-tools

ここにもこう書かれてる

This package has been deprecated
Author message:
Node.js now includes build tools for Windows. You probably no longer need this tool. See https://github.com/felixrieseberg/windows-build-tools for details.

https://www.npmjs.com/package/windows-build-tools

Yug (やぐ)Yug (やぐ)

さっきのClaudeの言う通りVisual Studio Installerを開き、C++デスクトップ開発ツールをインストールした方が良さそうだな

7.82GB...重いな、まぁしゃあない

(なんでこのnpm iしたパッケージはC++関連必要なんだろう?初めて遭遇した)

Yug (やぐ)Yug (やぐ)

インストール完了したので再度npm install -D @ts-safeql/eslint-plugin libpg-query実行してみる

よっしゃ!成功

Yug (やぐ)Yug (やぐ)

次これ

次に、eslint.config.mjsを以下のように記述します。

import safeql from "@ts-safeql/eslint-plugin/config";
import tseslint from "typescript-eslint";

export default tseslint.config(
  ...tseslint.configs.recommended,
  safeql.configs.connections({
    databaseUrl:
      "postgresql://postgres@localhost:5432/safeql-test?sslmode=disable", // 接続先の URL
    targets: [{ tag: "sql", transform: "{type}[]" }],
  })
);

最後

最後に、src/index.tsに以下のように記述します。

import postgres from "postgres";

const main = async () => {
  const sql = postgres(
    "postgresql://postgres@localhost:5432/safeql-test?sslmode=disable"
  );

  const result = await sql`SELECT id, name FROM users WHERE id = ${1}`;
  console.log(result);
};

main();

すでにhonoのプロジェクトということでindex.tsxがあるのでindex.tsではなくindex.tsxに追記するか

Yug (やぐ)Yug (やぐ)

んー、出てこないなぁ

ここまで記述すると、SafeQL によって以下のように型の提案が行われます。

ていうかbun run devしようとするとエラーで強制終了してサーバー落ちるな

index.tsxに追加したものが原因っぽい

さてどうすれば良いだろう

Yug (やぐ)Yug (やぐ)

そういやpdAdminというGUIツールもインストールしてあるので、これで接続など確認してみるか

んー、見てもよくわからんぞ...

Yug (やぐ)Yug (やぐ)

お!GPTに聞いてみたらとりあえずbun run devは繋がるようになった!

index.tsxとeslint.config.mjsにあるURL
""postgresql://postgres@localhost:5432/safeql-test?sslmode=disable""

"postgresql://postgres:<your-password>@localhost:5432/safeql-test?sslmode=disable"
というように、パスワード追加したらいけた!

<your-password>の部分を自分のpostgresqlパスワードに置き換える。

パスワードをハードコーディングすんのは良くないので.envファイル作って移しとくか

Yug (やぐ)Yug (やぐ)

ていうかenvの読み取りどうやってやるんだろう、honoって

...だめだ、わからん。wrangler.toml編集とかhonoで用意されてるやつとか試してみたけど全然取れない。全部undefined。
https://hono.dev/docs/helpers/adapter

仕方ないので普通のtsファイル作ってそこに変数書いてそれをgitignoreしておきつつ、index.tsxでimportするって感じにしよう。前やった方法

ちなみに同じ階層にそのtsファイル作らないとなぜか読み取れないので同じ階層内に.env.ts作成。

と言う感じでとりあえず読み取れたのでok

index.tsx
import { DATABASE_URL } from './env'

import postgres from "postgres";

const main = async () => {
  const sql = postgres(DATABASE_URL);

  const result = await sql`SELECT id, name FROM users WHERE id = ${1}`;
  console.log("resultだよ", result);
  console.log('urlだよ', DATABASE_URL)
};

main();
eslint.config.mjs
import safeql from "@ts-safeql/eslint-plugin/config";
import tseslint from "typescript-eslint";
import { DATABASE_URL } from './src/env';

export default tseslint.config(
  ...tseslint.configs.recommended,
  safeql.configs.connections({
    databaseUrl: DATABASE_URL,
    targets: [{ tag: "sql", transform: "{type}[]" }],
  })
);
Yug (やぐ)Yug (やぐ)

さて、そして残った問題としては、「なぜか型推論が効かない」ということ。index.tsx内

なんでだろう

Yug (やぐ)Yug (やぐ)

あーこれなのかな?tsxじゃなくてtsにしか効かないからこれだとダメ、みたいな?

eslint.config.mjs
import safeql from "@ts-safeql/eslint-plugin/config";
import tseslint from "typescript-eslint";

自分が今使ってるのはindex.tsxなので

ん?いやそもそもこのtypescript-eslintってやつ補完出てこない。単純にインストールしてないわこれ。エラー出てないのはなんでだろ、このmjsファイルには推論とかが効いてないのか。

eslint.config.mjs
import tseslint from "typescript-eslint";

とりあえずnpm i -D typescript-eslint実行

うーん、それでも変わらずindex.tsxで型推論は出ないなぁ...

Yug (やぐ)Yug (やぐ)

ん!package.json眺めてたらtypescriptそのものがインストールされていないことに気付いた

インストールする

npm install -D typescript

...でもだめだった。まじかぁ

Yug (やぐ)Yug (やぐ)

ぬ!!vscodeの右下の{}っていうところにバツついてたからホバーしてみたら、こうなってる!

ESLintが効いていない?これっぽいぞ、見てみよう

Open ESLint Outputを押すと、こんな感じ

Cannot find module 'C:\Users\〇〇\src\env' imported from C:\Users\〇〇\eslint.config.mjs

なるほど~~

実はESLintさんはenvが読み込めておらず、それゆえエラーが出てしまい、それに伴ってESLintさんの構文チェックの機能が死んでしまっている ということでは!?

しかしenvからコードジャンプでちゃんと定義元のenv.tsに飛べるので、vscodeはちゃんとenvファイルを捕捉できているはず。だがESLintは捕捉できてないってことか、おそらく

vscodeではエラー出てなかったから余計混乱した、落とし穴すぎるなこれ

とりあえず、.tsとかで適当にenv作るのはダメなのか...学びだ

じゃあやはりしっかりとenv作る/取得する方法を探さないといけないなー、調べるか

dotenvとかいうやつインストールするとかよく聞くけど、外部ライブラリインストールするのなんか嫌だな、本当に必要なんだろうか

Yug (やぐ)Yug (やぐ)

そういえばcloudflare pagesをテンプレートとして使用しているが、viteも使ってるらしいこれ
https://hono.dev/docs/getting-started/cloudflare-pages

確かにpackage.jsonにviteがインストールされてる。viteでrun devとかされるようにもなってる

んでViteの環境変数の取得方法を使えるのでは?と思ったらビンゴ!よっしゃ~~~
https://ja.vite.dev/guide/env-and-mode

viteは内部でdotenv使ってるっぽい。へぇ

Vite は、環境ディレクトリーにある以下のファイルから追加の環境変数を読み込むために dotenv を利用します。

VITE_で始まる名前にしないと取得できないのは注意

  • .env.developmentファイルをプロジェクト直下に作成
  • ファイル内にVITE_DATABASE_URL=〇〇と書く
  • import.meta.env.VITE_DATABASE_URLで取得できる
Yug (やぐ)Yug (やぐ)

ということでvscodeの右下にあるeslint見てみたら、まだバツがついたままだ、、、

ログ見てみたらこれ

TypeError: Cannot read properties of undefined (reading 'VITE_DATABASE_URL')

うーん、環境変数をViteの仕様通りちゃんと取得してもだめなのかぁ

とりあえず何とかこれのエラーを解消したいな

Yug (やぐ)Yug (やぐ)

試しに直接環境変数をハードコーディングしてみたらできたぞ...!!

eslintのバツも解消された

やっぱりviteのあれやこれやをeslintが検知できないのが問題だったんだな...

GPT

ESLint 実行時には Vite のビルド環境ではないため、そのままでは値を取得できません。

そしてproblemsターミナルのヒントボタンみたいなやつ押してfix ~~も押して自動修正もできた。ナイス。

だがハードコーディングするのは問題だな、どうしよう

Yug (やぐ)Yug (やぐ)

ん、新しい発見

エラー出るのは.mjsだけだ!db.tsの方はimport.metaしてもeslintエラー出ない!!

GPTに聞いた、なるほどー、そういうことなのか

import.meta.env は Node.js では利用できないためです。eslint.config.mjs は Node.js 環境で実行されるため、import.meta.env は未定義になり、エラーが発生します。

Vite の import.meta.env はビルド時に環境変数を静的に埋め込む仕組みです。一方で、Node.js の process.env は実行時に環境変数を動的に取得します。ESLint では後者の仕組みを使う必要があります。

.mjsはnode.js環境なのか。んでimport.metaはnode.jsでは利用できないのか。

だがどっちにしろ読み込めるようにしないとまずいので、何かしらせねばならないのは変わらない

Yug (やぐ)Yug (やぐ)

dotenvインストールしていろいろやったら、読み込み自体はちゃんとできることを確認。一応やり方をまとめておく。だがeslintからエラーが出るのは変わらん...。

  1. まずインストール
npm install dotenv --save-dev
  1. ルートディレクトリに.env.development作成

  2. urlは共通化したいのでconfig.tsをルートディレクトリに作成

config.ts
import dotenv from "dotenv";

dotenv.config({ path: ".env.development" });

const databaseUrl = process.env.VITE_DATABASE_URL;

if (!databaseUrl) {
  throw new Error("VITE_DATABASE_URL is not defined in .env.development");
}

export const config = {
  databaseUrl,
};
  1. あとは普通にそれを読み込むだけ
eslint.config.mjs
import safeql from "@ts-safeql/eslint-plugin/config";
import tseslint from "typescript-eslint";
import { config } from './config'

export default tseslint.config(
  ...tseslint.configs.recommended,
  safeql.configs.connections({
    databaseUrl: config.databaseUrl,
    targets: [{ tag: "sql", transform: "{type}[]" }],
  })
);
db.ts
import postgres from "postgres";
import { config } from "../config";

export const main = async () => {
  const sql = postgres(config.databaseUrl);

  const result = await sql`SELECT id, name FROM users WHERE id = ${1}`;
  console.log("resultだよ", result);
};

結局eslintで以下が出る

Cannot find module 'C:\Users\〇〇\config' imported from C:\Users\〇〇\eslint.config.mjs

jsしか読み込めないのだがconfig.tsはtsだから見つからない認定されてる、というのが理由ぽい

かといってconfig.jsにファイル名変更してもdb.tsから.jsをインポートできない。モジュールが見つからないと出てくる。

多分その理由はtsconfig.jsonで"allowJs": true,がされてないからっぽいが、それを追加すると謎のエラーが出る

Cannot write file 'c:/Users/〇〇/eslint.config.mjs' because it would overwrite input file.ts

構成ファイル系の知識が皆無なのでやばい。詰まった。

Yug (やぐ)Yug (やぐ)

戻ってきた。今のプロジェクトをCursorで開いて、Codebase Answersの機能を使ってみよう

ほぼVSCodeだから乗り換えの面倒がまったく無さそうなのが良い

calloc134calloc134

ブログ記事お読みいただきありがとうございます!!検証までしていただいてすごい・・・!
以前執筆したブログ記事なのですが、かなり勢いで書いたせいで結構ぐちゃぐちゃになっていまして、つい先程修正いたしました!お手数おかけします!!
ほぼ同じ内容でスライドも存在しているので、ぜひ!!
https://speakerdeck.com/calloc134/batukuendoniokerusqlhenoapurotibi-jiao-plus-safeqlnoshao-jie-matsuriba-tech-vol-dot-7

Yug (やぐ)Yug (やぐ)

ご丁寧にありがとうございます!!わぉ、すごい助かります...!
ぜひ拝見させていただきます!!

Yug (やぐ)Yug (やぐ)

んー、だめだCursorに聞いてみたけど補完効くようにできなかった

仕方ない、ハードコーディングで進めていくか...
vscode-eslintとsafeqlと.envの狭間で苦しんで敗北

git pushしないように気をつけねば

Yug (やぐ)Yug (やぐ)

まぁクエリ書くのはdb.tsだけなのでeslint.config.mjsは変更しないで良いか

db.tsだけハードコーディングに変更

...と思いきやeslint.config.mjsもハードコーディングしないとdb.tsの補完が効かなくなった、、連鎖するのか、、

しゃあない、どっちもハードコーディングしよう

Yug (やぐ)Yug (やぐ)

これエラー出ないぞ...

vscode再起動したら出るようになった。不安定なの嫌だな

Yug (やぐ)Yug (やぐ)

へー型キャストもできるのか

では、::uuidを指定してキャストしてみます。

Yug (やぐ)Yug (やぐ)

postgresqlのサーバーをコマンド実行で立てとかないと、vscodeで編集するたびにvscodeのeslintエラー通知が右下に出てくるっぽい

だからvscode内のターミナルじゃなくてpcから直接ターミナル開いてサーバー立てといた方が良いな
そうしないと、vscode再起動とかしたら落ちちゃうので


いや関係ないっぽい...??

なんなんだこれめっちゃ邪魔だ、しかもturn offしても出てくるという謎

このeslint serverというのはvscodeのeslint拡張機能だなぁ。オフにはできないから困るぞ。。。。

Yug (やぐ)Yug (やぐ)

気付いたこと

  • name=の後とかの引用符はダブルクォーテーションだと機能しない!シングルクォーテーションにしないといけない
    • これがvscode-eslint, safeql, postgresqlのどのルールなのかはわからん
  • 毎回bun devで開発サーバー立てないでも、普通にnodeがts実行できるようになったのでnode db.tsでファイル単体実行してもOK
Yug (やぐ)Yug (やぐ)

あーselectの時だけeslintエラーが出て型を提案してくれるっぽい
insert/update/deleteでは型書かなくてもエラーでない

Yug (やぐ)Yug (やぐ)

おーほんとだ、すごい

LEFT JOIN の場合は、post_id と title に null が許容されることが提案されました。

Yug (やぐ)Yug (やぐ)

nullが含まれるべきでないものにもnullが含まれることがあるということかな

注意点

まだ少し不安定な部分があり、型に null が含まれて出力されることがあります。
この場合、VSCode を再起動することで正確な型が生成されるようになる場合が多いです。
テーブルのスキーマ定義を変更した後は、VSCode を再起動することをおすすめします。

Yug (やぐ)Yug (やぐ)

コロケーションという概念、なるほど~

コード内部にクエリを記述でき、コロケーションを保つことができる

まとめすぎても見づらくなったりしそうだから、どういうものをどの程度までまとめるべきかとか考える必要があるんだろうなー

Yug (やぐ)Yug (やぐ)

コード生成アプローチも知らないので何とも言えないが、とにかく楽になってるらしい

コード生成・import の手間がかからない

コード生成アプローチを利用する場合、コマンドの実行の手間がかかります。また、生成されたコードを import する必要があります。
SafeQL ではコード内に SQL を記述するため、コード生成の手間がかかりません。

Yug (やぐ)Yug (やぐ)

本番環境に入りこまない

SafeQL は、単なる eslint ルールとして実装されています。

とりあえずここ重要だな

SafeQL は、単なる eslint ルールとして実装されています。

単なるeslintルールだとして覚えるか

でもeslintという構文チェッカーの役割を大いに超えてORMみたいなこともできているという点で「単なるeslintルール」と言うのは違和感もある

「safeqlというsqlの記述方法を使うとそれをeslintもサポートしてくれる」
「SafeQLにeslintルール実装されています」
みたいな方が自分の印象に近いなぁ

Yug (やぐ)Yug (やぐ)

なるほど

Postgres.js を利用する際にジェネリクス内に型を提案するだけなので、本番環境に余計なライブラリが入りこまないというメリットがあります。
本番環境にライブラリが入りこまないというのは、セキュリティやパフォーマンスの観点からも望ましいです。

devDependenciesにしか入らないから本番ではパフォーマンス上がる的な感じかな(セキュリティも良いのか、へぇ)

参考に、Postgres.js を利用した場合の依存関係は以下の通りです。

...
  "devDependencies": {
    "@ts-safeql/eslint-plugin": "^3.4.1",
    "libpg-query": "15.2.0-rc.deparse.3",
    "tsx": "^4.19.0",
    "typescript-eslint": "^8.3.0"
  },
  "dependencies": {
    "postgres": "^3.4.4",
    "typescript": "^5.5.4"
  }
...
Yug (やぐ)Yug (やぐ)

ほー

なお、特性としてマイグレーション周りは別途のライブラリを利用する形となります。
筆者の環境では dbmate を利用しています。

Yug (やぐ)Yug (やぐ)

おー純粋なsqlを理解するだけで書けるのは良さげ
あと発行クエリの確認ってやつも普通は必要なのか

SQL をそのまま記述でき、見通しが良い

sql タグとして記述された SQL は、そのまま実行されます。
したがって、ORM などのライブラリで提供されるメソッドを覚える必要がありません。
また、ORM を利用する際は発行されるクエリを確認するための手間がかかりますが、SafeQL ではそのような手間がかかりません。実行計画の確認なども容易です。
これはコード生成アプローチの場合と同様のメリットであると考えられます。

だがコード生成アプローチとやらも同じメリットはあるらしい

このスクラップは2ヶ月前にクローズされました
ログインするとコメントできます