Query Builder
Query builder は両エンジンで同一です。ここに書かれている全ては、
mutator の中 (ブラウザは IDB、Worker は D1)、useLiveQuery(...) の
factory 内、createServerDb 経由の raw executor に対して、そのまま
動きます。
select — カラム projection
Section titled “select — カラム projection”// 全カラムconst rows = await db.select().from(todos)
// 明示 projectionconst rows = await db.select({ id: todos.id, title: todos.title }).from(todos)
// 集約付き projectionconst stats = await db .select({ userId: todos.userId, n: count() }) .from(todos) .groupBy(todos.userId)Row 形状は projection に従います。projection を書かなければ全カラム、 書けばその shape。
where — フィルタ
Section titled “where — フィルタ”import { and, eq, gt, gte, inArray, isNull, isNotNull, like, lt, ne, not, or } from "@sh1n4ps/plasma-core"
// 比較db.select().from(todos).where(eq(todos.done, 0))db.select().from(todos).where(gt(todos.updatedAt, cutoff))db.select().from(todos).where(ne(todos.userId, banned))
// 論理db.select().from(todos).where(and(eq(todos.done, 0), gt(todos.updatedAt, cutoff)))db.select().from(todos).where(or(eq(todos.priority, 1), eq(todos.pinned, 1)))db.select().from(todos).where(not(eq(todos.done, 1)))
// 集合db.select().from(todos).where(inArray(todos.userId, ["u1", "u2", "u3"]))
// Null チェックdb.select().from(todos).where(isNull(todos.assignee))db.select().from(todos).where(isNotNull(todos.assignee))
// パターンdb.select().from(todos).where(like(todos.title, "% urgent %"))and / or は 2 個以上の述語を取ります。ネストも可能:
and(or(a, b), c)。
orderBy — ソート
Section titled “orderBy — ソート”import { asc, desc } from "@sh1n4ps/plasma-core"
db.select().from(todos).orderBy(asc(todos.updatedAt))db.select().from(todos).orderBy(desc(todos.priority), asc(todos.updatedAt))複数指定は安定ソート: 第 1 → 第 2 → …
limit と offset — ページング
Section titled “limit と offset — ページング”// 先頭 20 件db.select().from(todos).limit(20)
// 2 ページ目 (21〜40)db.select().from(todos).limit(20).offset(20)
// ソート + ページング — orderBy と併用して deterministic にdb.select() .from(todos) .orderBy(desc(todos.updatedAt)) .limit(20) .offset((page - 1) * 20)innerJoin / leftJoin — join
Section titled “innerJoin / leftJoin — join”db.select() .from(todos) .innerJoin(users, eq(todos.userId, users.id))
// Left join — users.* は nullabledb.select() .from(todos) .leftJoin(users, eq(todos.userId, users.id))Join 後の row 形状:
{ todos: { id, title, ... }, users: { id, name, ... } | null }複数 join:
db.select() .from(todos) .innerJoin(users, eq(todos.userId, users.id)) .leftJoin(comments, eq(comments.todoId, todos.id)) .where(eq(users.id, ctx.userId))groupBy + 集約
Section titled “groupBy + 集約”import { avg, count, max, min, sum } from "@sh1n4ps/plasma-core"
// ユーザーごとの todo 数db.select({ userId: todos.userId, n: count() }) .from(todos) .groupBy(todos.userId)
// priority ごとの averagedb.select({ priority: todos.priority, avg: avg(todos.score) }) .from(todos) .groupBy(todos.priority)groupBy なしの集約は 1 行の結果になります。
having — 集約後フィルタ
Section titled “having — 集約後フィルタ”where はグループ化前の行フィルタ、having は集約後のグループ
フィルタです。
// todo が 3 件超のユーザーのみdb.select({ userId: todos.userId, n: count() }) .from(todos) .groupBy(todos.userId) .having(gt(count(), 3))
// 組み合わせ — 最近完了した todo を、5 件以上あるユーザーだけdb.select({ userId: todos.userId, n: count() }) .from(todos) .where(and(eq(todos.done, 1), gt(todos.updatedAt, cutoff))) .groupBy(todos.userId) .having(gte(count(), 5))fromSubquery + colRef — サブクエリを FROM に
Section titled “fromSubquery + colRef — サブクエリを FROM に”サブクエリの結果を外側クエリの FROM 節として使います。「集約して から集約カラムで絞る」パターンで、単純な WHERE + HAVING では表せ ないケースに便利。
import { colRef, count, gt } from "@sh1n4ps/plasma-core"
// 内側: ユーザーごとの active todo 数const active = db .select({ userId: todos.userId, cnt: count() }) .from(todos) .where(eq(todos.done, 0)) .groupBy(todos.userId)
// 外側: active > 3 のユーザーだけを join して名前も取るconst busy = await db .select() .fromSubquery(active, "active") .innerJoin(users, eq(colRef("active", "userId"), users.id)) .where(gt(colRef("active", "cnt"), 3))colRef(tableAlias, column) はサブクエリの projection から
カラムを参照します。tableAlias は .fromSubquery(inner, "alias")
の第 2 引数と一致させます。
Live と one-shot
Section titled “Live と one-shot”上記の全 builder 形状は .live() で LiveQuery<T> に:
const live = db.select().from(todos).where(eq(todos.done, 0)).live()live.subscribe((rows) => setRows(rows))React の useLiveQuery factory として:
const rows = useLiveQuery( () => plasma.db.select().from(todos).where(eq(todos.done, 0)), [],)Reactivity の詳細は Live queries を 参照。
動的な述語構築
Section titled “動的な述語構築”述語は値のように合成できます — 事前に組み立てて where に渡す:
function todoFilter(criteria: { done?: number; userId?: string }) { const parts = [] if (criteria.done !== undefined) parts.push(eq(todos.done, criteria.done)) if (criteria.userId !== undefined) parts.push(eq(todos.userId, criteria.userId)) if (parts.length === 0) return undefined return parts.length === 1 ? parts[0] : and(...parts)}
const predicate = todoFilter({ done: 0, userId: currentUser })const rows = await db.select().from(todos).where(predicate!)次に読むページ
Section titled “次に読むページ”- Live queries — 上記 builder をリアク ティブ購読にする方法 (IVM 対応 / subscribeDelta / whenReady)
- Mutators — 変更用の同じ
db.insert / update / deleteの書き方 - Schema — このクエリの対象となる column DSL
- Drizzle からの移行 — 全 operator の Drizzle 対応表