qubu 0.0.0 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canonical-BbnqavJm.mjs +972 -0
- package/dist/codegen.d.mts +107 -0
- package/dist/codegen.mjs +1199 -0
- package/dist/column-CXMxx8Hq.mjs +118 -0
- package/dist/complete-D5Djh-zo.mjs +1788 -0
- package/dist/complete-types-BdFqUfbb.d.mts +371 -0
- package/dist/core.d.mts +33 -0
- package/dist/core.mjs +36 -0
- package/dist/ddl.d.mts +118 -0
- package/dist/ddl.mjs +1120 -0
- package/dist/dialect-b2-Z6uBF.mjs +13 -0
- package/dist/diff.d.mts +2 -0
- package/dist/diff.mjs +883 -0
- package/dist/drizzle-mysql.d.mts +24 -0
- package/dist/drizzle-mysql.mjs +72 -0
- package/dist/drizzle-postgres.d.mts +24 -0
- package/dist/drizzle-postgres.mjs +73 -0
- package/dist/drizzle-sqlite.d.mts +24 -0
- package/dist/drizzle-sqlite.mjs +82 -0
- package/dist/drizzle.d.mts +13 -0
- package/dist/drizzle.mjs +2 -0
- package/dist/errors-BGCoLe_r.mjs +14 -0
- package/dist/errors-Dxv73YJu.mjs +26 -0
- package/dist/explain-CkIK13L_.mjs +98 -0
- package/dist/index-Ds7-mhJi.d.mts +146 -0
- package/dist/index-Dug5HnLB.d.mts +204 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1276 -0
- package/dist/introspection.d.mts +117 -0
- package/dist/introspection.mjs +4341 -0
- package/dist/json-CUZlv4HT.mjs +169 -0
- package/dist/migration.d.mts +215 -0
- package/dist/migration.mjs +1161 -0
- package/dist/mysql-DqkqXB6A.mjs +355 -0
- package/dist/mysql.d.mts +5 -0
- package/dist/mysql.mjs +50 -0
- package/dist/naming-QVCOnSj2.mjs +20 -0
- package/dist/omit-OxV58AwX.mjs +5 -0
- package/dist/on-conflict-BxnxubMb.mjs +143 -0
- package/dist/postgres-DEBBeh52.mjs +235 -0
- package/dist/postgres.d.mts +123 -0
- package/dist/postgres.mjs +52 -0
- package/dist/registry-BufIskVN.mjs +569 -0
- package/dist/relational-DCZrrNia.mjs +77 -0
- package/dist/runtime-BTr-MTlo.mjs +193 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.mjs +5 -0
- package/dist/serialize-PF1cfH2P.mjs +556 -0
- package/dist/snapshot-CWPgzxNx.mjs +392 -0
- package/dist/snapshot.d.mts +4 -0
- package/dist/snapshot.mjs +8 -0
- package/dist/source-DUoJVXmL.mjs +68 -0
- package/dist/sqlite-BU6DBxef.mjs +320 -0
- package/dist/sqlite.d.mts +5 -0
- package/dist/sqlite.mjs +48 -0
- package/dist/standard-BTVYKh_F.mjs +12 -0
- package/dist/table-llv9tsZ8.mjs +384 -0
- package/dist/types-4Q076HKo.d.mts +3636 -0
- package/dist/types-BX0mckiU.d.mts +644 -0
- package/dist/types-CO1KaRNc.d.mts +46 -0
- package/dist/types-Cec0xzo4.mjs +116 -0
- package/dist/value-BvilP0oz.mjs +29 -0
- package/dist/vite/ambient.d.ts +449 -0
- package/dist/vite.d.mts +35 -0
- package/dist/vite.mjs +498 -0
- package/docs/config.json +21 -0
- package/docs/dialects-and-execution.md +427 -0
- package/docs/getting-started.md +96 -0
- package/docs/guides/compose-queries.md +225 -0
- package/docs/guides/drizzle.md +129 -0
- package/docs/guides/extensions/dialects.md +40 -0
- package/docs/guides/extensions/overview.md +36 -0
- package/docs/guides/extensions/sources-and-clauses.md +82 -0
- package/docs/guides/extensions/typed-expressions.md +94 -0
- package/docs/guides/extensions/unsafe-syntax.md +27 -0
- package/docs/guides/json.md +65 -0
- package/docs/guides/mutations.md +138 -0
- package/docs/guides/select/conditions.md +110 -0
- package/docs/guides/select/grouping-and-windows.md +82 -0
- package/docs/guides/select/ordering-and-pagination.md +78 -0
- package/docs/guides/select/overview.md +139 -0
- package/docs/guides/sql-templates.md +157 -0
- package/docs/guides/vite-plugin.md +92 -0
- package/docs/index.md +111 -0
- package/docs/query-model/fragments.md +109 -0
- package/docs/query-model/result-shapes.md +125 -0
- package/docs/query-model/source-scope.md +182 -0
- package/docs/reference/introspection-support.md +196 -0
- package/docs/reference/mysql-snapshot.md +55 -0
- package/docs/reference/postgres-snapshot.md +61 -0
- package/docs/reference/sqlite-snapshot.md +53 -0
- package/docs/reference/supported-surface.md +104 -0
- package/docs/schema/catalog-model.md +58 -0
- package/docs/schema/code-generation.md +139 -0
- package/docs/schema/columns-and-writes.md +130 -0
- package/docs/schema/constraints-and-indexes.md +180 -0
- package/docs/schema/ddl-emission.md +75 -0
- package/docs/schema/diff.md +75 -0
- package/docs/schema/introspection.md +214 -0
- package/docs/schema/migration-plans.md +76 -0
- package/docs/schema/snapshots.md +93 -0
- package/docs/schema/storage-and-schema-sql.md +104 -0
- package/docs/schema/tables-and-names.md +106 -0
- package/docs/sql-semantic-types.md +141 -0
- package/docs/troubleshooting.md +151 -0
- package/package.json +132 -10
- package/skills/qubu/SKILL.md +42 -0
- package/skills/qubu/agents/openai.yaml +6 -0
- package/readme.md +0 -1
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Dialects and execution
|
|
2
|
+
|
|
3
|
+
> Keep portable query construction separate from placeholder, identifier, pagination, cast-target, and driver decisions at the rendering boundary.
|
|
4
|
+
|
|
5
|
+
## Render once, choose a policy at the boundary
|
|
6
|
+
|
|
7
|
+
`render()` returns a `RenderedQuery` with SQL text and raw parameter values. The
|
|
8
|
+
default renderer uses Qubu's standard SQL policy. Select a concrete dialect
|
|
9
|
+
subpath when a driver needs another placeholder, identifier, pagination, or
|
|
10
|
+
capability policy:
|
|
11
|
+
|
|
12
|
+
| Dialect | Import | Identifiers | Placeholders | Pagination policy |
|
|
13
|
+
| ------------ | ------------------- | ------------- | --------------- | ------------------------------------- |
|
|
14
|
+
| Standard SQL | `render(query)` | double quotes | `?` | `OFFSET ... ROWS FETCH ... ROWS ONLY` |
|
|
15
|
+
| PostgreSQL | `postgresDialect()` | double quotes | `$1`, `$2`, ... | `LIMIT ... OFFSET ...`; `ILIKE` |
|
|
16
|
+
| SQLite | `sqliteDialect()` | double quotes | `?` | `LIMIT ... OFFSET ...` |
|
|
17
|
+
| MySQL | `mysqlDialect()` | backticks | `?` | `LIMIT ... OFFSET ...` |
|
|
18
|
+
|
|
19
|
+
Construct the query without choosing a driver, then render it with the policy
|
|
20
|
+
the adapter expects:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { render } from 'qubu'
|
|
24
|
+
import { postgresDialect } from 'qubu/postgres'
|
|
25
|
+
|
|
26
|
+
const standard = render(query)
|
|
27
|
+
const postgres = render(query, postgresDialect())
|
|
28
|
+
|
|
29
|
+
standard.text
|
|
30
|
+
// ... WHERE ("users"."id" = ?)
|
|
31
|
+
|
|
32
|
+
postgres.text
|
|
33
|
+
// ... WHERE ("users"."id" = $1)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Capability requirements
|
|
37
|
+
|
|
38
|
+
Portable syntax stays portable, while dialect-specific syntax carries a
|
|
39
|
+
capability requirement to the rendering boundary. PostgreSQL's `ilike()` is
|
|
40
|
+
the first such feature:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { from, like, render, select, where } from 'qubu'
|
|
44
|
+
import { ilike, postgresDialect } from 'qubu/postgres'
|
|
45
|
+
import { sqliteDialect } from 'qubu/sqlite'
|
|
46
|
+
|
|
47
|
+
const postgresQuery = select(
|
|
48
|
+
{ name: users.name },
|
|
49
|
+
from(users),
|
|
50
|
+
where(ilike(users.name, '%ada%'))
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
render(postgresQuery, postgresDialect()) // supported
|
|
54
|
+
render(postgresQuery, sqliteDialect()) // TypeScript error
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The same check runs at runtime when a dialect or query has been widened or
|
|
58
|
+
received from an untyped integration. Use the portable operator when the
|
|
59
|
+
query must render across dialects:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { from, like, render, select, where } from 'qubu'
|
|
63
|
+
import { sqliteDialect } from 'qubu/sqlite'
|
|
64
|
+
|
|
65
|
+
const portableQuery = select(
|
|
66
|
+
{ name: users.name },
|
|
67
|
+
from(users),
|
|
68
|
+
where(like(users.name, '%ada%'))
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
render(portableQuery)
|
|
72
|
+
render(portableQuery, sqliteDialect())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Custom dialects that implement a supported capability advertise it through
|
|
76
|
+
`createDialect({ capabilities: ['ilike'] })`. A dialect without that
|
|
77
|
+
advertisement is rejected for capability-bearing fragments.
|
|
78
|
+
|
|
79
|
+
Import `ilike` and `postgresDialect` from `qubu/postgres`. The root entrypoint
|
|
80
|
+
does not re-export concrete dialect constructors.
|
|
81
|
+
|
|
82
|
+
## The adapter owns the driver
|
|
83
|
+
|
|
84
|
+
Qubu does not open connections, bind values for a particular client, or decode
|
|
85
|
+
rows. An adapter receives an `ExecutionRequest` and returns an
|
|
86
|
+
`ExecutionResult`. A `TransactionalQueryAdapter` can also pin one driver
|
|
87
|
+
connection for a callback transaction:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { qubu } from 'qubu'
|
|
91
|
+
import { postgresDialect } from 'qubu/postgres'
|
|
92
|
+
import type { ExecutionRequest, ExecutionResult, QueryAdapter } from 'qubu'
|
|
93
|
+
|
|
94
|
+
declare const driver: {
|
|
95
|
+
query<TRow extends object>(
|
|
96
|
+
text: string,
|
|
97
|
+
parameters: readonly unknown[],
|
|
98
|
+
options: { signal?: AbortSignal }
|
|
99
|
+
): Promise<{ rows: readonly TRow[]; rowCount: number | null }>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const adapter: QueryAdapter = {
|
|
103
|
+
dialect: postgresDialect(),
|
|
104
|
+
async execute<TRow extends object>(request: ExecutionRequest) {
|
|
105
|
+
const { statement, queryKind, signal } = request
|
|
106
|
+
const result = await driver.query<TRow>(
|
|
107
|
+
statement.text,
|
|
108
|
+
statement.parameters,
|
|
109
|
+
{ signal }
|
|
110
|
+
)
|
|
111
|
+
return {
|
|
112
|
+
rows: result.rows,
|
|
113
|
+
...(queryKind !== 'select' &&
|
|
114
|
+
queryKind !== 'set' &&
|
|
115
|
+
result.rowCount !== null
|
|
116
|
+
? { affectedRows: result.rowCount }
|
|
117
|
+
: {}),
|
|
118
|
+
} satisfies ExecutionResult<TRow>
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Stream read results
|
|
124
|
+
|
|
125
|
+
Add `StreamingQueryAdapter` when a driver can return rows through an
|
|
126
|
+
adapter-owned `AsyncIterable`. The standalone `stream()` function and the
|
|
127
|
+
bound `db.stream()` method accept only `SELECT` and set-operation queries.
|
|
128
|
+
Mutations stay on `execute()` and `executeRows()`, including mutations with
|
|
129
|
+
`RETURNING`.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { qubu } from 'qubu'
|
|
133
|
+
import type {
|
|
134
|
+
ExecutionRequest,
|
|
135
|
+
ExecutionResult,
|
|
136
|
+
StreamingQueryAdapter,
|
|
137
|
+
} from 'qubu'
|
|
138
|
+
import { postgresDialect } from 'qubu/postgres'
|
|
139
|
+
|
|
140
|
+
declare const driver: {
|
|
141
|
+
query<TRow extends object>(
|
|
142
|
+
text: string,
|
|
143
|
+
parameters: readonly unknown[],
|
|
144
|
+
options: { signal?: AbortSignal }
|
|
145
|
+
): Promise<{ rows: readonly TRow[]; rowCount: number | null }>
|
|
146
|
+
stream<TRow extends object>(
|
|
147
|
+
text: string,
|
|
148
|
+
parameters: readonly unknown[],
|
|
149
|
+
options: { signal?: AbortSignal }
|
|
150
|
+
): AsyncIterable<TRow>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const adapter: StreamingQueryAdapter = {
|
|
154
|
+
dialect: postgresDialect(),
|
|
155
|
+
async execute<TRow extends object>(request: ExecutionRequest) {
|
|
156
|
+
const result = await driver.query<TRow>(
|
|
157
|
+
request.statement.text,
|
|
158
|
+
request.statement.parameters,
|
|
159
|
+
{ signal: request.signal }
|
|
160
|
+
)
|
|
161
|
+
return {
|
|
162
|
+
rows: result.rows,
|
|
163
|
+
...(result.rowCount === null ? {} : { affectedRows: result.rowCount }),
|
|
164
|
+
} satisfies ExecutionResult<TRow>
|
|
165
|
+
},
|
|
166
|
+
stream<TRow extends object>(request: ExecutionRequest) {
|
|
167
|
+
return driver.stream<TRow>(
|
|
168
|
+
request.statement.text,
|
|
169
|
+
request.statement.parameters,
|
|
170
|
+
{ signal: request.signal }
|
|
171
|
+
)
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const db = qubu(adapter)
|
|
176
|
+
for await (const row of db.stream(readQuery)) {
|
|
177
|
+
consume(row)
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Qubu renders the query before calling `stream()` and passes the selected
|
|
182
|
+
dialect, ordered raw parameters, query kind, and optional `AbortSignal` in the
|
|
183
|
+
same `ExecutionRequest` used by `execute()`. The adapter binds values, decodes
|
|
184
|
+
rows, and returns the iterable. Qubu does not open a cursor or connection,
|
|
185
|
+
iterate the result, buffer rows, or impose a fetch size.
|
|
186
|
+
|
|
187
|
+
The adapter owns the iterator's cleanup contract:
|
|
188
|
+
|
|
189
|
+
| Event | Adapter responsibility |
|
|
190
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
191
|
+
| Normal completion | Close the cursor and release stream-only resources before the iterable completes. |
|
|
192
|
+
| Early iterator close | Implement `return()` so a consumer can stop without leaking resources. |
|
|
193
|
+
| Iteration failure | Close the cursor and release resources before the failure reaches the consumer. |
|
|
194
|
+
| Aborted signal | Stop the driver operation and clean up any open stream resources. |
|
|
195
|
+
| Transaction callback completion | Consume or close every stream before the callback resolves, then commit or release the transaction resources. |
|
|
196
|
+
|
|
197
|
+
`for await` closes an iterator when a loop exits early. Code that manually
|
|
198
|
+
holds an iterator should close it in a `finally` block:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const iterator = db.stream(readQuery)[Symbol.asyncIterator]()
|
|
202
|
+
try {
|
|
203
|
+
const first = await iterator.next()
|
|
204
|
+
if (!first.done) consume(first.value)
|
|
205
|
+
} finally {
|
|
206
|
+
await iterator.return?.()
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Inside a transaction, use a `StreamingTransactionalQueryAdapter` so the
|
|
211
|
+
transaction callback receives a streaming client. The adapter must keep its
|
|
212
|
+
cursor and connection valid until the callback's streams finish or close:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
declare const transactionalAdapter: import('qubu').StreamingTransactionalQueryAdapter
|
|
216
|
+
const transactionalDb = qubu(transactionalAdapter)
|
|
217
|
+
|
|
218
|
+
await transactionalDb.transaction(async transaction => {
|
|
219
|
+
for await (const row of transaction.stream(readQuery)) {
|
|
220
|
+
consume(row)
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The adapter decides how `next()` drives driver reads, whether it prefetches,
|
|
226
|
+
and how much data it buffers. Qubu only forwards the async-iterator protocol
|
|
227
|
+
and the abort signal. Driver errors and cancellation errors pass through
|
|
228
|
+
unchanged.
|
|
229
|
+
|
|
230
|
+
## Inspect query plans
|
|
231
|
+
|
|
232
|
+
Add `ExplainableQueryAdapter` when the driver can decode its EXPLAIN rows. The
|
|
233
|
+
standalone `explain()` function and the bound `db.explain()` method render a
|
|
234
|
+
plan request without calling `execute()`:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { explain, qubu } from 'qubu'
|
|
238
|
+
import type { ExplainableQueryAdapter } from 'qubu'
|
|
239
|
+
import { postgresDialect } from 'qubu/postgres'
|
|
240
|
+
|
|
241
|
+
type PostgresPlanRow = { 'QUERY PLAN': string }
|
|
242
|
+
|
|
243
|
+
declare const driver: {
|
|
244
|
+
query<TRow extends object>(
|
|
245
|
+
text: string,
|
|
246
|
+
parameters: readonly unknown[],
|
|
247
|
+
options: { signal?: AbortSignal }
|
|
248
|
+
): Promise<{ rows: readonly TRow[] }>
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const adapter: ExplainableQueryAdapter<PostgresPlanRow> = {
|
|
252
|
+
dialect: postgresDialect(),
|
|
253
|
+
async execute() {
|
|
254
|
+
return { rows: [] }
|
|
255
|
+
},
|
|
256
|
+
async explain(request) {
|
|
257
|
+
const result = await driver.query<PostgresPlanRow>(
|
|
258
|
+
request.statement.text,
|
|
259
|
+
request.statement.parameters,
|
|
260
|
+
{ signal: request.signal }
|
|
261
|
+
)
|
|
262
|
+
return { rows: result.rows }
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const plan = await explain(readQuery, adapter, {
|
|
267
|
+
analyze: true,
|
|
268
|
+
verbose: true,
|
|
269
|
+
})
|
|
270
|
+
const db = qubu(adapter)
|
|
271
|
+
const samePlan = await db.explain(readQuery)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Qubu keeps `ExplainResult.rows` in the adapter's vendor-specific shape. It
|
|
275
|
+
does not normalize PostgreSQL, SQLite, or MySQL plans into one tree. The
|
|
276
|
+
adapter owns parameter binding, plan-row decoding, connections, transactions,
|
|
277
|
+
and cancellation. `ExplainRequest` carries the rendered statement, ordered
|
|
278
|
+
raw parameters, query kind, and optional abort signal just like an ordinary
|
|
279
|
+
execution request.
|
|
280
|
+
|
|
281
|
+
The first-party policies accept these options:
|
|
282
|
+
|
|
283
|
+
| Dialect | Plan options | Restrictions |
|
|
284
|
+
| ---------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
285
|
+
| PostgreSQL | `analyze`, `verbose`, `buffers`, and `format: 'text' \| 'xml' \| 'json' \| 'yaml'` | `buffers` requires `analyze`; analysis is read-only |
|
|
286
|
+
| SQLite | `format: 'query-plan' \| 'bytecode'` or `queryPlan` | `analyze`, `verbose`, and `buffers` are unsupported |
|
|
287
|
+
| MySQL | `analyze` or `format: 'traditional' \| 'json' \| 'tree'` | `analyze` cannot be combined with `format`; analysis is read-only |
|
|
288
|
+
|
|
289
|
+
All supported queries can be explained, including `INSERT`, `UPDATE`, and
|
|
290
|
+
`DELETE`. Mutation EXPLAIN is always plan-only. The type and runtime
|
|
291
|
+
boundaries reject `analyze` for mutations so an inspection call cannot apply a
|
|
292
|
+
write. Unsupported options and invalid combinations raise a structured
|
|
293
|
+
`QueryValidationError` before the adapter is called.
|
|
294
|
+
|
|
295
|
+
## Bind the adapter once
|
|
296
|
+
|
|
297
|
+
Use `qubu()` when several calls share one adapter. The returned client keeps
|
|
298
|
+
the adapter available as `db.adapter` and accepts the same execution options as
|
|
299
|
+
the standalone functions:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
const db = qubu(adapter)
|
|
303
|
+
|
|
304
|
+
const controller = new AbortController()
|
|
305
|
+
const result = await db.execute(query, {
|
|
306
|
+
signal: controller.signal,
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
result.rows
|
|
310
|
+
result.affectedRows
|
|
311
|
+
|
|
312
|
+
const rows = await db.rows(readQuery)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`db.execute()` returns the structured result. `db.rows()` returns only its row
|
|
316
|
+
array. Both methods infer each row from the query projection. They do not make
|
|
317
|
+
query values executable or transfer connection ownership to Qubu.
|
|
318
|
+
|
|
319
|
+
## Run a transaction
|
|
320
|
+
|
|
321
|
+
Use a transactional adapter when several queries must share one commit or
|
|
322
|
+
rollback boundary:
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
import { qubu } from 'qubu'
|
|
326
|
+
import type { TransactionalQueryAdapter } from 'qubu'
|
|
327
|
+
|
|
328
|
+
declare const transactionalAdapter: TransactionalQueryAdapter
|
|
329
|
+
const transactionalDb = qubu(transactionalAdapter)
|
|
330
|
+
|
|
331
|
+
const result = await transactionalDb.transaction(async transaction => {
|
|
332
|
+
await transaction.execute(firstMutation)
|
|
333
|
+
await transaction.execute(secondMutation)
|
|
334
|
+
return transaction.rows(readQuery)
|
|
335
|
+
})
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The adapter owns the driver lifecycle. It acquires and pins one connection,
|
|
339
|
+
begins the transaction, invokes the callback, commits after it resolves, rolls
|
|
340
|
+
back after it rejects, and releases the connection in every case. Qubu only
|
|
341
|
+
creates the scoped client and passes the callback result through. It never
|
|
342
|
+
emits `BEGIN`, `COMMIT`, or `ROLLBACK` itself.
|
|
343
|
+
|
|
344
|
+
The transaction client exposes `execute()` and `rows()` but no public
|
|
345
|
+
`transaction()` method, so nested transactions are not part of this contract.
|
|
346
|
+
When the adapter also implements `StreamingQueryAdapter`, the scoped client
|
|
347
|
+
also exposes `stream()` and its streams follow the cleanup rule above. Use
|
|
348
|
+
adapter-specific savepoints when a driver needs nested partial rollback.
|
|
349
|
+
`TransactionOptions.signal` is passed to the adapter. Isolation levels and
|
|
350
|
+
other driver-specific settings remain adapter-specific.
|
|
351
|
+
|
|
352
|
+
The standalone functions remain useful when the adapter varies by call or a
|
|
353
|
+
small module does not need a bound client:
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
import { execute, executeRows } from 'qubu'
|
|
357
|
+
|
|
358
|
+
const result = await execute(query, adapter)
|
|
359
|
+
const rows = await executeRows(readQuery, adapter)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
| Result field | Type | Adapter contract |
|
|
363
|
+
| -------------- | ---------------------------- | ------------------------------------------------------------------------------------- |
|
|
364
|
+
| `rows` | `readonly TRow[]` | Always present; use an empty array for a mutation without returned rows |
|
|
365
|
+
| `affectedRows` | `number \| bigint` | Rows inserted, updated, or deleted when the driver reports an affected count |
|
|
366
|
+
| `changedRows` | `number \| bigint` | Rows whose stored values changed when the driver distinguishes them from matched rows |
|
|
367
|
+
| `insertId` | `string \| number \| bigint` | One insert identifier when the driver reports it |
|
|
368
|
+
|
|
369
|
+
The last three fields are optional. For example, an adapter can map PostgreSQL
|
|
370
|
+
`rowCount`, MySQL `affectedRows`, `changedRows`, and `insertId`, or SQLite
|
|
371
|
+
`changes` and `lastInsertRowid`. Omit a fact that the selected driver cannot
|
|
372
|
+
report accurately. Qubu does not derive mutation metadata from returned rows.
|
|
373
|
+
|
|
374
|
+
The adapter's `dialect` becomes the default for standalone and bound execution.
|
|
375
|
+
A `dialect` in the execution options overrides that rendering policy. Qubu
|
|
376
|
+
passes `signal` and the query's `queryKind` to the adapter without changing
|
|
377
|
+
them. The adapter decides whether and how its driver supports cancellation.
|
|
378
|
+
Driver errors pass through unchanged.
|
|
379
|
+
|
|
380
|
+
```mermaid
|
|
381
|
+
sequenceDiagram
|
|
382
|
+
participant App as Application
|
|
383
|
+
participant Qubu as Qubu renderer
|
|
384
|
+
participant Adapter as QueryAdapter
|
|
385
|
+
participant Driver as Database driver
|
|
386
|
+
|
|
387
|
+
App->>Qubu: db.execute(query, options)
|
|
388
|
+
Qubu->>Qubu: render with selected dialect
|
|
389
|
+
Qubu->>Adapter: statement + queryKind + signal
|
|
390
|
+
Adapter->>Driver: bind and execute
|
|
391
|
+
Driver-->>Adapter: driver result or error
|
|
392
|
+
Adapter-->>Qubu: typed rows + optional mutation facts
|
|
393
|
+
Qubu-->>App: result envelope or rows
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Create a small custom dialect
|
|
397
|
+
|
|
398
|
+
Use `createDialect()` when a driver needs a different policy but the query
|
|
399
|
+
syntax stays portable:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import { render } from 'qubu'
|
|
403
|
+
import { createDialect } from 'qubu/core'
|
|
404
|
+
|
|
405
|
+
const namedParameters = createDialect({
|
|
406
|
+
name: 'named-parameters',
|
|
407
|
+
placeholder: position => `:p${position}`,
|
|
408
|
+
castTypes: { text: 'STRING' },
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
const statement = render(query, namedParameters)
|
|
412
|
+
// ... WHERE ("users"."id" = :p1)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
`castTypes` overrides how logical targets from definitions such as `text()`
|
|
416
|
+
render in `CAST` expressions. Omitted entries use the standard spelling. A
|
|
417
|
+
custom definition's explicit `castType` is emitted verbatim instead of passing
|
|
418
|
+
through this map.
|
|
419
|
+
|
|
420
|
+
For syntax that is not a small policy decision, add a [custom fragment or
|
|
421
|
+
clause](guides/extensions/overview.md) instead of making the standard dialect
|
|
422
|
+
pretend that vendor behavior is portable.
|
|
423
|
+
|
|
424
|
+
> [!WARNING]
|
|
425
|
+
> `RenderedQuery.parameters` contains raw application values. The adapter must
|
|
426
|
+
> bind or encode them using the driver API; do not concatenate them into
|
|
427
|
+
> `RenderedQuery.text`.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
> Define a typed table, build one parameterized query, and inspect the exact SQL before connecting a driver.
|
|
4
|
+
|
|
5
|
+
## Install Qubu
|
|
6
|
+
|
|
7
|
+
Add the package to a TypeScript project:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add qubu
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Import query-building functions from the package root. Qubu does not need a
|
|
14
|
+
database connection to construct or render a query.
|
|
15
|
+
|
|
16
|
+
The examples use the same order as the rendered statement: projection, `FROM`,
|
|
17
|
+
then `WHERE`, ordering, grouping, and pagination. `select()` still accepts
|
|
18
|
+
independent clauses in any order, which lets reusable values be composed, but
|
|
19
|
+
keeping the final call in SQL order makes the query easy to scan and repair.
|
|
20
|
+
|
|
21
|
+
## Define a table
|
|
22
|
+
|
|
23
|
+
Use `table()` once for each query-facing table. Column helpers describe the
|
|
24
|
+
application values that can be selected and, for mutations, written.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { integer, table, text } from 'qubu'
|
|
28
|
+
|
|
29
|
+
const users = table('users', {
|
|
30
|
+
id: integer(),
|
|
31
|
+
name: text(),
|
|
32
|
+
email: text({ nullable: true }),
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`users.id`, `users.name`, and `users.email` are typed column expressions. The
|
|
37
|
+
nullable email column is inferred as `string | null` when selected.
|
|
38
|
+
|
|
39
|
+
## Build and render a query
|
|
40
|
+
|
|
41
|
+
Pass a named projection and the final clauses to `select()` in SQL order. Qubu
|
|
42
|
+
also accepts independent clause values in another order when composition needs
|
|
43
|
+
it, then renders the normalized statement in SQL order. The example uses the
|
|
44
|
+
`users` table from the previous section.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { eq, from, render, select, where } from 'qubu'
|
|
48
|
+
|
|
49
|
+
const query = select(
|
|
50
|
+
{
|
|
51
|
+
id: users.id,
|
|
52
|
+
displayName: users.name,
|
|
53
|
+
},
|
|
54
|
+
from(users),
|
|
55
|
+
where(eq(users.id, 7))
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const statement = render(query)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The default dialect quotes identifiers with double quotes and uses `?` for
|
|
62
|
+
parameters:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
statement.text
|
|
66
|
+
// SELECT "users"."id" AS "id", "users"."name" AS "displayName" FROM "users" WHERE ("users"."id" = ?)
|
|
67
|
+
|
|
68
|
+
statement.parameters
|
|
69
|
+
// [7]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The selected row type is available on the query value:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
type UserRow = typeof query.row
|
|
76
|
+
// { id: number; displayName: string }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> [!NOTE]
|
|
80
|
+
> Rendering produces a statement; it does not execute it. Keep the
|
|
81
|
+
> `RenderedQuery` value for logging or testing. Bind an
|
|
82
|
+
> [application-owned adapter](dialects-and-execution.md) with `qubu()`, or use
|
|
83
|
+
> `execute()` and `executeRows()` directly, to run the query.
|
|
84
|
+
|
|
85
|
+
## Next steps
|
|
86
|
+
|
|
87
|
+
- [Build a `SELECT`](guides/select/overview.md) with joins, predicates, aggregates, and
|
|
88
|
+
pagination.
|
|
89
|
+
- [Compose queries](guides/compose-queries.md) from CTEs and derived sources.
|
|
90
|
+
- [Write mutations](guides/mutations.md) with typed insert/update/delete
|
|
91
|
+
inputs.
|
|
92
|
+
- [Choose a database dialect](dialects-and-execution.md) when the
|
|
93
|
+
driver expects different identifier, placeholder, or pagination syntax.
|
|
94
|
+
- [Read JSON scalars](guides/json.md) from a JSON column.
|
|
95
|
+
- [Use the Vite compiler hint](guides/vite-plugin.md) for directive-based
|
|
96
|
+
imports.
|