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,157 @@
|
|
|
1
|
+
# Compose SQL templates
|
|
2
|
+
|
|
3
|
+
> Use trusted SQL syntax with bound runtime values while retaining the Qubu metadata carried by interpolated expressions, fragments, and queries.
|
|
4
|
+
|
|
5
|
+
## Bind every runtime value
|
|
6
|
+
|
|
7
|
+
The `sql` tag treats template text as SQL syntax and every ordinary
|
|
8
|
+
substitution as a parameter. This includes strings, numbers, objects, arrays,
|
|
9
|
+
and `null`:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { integer, render, sql, table, text } from 'qubu'
|
|
13
|
+
|
|
14
|
+
const users = table('users', { name: text() })
|
|
15
|
+
const posts = table('posts', { id: integer() })
|
|
16
|
+
const search = 'Ada%'
|
|
17
|
+
const predicate = sql`${users.name} LIKE ${search}`
|
|
18
|
+
|
|
19
|
+
render(predicate)
|
|
20
|
+
// {
|
|
21
|
+
// text: '"users"."name" LIKE ?',
|
|
22
|
+
// parameters: ['Ada%'],
|
|
23
|
+
// }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`${users.name}` is a Qubu expression, so the tag renders its quoted column
|
|
27
|
+
reference. `${search}` is an ordinary value, so it cannot become SQL text.
|
|
28
|
+
|
|
29
|
+
## Compose expressions, templates, and queries
|
|
30
|
+
|
|
31
|
+
Fragment substitutions use the same rendering context as the enclosing
|
|
32
|
+
statement. Parameters keep one placeholder sequence across nested templates
|
|
33
|
+
and queries:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { eq, from, render, select, sql, where } from 'qubu'
|
|
37
|
+
import { postgresDialect } from 'qubu/postgres'
|
|
38
|
+
|
|
39
|
+
const selectedNames = select(
|
|
40
|
+
{ displayName: users.name },
|
|
41
|
+
from(users),
|
|
42
|
+
where(eq(users.name, 'Ada'))
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const exists = sql`EXISTS (${selectedNames}) AND ${users.name} <> ${'root'}`
|
|
46
|
+
|
|
47
|
+
render(exists, postgresDialect())
|
|
48
|
+
// {
|
|
49
|
+
// text: 'EXISTS (SELECT "users"."name" AS "display_name" FROM "users" WHERE ("users"."name" = $1)) AND "users"."name" <> $2',
|
|
50
|
+
// parameters: ['Ada', 'root'],
|
|
51
|
+
// }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The template owns punctuation such as the parentheses around the query. Qubu
|
|
55
|
+
renders a query substitution with SQL-facing projection names, as it does for
|
|
56
|
+
CTEs and scalar subqueries.
|
|
57
|
+
|
|
58
|
+
## Declare a result domain
|
|
59
|
+
|
|
60
|
+
An unannotated template has application output `unknown` and SQL domain
|
|
61
|
+
`SqlUnknown`. State both facts when the expression feeds typed operations or a
|
|
62
|
+
named projection:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { from, select, sql } from 'qubu'
|
|
66
|
+
import type { SqlText } from 'qubu'
|
|
67
|
+
|
|
68
|
+
const normalizedName = sql.type<string, SqlText>()`LOWER(${users.name})`
|
|
69
|
+
|
|
70
|
+
const query = select({ name: normalizedName }, from(users))
|
|
71
|
+
// typeof query.row is { name: string }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`sql.type<Output, SqlType>()` changes only TypeScript metadata. It does not
|
|
75
|
+
parse the SQL or validate the declared types against the database. The tag
|
|
76
|
+
still binds ordinary substitutions.
|
|
77
|
+
|
|
78
|
+
Interpolated fragments contribute their source requirements and
|
|
79
|
+
nullability. For example, a `sql.type<string, SqlText>()` template containing a
|
|
80
|
+
column from the right side of a `leftJoin()` produces `string | null` in the
|
|
81
|
+
selected row.
|
|
82
|
+
|
|
83
|
+
## Keep identifiers and dynamic syntax explicit
|
|
84
|
+
|
|
85
|
+
Use the substitution kind that matches the value:
|
|
86
|
+
|
|
87
|
+
| Input | Rendering behavior |
|
|
88
|
+
| ------------------------------------- | ------------------------------------------------------------- |
|
|
89
|
+
| Fixed template text | Trusted SQL syntax, appended unchanged |
|
|
90
|
+
| An ordinary substitution | Bound parameter |
|
|
91
|
+
| A Qubu expression, query, or fragment | Rendered through the current dialect and placeholder sequence |
|
|
92
|
+
| `identifier(name)` | One dialect-quoted identifier |
|
|
93
|
+
| `qualifiedIdentifier(schema, table)` | Several dialect-quoted identifier parts |
|
|
94
|
+
| `unsafeExpression(text)` | Unchecked SQL text |
|
|
95
|
+
|
|
96
|
+
Do not use a dotted string as an identifier. Pass each part to
|
|
97
|
+
`qualifiedIdentifier()`. Keep `unsafeExpression()` for application-controlled
|
|
98
|
+
syntax that cannot use a fixed template segment:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { sql } from 'qubu'
|
|
102
|
+
import { identifier, unsafeExpression } from 'qubu/core'
|
|
103
|
+
|
|
104
|
+
const sortColumn = 'display_name'
|
|
105
|
+
const direction = 'DESC' as const
|
|
106
|
+
|
|
107
|
+
const ordering = sql`ORDER BY ${identifier(sortColumn)} ${unsafeExpression(direction)}`
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Validate any dynamic syntax against an application-owned allowlist before it
|
|
111
|
+
reaches `unsafeExpression()`.
|
|
112
|
+
|
|
113
|
+
## Preserve metadata through interpolated fragments
|
|
114
|
+
|
|
115
|
+
The tag inherits source dependencies, conservative outer-join nullability,
|
|
116
|
+
grouping facts, aggregate and window state, subquery state, and dialect
|
|
117
|
+
capability requirements from Qubu fragment substitutions. It does not infer
|
|
118
|
+
those facts from unchecked template text.
|
|
119
|
+
|
|
120
|
+
Use a built-in expression as the substitution when its semantics matter:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { count, sql } from 'qubu'
|
|
124
|
+
import type { SqlInteger } from 'qubu'
|
|
125
|
+
|
|
126
|
+
const postCount = sql.type<number, SqlInteger>()`${count(posts.id)}`
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This wrapper retains the aggregate dependency recorded by `count()`. Writing
|
|
130
|
+
the aggregate name in template text would not record that fact:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const untrackedPostCount = sql.type<number, SqlInteger>()`COUNT(${posts.id})`
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This renders valid SQL, but Qubu sees an ordinary column dependency because it
|
|
137
|
+
does not parse `COUNT` from the template text.
|
|
138
|
+
|
|
139
|
+
Declare a capability when the template text itself uses dialect-specific
|
|
140
|
+
syntax:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { sql } from 'qubu'
|
|
144
|
+
import { withDialectCapability } from 'qubu/core'
|
|
145
|
+
import type { SqlBoolean } from 'qubu'
|
|
146
|
+
|
|
147
|
+
const postgresMatch = withDialectCapability(
|
|
148
|
+
sql.type<boolean, SqlBoolean>()`${users.name} ILIKE ${search}`,
|
|
149
|
+
'ilike'
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Rendering `postgresMatch` now requires a dialect that advertises `ilike`.
|
|
154
|
+
|
|
155
|
+
Use [Fragments and metadata](../query-model/fragments.md) for the inherited
|
|
156
|
+
facts, and [SQL semantic types](../sql-semantic-types.md) for result-domain
|
|
157
|
+
compatibility.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Vite compiler hint
|
|
2
|
+
|
|
3
|
+
> Opt a JavaScript or TypeScript module into Qubu's ambient query API while keeping the transform limited to explicit directive-bearing files.
|
|
4
|
+
|
|
5
|
+
The optional Vite plugin recognizes the `"use qubu"` directive and injects only
|
|
6
|
+
the referenced named imports from the configured module.
|
|
7
|
+
|
|
8
|
+
## Install the plugin
|
|
9
|
+
|
|
10
|
+
Add the plugin to Vite and add the matching ambient declarations to TypeScript:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// vite.config.ts
|
|
14
|
+
import { defineConfig } from 'vite'
|
|
15
|
+
import { qubu } from 'qubu/vite'
|
|
16
|
+
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
plugins: [qubu()],
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
{
|
|
24
|
+
"compilerOptions": {
|
|
25
|
+
// Merge this with any existing project-specific entries.
|
|
26
|
+
"types": ["qubu/globals"],
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The plugin supplies runtime imports. `qubu/globals` supplies the corresponding
|
|
32
|
+
ambient value and type declarations for the TypeScript compiler.
|
|
33
|
+
|
|
34
|
+
## Mark a module explicitly
|
|
35
|
+
|
|
36
|
+
Put the directive in the module's initial directive prologue:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
'use qubu'
|
|
40
|
+
|
|
41
|
+
const users = table('users', {
|
|
42
|
+
id: integer(),
|
|
43
|
+
name: text(),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const query = select(
|
|
47
|
+
{ id: users.id, name: users.name },
|
|
48
|
+
from(users),
|
|
49
|
+
where(eq(users.id, 42))
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Conceptually, the transform adds the imports that this module references:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { eq, from, integer, select, table, text, where } from 'qubu'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Existing imports remain valid. The transform does not rewrite member properties,
|
|
60
|
+
strings, comments, or names outside the public Qubu global catalog.
|
|
61
|
+
|
|
62
|
+
## Configure the transform
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
qubu({
|
|
66
|
+
module: 'qubu',
|
|
67
|
+
include: id => id.includes('/src/'),
|
|
68
|
+
exclude: /\.stories\./,
|
|
69
|
+
globals: ['select', 'from', 'where', 'eq', 'table'],
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Option | Effect | Default |
|
|
74
|
+
| --------- | --------------------------------------- | --------------------- |
|
|
75
|
+
| `module` | Import source for injected names | `'qubu'` |
|
|
76
|
+
| `include` | Restrict matching module IDs | no filter |
|
|
77
|
+
| `exclude` | Skip matching module IDs | no filter |
|
|
78
|
+
| `globals` | Narrow the names eligible for injection | Qubu's public catalog |
|
|
79
|
+
|
|
80
|
+
## Know the boundaries
|
|
81
|
+
|
|
82
|
+
- Only JavaScript and TypeScript script extensions are considered.
|
|
83
|
+
- Files under `node_modules` are skipped.
|
|
84
|
+
- `include` and `exclude` filters run against the module ID.
|
|
85
|
+
- A module without the directive is unchanged.
|
|
86
|
+
- A module that references no eligible Qubu global is unchanged.
|
|
87
|
+
- Normal ES module imports remain the explicit fallback when the hint is not a
|
|
88
|
+
good fit.
|
|
89
|
+
|
|
90
|
+
The plugin has no runtime dependency on Vite; it returns the small transform
|
|
91
|
+
shape Vite expects. Use the package root directly when you want ordinary import
|
|
92
|
+
semantics everywhere.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Qubu
|
|
2
|
+
|
|
3
|
+
> Build parameterized SQL from typed tables, expressions, and clauses.
|
|
4
|
+
|
|
5
|
+
Qubu builds SQL from values. Tables, expressions, clauses, and complete queries
|
|
6
|
+
compose without a mutable query builder. TypeScript tracks selected row shapes,
|
|
7
|
+
source scope, and nullability, while rendering returns SQL text and ordered
|
|
8
|
+
parameters.
|
|
9
|
+
|
|
10
|
+
The preferred source style names each projected field and writes the final
|
|
11
|
+
`select()` clauses in SQL order. Clause values remain order-independent at
|
|
12
|
+
runtime, so a reusable `where()` or `orderBy()` fragment can be built earlier
|
|
13
|
+
and placed in that final call where it reads best.
|
|
14
|
+
|
|
15
|
+
## Start here
|
|
16
|
+
|
|
17
|
+
If this is your first query, follow [Getting started](getting-started.md) to
|
|
18
|
+
define a table, build a `SELECT`, and inspect its SQL and parameters.
|
|
19
|
+
|
|
20
|
+
## Choose a task
|
|
21
|
+
|
|
22
|
+
- [Build a `SELECT`](guides/select/overview.md) with projections, joins,
|
|
23
|
+
predicates, ordering, and grouping.
|
|
24
|
+
- [Compose queries](guides/compose-queries.md) with CTEs, derived tables,
|
|
25
|
+
subqueries, and set operations.
|
|
26
|
+
- [Compose SQL templates](guides/sql-templates.md) for trusted syntax with
|
|
27
|
+
bound values and metadata-preserving fragment substitutions.
|
|
28
|
+
- [Write mutations](guides/mutations.md) with typed `INSERT`, `UPDATE`, and
|
|
29
|
+
`DELETE` statements.
|
|
30
|
+
- [Use Qubu tables with Drizzle](guides/drizzle.md) while moving query call
|
|
31
|
+
sites without duplicating schema declarations.
|
|
32
|
+
- [Extend Qubu](guides/extensions/overview.md) with a custom source, clause,
|
|
33
|
+
dialect policy, or typed expression.
|
|
34
|
+
- [Read JSON scalars](guides/json.md) from structured JSON paths.
|
|
35
|
+
- [Enable the Vite compiler hint](guides/vite-plugin.md) when query modules
|
|
36
|
+
should opt into named imports through a directive.
|
|
37
|
+
- [Inspect an existing database](schema/introspection.md) through the optional
|
|
38
|
+
user-owned catalog boundary.
|
|
39
|
+
- [Generate a schema module](schema/code-generation.md) from one complete,
|
|
40
|
+
non-lossy Snapshot v1 introspection result.
|
|
41
|
+
- [Compare snapshots](schema/diff.md) with explicit rename hints and reviewable
|
|
42
|
+
safety diagnostics.
|
|
43
|
+
- [Build migration plans](schema/migration-plans.md) as reviewed, deterministic
|
|
44
|
+
data before DDL emission.
|
|
45
|
+
- [Emit DDL](schema/ddl-emission.md) from an approved migration plan without
|
|
46
|
+
handing Qubu a database connection.
|
|
47
|
+
|
|
48
|
+
## The query pipeline
|
|
49
|
+
|
|
50
|
+
The same query value can be rendered for inspection or passed to an adapter for
|
|
51
|
+
execution. The application-owned adapter handles the driver, database
|
|
52
|
+
connection, and driver-specific row and mutation-result details.
|
|
53
|
+
|
|
54
|
+
```mermaid
|
|
55
|
+
flowchart LR
|
|
56
|
+
A["Tables and columns"] --> B["Expressions and clauses"]
|
|
57
|
+
B --> C["Typed query"]
|
|
58
|
+
C --> D["Dialect renderer"]
|
|
59
|
+
D --> E["SQL text + ordered parameters"]
|
|
60
|
+
E --> F["Application-owned adapter"]
|
|
61
|
+
F --> G["Rows + optional mutation facts"]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Values become bound parameters, and the active dialect quotes identifiers. Raw
|
|
65
|
+
SQL is available through explicit unsafe helpers. The call site shows where
|
|
66
|
+
that unchecked syntax enters the query.
|
|
67
|
+
|
|
68
|
+
## Understand the Qubu model
|
|
69
|
+
|
|
70
|
+
Use these pages when a guide leaves a rule unexplained or when an extension
|
|
71
|
+
needs to preserve a fact across composition:
|
|
72
|
+
|
|
73
|
+
| Model | Start with | Covers |
|
|
74
|
+
| ------------------------ | --------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
75
|
+
| Query model | [Source scope](query-model/source-scope.md) | Source identity, result shapes, fragments, metadata, and query composition |
|
|
76
|
+
| Schema model | [Tables and names](schema/tables-and-names.md) | Tables, snapshots, diffs, migration plans, and DDL emission |
|
|
77
|
+
| Database introspection | [Database introspection](schema/introspection.md) | Catalog readers, Snapshot v1 mapping, identities, diagnostics, and support limits |
|
|
78
|
+
| Schema source generation | [Generate a schema](schema/code-generation.md) | Machine-owned TypeScript, identity handoff, controlled mappings, and v1 exclusions |
|
|
79
|
+
| Rendering and execution | [Dialects and execution](dialects-and-execution.md) | Placeholder and identifier policies, capabilities, adapters, and raw-SQL boundaries |
|
|
80
|
+
| SQL semantic types | [SQL semantic types](sql-semantic-types.md) | Application types, SQL domains, nullability, and compatible operations |
|
|
81
|
+
|
|
82
|
+
## A small example
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { eq, from, integer, render, select, table, text, where } from 'qubu'
|
|
86
|
+
|
|
87
|
+
const users = table('users', {
|
|
88
|
+
id: integer(),
|
|
89
|
+
name: text(),
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const query = select(
|
|
93
|
+
{ id: users.id, name: users.name },
|
|
94
|
+
from(users),
|
|
95
|
+
where(eq(users.id, 7))
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
render(query)
|
|
99
|
+
// {
|
|
100
|
+
// text: 'SELECT "users"."id" AS "id", "users"."name" AS "name" FROM "users" WHERE ("users"."id" = ?)',
|
|
101
|
+
// parameters: [7],
|
|
102
|
+
// }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The inferred row is `{ id: number; name: string }`. The value `7` stays out
|
|
106
|
+
of the SQL text and appears in the `parameters` array in placeholder order.
|
|
107
|
+
|
|
108
|
+
The [supported features](reference/supported-surface.md) page is the canonical
|
|
109
|
+
package-entrypoint and ownership map. Qubu emits DDL, but the application owns
|
|
110
|
+
migration execution and database lifecycle. [Troubleshooting](troubleshooting.md)
|
|
111
|
+
starts from common errors and points to the concept page behind each one.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Fragments and metadata
|
|
2
|
+
|
|
3
|
+
> Build custom SQL from values while preserving the source, result, grouping, and capability facts that later composition checks.
|
|
4
|
+
|
|
5
|
+
## A fragment has a renderer and metadata
|
|
6
|
+
|
|
7
|
+
The runtime unit is a `Fragment`:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
Fragment<Metadata>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The renderer appends SQL and parameters to a context. The metadata type records
|
|
14
|
+
facts that the next composition step needs. The main groups are:
|
|
15
|
+
|
|
16
|
+
| Fact | What it records |
|
|
17
|
+
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| `ResultMeta<Output, NullableFrom, SqlType>` | The value or row produced, sources that can make it `null`, and its SQL domain. |
|
|
19
|
+
| `CardinalityMeta<QueryCardinality>` | Whether a query returns `many`, `zero-or-one`, or `exactly-one` rows. |
|
|
20
|
+
| `RequiresSourceMeta` and `ProvidesSourceMeta` | Which source a fragment needs and which source a `FROM` or join introduces. |
|
|
21
|
+
| `RequiresOuterSourceMeta` and `ProvidesOuterSourceMeta` | Which enclosing source a correlated or LATERAL query reads and how `correlate()` provides it. |
|
|
22
|
+
| `NullableSourceMeta` | Which source became nullable through an outer join. |
|
|
23
|
+
| `ExpressionMeta`, `AggregateMeta`, and `GroupingMeta` | The columns an expression reads, consumes inside an aggregate, or makes available through `GROUP BY`. |
|
|
24
|
+
|
|
25
|
+
An untyped extension uses `SqlUnknown` for its SQL domain. Declare a domain when
|
|
26
|
+
the extension needs incompatible operations to fail at the call site. See
|
|
27
|
+
[SQL semantic types](../sql-semantic-types.md).
|
|
28
|
+
|
|
29
|
+
## Preserve metadata when composing fragments
|
|
30
|
+
|
|
31
|
+
Composition helpers keep the facts that their children already carry:
|
|
32
|
+
|
|
33
|
+
| Composition | Result |
|
|
34
|
+
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
35
|
+
| `sequence()`, `commaSeparated()`, `keyword()`, `parenthesize()` | Preserve inherited source and nullability facts without inventing a result or query cardinality. |
|
|
36
|
+
| An expression wrapper such as `expressionFragment()` | Preserve the wrapped result, source, nullability, and SQL domain. |
|
|
37
|
+
| A source-aware operator such as `upper(column)` | Create a new result while inheriting the operand's source and nullability facts. |
|
|
38
|
+
| An aggregate such as `count(column)` | Mark argument dependencies as aggregate-consumed, so they do not need to appear in `GROUP BY`. |
|
|
39
|
+
| `groupBy()` | Record grouping expressions and the column dependencies they make available. |
|
|
40
|
+
| `leftJoin()` | Add `NullableSourceMeta` for the joined source. |
|
|
41
|
+
|
|
42
|
+
The type-level contract stays small. `OutputOf<T>` describes a result,
|
|
43
|
+
`SqlTypeOf<T>` its SQL domain, `RequiresOf<T>` its required sources, and
|
|
44
|
+
`NullabilityOf<T>` the sources that can make it null after an outer join.
|
|
45
|
+
|
|
46
|
+
Qubu does not infer every SQL rule. Grouping checks use declared dependencies,
|
|
47
|
+
and functional dependencies from database keys are handled where the source
|
|
48
|
+
model explicitly proves them. New metadata belongs in this union only when a
|
|
49
|
+
producer, consumer, and regression test all exist.
|
|
50
|
+
|
|
51
|
+
## Parameters are runtime data
|
|
52
|
+
|
|
53
|
+
Parameter values are not fragment metadata. A renderer calls
|
|
54
|
+
`context.parameter(value)`, and `render()` collects values in placeholder order:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import {
|
|
58
|
+
and,
|
|
59
|
+
eq,
|
|
60
|
+
from,
|
|
61
|
+
integer,
|
|
62
|
+
like,
|
|
63
|
+
render,
|
|
64
|
+
select,
|
|
65
|
+
table,
|
|
66
|
+
text,
|
|
67
|
+
where,
|
|
68
|
+
} from 'qubu'
|
|
69
|
+
|
|
70
|
+
const users = table('users', {
|
|
71
|
+
id: integer(),
|
|
72
|
+
name: text(),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const query = select(
|
|
76
|
+
{ id: users.id },
|
|
77
|
+
from(users),
|
|
78
|
+
where(and(eq(users.id, 7), like(users.name, '%Ada%')))
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
render(query)
|
|
82
|
+
// text: ... WHERE (("users"."id" = ?) AND ("users"."name" LIKE ?))
|
|
83
|
+
// parameters: [7, '%Ada%']
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The parameter array follows the placeholders in the rendered text. `select()`
|
|
87
|
+
normalizes independent clause values, but keep the final call in SQL order in
|
|
88
|
+
new code so source scope and repair hints are visible at a glance.
|
|
89
|
+
|
|
90
|
+
The public [`sql` template tag](../guides/sql-templates.md) uses the same
|
|
91
|
+
renderer. Ordinary substitutions call `context.parameter()`, while expression,
|
|
92
|
+
query, and fragment substitutions call back into the active render context.
|
|
93
|
+
The tag therefore keeps placeholder numbering, dialect behavior, and inherited
|
|
94
|
+
metadata in one composition path.
|
|
95
|
+
|
|
96
|
+
## Keep the boundary explicit
|
|
97
|
+
|
|
98
|
+
Qubu tracks source scope, result shape, nullability, cardinality, and portable
|
|
99
|
+
SQL capabilities. It does not encode every vendor grammar rule or implicit
|
|
100
|
+
conversion. Use standard fragments for portable SQL and move vendor-specific
|
|
101
|
+
syntax to [dialects or custom extensions](../dialects-and-execution.md).
|
|
102
|
+
|
|
103
|
+
## Read next
|
|
104
|
+
|
|
105
|
+
- [Source scope](source-scope.md) covers source identities and correlation.
|
|
106
|
+
- [Result shapes and cardinality](result-shapes.md) covers projections and
|
|
107
|
+
nullable results.
|
|
108
|
+
- [Extend Qubu](../guides/extensions/overview.md) applies this model to custom
|
|
109
|
+
expressions, clauses, and sources.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Result shapes and cardinality
|
|
2
|
+
|
|
3
|
+
> Choose a projection, understand how joins change its nullability, and check when a nested query can return no row.
|
|
4
|
+
|
|
5
|
+
## Name the selected row
|
|
6
|
+
|
|
7
|
+
An object projection uses its keys as result names:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { from, integer, select, table, text, upper } from 'qubu'
|
|
11
|
+
|
|
12
|
+
const users = table('users', {
|
|
13
|
+
id: integer(),
|
|
14
|
+
name: text(),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const query = select(
|
|
18
|
+
{
|
|
19
|
+
id: users.id,
|
|
20
|
+
displayName: upper(users.name),
|
|
21
|
+
},
|
|
22
|
+
from(users)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
type Row = typeof query.row
|
|
26
|
+
// { id: number; displayName: string }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The projection key also names the SQL output column. Use explicit fields for a
|
|
30
|
+
shaped result. Reserve `all(source)` for a whole-source result contract. It
|
|
31
|
+
expands to named columns, so the SQL columns and inferred row keys stay aligned:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { all, from, select, upper } from 'qubu'
|
|
35
|
+
|
|
36
|
+
const query = select(
|
|
37
|
+
{ ...all(users), normalizedName: upper(users.name) },
|
|
38
|
+
from(users)
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
When a query becomes a CTE or derived table, its row shape becomes the columns
|
|
43
|
+
available from that new source. `RETURNING` uses the same projection rules.
|
|
44
|
+
See [Compose queries](../guides/compose-queries.md) and
|
|
45
|
+
[Write mutations](../guides/mutations.md) for those workflows.
|
|
46
|
+
|
|
47
|
+
The examples below continue with the `users` table from the first example.
|
|
48
|
+
|
|
49
|
+
## Account for nullable joins
|
|
50
|
+
|
|
51
|
+
`leftJoin()` marks the joined source as nullable. A selected column from that
|
|
52
|
+
source widens with `null`, while an expression with its own non-null result
|
|
53
|
+
contract can stay non-null:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { count, eq, from, integer, leftJoin, select, table, text } from 'qubu'
|
|
57
|
+
|
|
58
|
+
const users = table('users', {
|
|
59
|
+
id: integer(),
|
|
60
|
+
name: text(),
|
|
61
|
+
})
|
|
62
|
+
const posts = table('posts', {
|
|
63
|
+
id: integer(),
|
|
64
|
+
authorId: integer(),
|
|
65
|
+
title: text(),
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const query = select(
|
|
69
|
+
{
|
|
70
|
+
userName: users.name,
|
|
71
|
+
postTitle: posts.title,
|
|
72
|
+
postCount: count(posts.id),
|
|
73
|
+
},
|
|
74
|
+
from(users),
|
|
75
|
+
leftJoin(posts, eq(users.id, posts.authorId))
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
type Row = typeof query.row
|
|
79
|
+
// { userName: string; postTitle: string | null; postCount: number }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The same rule applies inside expressions. `upper(posts.title)` remains
|
|
83
|
+
nullable because it depends on the joined row. `coalesce()` and a `CASE`
|
|
84
|
+
expression with non-null branches can return a non-null result.
|
|
85
|
+
|
|
86
|
+
## Check scalar cardinality
|
|
87
|
+
|
|
88
|
+
`scalar()` turns a query with exactly one selected field into an expression. The
|
|
89
|
+
result includes `null` when the query may return no rows:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { fetchFirst, from, scalar, select, table, value } from 'qubu'
|
|
93
|
+
|
|
94
|
+
const users = table('users', { id: integer() })
|
|
95
|
+
const firstUser = select({ id: users.id }, from(users), fetchFirst(1))
|
|
96
|
+
|
|
97
|
+
const firstId = scalar(firstUser)
|
|
98
|
+
// OutputOf<typeof firstId> is number | null
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`fetchFirst(1)` proves an upper bound, not that a row exists. A source-free
|
|
102
|
+
select is different:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const constant = select({ value: value(42) })
|
|
106
|
+
const constantValue = scalar(constant)
|
|
107
|
+
// OutputOf<typeof constantValue> is number
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Qubu does not infer exactness from a predicate such as `WHERE id = 1`. That
|
|
111
|
+
predicate can match no rows, so the scalar result remains nullable.
|
|
112
|
+
|
|
113
|
+
## Keep SQL domains with the row
|
|
114
|
+
|
|
115
|
+
Projections preserve the SQL domains of their expressions. A text expression
|
|
116
|
+
stays `SqlText` through a CTE, derived table, scalar subquery, set operation, or
|
|
117
|
+
mutation `RETURNING` projection. See [SQL semantic types](../sql-semantic-types.md)
|
|
118
|
+
for the domain rules.
|
|
119
|
+
|
|
120
|
+
## Read next
|
|
121
|
+
|
|
122
|
+
- [Source scope](source-scope.md) explains why a column must come from `FROM`,
|
|
123
|
+
a join, or an intentional correlation.
|
|
124
|
+
- [Fragments and metadata](fragments.md) explains how result and nullability
|
|
125
|
+
facts move through custom composition.
|