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,75 @@
|
|
|
1
|
+
# DDL emission
|
|
2
|
+
|
|
3
|
+
> Turn an approved migration plan into deterministic SQL while keeping database execution outside Qubu.
|
|
4
|
+
|
|
5
|
+
The `qubu/ddl` entrypoint accepts only a `MigrationPlan` and a `SchemaDialect`.
|
|
6
|
+
It does not read a catalog, open a connection, start a transaction, or write a
|
|
7
|
+
migration journal. Preflight runs before rendering, so a blocked or incompatible
|
|
8
|
+
plan returns diagnostics and no SQL.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { emitMigrationPlan } from 'qubu/ddl'
|
|
12
|
+
import { postgresSchemaDialect } from 'qubu/snapshot'
|
|
13
|
+
|
|
14
|
+
const result = emitMigrationPlan(plan, postgresSchemaDialect)
|
|
15
|
+
if (!result.ok) {
|
|
16
|
+
// Review result.diagnostics. result.sql is an empty string.
|
|
17
|
+
throw new Error(result.diagnostics.map(item => item.message).join('\n'))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const statement of result.statements) {
|
|
21
|
+
console.log(statement.operationId, statement.sql, statement.parameters)
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`statements` is the stable handoff for an application-owned executor. Each
|
|
26
|
+
statement carries its operation ID, topological position, SQL text, and an
|
|
27
|
+
ordered parameter list.
|
|
28
|
+
Schema literals and expressions are parameter-free by contract. `sql` joins
|
|
29
|
+
the statements with a newline and adds a semicolon for migration-file writers.
|
|
30
|
+
|
|
31
|
+
## Review gates
|
|
32
|
+
|
|
33
|
+
The emitter rejects a plan with `ready: false`, `decision-required` operations,
|
|
34
|
+
unknown or lossy facts, unsupported safety, or destructive changes unless the
|
|
35
|
+
caller supplies the matching explicit option. `allowUnsafe` is available for a
|
|
36
|
+
reviewed integration that owns all of those decisions, but it does not make an
|
|
37
|
+
opaque object renderable. Opaque and deferred catalog records always need an
|
|
38
|
+
explicit tagged `custom-sql` operation.
|
|
39
|
+
|
|
40
|
+
Lock and transaction requirements describe what a later executor must provide.
|
|
41
|
+
Pass `lock` or `transaction` to preflight those requirements against the
|
|
42
|
+
executor's context. A required transaction with `transaction: 'autocommit'`
|
|
43
|
+
produces `transaction-conflict`; no transaction is opened by the emitter.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const result = emitMigrationPlan(plan, postgresSchemaDialect, {
|
|
47
|
+
transaction: 'managed',
|
|
48
|
+
lock: 'exclusive',
|
|
49
|
+
serverVersion: '16',
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## First-party support
|
|
54
|
+
|
|
55
|
+
| Object or operation | PostgreSQL | SQLite | MySQL |
|
|
56
|
+
| ----------------------------------------------- | ------------------------ | ------------------------------------------------------------------------ | ------------------------ |
|
|
57
|
+
| Tables, columns, constraints, indexes | yes | tables, columns, and indexes; constraints require an inline/rebuild path | yes |
|
|
58
|
+
| Views | yes | yes | yes |
|
|
59
|
+
| Materialized views | yes | no | no |
|
|
60
|
+
| Sequences, enums, domains, policies, extensions | yes | no | no |
|
|
61
|
+
| Routines | functions and procedures | no | functions and procedures |
|
|
62
|
+
| Partitions | yes | no | add/drop partition forms |
|
|
63
|
+
| Triggers | yes | yes | yes |
|
|
64
|
+
| Comments and ownership | yes | no | table comments only |
|
|
65
|
+
| Explicit custom SQL | dialect tag must match | dialect tag must match | dialect tag must match |
|
|
66
|
+
|
|
67
|
+
The support table describes syntax Qubu can render from modeled snapshot facts.
|
|
68
|
+
Server-version checks still apply, such as SQLite column rename and drop-column
|
|
69
|
+
limits and MySQL check-constraint support. An unsupported operation is reported
|
|
70
|
+
as a diagnostic rather than silently omitted. SQLite table constraints are not
|
|
71
|
+
emitted as `ALTER TABLE ... ADD CONSTRAINT`; use a schema snapshot that carries
|
|
72
|
+
an inline constraint declaration or an explicit rebuild/custom-SQL operation.
|
|
73
|
+
|
|
74
|
+
Custom SQL stays opaque and appears at its plan position. The emitter does not
|
|
75
|
+
inspect it for object names or infer SQL from an opaque catalog record.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Snapshot diffing
|
|
2
|
+
|
|
3
|
+
> Compare canonical Snapshot v1 or v2 values and review identity changes before a later planning step.
|
|
4
|
+
|
|
5
|
+
The optional `qubu/diff` entrypoint compares immutable snapshot data. It does
|
|
6
|
+
not open a connection, render SQL, execute a change, or infer migration history.
|
|
7
|
+
It keeps the object kind, namespace, logical ID, physical name, dialect, path,
|
|
8
|
+
and catalog evidence on every result object.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { diffSnapshots } from 'qubu/diff'
|
|
12
|
+
|
|
13
|
+
const result = diffSnapshots(previousSnapshot, currentSnapshot)
|
|
14
|
+
|
|
15
|
+
for (const change of result.changes) {
|
|
16
|
+
console.log(change.type, change.kind, change.logicalId)
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The matcher first uses an explicit rename hint, then a stable logical ID. A
|
|
21
|
+
physical-name change for a stable match is a `physical-rename` operation. Other
|
|
22
|
+
matched fields become `property-change` operations. Unmatched records remain
|
|
23
|
+
separate `remove` and `add` operations.
|
|
24
|
+
|
|
25
|
+
## Explicit rename hints
|
|
26
|
+
|
|
27
|
+
Use a hint when an introspector or schema edit changed the logical ID as well as
|
|
28
|
+
the physical name. Hints are scoped by object kind and namespace. A target may
|
|
29
|
+
use an ID, a physical name, or an exact path when a nested scope repeats an ID.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const result = diffSnapshots(previousSnapshot, currentSnapshot, {
|
|
33
|
+
renameHints: [
|
|
34
|
+
{
|
|
35
|
+
kind: 'table',
|
|
36
|
+
namespace: 'public',
|
|
37
|
+
from: 'legacy_accounts',
|
|
38
|
+
to: 'accounts',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
result.renames[0]?.source // 'explicit-hint'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Hints must resolve to one object on each side. A wrong namespace, unsupported
|
|
47
|
+
kind, empty target, duplicate mapping, or target that resolves more than once
|
|
48
|
+
produces a diagnostic. `encodeSnapshotRenameHints()` writes deterministic JSON;
|
|
49
|
+
`decodeSnapshotRenameHints()` validates it without throwing.
|
|
50
|
+
|
|
51
|
+
## Suggestions and safety diagnostics
|
|
52
|
+
|
|
53
|
+
Structural matching can report a `rename-suggestion` with a confidence score and
|
|
54
|
+
evidence. Suggestions never enter `result.renames` and never replace the
|
|
55
|
+
corresponding add and remove operations. An ambiguous structural match reports
|
|
56
|
+
an `ambiguous` diagnostic and leaves both operations visible for review.
|
|
57
|
+
|
|
58
|
+
Removing an object is marked `destructive`. Narrowing nullability, changing
|
|
59
|
+
storage, removing a value, or changing a constraint can also receive that
|
|
60
|
+
classification. Opaque and deferred Snapshot v2 records remain visible as
|
|
61
|
+
`add` or `remove` data and produce `lossy` or `unsupported` diagnostics. They
|
|
62
|
+
cannot be silently promoted to a rename.
|
|
63
|
+
|
|
64
|
+
`result.equal` means that no diff operation was emitted. A result can therefore
|
|
65
|
+
be equal while still carrying a warning about an unchanged opaque record. The
|
|
66
|
+
diagnostics remain part of the review boundary.
|
|
67
|
+
|
|
68
|
+
Snapshot arrays are normalized by stable IDs or positions before comparison, so
|
|
69
|
+
reordering tables, object groups, columns, or other canonical collections does
|
|
70
|
+
not create changes. Ordered index terms, foreign-key columns, enum positions,
|
|
71
|
+
and routine parameter positions retain their meaning.
|
|
72
|
+
|
|
73
|
+
Diff output is data only. Feed a resolved result into the
|
|
74
|
+
[migration-plan boundary](migration-plans.md) when a reviewed change list is
|
|
75
|
+
needed; DDL rendering and database execution remain later boundaries.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Database introspection
|
|
2
|
+
|
|
3
|
+
> Read one existing database namespace into explainable catalog data and an optional canonical Snapshot v1 or complete Snapshot v2 without giving Qubu ownership of the connection.
|
|
4
|
+
|
|
5
|
+
Database introspection is an optional capability exported from
|
|
6
|
+
`qubu/introspection`. It discovers database facts; it does not recreate the
|
|
7
|
+
original TypeScript declarations. Planning and DDL emission use the separate
|
|
8
|
+
`qubu/migration` and `qubu/ddl` entrypoints, while migration execution remains
|
|
9
|
+
application-owned. The separate
|
|
10
|
+
`qubu/codegen` entrypoint can create a new machine-owned schema module from a
|
|
11
|
+
complete Snapshot v1 result.
|
|
12
|
+
|
|
13
|
+
## The pipeline
|
|
14
|
+
|
|
15
|
+
The caller owns the connection and supplies a small catalog query adapter:
|
|
16
|
+
|
|
17
|
+
```mermaid
|
|
18
|
+
flowchart LR
|
|
19
|
+
A[User-owned connection] --> B[Dialect catalog reader]
|
|
20
|
+
B --> C[Normalized catalog]
|
|
21
|
+
C --> D[Pure snapshot mapper]
|
|
22
|
+
D --> E[Canonical snapshot]
|
|
23
|
+
E -. later .-> F[Snapshot diffing or migration planning]
|
|
24
|
+
E -. optional .-> G[Generated TypeScript schema]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The reader owns catalog SQL and dialect-specific row normalization. The
|
|
28
|
+
normalized catalog retains physical names, native types, opaque SQL text,
|
|
29
|
+
provenance, current-run catalog references, capabilities, deferred objects,
|
|
30
|
+
and diagnostics. The mapper owns Snapshot v1 shape, stable ordering, identity
|
|
31
|
+
continuity, and strict versus lossy output.
|
|
32
|
+
|
|
33
|
+
## Supply a connection
|
|
34
|
+
|
|
35
|
+
Qubu does not open or close a connection, select a driver, manage a pool, retry
|
|
36
|
+
queries, authenticate, or start a transaction. Adapt the driver you already
|
|
37
|
+
use to `CatalogConnection`:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import type { CatalogConnection } from 'qubu/introspection'
|
|
41
|
+
|
|
42
|
+
const connection: CatalogConnection = {
|
|
43
|
+
dialect: 'sqlite',
|
|
44
|
+
query(statement, options) {
|
|
45
|
+
// Adapt this call to the driver used by the application.
|
|
46
|
+
return db.query(statement, options)
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The adapter receives fixed catalog statements and bound parameters. Catalog
|
|
52
|
+
text returned by the database is data. Qubu never evaluates it or turns it
|
|
53
|
+
into an executable schema expression.
|
|
54
|
+
|
|
55
|
+
## Read and map a catalog
|
|
56
|
+
|
|
57
|
+
Readers return normalized facts. Mapping is a separate operation so the same
|
|
58
|
+
catalog can later support inspection, source generation, or another snapshot
|
|
59
|
+
format:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { mapCatalogToSnapshot, readSqliteCatalog } from 'qubu/introspection'
|
|
63
|
+
|
|
64
|
+
const catalog = await readSqliteCatalog(connection, { namespace: 'main' })
|
|
65
|
+
const result = mapCatalogToSnapshot(catalog, {
|
|
66
|
+
namespace: 'main',
|
|
67
|
+
mode: 'strict',
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (!result.ok) {
|
|
71
|
+
throw new Error(result.diagnostics.map(issue => issue.message).join('\n'))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
result.snapshot.tables // canonical Snapshot v1 data
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Pass the successful result itself—not a detached or edited snapshot—to
|
|
78
|
+
`generateSchemaSource()` when a replaceable TypeScript schema is needed. See
|
|
79
|
+
[Generate a schema from introspection](code-generation.md) for that workflow
|
|
80
|
+
and its identity handoff.
|
|
81
|
+
|
|
82
|
+
Readers may expose additional typed object families through the normalized
|
|
83
|
+
catalog. Use `createCompleteIntrospectionCatalog()` to materialize and freeze
|
|
84
|
+
all optional collections, then `mapCatalogToCompleteSnapshot()` when an
|
|
85
|
+
adapter-supported family such as views, routines, triggers, partitions,
|
|
86
|
+
collations, comments, or retained opaque and deferred objects must cross the
|
|
87
|
+
strict Snapshot v2 boundary. Snapshot v1 is still selected explicitly by
|
|
88
|
+
`mapCatalogToSnapshot()` and remains table-shaped.
|
|
89
|
+
|
|
90
|
+
The result is successful only when Snapshot v1 validation succeeds. A failed
|
|
91
|
+
result may retain the partial catalog and structured diagnostics, but it has no
|
|
92
|
+
snapshot.
|
|
93
|
+
|
|
94
|
+
## Identity and physical names
|
|
95
|
+
|
|
96
|
+
Introspection uses this identity precedence:
|
|
97
|
+
|
|
98
|
+
1. an explicit identity hint;
|
|
99
|
+
2. a matching entity in a previous snapshot;
|
|
100
|
+
3. the physical database name;
|
|
101
|
+
4. a deterministic fallback for unnamed or invalid names.
|
|
102
|
+
|
|
103
|
+
Physical names remain unchanged in the snapshot. OIDs, SQLite rowids, and
|
|
104
|
+
internal `sqlite_autoindex_*` names are current-run or implementation details,
|
|
105
|
+
not persisted Qubu identities. A changed physical name is not automatically a
|
|
106
|
+
rename. Pass the previous snapshot or an identity hint when a later diff must
|
|
107
|
+
preserve identity across a rename. See [snapshot diffing](diff.md) for the
|
|
108
|
+
comparison and hint boundary.
|
|
109
|
+
|
|
110
|
+
The first version selects one namespace: a PostgreSQL schema, MySQL database,
|
|
111
|
+
or SQLite database such as `main`. It does not combine attached databases or
|
|
112
|
+
multiple PostgreSQL schemas into one Snapshot v1 value.
|
|
113
|
+
|
|
114
|
+
## Strict and lossy output
|
|
115
|
+
|
|
116
|
+
Strict mode is the default. If an included table, column, default, generated
|
|
117
|
+
expression, identity, constraint, foreign key, or index cannot be represented
|
|
118
|
+
soundly, mapping returns diagnostics and no snapshot.
|
|
119
|
+
|
|
120
|
+
Lossy mode is explicit. It may return a snapshot with warnings, but the result
|
|
121
|
+
is marked lossy. Migration planning blocks lossy facts unless the caller
|
|
122
|
+
explicitly handles the omitted behavior.
|
|
123
|
+
|
|
124
|
+
Only unambiguous literal defaults become snapshot literals. Other defaults,
|
|
125
|
+
generated expressions, checks, predicates, and expression index terms remain
|
|
126
|
+
dialect-tagged SQL. Falsy values such as `0`, `false`, `NULL`, and empty
|
|
127
|
+
strings are preserved.
|
|
128
|
+
|
|
129
|
+
PostgreSQL readers expose views, materialized views, sequences, enums, domains,
|
|
130
|
+
collations, routines, triggers, policies, partitions, extensions, comments,
|
|
131
|
+
and ownership as typed complete catalog records. `mapCatalogToCompleteSnapshot`
|
|
132
|
+
retains those records in Snapshot v2. The existing `mapCatalogToSnapshot`
|
|
133
|
+
mapper still emits the table-only Snapshot v1 and does not fabricate these
|
|
134
|
+
objects into tables. If a PostgreSQL catalog row lacks the evidence needed for
|
|
135
|
+
safe normalization, the reader retains a deferred or opaque record and emits a
|
|
136
|
+
diagnostic.
|
|
137
|
+
|
|
138
|
+
SQLite readers expose recoverable views and triggers as typed complete records.
|
|
139
|
+
They retain virtual and shadow tables as deferred objects, and keep attached
|
|
140
|
+
databases outside the selected namespace as opaque boundary records. SQLite
|
|
141
|
+
declared types, derived affinity, generated expressions, rowid identity, and
|
|
142
|
+
`AUTOINCREMENT` stay tagged with SQLite dialect metadata. When an attached
|
|
143
|
+
database is selected, table PRAGMAs may be visible but CREATE SQL remains
|
|
144
|
+
limited to the fixed `main` and `temp` catalog statements, so the reader marks
|
|
145
|
+
the catalog visibility as limited instead of combining namespaces.
|
|
146
|
+
|
|
147
|
+
## MySQL 8 complete catalog surface
|
|
148
|
+
|
|
149
|
+
The MySQL reader supports MySQL 8.0.16 and later within the MySQL 8 series and
|
|
150
|
+
rejects MariaDB. It reads one selected database from `INFORMATION_SCHEMA` and
|
|
151
|
+
keeps catalog SQL as tagged, unevaluated MySQL data. The complete catalog has
|
|
152
|
+
typed records for views, routines and parameters, triggers, partitions,
|
|
153
|
+
collations used by the selected tables or columns, and comments.
|
|
154
|
+
|
|
155
|
+
View definitions come from `INFORMATION_SCHEMA.VIEWS`. The reader cross-
|
|
156
|
+
references each view with its `INFORMATION_SCHEMA.COLUMNS` rows by physical
|
|
157
|
+
table name, so view columns remain attached to the view and Snapshot v2 can
|
|
158
|
+
validate their own column IDs. A missing definition or an unresolved
|
|
159
|
+
cross-object reference becomes a deferred record with a diagnostic.
|
|
160
|
+
|
|
161
|
+
MySQL scheduled events are kept as `CatalogOpaqueObject` records with their
|
|
162
|
+
metadata and definition tagged as opaque SQL. The reader emits an
|
|
163
|
+
`unmodeled-object` warning, and Snapshot v2 retains the record in
|
|
164
|
+
`opaqueObjects` without treating it as a typed routine, trigger, or migration
|
|
165
|
+
operation.
|
|
166
|
+
|
|
167
|
+
Sequences and materialized views are not typed MySQL families. A sequence-like
|
|
168
|
+
or other non-base table row reported by the catalog is retained as a deferred
|
|
169
|
+
object. MySQL row-level security (RLS) policies, extension objects, and
|
|
170
|
+
ownership are unsupported. Definers are retained as dialect metadata on
|
|
171
|
+
objects that expose them; they do not become ownership records. MySQL capability flags mark
|
|
172
|
+
these families as unsupported.
|
|
173
|
+
|
|
174
|
+
The query and normalization layout follows the catalog-reading portions of the
|
|
175
|
+
[Drizzle MySQL introspector](https://github.com/drizzle-team/drizzle-orm/blob/main/drizzle-kit/src/introspect-mysql.ts).
|
|
176
|
+
The reader keeps that metadata as normalized typed data and never evaluates
|
|
177
|
+
database-provided SQL. Optional source generation remains a later, pure step
|
|
178
|
+
with a controlled literal printer.
|
|
179
|
+
|
|
180
|
+
Use `mapCatalogToCompleteSnapshot()` for the typed MySQL families and its
|
|
181
|
+
opaque or deferred boundaries. Use `mapCatalogToSnapshot()` when the caller
|
|
182
|
+
needs the table-only Snapshot v1.
|
|
183
|
+
|
|
184
|
+
## Diagnostics and safety
|
|
185
|
+
|
|
186
|
+
Diagnostics include a severity, stable code, catalog path, physical reference
|
|
187
|
+
when available, and a remediation hint. They distinguish connection/query
|
|
188
|
+
failures, permission limits, unsupported products or versions, unresolved
|
|
189
|
+
references, expression recovery failures, unmodeled objects, and lossy
|
|
190
|
+
mappings.
|
|
191
|
+
|
|
192
|
+
Introspection is read-only from Qubu's perspective. Do not pass credentials or
|
|
193
|
+
DSNs through diagnostic fields. Keep driver-specific error text in the
|
|
194
|
+
application's logging boundary, and treat database-provided SQL as opaque
|
|
195
|
+
input.
|
|
196
|
+
|
|
197
|
+
## What comes next
|
|
198
|
+
|
|
199
|
+
The canonical snapshot is the handoff to Qubu's pure schema pipeline:
|
|
200
|
+
|
|
201
|
+
- diffing compares canonical snapshots;
|
|
202
|
+
- rename resolution consumes previous/current snapshots and explicit hints;
|
|
203
|
+
- migration planning consumes semantic diff operations;
|
|
204
|
+
- DDL emitters consume approved plans and dialect capabilities;
|
|
205
|
+
- source generation creates a new machine-owned Snapshot v1 schema baseline.
|
|
206
|
+
|
|
207
|
+
None of those layers opens a database connection or changes how introspection
|
|
208
|
+
represents catalog facts. DDL emission produces statements; it does not apply
|
|
209
|
+
them. The [ownership map](../reference/supported-surface.md#ownership-boundary)
|
|
210
|
+
keeps this handoff separate from application-owned migration execution.
|
|
211
|
+
|
|
212
|
+
See the [introspection support matrix](../reference/introspection-support.md)
|
|
213
|
+
for version baselines and dialect-specific limits. Snapshot serialization
|
|
214
|
+
itself remains documented in [Canonical schema snapshots](snapshots.md).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Migration plans
|
|
2
|
+
|
|
3
|
+
> Describe reviewed snapshot changes as deterministic data before selecting a DDL emitter.
|
|
4
|
+
|
|
5
|
+
The optional `qubu/migration` entrypoint consumes a resolved `SnapshotDiff` and
|
|
6
|
+
returns an immutable migration-plan IR. It contains operation IDs, paths,
|
|
7
|
+
logical and physical identity evidence, dependency edges, preconditions, safety,
|
|
8
|
+
lock and transaction requirements, and reversibility markers.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createMigrationPlan } from 'qubu/migration'
|
|
12
|
+
|
|
13
|
+
const result = createMigrationPlan(diff)
|
|
14
|
+
if (!result.ok) {
|
|
15
|
+
// Review result.plan.diagnostics and provide explicit decisions.
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Creation is pure. The planner does not open a connection, execute a transaction,
|
|
20
|
+
render SQL, or create migration history. A physical rename remains a
|
|
21
|
+
`physical-rename` operation; it is never represented as custom SQL or silently
|
|
22
|
+
changed into a drop and add.
|
|
23
|
+
|
|
24
|
+
## Safety decisions
|
|
25
|
+
|
|
26
|
+
Safe operations can be inspected immediately. Destructive, review-required,
|
|
27
|
+
unsupported, unknown, and lossy facts remain blocked until an explicit decision
|
|
28
|
+
or matching option is supplied. Decisions are tied to an operation ID or to a
|
|
29
|
+
kind, namespace, and path, and each decision carries a review reason.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const reviewed = createMigrationPlan(diff, {
|
|
33
|
+
decisions: result.plan.operations
|
|
34
|
+
.filter(operation => operation.status === 'decision-required')
|
|
35
|
+
.map(operation => ({
|
|
36
|
+
operationId: operation.id,
|
|
37
|
+
action: 'allow',
|
|
38
|
+
reason: 'Reviewed against the deployment change request',
|
|
39
|
+
})),
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Unknown or lossy records are not converted into SQL. If a dialect-specific step
|
|
44
|
+
is genuinely needed, attach it explicitly with `customSql`, including its
|
|
45
|
+
dialect, safety declaration, reason, reversibility, and dependency position:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
createMigrationPlan(diff, {
|
|
49
|
+
customSql: [
|
|
50
|
+
{
|
|
51
|
+
sql: 'ALTER TABLE accounts VALIDATE CONSTRAINT accounts_check',
|
|
52
|
+
dialect: { name: 'postgresql', version: 1 },
|
|
53
|
+
safety: 'review-required',
|
|
54
|
+
reason: 'The dialect emitter does not model this catalog fact yet',
|
|
55
|
+
reversible: false,
|
|
56
|
+
position: 3,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The string is retained as an explicit custom operation only. The planner never
|
|
63
|
+
extracts SQL from opaque catalog payloads.
|
|
64
|
+
|
|
65
|
+
## Ordering and validation
|
|
66
|
+
|
|
67
|
+
Parent creation precedes child creation, while child removal precedes parent
|
|
68
|
+
removal. Reference edges and explicit custom-SQL dependencies are included in
|
|
69
|
+
the stable topological ordering. `encodeMigrationPlan()` emits canonical JSON;
|
|
70
|
+
`decodeMigrationPlan()` and `validateMigrationPlan()` reject unknown fields,
|
|
71
|
+
future versions, malformed operations, missing edges, and dependency cycles.
|
|
72
|
+
|
|
73
|
+
After review and approval, pass the plan to
|
|
74
|
+
[`qubu/ddl`](./ddl-emission.md). DDL emission remains separate from
|
|
75
|
+
application-owned migration execution in the package-wide [ownership
|
|
76
|
+
map](../reference/supported-surface.md#ownership-boundary).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Canonical schema snapshots
|
|
2
|
+
|
|
3
|
+
> Serialize schema metadata into strict, deterministic data and keep serialization separate from diffing, planning, and DDL emission.
|
|
4
|
+
|
|
5
|
+
Qubu's schema tooling lives behind the `qubu/snapshot` entrypoint. It converts
|
|
6
|
+
an immutable `schema()` registry into versioned data that can be inspected,
|
|
7
|
+
hashed, checked into source control, and handed to a dialect adapter. Importing
|
|
8
|
+
the snapshot entrypoint is optional; ordinary query imports do not load it.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createSchemaSnapshot, encodeSchemaSnapshot } from 'qubu/snapshot'
|
|
12
|
+
|
|
13
|
+
const snapshot = createSchemaSnapshot(appSchema)
|
|
14
|
+
const json = encodeSchemaSnapshot(snapshot)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The v1 envelope contains a format version, an independently versioned dialect
|
|
18
|
+
extension, a versioned naming-policy description, an optional namespace, and
|
|
19
|
+
arrays of tables. Tables, columns, constraints, and indexes are sorted by
|
|
20
|
+
stable logical ID. Physical names are values in the snapshot, not identities:
|
|
21
|
+
changing a physical name does not change the TypeScript field or metadata key.
|
|
22
|
+
|
|
23
|
+
Snapshot data is deliberately not executable Qubu state. Expressions are
|
|
24
|
+
parameter-free data records, and decoding never creates tables, column
|
|
25
|
+
references, or render closures. The neutral fallback renders branded built-in
|
|
26
|
+
expressions through the standard schema context; a dialect adapter may replace
|
|
27
|
+
that hook with its own literal and expression policy. An explicitly unsafe
|
|
28
|
+
expression retains its dialect tag and is rejected when it does not match the
|
|
29
|
+
selected snapshot dialect.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { decodeSchemaSnapshot } from 'qubu/snapshot'
|
|
33
|
+
|
|
34
|
+
const decoded = decodeSchemaSnapshot(json)
|
|
35
|
+
if (!decoded.ok) {
|
|
36
|
+
for (const issue of decoded.diagnostics) {
|
|
37
|
+
console.error(issue.path.join('.'), issue.code, issue.message)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The decoder is strict. It reports unknown fields, malformed nodes, unsupported
|
|
43
|
+
future format or extension versions, non-canonical entity ordering, wrong
|
|
44
|
+
dialect metadata, and broken foreign-key or column references as structured
|
|
45
|
+
diagnostics. It does not call `process.exit()` and has no runtime validation
|
|
46
|
+
library dependency.
|
|
47
|
+
|
|
48
|
+
`schemaSnapshotDigest()` computes a deterministic content digest from canonical
|
|
49
|
+
JSON. The digest is useful for cache keys and fixture assertions only. It is not
|
|
50
|
+
an entity identity, a rename marker, or migration lineage.
|
|
51
|
+
|
|
52
|
+
## Adapter boundary
|
|
53
|
+
|
|
54
|
+
`SchemaDialect` is a capability superset of `Dialect`. Create one with
|
|
55
|
+
`createSchemaDialect(queryDialect, hooks)`; the resulting object retains the
|
|
56
|
+
query dialect's name, identifier quoting, placeholders, literals, JSON, casts,
|
|
57
|
+
and advertised capabilities while adding schema encoders and validation under
|
|
58
|
+
`.schema`. Snapshot adapters reference that object instead of constructing a
|
|
59
|
+
second query dialect. The schema snapshot format version remains independent
|
|
60
|
+
from the dialect identity.
|
|
61
|
+
|
|
62
|
+
The common traversal owns logical IDs, fixed property order, canonical sorting,
|
|
63
|
+
portable constraints, cross-reference checks, and the immutable snapshot
|
|
64
|
+
envelope. A dialect adapter owns physical storage mapping, SQL literal and
|
|
65
|
+
expression encoding, dialect extensions, capability checks, and any dialect
|
|
66
|
+
naming policy. PostgreSQL, SQLite, and MySQL adapters can implement
|
|
67
|
+
`SchemaSnapshotAdapter` without duplicating traversal or decoder rules.
|
|
68
|
+
The PostgreSQL adapter is documented in the [PostgreSQL snapshot support
|
|
69
|
+
matrix](../reference/postgres-snapshot.md). Its schema dialect extends the
|
|
70
|
+
`postgresql` query dialect, and snapshot metadata uses that same identity.
|
|
71
|
+
The SQLite adapter is documented in the [SQLite snapshot support
|
|
72
|
+
matrix](../reference/sqlite-snapshot.md).
|
|
73
|
+
The MySQL adapter is documented in the [MySQL snapshot support
|
|
74
|
+
matrix](../reference/mysql-snapshot.md). Its query and snapshot dialects both
|
|
75
|
+
use `mysql`, while MySQL-only `ON UPDATE` and `AUTO_INCREMENT` details remain
|
|
76
|
+
inside the column and identity metadata they describe.
|
|
77
|
+
|
|
78
|
+
Snapshot serialization remains separate from database introspection,
|
|
79
|
+
comparison, rename resolution, migration planning, and DDL emission. The
|
|
80
|
+
optional `qubu/introspection` entrypoint can produce the same canonical
|
|
81
|
+
Snapshot v1 data from a user-owned catalog connection. The complete normalized
|
|
82
|
+
catalog can also be encoded as strict Snapshot v2 with the dedicated
|
|
83
|
+
complete-snapshot APIs described in [the catalog model](catalog-model.md).
|
|
84
|
+
Readers and connection lifecycle do not belong to this pure serialization
|
|
85
|
+
layer. Diffing can compare either snapshot version. Resolved diffs feed
|
|
86
|
+
migration plans, and approved plans feed DDL emission. The package-wide
|
|
87
|
+
[ownership map](../reference/supported-surface.md#ownership-boundary) keeps
|
|
88
|
+
those pure steps separate from application-owned database execution.
|
|
89
|
+
|
|
90
|
+
The optional [schema source generator](code-generation.md) consumes a complete,
|
|
91
|
+
non-lossy introspection result and makes its generated schema the next identity
|
|
92
|
+
baseline. It does not replace snapshot serialization or generate Snapshot v2
|
|
93
|
+
object families.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Storage and schema SQL
|
|
2
|
+
|
|
3
|
+
> Keep application types, SQL domains, physical storage, and schema expressions separate so each adapter can make its own rendering decision.
|
|
4
|
+
|
|
5
|
+
## Keep application and SQL types separate
|
|
6
|
+
|
|
7
|
+
A column can have an application value type, a SQL semantic domain, a physical
|
|
8
|
+
storage descriptor, and a cast target. These facts answer different questions.
|
|
9
|
+
|
|
10
|
+
For example, `numeric()` decodes to a TypeScript number, carries `SqlDecimal`,
|
|
11
|
+
uses portable numeric storage, and has a logical decimal cast target. Read
|
|
12
|
+
[SQL semantic types](../sql-semantic-types.md) for operator compatibility and
|
|
13
|
+
contextual literals.
|
|
14
|
+
|
|
15
|
+
## Record physical storage
|
|
16
|
+
|
|
17
|
+
Built-in helpers use portable storage descriptors:
|
|
18
|
+
|
|
19
|
+
| Helper | Portable storage |
|
|
20
|
+
| ----------- | ---------------- |
|
|
21
|
+
| integer() | integer |
|
|
22
|
+
| numeric() | numeric |
|
|
23
|
+
| text() | text |
|
|
24
|
+
| boolean() | boolean |
|
|
25
|
+
| date() | date |
|
|
26
|
+
| timestamp() | timestamp |
|
|
27
|
+
| uuid() | uuid |
|
|
28
|
+
| json<T>() | json |
|
|
29
|
+
| bigint() | bigint |
|
|
30
|
+
| binary() | binary |
|
|
31
|
+
|
|
32
|
+
Use a dialect-native descriptor when a column needs an exact vendor
|
|
33
|
+
declaration:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { nativeColumn, nativeStorage, table } from 'qubu'
|
|
37
|
+
|
|
38
|
+
const accounts = table('accounts', {
|
|
39
|
+
handle: nativeColumn(nativeStorage('postgresql', 'citext COLLATE "C"')),
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`nativeStorage()` preserves the declaration text and freezes the descriptor. The
|
|
44
|
+
`ColumnStorageOf`, `ColumnStorageTypeOf`, `ColumnStorageDialectOf`, and
|
|
45
|
+
`ColumnStorageDeclarationOf` helpers read its metadata. Native storage is
|
|
46
|
+
descriptive. It does not change selection, mutation, or query rendering.
|
|
47
|
+
|
|
48
|
+
## Render deterministic schema expressions
|
|
49
|
+
|
|
50
|
+
Schema SQL uses a different context from query SQL. Built-in scalar expressions
|
|
51
|
+
can be rendered for checks, generated columns, indexes, and other declaration
|
|
52
|
+
metadata:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { eq, table, text } from 'qubu'
|
|
56
|
+
import { renderSchemaSql } from 'qubu/schema'
|
|
57
|
+
|
|
58
|
+
const accounts = table('accounts', { status: text() })
|
|
59
|
+
|
|
60
|
+
renderSchemaSql(eq(accounts.status, 'active'), { mode: 'check' })
|
|
61
|
+
// ("status" = 'active')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The schema context emits SQL literals instead of placeholders. It supports
|
|
65
|
+
strings, finite numbers, booleans, bigint, and NULL through the portable
|
|
66
|
+
fallback. A dialect can provide renderSchemaLiteral for another spelling.
|
|
67
|
+
Unsupported values and direct calls to parameter() fail.
|
|
68
|
+
|
|
69
|
+
Column references render as bare physical identifiers for generated, check, and
|
|
70
|
+
index expressions. Default expressions reject column references because a
|
|
71
|
+
column default cannot depend on another row value. Aggregates, windows, and
|
|
72
|
+
subqueries are rejected in every schema mode.
|
|
73
|
+
|
|
74
|
+
An extension must opt into the schema contract with `defineSchemaExpression()`:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { defineSchemaExpression, renderSchemaSql } from 'qubu/schema'
|
|
78
|
+
|
|
79
|
+
const currentDate = defineSchemaExpression('function', context => {
|
|
80
|
+
context.append('CURRENT_DATE')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
renderSchemaSql(currentDate, { mode: 'default' })
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Use raw schema SQL only when necessary
|
|
87
|
+
|
|
88
|
+
Use `unsafeSchemaSql(dialect, sql)` only for trusted, parameter-free syntax that
|
|
89
|
+
Qubu does not model. Its dialect tag is checked when rendering, and its text is
|
|
90
|
+
preserved apart from normalizing line endings. A normal `makeExpression()`
|
|
91
|
+
extension must pass through the explicit `schemaExpression()` audit boundary
|
|
92
|
+
before schema rendering accepts it.
|
|
93
|
+
|
|
94
|
+
> [!WARNING]
|
|
95
|
+
> Raw schema SQL does not quote identifiers or bind values. Keep it in trusted
|
|
96
|
+
> extension code.
|
|
97
|
+
|
|
98
|
+
## Read next
|
|
99
|
+
|
|
100
|
+
- [Tables and names](tables-and-names.md) covers logical IDs and SQL naming.
|
|
101
|
+
- [Constraints, keys, and indexes](constraints-and-indexes.md) covers metadata
|
|
102
|
+
consumed by grouped queries and schema adapters.
|
|
103
|
+
- [Canonical schema snapshots](snapshots.md) explains how storage and
|
|
104
|
+
constraints become serialized data.
|