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,225 @@
|
|
|
1
|
+
# Compose queries
|
|
2
|
+
|
|
3
|
+
> Reuse a query's inferred row shape as a typed source for CTEs, derived tables, subqueries, and set operations.
|
|
4
|
+
|
|
5
|
+
## Turn a query into a CTE
|
|
6
|
+
|
|
7
|
+
`cte()` exposes the selected fields of a query as columns on a new source.
|
|
8
|
+
Attach it with `withCte()` and use the CTE source in `from()`:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import {
|
|
12
|
+
cte,
|
|
13
|
+
eq,
|
|
14
|
+
from,
|
|
15
|
+
integer,
|
|
16
|
+
select,
|
|
17
|
+
table,
|
|
18
|
+
text,
|
|
19
|
+
where,
|
|
20
|
+
withCte,
|
|
21
|
+
} from 'qubu'
|
|
22
|
+
|
|
23
|
+
const users = table('users', {
|
|
24
|
+
id: integer(),
|
|
25
|
+
name: text(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const activeUsers = cte(
|
|
29
|
+
'active_users',
|
|
30
|
+
select(
|
|
31
|
+
{ id: users.id, name: users.name },
|
|
32
|
+
from(users),
|
|
33
|
+
where(eq(users.id, 7))
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const report = select(
|
|
38
|
+
{ displayName: activeUsers.name },
|
|
39
|
+
withCte(activeUsers),
|
|
40
|
+
from(activeUsers)
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`activeUsers.name` is a typed column derived from the first query's row shape.
|
|
45
|
+
The rendered statement includes the `WITH` clause before `SELECT`. Selected
|
|
46
|
+
camelCase keys use snake_case while they belong to the CTE relation; the outer
|
|
47
|
+
result projection aliases them back to camelCase for the returned row.
|
|
48
|
+
|
|
49
|
+
## Build a recursive CTE
|
|
50
|
+
|
|
51
|
+
`recursiveCte()` uses the anchor projection as the contract for a recursive
|
|
52
|
+
member. The callback receives a typed self-reference; introduce it through
|
|
53
|
+
`from()` or a join before selecting its fields:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import {
|
|
57
|
+
add,
|
|
58
|
+
cast,
|
|
59
|
+
from,
|
|
60
|
+
integer,
|
|
61
|
+
lt,
|
|
62
|
+
recursiveCte,
|
|
63
|
+
select,
|
|
64
|
+
value,
|
|
65
|
+
where,
|
|
66
|
+
withCte,
|
|
67
|
+
} from 'qubu'
|
|
68
|
+
|
|
69
|
+
const numbers = recursiveCte(
|
|
70
|
+
'numbers',
|
|
71
|
+
select({ value: cast(value(1), integer()) }),
|
|
72
|
+
self =>
|
|
73
|
+
select({ value: add(self.value, 1) }, from(self), where(lt(self.value, 3)))
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const query = select({ value: numbers.value }, withCte(numbers), from(numbers))
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The anchor names the fields, application types, nullability, and SQL domains
|
|
80
|
+
that the returned source exposes. The member must project those same fields
|
|
81
|
+
with compatible types. Give bound anchor values an explicit SQL type with
|
|
82
|
+
`cast()` when the database cannot infer it from surrounding columns; PostgreSQL
|
|
83
|
+
requires this for recursive CTE anchors. Qubu renders `WITH RECURSIVE`, an
|
|
84
|
+
explicit relation column list, and `anchor UNION ALL member`; ordinary and
|
|
85
|
+
recursive CTEs can share one `withCte()` clause.
|
|
86
|
+
|
|
87
|
+
## Use a derived table
|
|
88
|
+
|
|
89
|
+
Alias a query when it should be used as an inline source:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { alias, from, lower, select } from 'qubu'
|
|
93
|
+
import type { SqlTypeOf } from 'qubu'
|
|
94
|
+
|
|
95
|
+
const names = select({ name: lower(users.name) }, from(users))
|
|
96
|
+
const namesSource = alias(names, 'names')
|
|
97
|
+
|
|
98
|
+
const query = select({ name: namesSource.name }, from(namesSource))
|
|
99
|
+
type NameSqlDomain = SqlTypeOf<typeof namesSource.name>
|
|
100
|
+
// SqlText
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The alias gets the selected query's fields, while the new source identity keeps
|
|
104
|
+
scope checks from confusing `namesSource.name` with `users.name`.
|
|
105
|
+
|
|
106
|
+
Its SQL domain is retained too. The projected `lower(users.name)` remains
|
|
107
|
+
`SqlText` through this query alias, and the same preservation applies to a CTE,
|
|
108
|
+
so downstream text operations remain checked without redeclaring the field.
|
|
109
|
+
|
|
110
|
+
## Nest a scalar subquery
|
|
111
|
+
|
|
112
|
+
`scalar()` turns a query with exactly one selected field into an expression:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { from, scalar, select, value } from 'qubu'
|
|
116
|
+
|
|
117
|
+
const firstId = select({ id: users.id }, from(users))
|
|
118
|
+
const query = select(
|
|
119
|
+
{
|
|
120
|
+
name: users.name,
|
|
121
|
+
firstId: scalar(firstId),
|
|
122
|
+
},
|
|
123
|
+
from(users)
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`scalar()` throws at runtime when the query selects more than one field. Its
|
|
128
|
+
type is the selected field's value type, widened with `null` when the query may
|
|
129
|
+
return no rows. An ordinary select and `fetchFirst(1)` are both nullable: the
|
|
130
|
+
limit proves at most one row, not that a row exists. A source-free select such
|
|
131
|
+
as `select({ value: value(42) })` is known to produce exactly one row.
|
|
132
|
+
|
|
133
|
+
Qubu does not treat an arbitrary predicate as proof of exactness. Use
|
|
134
|
+
`exists()`, `notExists()`, or `inQuery()` for boolean subquery predicates.
|
|
135
|
+
|
|
136
|
+
## Combine compatible queries
|
|
137
|
+
|
|
138
|
+
Set operations preserve the left query's row shape. Both queries must select
|
|
139
|
+
compatible rows:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { eq, from, select, unionAll, where } from 'qubu'
|
|
143
|
+
|
|
144
|
+
const first = select({ id: users.id }, from(users))
|
|
145
|
+
const second = select({ id: users.id }, from(users), where(eq(users.id, 7)))
|
|
146
|
+
|
|
147
|
+
const allUsers = unionAll(first, second)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Use `union`, `unionAll`, `intersect`, or `except` depending on the SQL
|
|
151
|
+
operation. Each input renderer still contributes its runtime parameters, which
|
|
152
|
+
are collected in traversal order.
|
|
153
|
+
|
|
154
|
+
## Prefer values over a builder chain
|
|
155
|
+
|
|
156
|
+
Build reusable pieces as ordinary values and pass them into the final query:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { desc, eq, from, orderBy, select, where } from 'qubu'
|
|
160
|
+
|
|
161
|
+
const byId = where(eq(users.id, 7))
|
|
162
|
+
const newest = orderBy(desc(users.id))
|
|
163
|
+
|
|
164
|
+
const query = select(
|
|
165
|
+
{ id: users.id, name: users.name },
|
|
166
|
+
from(users),
|
|
167
|
+
byId,
|
|
168
|
+
newest
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
This makes it possible to share a predicate or projection without mutating a
|
|
173
|
+
query object. The final `select()` call remains the place where source scope
|
|
174
|
+
and result shape are checked. Qubu also accepts these independent values in
|
|
175
|
+
another order, but SQL order is the canonical visual style for finished query
|
|
176
|
+
code.
|
|
177
|
+
|
|
178
|
+
## Constrain a reusable fragment by required fields
|
|
179
|
+
|
|
180
|
+
Use `TableLike` when a fragment requires a physical table and `SourceLike`
|
|
181
|
+
when aliases, CTEs, derived tables, or custom sources are also valid. Both are
|
|
182
|
+
lower-bound constraints: the source may contain additional fields, and the
|
|
183
|
+
generic function retains its exact source identity.
|
|
184
|
+
|
|
185
|
+
For an application-level requirement, describe the required JavaScript row:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import { eq, where } from 'qubu'
|
|
189
|
+
import type { TableLike } from 'qubu'
|
|
190
|
+
|
|
191
|
+
function byStringId<TTable extends TableLike<{ id: string }>>(
|
|
192
|
+
table: TTable,
|
|
193
|
+
id: string
|
|
194
|
+
) {
|
|
195
|
+
return where(eq(table.columns.id, id))
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`{ id: string }` means a non-null selected string. It accepts a table with
|
|
200
|
+
extra fields and rejects `string | null`, but it does not distinguish
|
|
201
|
+
`SqlText` from `SqlUuid` because both have a JavaScript output of `string`.
|
|
202
|
+
|
|
203
|
+
Use `FieldLike` when the fragment depends on SQL semantics:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { eq, where } from 'qubu'
|
|
207
|
+
import type { FieldLike, SourceLike, SqlTextLike } from 'qubu'
|
|
208
|
+
|
|
209
|
+
type NonNullTextId = FieldLike<{
|
|
210
|
+
sqlType: SqlTextLike
|
|
211
|
+
nullable: false
|
|
212
|
+
}>
|
|
213
|
+
|
|
214
|
+
function byTextId<TSource extends SourceLike<{ id: NonNullTextId }>>(
|
|
215
|
+
source: TSource,
|
|
216
|
+
id: string
|
|
217
|
+
) {
|
|
218
|
+
return where(eq(source.columns.id, id))
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
This version accepts known text-like and permissive `SqlUnknown` fields. It
|
|
223
|
+
rejects nullable text and known non-text domains such as `SqlUuid`. Add an
|
|
224
|
+
`output` property to the `FieldLike` descriptor when the fragment also needs a
|
|
225
|
+
specific JavaScript result type.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Use a Qubu schema with Drizzle
|
|
2
|
+
|
|
3
|
+
> Derive Drizzle tables at runtime so Qubu and Drizzle queries can share one
|
|
4
|
+
> schema declaration during a gradual migration.
|
|
5
|
+
|
|
6
|
+
## Install the optional integration
|
|
7
|
+
|
|
8
|
+
Install Drizzle next to Qubu. The Drizzle entrypoints use an optional peer
|
|
9
|
+
dependency, so importing the rest of Qubu does not load the ORM.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add qubu drizzle-orm
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The converter supports PostgreSQL, MySQL, and SQLite with Drizzle 0.45.2 through
|
|
16
|
+
the current 0.x line.
|
|
17
|
+
|
|
18
|
+
## Convert the schema
|
|
19
|
+
|
|
20
|
+
Declare tables and the root registry with Qubu, then import the converter for
|
|
21
|
+
your database:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { integer, schema, table, text } from 'qubu'
|
|
25
|
+
import { toPostgresDrizzleSchema } from 'qubu/drizzle/postgres'
|
|
26
|
+
|
|
27
|
+
const users = table('user_records', {
|
|
28
|
+
id: integer({ generated: true }),
|
|
29
|
+
name: text(),
|
|
30
|
+
nickname: text({ nullable: true }),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const appSchema = schema({ users }, { namespace: 'app' })
|
|
34
|
+
const drizzleTables = toPostgresDrizzleSchema(appSchema)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The import path selects the dialect. Each module imports only its matching
|
|
38
|
+
Drizzle core package:
|
|
39
|
+
|
|
40
|
+
| Database | Import | Converter |
|
|
41
|
+
| ---------- | ----------------------- | --------------------------- |
|
|
42
|
+
| PostgreSQL | `qubu/drizzle/postgres` | `toPostgresDrizzleSchema()` |
|
|
43
|
+
| MySQL | `qubu/drizzle/mysql` | `toMysqlDrizzleSchema()` |
|
|
44
|
+
| SQLite | `qubu/drizzle/sqlite` | `toSqliteDrizzleSchema()` |
|
|
45
|
+
|
|
46
|
+
`qubu/drizzle` exports the shared conversion error and dialect types. It does
|
|
47
|
+
not import a dialect core or provide a universal runtime converter.
|
|
48
|
+
|
|
49
|
+
`drizzleTables.users` is a real Drizzle `PgTable`. The logical `users` key,
|
|
50
|
+
physical `user_records` name, `app` namespace, field keys, and physical column
|
|
51
|
+
names all come from the Qubu declaration.
|
|
52
|
+
|
|
53
|
+
Pass the converted record to Drizzle and use it in ordinary queries:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { eq } from 'drizzle-orm'
|
|
57
|
+
import { drizzle } from 'drizzle-orm/node-postgres'
|
|
58
|
+
|
|
59
|
+
const db = drizzle(pool, { schema: drizzleTables })
|
|
60
|
+
|
|
61
|
+
const rows = await db
|
|
62
|
+
.select({ id: drizzleTables.users.id, name: drizzleTables.users.name })
|
|
63
|
+
.from(drizzleTables.users)
|
|
64
|
+
.where(eq(drizzleTables.users.id, 7))
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Existing Qubu queries can keep importing `users`. Move call sites to
|
|
68
|
+
`drizzleTables.users` one at a time without duplicating the table declaration.
|
|
69
|
+
|
|
70
|
+
## Type behavior
|
|
71
|
+
|
|
72
|
+
The converted columns preserve selected values, nullability, required insert
|
|
73
|
+
keys, defaults, and generated-column omission:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
type User = typeof drizzleTables.users.$inferSelect
|
|
77
|
+
// { id: number; name: string; nickname: string | null }
|
|
78
|
+
|
|
79
|
+
type NewUser = typeof drizzleTables.users.$inferInsert
|
|
80
|
+
// { name: string; nickname: string | null }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Qubu requires a nullable insert field unless that field has a default. The
|
|
84
|
+
adapter retains that rule even though hand-written Drizzle schemas normally
|
|
85
|
+
make nullable fields optional.
|
|
86
|
+
|
|
87
|
+
Drizzle has one application value type per column. Qubu can instead declare
|
|
88
|
+
different select, insert, and update types with
|
|
89
|
+
`column<Output, Insert, Update>()`. That declaration has no lossless Drizzle
|
|
90
|
+
equivalent, so each dialect converter rejects it at compile time. Built-in
|
|
91
|
+
Qubu columns and `$type()` narrowing use one value type and convert without an
|
|
92
|
+
override.
|
|
93
|
+
|
|
94
|
+
## Runtime metadata
|
|
95
|
+
|
|
96
|
+
Each dialect adapter maps Qubu storage descriptors to its own Drizzle builders.
|
|
97
|
+
It also transfers concrete defaults, generated expressions, common primary and
|
|
98
|
+
unique constraints, checks, foreign keys, and indexes. Native storage must
|
|
99
|
+
belong to the selected dialect:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { nativeColumn, schema, table } from 'qubu'
|
|
103
|
+
import { toPostgresDrizzleSchema } from 'qubu/drizzle/postgres'
|
|
104
|
+
|
|
105
|
+
const records = table('records', {
|
|
106
|
+
handle: nativeColumn('postgresql', 'CITEXT'),
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const tables = toPostgresDrizzleSchema(schema({ records }))
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Conversion first runs Qubu's snapshot validation for the selected dialect.
|
|
113
|
+
Metadata that Drizzle 0.45 cannot express, such as deferred constraints or
|
|
114
|
+
included index columns, raises `DrizzleSchemaConversionError` with a `code` and
|
|
115
|
+
`path`.
|
|
116
|
+
|
|
117
|
+
PostgreSQL schemas and MySQL databases become Drizzle table namespaces. Drizzle
|
|
118
|
+
SQLite tables have no namespace field, so a Qubu SQLite namespace remains
|
|
119
|
+
snapshot metadata and is not attached to the converted table.
|
|
120
|
+
|
|
121
|
+
> [!IMPORTANT]
|
|
122
|
+
> An external default or generated descriptor records that another authority
|
|
123
|
+
> owns the SQL definition. The adapter preserves its Drizzle write types but
|
|
124
|
+
> cannot invent the missing expression. Keep Qubu snapshots and migration plans
|
|
125
|
+
> as the DDL authority while those descriptors remain external.
|
|
126
|
+
|
|
127
|
+
The returned record contains tables, not Drizzle `relations()` declarations.
|
|
128
|
+
Add relations beside the converted record if the application uses Drizzle's
|
|
129
|
+
relational query API.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Add a dialect policy
|
|
2
|
+
|
|
3
|
+
> Change identifiers, placeholders, pagination, or cast targets at the rendering boundary without changing portable query construction.
|
|
4
|
+
|
|
5
|
+
Use `createDialect()` when the query is portable but the driver changes
|
|
6
|
+
identifiers, placeholders, or pagination:
|
|
7
|
+
|
|
8
|
+
The examples assume a query has already been built.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { render } from 'qubu'
|
|
12
|
+
import { createDialect } from 'qubu/core'
|
|
13
|
+
|
|
14
|
+
const colonDialect = createDialect({
|
|
15
|
+
name: 'colon',
|
|
16
|
+
placeholder: position => ':p' + position,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
render(query, colonDialect)
|
|
20
|
+
// ... WHERE ... = :p1
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`createDialect()` uses Qubu's standard double-quoted identifier policy unless
|
|
24
|
+
you provide `quoteIdentifier`. A complete pagination policy can be supplied
|
|
25
|
+
through the optional `pagination` renderer.
|
|
26
|
+
|
|
27
|
+
For the full rendering and execution boundary, read
|
|
28
|
+
[Dialects and execution](../../dialects-and-execution.md). It covers capability
|
|
29
|
+
requirements, the driver adapter, and the values that cross the boundary.
|
|
30
|
+
|
|
31
|
+
For syntax that is not a small policy decision, add a
|
|
32
|
+
[custom fragment or clause](sources-and-clauses.md) instead of making the
|
|
33
|
+
standard dialect pretend that vendor behavior is portable.
|
|
34
|
+
|
|
35
|
+
## Read next
|
|
36
|
+
|
|
37
|
+
- [Add sources and clauses](sources-and-clauses.md) covers custom relations and
|
|
38
|
+
clause placement.
|
|
39
|
+
- [Add typed expressions](typed-expressions.md) covers custom SQL domains and
|
|
40
|
+
cast targets.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Extend Qubu
|
|
2
|
+
|
|
3
|
+
> Choose an extension boundary when the built-in API does not cover a driver-specific or uncommon SQL feature.
|
|
4
|
+
|
|
5
|
+
Qubu extensions are values that render SQL and carry the metadata later
|
|
6
|
+
composition needs. Choose the page that matches the thing you are adding:
|
|
7
|
+
|
|
8
|
+
Ordinary query code stays on the `qubu` root entrypoint. Import fragment,
|
|
9
|
+
dialect, and extension constructors from `qubu/core`; import custom sources and
|
|
10
|
+
schema-expression constructors from `qubu/schema`. The [supported
|
|
11
|
+
surface](../../reference/supported-surface.md) keeps the full entrypoint map in
|
|
12
|
+
one place.
|
|
13
|
+
|
|
14
|
+
| You need to add... | Read... |
|
|
15
|
+
| ----------------------------------- | --------------------------------------------- |
|
|
16
|
+
| A custom relation or clause | [Sources and clauses](sources-and-clauses.md) |
|
|
17
|
+
| A dialect-specific rendering policy | [A dialect policy](dialects.md) |
|
|
18
|
+
| A typed expression or SQL domain | [Typed expressions](typed-expressions.md) |
|
|
19
|
+
| Parameterized trusted SQL syntax | [SQL templates](../sql-templates.md) |
|
|
20
|
+
| Syntax Qubu does not model | [Unsafe syntax](unsafe-syntax.md) |
|
|
21
|
+
|
|
22
|
+
Use `context.parameter()` for runtime values. Use the typed forms when a
|
|
23
|
+
custom expression or source must preserve source, result, nullability, or SQL
|
|
24
|
+
domain metadata. The [sources and clauses](sources-and-clauses.md) page starts
|
|
25
|
+
with a complete custom-clause example.
|
|
26
|
+
|
|
27
|
+
## Read the concept pages
|
|
28
|
+
|
|
29
|
+
- [Source scope](../../query-model/source-scope.md) explains the identities
|
|
30
|
+
custom sources must provide.
|
|
31
|
+
- [Fragments and metadata](../../query-model/fragments.md) explains the facts
|
|
32
|
+
composition carries between fragments.
|
|
33
|
+
- [Dialects and execution](../../dialects-and-execution.md) explains the
|
|
34
|
+
rendering and driver boundary.
|
|
35
|
+
- [SQL semantic types](../../sql-semantic-types.md) explains capability checks
|
|
36
|
+
for typed expressions.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Add sources and clauses
|
|
2
|
+
|
|
3
|
+
> Publish a custom SQL clause or relation while preserving parameter order and source-scope checks.
|
|
4
|
+
|
|
5
|
+
## Add a custom clause
|
|
6
|
+
|
|
7
|
+
Qubu extensions are ordinary fragments. A custom clause supplies a placement,
|
|
8
|
+
render order, and renderer:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { from, render, select, table, text } from 'qubu'
|
|
12
|
+
import { customClause } from 'qubu/core'
|
|
13
|
+
|
|
14
|
+
const users = table('users', { name: text() })
|
|
15
|
+
|
|
16
|
+
const fetchWithTies = customClause({
|
|
17
|
+
name: 'fetch-with-ties',
|
|
18
|
+
order: 100,
|
|
19
|
+
render(context) {
|
|
20
|
+
context.append('FETCH FIRST ')
|
|
21
|
+
context.parameter(10)
|
|
22
|
+
context.append(' ROWS WITH TIES')
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const query = select({ name: users.name }, from(users), fetchWithTies)
|
|
27
|
+
|
|
28
|
+
render(query)
|
|
29
|
+
// ... FETCH FIRST ? ROWS WITH TIES
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`context.parameter(10)` binds the value at render time, and the rendered
|
|
33
|
+
parameter is still ordered with every other parameter in the query. Parameter
|
|
34
|
+
value types are intentionally not part of fragment metadata.
|
|
35
|
+
|
|
36
|
+
## Add a typed custom FROM source
|
|
37
|
+
|
|
38
|
+
Use `customSource()` for a table-valued function or another relation whose row
|
|
39
|
+
shape is known to the application but cannot be declared with `table()`. Its
|
|
40
|
+
`columns` definitions create the same direct and `.columns` references as a
|
|
41
|
+
table, while `from()` or a join supplies the source to the query scope:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { from, integer, render, select, text } from 'qubu'
|
|
45
|
+
import { identifier } from 'qubu/core'
|
|
46
|
+
import { customSource } from 'qubu/schema'
|
|
47
|
+
|
|
48
|
+
const rows = customSource({
|
|
49
|
+
identity: { sourceKind: 'table-function', name: 'json_each', alias: 'row' },
|
|
50
|
+
sourceKind: 'table-function',
|
|
51
|
+
reference: identifier('row'),
|
|
52
|
+
columns: {
|
|
53
|
+
key: integer(),
|
|
54
|
+
value: text({ nullable: true }),
|
|
55
|
+
},
|
|
56
|
+
render(context) {
|
|
57
|
+
context.append('json_each(')
|
|
58
|
+
context.parameter('{"a":1}')
|
|
59
|
+
context.append(') AS ')
|
|
60
|
+
context.render(identifier('row'))
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const query = select({ value: rows.value }, from(rows))
|
|
65
|
+
render(query)
|
|
66
|
+
// ... FROM json_each(?) AS "row"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`identity` is the source-scope key; `reference` is the SQL qualifier used by
|
|
70
|
+
the generated columns. A nullable column remains nullable intrinsically, and a
|
|
71
|
+
`leftJoin(rows, ...)` adds outer-join nullability to every selected row
|
|
72
|
+
column. Render the complete relation in the producer and bind values with
|
|
73
|
+
`context.parameter()`; the normal renderer preserves parameter order.
|
|
74
|
+
|
|
75
|
+
## Read next
|
|
76
|
+
|
|
77
|
+
- [Add typed expressions](typed-expressions.md) covers custom SQL domains and
|
|
78
|
+
metadata-preserving expression wrappers.
|
|
79
|
+
- [Add a dialect policy](dialects.md) covers identifier, placeholder, and
|
|
80
|
+
pagination policies.
|
|
81
|
+
- [Source scope](../../query-model/source-scope.md) explains the identity rules
|
|
82
|
+
custom sources must satisfy.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Add typed expressions
|
|
2
|
+
|
|
3
|
+
> Extend Qubu with expressions that retain source, nullability, result, and SQL-domain metadata.
|
|
4
|
+
|
|
5
|
+
## Build expressions from public helpers
|
|
6
|
+
|
|
7
|
+
The examples use the `users` table from [Sources and clauses](sources-and-clauses.md).
|
|
8
|
+
|
|
9
|
+
`fragment()`, `makeExpression()`, `parameter()`, `identifier()`, `syntax()`,
|
|
10
|
+
`customClause()`, and `customSource()` are public extension building blocks.
|
|
11
|
+
Import fragment, dialect, and expression constructors from `qubu/core`; import
|
|
12
|
+
custom source and schema metadata constructors from `qubu/schema`. Preserve the
|
|
13
|
+
same metadata model that built-ins use:
|
|
14
|
+
|
|
15
|
+
- use `RequiresSourceMeta<Source>` for every source that the expression reads;
|
|
16
|
+
- use `ResultMeta<Output, NullableFrom, SqlType>` when the fragment exposes a
|
|
17
|
+
typed result, or accept its default `SqlUnknown` domain intentionally;
|
|
18
|
+
- inherit child source and nullability facts when composing fragments; and
|
|
19
|
+
- use `context.parameter()` for values instead of concatenating them into SQL.
|
|
20
|
+
|
|
21
|
+
`sequence()` is useful for a reusable fragment assembled from arbitrary child
|
|
22
|
+
fragments. Its `const` type parameter preserves the children's metadata, so
|
|
23
|
+
source-scope checking continues to work without `as const` at the call site.
|
|
24
|
+
|
|
25
|
+
## Declare a custom SQL domain
|
|
26
|
+
|
|
27
|
+
Extend `SqlSemanticType` and only the portable capabilities the database type
|
|
28
|
+
actually supports. Compatibility groups allow a dialect-specific domain to
|
|
29
|
+
interoperate with a built-in family:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { cast, column } from 'qubu'
|
|
33
|
+
import type {
|
|
34
|
+
SqlEqualityComparable,
|
|
35
|
+
SqlOrderable,
|
|
36
|
+
SqlSemanticType,
|
|
37
|
+
SqlTextLike,
|
|
38
|
+
} from 'qubu'
|
|
39
|
+
|
|
40
|
+
interface SqlCitext
|
|
41
|
+
extends SqlSemanticType<'postgres.citext'>,
|
|
42
|
+
SqlTextLike,
|
|
43
|
+
SqlOrderable<'text'>,
|
|
44
|
+
SqlEqualityComparable<'text'> {}
|
|
45
|
+
|
|
46
|
+
const citext = column<string, string, string, SqlCitext>({
|
|
47
|
+
castType: 'CITEXT',
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const nameAsCitext = cast(users.name, citext)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The first three `column` type arguments are output, insert, and update values;
|
|
54
|
+
the fourth is the SQL domain. The `text` equality and ordering groups make the
|
|
55
|
+
custom domain compatible with `SqlText`. Use a distinct group when cross-type
|
|
56
|
+
comparison is not portable. `castType` also makes this definition a cast
|
|
57
|
+
target; its SQL text is emitted verbatim, so keep it in trusted extension code.
|
|
58
|
+
Definitions with schema flags are not accepted as cast targets because cast
|
|
59
|
+
nullability comes from the operand and write flags have no cast meaning.
|
|
60
|
+
|
|
61
|
+
Declare result domains at other extension boundaries too:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { typedCall, typedCast, typedValue, unsafeExpression } from 'qubu/core'
|
|
65
|
+
import type { SqlText, SqlUuid } from 'qubu'
|
|
66
|
+
|
|
67
|
+
const id = typedValue<SqlUuid, string>('108cb836-20d2-41b2-8c23-f0c94700aa7e')
|
|
68
|
+
const normalized = typedCall<SqlText, string>()('custom_text', users.name)
|
|
69
|
+
const rawNameAsText = typedCast<string, SqlText>()(users.name, 'TEXT')
|
|
70
|
+
const generated = unsafeExpression<string, SqlText>('custom_text()')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`typedCall()` preserves source requirements from its arguments. `typedCast()`
|
|
74
|
+
is the fallback when no reusable definition describes the target. It preserves
|
|
75
|
+
operand nullability and source metadata while emitting its supplied type name
|
|
76
|
+
verbatim. `typedValue()` binds a parameter. `unsafeExpression()` emits its
|
|
77
|
+
string unchanged and should remain a last resort.
|
|
78
|
+
|
|
79
|
+
The lower-level forms also expose the SQL domain in their generic lists:
|
|
80
|
+
`call<Output, Name, Arguments, NullableFrom, SqlType>()` and
|
|
81
|
+
`cast<Output, SqlType>()`. They are useful when an extension already computes
|
|
82
|
+
argument or nullability types in its own generic signature.
|
|
83
|
+
|
|
84
|
+
Untyped `column()`, `value()`, `call()`, and custom expressions use
|
|
85
|
+
`SqlUnknown`, which stays permissive for backward compatibility. Declaring a
|
|
86
|
+
known domain opts the extension into incompatible-operation errors. See
|
|
87
|
+
[SQL semantic types](../../sql-semantic-types.md) for the capability model and
|
|
88
|
+
its limits.
|
|
89
|
+
|
|
90
|
+
## Read next
|
|
91
|
+
|
|
92
|
+
- [Add sources and clauses](sources-and-clauses.md) covers custom relations and
|
|
93
|
+
clause renderers.
|
|
94
|
+
- [Use unsafe syntax](unsafe-syntax.md) covers the raw-SQL boundary.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Use unsafe syntax
|
|
2
|
+
|
|
3
|
+
> Keep raw SQL visible and trusted when Qubu does not model the syntax you need.
|
|
4
|
+
|
|
5
|
+
`unsafeExpression()` and related escape hatches exist for syntax that cannot be
|
|
6
|
+
modeled yet. They do not quote identifiers or bind values for you:
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { select } from 'qubu'
|
|
10
|
+
import { unsafeExpression } from 'qubu/core'
|
|
11
|
+
|
|
12
|
+
const query = select({
|
|
13
|
+
today: unsafeExpression('CURRENT_DATE'),
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Keep raw identifiers and values out of the string. Prefer a typed custom
|
|
18
|
+
fragment when the syntax will be reused. Use the [`sql` template
|
|
19
|
+
tag](../sql-templates.md) when fixed trusted syntax needs bound runtime values
|
|
20
|
+
or existing Qubu fragments. Keep dynamic SQL text on `unsafeExpression()` and
|
|
21
|
+
runtime identifiers on `identifier()` or `qualifiedIdentifier()` from
|
|
22
|
+
`qubu/core`.
|
|
23
|
+
|
|
24
|
+
Read [Dialects and execution](../../dialects-and-execution.md) for the boundary
|
|
25
|
+
between rendering and driver behavior. Read [Add typed
|
|
26
|
+
expressions](typed-expressions.md) when the extension needs a result domain or
|
|
27
|
+
source metadata.
|