@vibeorm/sql 4.2.0 → 5.0.0

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.
Files changed (37) hide show
  1. package/dist/ast.d.ts +322 -9
  2. package/dist/codecs.d.ts +46 -0
  3. package/dist/dialect.d.ts +155 -5
  4. package/dist/dialects/migration-inventory.d.ts +58 -1
  5. package/dist/dialects/migration-objects.d.ts +36 -0
  6. package/dist/dialects/migration-visibility.d.ts +6 -1
  7. package/dist/dialects/mysql-check-probe.d.ts +1 -0
  8. package/dist/dialects/mysql-generated-columns.d.ts +43 -0
  9. package/dist/dialects/mysql-index-expressions.d.ts +20 -0
  10. package/dist/dialects/mysql-row-rules.d.ts +8 -0
  11. package/dist/dialects/mysql.d.ts +3 -2
  12. package/dist/dialects/postgres-generated-columns.d.ts +30 -0
  13. package/dist/dialects/postgres-index-expressions.d.ts +10 -0
  14. package/dist/dialects/postgres-index-keys.d.ts +15 -0
  15. package/dist/dialects/postgres-rls.d.ts +16 -1
  16. package/dist/dialects/postgres-row-rules.d.ts +25 -0
  17. package/dist/dialects/sqlite-generated-columns.d.ts +18 -0
  18. package/dist/dialects/sqlite-index-expressions.d.ts +23 -0
  19. package/dist/dialects/sqlite-row-rules.d.ts +8 -0
  20. package/dist/dialects/sqlite-table-definition.d.ts +25 -5
  21. package/dist/generated-columns.d.ts +18 -0
  22. package/dist/index-expressions.d.ts +61 -0
  23. package/dist/index.d.ts +23 -9
  24. package/dist/index.js +2662 -1161
  25. package/dist/index.js.map +35 -21
  26. package/dist/key-list.d.ts +83 -0
  27. package/dist/like.d.ts +17 -0
  28. package/dist/local-date.d.ts +9 -0
  29. package/dist/pg-array.d.ts +6 -0
  30. package/dist/predicate.d.ts +21 -7
  31. package/dist/query/index.js +59 -19
  32. package/dist/query/index.js.map +7 -7
  33. package/dist/query/state.d.ts +5 -0
  34. package/dist/query/types.d.ts +2 -0
  35. package/dist/row-rules.d.ts +57 -0
  36. package/dist/timestamp-identity.d.ts +8 -2
  37. package/package.json +2 -2
package/dist/dialect.d.ts CHANGED
@@ -121,10 +121,146 @@ export type SqlDialect = {
121
121
  * member, so composite membership always chunks against `paramBudget`.
122
122
  */
123
123
  readonly tupleInStrategy: "rowValue" | "expand";
124
+ /**
125
+ * Key-list membership (`SqlExpr` `keyList`, the public `where: { IN }`).
126
+ * `unnest` binds ONE typed array per member — `("a", "b") IN (SELECT * FROM
127
+ * unnest($1::int8[], $2::text[]))` — so the text and its planning cost do
128
+ * not grow with the list (postgres 18.6, 1 000 two-column keys: planning
129
+ * 0.09 ms against 2.0 ms for the OR of conjunctions; 5 000 keys: 0.09 ms
130
+ * against 12.6 ms, and 10 ms against 285 ms execution). `rowValue` renders
131
+ * the row-constructor list `(a, b) IN ((?, ?), …)` (mysql: 3.9 ms for 1 000
132
+ * keys through an index range scan, equal to a VALUES derived table and
133
+ * without its numeric casts; every probed type compared exactly like `=`).
134
+ * `values` renders `("a", "b") IN (VALUES (?, ?), …)` (sqlite 3.51: 0.9 ms
135
+ * prepare + run for 1 000 keys against 1.9 ms for the row list; an OR of
136
+ * 1 000 conjunctions is refused there, "Expression tree is too large").
137
+ * The row forms bind one parameter per member, like scalar `IN (?, …)`.
138
+ */
139
+ readonly keyListStrategy: "unnest" | "rowValue" | "values";
140
+ /**
141
+ * The keyset seek (`after`, `findPage`, a compound `cursor`, graph pages;
142
+ * D-01). `rowValue`: where every key sorts in one direction and no key or
143
+ * position value is NULL, one row-value comparison `("a", "b") > ($1, $2)`
144
+ * (`SqlExpr` `rowCompare`) that an index on `(a, b)` bounds as a range;
145
+ * with mixed directions the OR expansion ANDed with the inclusive bound on
146
+ * the leading key (`"a" >= $1 AND (…)`). Measured: PostgreSQL 18.6 at
147
+ * 500 000 rows deep 25.4 ms → 0.013 ms, SQLite 3.51 at 490 000 rows 12.9 →
148
+ * 0.007 ms — the OR alone is a full index walk from the first row. `or`:
149
+ * the OR expansion alone — MySQL's range optimizer already turns it into
150
+ * index ranges (22 rows touched at any depth) and SCANS a row-value
151
+ * inequality (194 ms at 490 000 rows deep). Nullable keys keep the OR on
152
+ * every dialect.
153
+ */
154
+ readonly keysetSeekStrategy: "rowValue" | "or";
155
+ /**
156
+ * Fix lane Q1 (round 3, DB-01): how a paged include (`include: { rel: {
157
+ * orderBy, take, skip } }`) cuts each parent's page when an index proves the
158
+ * per-key form (`SelectStatement.keyPage`, `KeyPage`). `lateral`: one
159
+ * primary-key page per key in a `CROSS JOIN LATERAL` over the unnested key
160
+ * arrays — PostgreSQL 18.6, 1 000 000 children, 50 parents: 59.1 → 1.08 ms
161
+ * (the window sorted every child of every parent); the key-only page keeps
162
+ * an index-only scan the planner prefers at every `skip`, where a lateral
163
+ * fetching whole rows flipped to bitmap + sort and LOST (40 → 64 ms).
164
+ * `inLimit`: SQLite has no LATERAL — `(VALUES …)` keys joined to the child
165
+ * by `IN (SELECT key … LIMIT)`, a correlated list subquery per key (SQLite
166
+ * 3.51, 500 000 children, 50 parents: 75.2 → 0.47 ms). `window`: keep the
167
+ * `ROW_NUMBER()` window (MySQL: a lateral page still read every child).
168
+ */
169
+ readonly keyPageStrategy: "lateral" | "inLimit" | "window";
170
+ /**
171
+ * How the `"join"` relation strategy's lateral subselects build their JSON
172
+ * payload (`LateralJoin`), or `null` where the dialect has no LATERAL
173
+ * (`capabilities.lateralJoin` false). `rowArray` builds one positional row,
174
+ * `arrayAgg` aggregates the rows, `emptyArray` is the payload of a list
175
+ * level without rows. `order` says how a list level keeps its order:
176
+ * `"inAggregate"` — the aggregate takes the ORDER BY itself (postgres
177
+ * `json_agg(… ORDER BY …)`); `"positionSlot"` — the aggregate cannot order
178
+ * (MySQL `JSON_ARRAYAGG` has no ORDER BY and documents its element order as
179
+ * undefined), so an ordered level projects a `ROW_NUMBER()` position as
180
+ * its last positional slot and the runtime sorts the children by it.
181
+ * MySQL needs 8.0.14 or later (LATERAL and outer references in derived
182
+ * tables); MariaDB has no LATERAL.
183
+ */
184
+ readonly lateralJson: {
185
+ readonly rowArray: string;
186
+ readonly arrayAgg: string;
187
+ readonly emptyArray: string;
188
+ readonly order: "inAggregate" | "positionSlot";
189
+ /**
190
+ * Time-of-day values ride the payload as text (`CAST(… AS CHAR)`, the
191
+ * query path's form): MySQL 8.4.11 prints a TIME beyond a day inside JSON
192
+ * wrapped modulo 24 hours (`838:59:59` → `"06:59:59.000000"`), which would
193
+ * turn the query strategy's refusal into a wrong value.
194
+ */
195
+ readonly timeOfDayAsText: boolean;
196
+ /**
197
+ * Zone-less date-times (MySQL DATETIME / TIMESTAMP / DATE) ride the
198
+ * payload as the session's zone-less text, which the JSON transport
199
+ * decodes as UTC, while the query strategy's driver reads the same text
200
+ * in ITS configured zone (mysql2 `timezone`). `true`: the dialect default
201
+ * (`relationStrategyDefault`) joins only on an adapter whose wire declares
202
+ * `zoneLessDateTime: "utc"`; an explicit `"join"` is unaffected. `false`
203
+ * where every adapter pins zone-less values to UTC (postgres).
204
+ */
205
+ readonly requiresUtcDateTimeWire: boolean;
206
+ } | null;
207
+ /**
208
+ * The relation strategy a client uses when neither the call nor the client
209
+ * option names one (5.0 rule D-2). `"join"` only where `lateralJoin` holds.
210
+ * On MySQL (lane J, wave 3b) the runtime still loads `"query"` until the
211
+ * adapter knows its server runs LATERAL and declares a UTC date-time wire
212
+ * (`serverLateralJoins` in the runtime).
213
+ */
214
+ readonly relationStrategyDefault: "join" | "query";
215
+ /**
216
+ * Fix lane Q1 (round 3, DB-03): how a positive relation filter that pins ONE
217
+ * related row by a unique key renders. `exists`: the correlated `EXISTS` —
218
+ * PostgreSQL and MySQL turn it into a semi-join. `inSelect`: an uncorrelated
219
+ * `key IN (SELECT key …)` — SQLite runs a correlated `EXISTS` once per outer
220
+ * row (`post where author.email = x`: 134 → 0.15 ms, Post 500 000).
221
+ */
222
+ readonly relationFilterStrategy: "exists" | "inSelect";
223
+ /**
224
+ * DB-02: when a find with a TOTAL order (it ends with the full primary key)
225
+ * pages with `skip` ≥ `minSkip`, the runtime cuts the page as primary keys
226
+ * in a derived table (`SELECT pk FROM t WHERE … ORDER BY … LIMIT ? OFFSET ?`)
227
+ * and joins the rows back by primary key (`fromSelect` + `innerJoins`; MySQL
228
+ * refuses LIMIT inside `IN (subquery)`). MySQL 26.7 / 8.4 drop the index from
229
+ * skip ≈ 5 000 and sort the table (500 000 rows, skip 5 000: 116.6 → 1.21 ms;
230
+ * 100 000: 144 → 9.3 ms) — there it applies under that rule alone.
231
+ * PostgreSQL gains only when the key page is an index-only scan (`authorId =
232
+ * 1`, skip 4 000: 4.37 → 0.24 ms) and loses up to 15 % when the filter is not
233
+ * covered, so `requiresCoveringIndex` asks for one declared index that serves
234
+ * the filter, the order and the key. `null`: never (SQLite walks an index
235
+ * plus rowid lookups cheaply — skip 100 000: 1.02 ms).
236
+ */
237
+ readonly deferredJoinPage: {
238
+ readonly minSkip: number;
239
+ readonly requiresCoveringIndex: boolean;
240
+ } | null;
241
+ /**
242
+ * DB-07: where this engine sorts NULL when ORDER BY names no placement —
243
+ * `high`: above every value (postgres: last on ASC, first on DESC), `low`:
244
+ * below (sqlite, mysql: first on ASC, last on DESC). A b-tree stores NULLs in
245
+ * that position, so only the matching `nulls` placement is index order.
246
+ */
247
+ readonly nativeNullsOrder: "high" | "low";
248
+ /**
249
+ * DB-07: whether a paged find whose LEADING order key is nullable and asks
250
+ * for the non-native placement reads through a `UNION ALL` of two
251
+ * index-ordered branches (`SelectStatement.fromUnion`: the key's non-NULL
252
+ * rows, then its NULL rows, each limited to `skip + take`), re-ordered and
253
+ * paged outside. Today's statement sorts the whole table on every page:
254
+ * PostgreSQL 18 `desc nulls last` 34.8 → 0.075 ms, MySQL 26.7 `asc nulls
255
+ * last` 54.5 → 0.85 ms. SQLite 3.51 already walks the index for either
256
+ * placement (0.005 ms today, 0.020 ms as a union) — `false` there.
257
+ */
258
+ readonly nullsUnionPage: boolean;
124
259
  /**
125
260
  * Membership on a time-of-day column (`inArray` with `timeOfDay`): `in`
126
261
  * renders like any other membership; `equalities` renders an OR of `=`
127
- * comparisons. MySQL 8.4 types a `?` inside a multi-value IN list against a
262
+ * comparisons (a key list with a time-of-day member: an OR of complete
263
+ * conjunctions). MySQL 8.4 types a `?` inside a multi-value IN list against a
128
264
  * TIME(n) column as TIME(6) and matches nothing unless n is 6 (live on
129
265
  * 8.4.11; 26.7 matches); `=` converts the parameter to the column's TIME on
130
266
  * every version.
@@ -150,11 +286,14 @@ export type SqlDialect = {
150
286
  };
151
287
  /**
152
288
  * Skip-conflicting-rows form. `onConflictDoNothing` appends
153
- * `ON CONFLICT [(target)] DO NOTHING`; `insertIgnore` turns the statement into
154
- * `INSERT IGNORE INTO` — which ignores a wider class of errors than a pure
155
- * duplicate-key skip (see the catalog case).
289
+ * `ON CONFLICT [(target)] DO NOTHING`; `duplicateKeyKeep` appends
290
+ * `ON DUPLICATE KEY UPDATE k = t.k` — a self-assignment that skips ANY
291
+ * duplicate key and, unlike `INSERT IGNORE` (never emitted: it also turns NOT
292
+ * NULL, out-of-range and foreign-key errors into warnings), leaves every other
293
+ * error an error. A duplicate takes the update arm, so the table's UPDATE
294
+ * triggers fire (nothing changes).
156
295
  */
157
- readonly doNothingForm: "onConflictDoNothing" | "insertIgnore";
296
+ readonly doNothingForm: "onConflictDoNothing" | "duplicateKeyKeep";
158
297
  /** Case-insensitive LIKE: native ILIKE or LOWER(col) LIKE LOWER(pattern). */
159
298
  readonly insensitiveLike: "ilike" | "lower";
160
299
  /**
@@ -243,6 +382,17 @@ export type SqlDialect = {
243
382
  * rows are grouped by column set into separate statements there (M4).
244
383
  */
245
384
  readonly rowDefaultKeyword: boolean;
385
+ /**
386
+ * How a multi-row INSERT whose rows all bind plain values may render
387
+ * (`InsertStatement.unnest`). `unnest` binds ONE typed array per column —
388
+ * `INSERT … SELECT … FROM unnest($1::text[], …) WITH ORDINALITY … ORDER BY`
389
+ * — so the text is constant and a statement binds one parameter per column
390
+ * (postgres and PGlite; the type table, efficiency campaign EPIC C, proved
391
+ * equal stored values and errors on adapter-pg, adapter-bun and PGlite).
392
+ * `values` keeps one parameter per value (mysql and sqlite have no typed
393
+ * array parameters); the renderer refuses an `unnest` statement there.
394
+ */
395
+ readonly bulkInsertForm: "unnest" | "values";
246
396
  /** How an INSERT that names no column renders ({@link EmptyInsertSyntax}). */
247
397
  readonly emptyInsert: EmptyInsertSyntax;
248
398
  /**
@@ -1,5 +1,16 @@
1
1
  import type { SqlDialect } from "../dialect.ts";
2
2
  import type { RenderedQuery } from "../render.ts";
3
+ /**
4
+ * Ends a MySQL catalog listing whose row count has no small bound (the
5
+ * migration inventory, the visibility proof). `sql_select_limit` (session or
6
+ * global, e.g. 1 000 under `mysql --safe-updates`) silently cuts the rows of a
7
+ * SELECT without its own LIMIT, and a cut listing reads its last objects (or
8
+ * grants) as missing; an explicit LIMIT takes precedence over it. After the
9
+ * last `UNION` branch (and its ORDER BY) the LIMIT applies to the whole union.
10
+ * The value is MySQL's largest row count, so nothing is ever cut. The batched
11
+ * object readers end the same way (`migration-objects.ts`, LEFTOVERS-01).
12
+ */
13
+ export declare const MYSQL_NO_ROW_LIMIT: string;
3
14
  /** SQLite column metadata includes generated columns omitted by table_info. */
4
15
  export declare function renderSqliteColumnInventory(params: {
5
16
  dialect: SqlDialect;
@@ -12,11 +23,57 @@ export declare function renderSqliteForeignKeyInventory(params: {
12
23
  namespace: string;
13
24
  table: string;
14
25
  }): RenderedQuery;
26
+ /**
27
+ * {@link renderSqliteColumnInventory} for many tables in one statement. Rows
28
+ * carry `vibe_request` = the table's index in `tables`, then the single query's
29
+ * columns in its order (`cid`). The table-valued pragma runs the same PRAGMA
30
+ * per table, with the same namespace argument. Every table answers at least one
31
+ * row (`LEFT JOIN`): one whose `name` is NULL when its PRAGMA has no rows, so a
32
+ * table missing from the answer was not read.
33
+ */
34
+ export declare function renderSqliteColumnInventoryBatch(params: {
35
+ dialect: SqlDialect;
36
+ namespace: string;
37
+ tables: readonly string[];
38
+ }): RenderedQuery;
39
+ /** {@link renderSqliteForeignKeyInventory} for many tables in one statement; rows as in {@link renderSqliteColumnInventoryBatch} (no-row marker: `id` NULL), ordered `id, seq` per table. */
40
+ export declare function renderSqliteForeignKeyInventoryBatch(params: {
41
+ dialect: SqlDialect;
42
+ namespace: string;
43
+ tables: readonly string[];
44
+ }): RenderedQuery;
45
+ /** A PRAGMA SQLite introspection reads once per table (`table_xinfo`, `index_list`, `foreign_key_list`) or per index (`index_xinfo`). */
46
+ export type SqliteIntrospectionPragma = "table_xinfo" | "index_list" | "index_xinfo" | "foreign_key_list";
47
+ /**
48
+ * `PRAGMA <pragma>("<name>")` for every name in one statement. Rows carry
49
+ * `vibe_request` = the name's index in `names`, then exactly the PRAGMA's own
50
+ * columns (a table-valued pragma's hidden `arg`/`schema` columns are not in
51
+ * `p.*`), per name in the order the PRAGMA emits them. No schema argument, as
52
+ * in the single statement: the table-valued pragma runs `PRAGMA <pragma>='name'`
53
+ * internally, with the same temp-then-main name lookup.
54
+ *
55
+ * Literal text with no bound values, like every statement SQLite introspection
56
+ * sends (the names list is one quoted string literal): an executor that runs a
57
+ * value-less statement without binding anything reads the same rows.
58
+ *
59
+ * Every name answers at least one row (`LEFT JOIN`): a name whose PRAGMA has no
60
+ * rows answers one row whose PRAGMA columns are all NULL, so a name missing
61
+ * from the answer was not read (and an empty answer is never "no rows").
62
+ */
63
+ export declare function renderSqliteIntrospectionPragmaBatch(params: {
64
+ dialect: SqlDialect;
65
+ pragma: SqliteIntrospectionPragma;
66
+ names: readonly string[];
67
+ }): RenderedQuery;
15
68
  /** Exact SQL needed to restore independently existing indexes and triggers during a rebuild. */
16
69
  export declare function renderSqliteRebuildObjects(params: {
17
70
  dialect: SqlDialect;
18
71
  }): RenderedQuery;
19
- /** Enumerate scoped catalog identities, including constraints and their backing indexes. */
72
+ /**
73
+ * Enumerate scoped catalog identities, including constraints and their backing
74
+ * indexes. The MySQL listing ends with {@link MYSQL_NO_ROW_LIMIT}, so
75
+ * `sql_select_limit` cannot drop objects from it.
76
+ */
20
77
  export declare function renderMigrationInventory(params: {
21
78
  dialect: SqlDialect;
22
79
  namespace: string;
@@ -11,6 +11,42 @@ export declare function renderMysqlObjectMembers(params: {
11
11
  dialect: SqlDialect;
12
12
  identity: SqlObjectIdentity;
13
13
  }): RenderedQuery | undefined;
14
+ /**
15
+ * One batched MySQL catalog read. Its rows carry `vibe_position` = i, the
16
+ * branch that answers for request position `positions[i]`; member reads also
17
+ * carry `vibe_ordinal`, the single query's ORDER BY column.
18
+ */
19
+ export type MysqlObjectBatch = RenderedQuery & {
20
+ readonly positions: readonly number[];
21
+ };
22
+ /**
23
+ * MySQL facts reads in bounded statements, grouped by query shape in request
24
+ * order, 100 objects per statement. Each `UNION ALL` branch is the UNCHANGED
25
+ * single-object query as a derived table, so the server matches names with the
26
+ * single read's own `WHERE` and collation: the rows of a position are exactly the
27
+ * rows {@link renderMigrationObjectQuery} returns for it. An identity MySQL cannot
28
+ * inspect is in no batch; the caller reads it on its own (and gets its refusal).
29
+ * The statement ends with `LIMIT 18446744073709551615`, so `sql_select_limit`
30
+ * cannot cut it.
31
+ */
32
+ export declare function renderMysqlObjectBatch(params: {
33
+ dialect: SqlDialect;
34
+ identities: readonly SqlObjectIdentity[];
35
+ }): readonly MysqlObjectBatch[];
36
+ /**
37
+ * MySQL member reads (function parameters, index and constraint columns) in
38
+ * bounded statements, grouped by kind in request order, 100 objects per
39
+ * statement. Each branch is the single member query's select list and `WHERE`
40
+ * plus its ORDER BY column as `vibe_ordinal`; MySQL ignores an ORDER BY inside a
41
+ * union branch, so the statement orders by `vibe_position, vibe_ordinal` (then
42
+ * `LIMIT 18446744073709551615`, so `sql_select_limit` cannot cut it). Rows
43
+ * of a position equal {@link renderMysqlObjectMembers}' rows in its order
44
+ * whenever that object's ordinals are distinct; the caller checks that.
45
+ */
46
+ export declare function renderMysqlObjectMembersBatch(params: {
47
+ dialect: SqlDialect;
48
+ identities: readonly SqlObjectIdentity[];
49
+ }): readonly MysqlObjectBatch[];
14
50
  /** PostgreSQL dependency addresses are returned as server-owned strings, never guessed from SQL. */
15
51
  export declare function renderMigrationDependencies(params: {
16
52
  dialect: SqlDialect;
@@ -1,6 +1,11 @@
1
1
  import type { SqlDialect } from "../dialect.ts";
2
2
  import type { RenderedQuery } from "../render.ts";
3
- /** Direct grants needed to enumerate MySQL objects without privilege-filtered omissions. */
3
+ /**
4
+ * Direct grants needed to enumerate MySQL objects without privilege-filtered
5
+ * omissions. Ends with {@link MYSQL_NO_ROW_LIMIT}: a `sql_select_limit` below
6
+ * the grant count would otherwise cut grants and refuse a namespace whose
7
+ * grants are complete.
8
+ */
4
9
  export declare function renderMigrationVisibility(params: {
5
10
  dialect: SqlDialect;
6
11
  namespace: string;
@@ -5,6 +5,7 @@ export declare function renderMysqlCheckProbe(params: {
5
5
  dialect: SqlDialect;
6
6
  operation: "clone" | "add" | "read" | "drop";
7
7
  probe: string;
8
+ /** Source for clone, or qualified live table for a transient SHOW CREATE comparison. */
8
9
  table?: {
9
10
  readonly namespace: string;
10
11
  readonly name: string;
@@ -0,0 +1,43 @@
1
+ import type { QualifiedSqlName } from "@vibeorm/schema";
2
+ import type { SqlDialect } from "../dialect.ts";
3
+ import type { ExtractedGeneratedColumn, GeneratedColumnDeclaration } from "../generated-columns.ts";
4
+ import type { RenderedQuery } from "../render.ts";
5
+ /** MySQL stored syntax, placed after complete type/charset/collation information. */
6
+ export declare function renderMysqlGeneratedColumnClause({ generated }: {
7
+ readonly generated: GeneratedColumnDeclaration;
8
+ }): string;
9
+ /** Temporary CREATE/DROP operations avoid ALTER's implicit-commit boundary. */
10
+ export type MysqlGeneratedColumnProbe = {
11
+ readonly operation: "context";
12
+ } | {
13
+ readonly operation: "clone";
14
+ readonly probe: string;
15
+ readonly table: QualifiedSqlName;
16
+ } | {
17
+ readonly operation: "create";
18
+ readonly probe: string;
19
+ readonly definition: string;
20
+ readonly column: string;
21
+ readonly columnDefinition: string;
22
+ readonly backslashEscapes: boolean;
23
+ readonly after?: string;
24
+ } | {
25
+ readonly operation: "read";
26
+ readonly probe: string;
27
+ readonly table?: QualifiedSqlName;
28
+ } | {
29
+ readonly operation: "drop";
30
+ readonly probe: string;
31
+ };
32
+ /** Render scratch SQL from independently provisioned table definitions; migrate owns cleanup. */
33
+ export declare function renderMysqlGeneratedColumnProbe({ dialect, probe }: {
34
+ readonly dialect: SqlDialect;
35
+ readonly probe: MysqlGeneratedColumnProbe;
36
+ }): RenderedQuery;
37
+ /** SHOW CREATE's own escaping rules apply; information_schema uses a different spelling channel. */
38
+ export declare function extractMysqlGeneratedColumn({ definition, column, backslashEscapes }: {
39
+ readonly definition: string;
40
+ readonly column: string;
41
+ readonly backslashEscapes: boolean;
42
+ }): ExtractedGeneratedColumn | undefined;
43
+ //# sourceMappingURL=mysql-generated-columns.d.ts.map
@@ -0,0 +1,20 @@
1
+ import type { IndexKeyTermIR } from "@vibeorm/schema";
2
+ import type { SqlDialect } from "../dialect.ts";
3
+ import type { IndexExpressionProbeParams, IndexKeyOperand } from "../index-expressions.ts";
4
+ import type { PredicateModel } from "../predicate.ts";
5
+ import type { RenderedQuery } from "../render.ts";
6
+ /** Qualify only expression-bearing keys through schema's shared width rule; never impose new bounds on ordinary indexes. */
7
+ export declare function assertMysqlIndexKeyStorage({ model, terms }: {
8
+ readonly dialect: SqlDialect;
9
+ readonly model: PredicateModel;
10
+ readonly terms: readonly IndexKeyTermIR[];
11
+ }): void;
12
+ /** Recover SHOW CREATE key interiors, including exact CAST/charset/collation bytes. */
13
+ export declare function extractMysqlIndexKeys({ definition, index, backslashEscapes }: {
14
+ readonly definition: string;
15
+ readonly index: string;
16
+ readonly backslashEscapes: boolean;
17
+ }): readonly IndexKeyOperand[] | undefined;
18
+ /** Reuse E's temporary-clone/read/drop operations, extending only CREATE's key replacement seam. */
19
+ export declare function renderMysqlIndexExpressionProbe({ ...params }: IndexExpressionProbeParams): readonly RenderedQuery[];
20
+ //# sourceMappingURL=mysql-index-expressions.d.ts.map
@@ -0,0 +1,8 @@
1
+ import type { SqlDialect } from "../dialect.ts";
2
+ import type { EventRowRuleContext, RenderedRowRule } from "../row-rules.ts";
3
+ /** Emit mode-safe SIGNAL diagnostics and one unfiltered BEFORE trigger per event. */
4
+ export declare function renderMysqlRowRule({ dialect, context }: {
5
+ readonly dialect: SqlDialect;
6
+ readonly context: EventRowRuleContext;
7
+ }): RenderedRowRule;
8
+ //# sourceMappingURL=mysql-row-rules.d.ts.map
@@ -7,8 +7,9 @@
7
7
  * - Upserts use ON DUPLICATE KEY UPDATE (no conflict-target refinement).
8
8
  * - OFFSET requires LIMIT → the documented huge-limit idiom.
9
9
  * - No NULLS FIRST/LAST → `col IS NULL` emulation terms.
10
- * - No `ON CONFLICT ... DO NOTHING` → `INSERT IGNORE`, which is broader: it
11
- * downgrades other row-level errors to warnings too.
10
+ * - No `ON CONFLICT ... DO NOTHING` → `ON DUPLICATE KEY UPDATE k = t.k` (a
11
+ * no-op self-assignment on ANY duplicate key). Never `INSERT IGNORE`, which
12
+ * also downgrades NOT NULL, range and foreign-key errors to warnings.
12
13
  */
13
14
  import type { SqlDialect } from "../dialect.ts";
14
15
  export declare const mysqlDialect: SqlDialect;
@@ -0,0 +1,30 @@
1
+ import type { QualifiedSqlName } from "@vibeorm/schema";
2
+ import type { SqlDialect } from "../dialect.ts";
3
+ import type { GeneratedColumnDeclaration } from "../generated-columns.ts";
4
+ import type { RenderedQuery } from "../render.ts";
5
+ /** PostgreSQL stored syntax; the public dispatcher performs declaration admission. */
6
+ export declare function renderPostgresGeneratedColumnClause({ generated }: {
7
+ readonly generated: GeneratedColumnDeclaration;
8
+ }): string;
9
+ /** The migration owner creates the desired base context inside the private savepoint. */
10
+ export type PostgresGeneratedColumnProbe = {
11
+ readonly operation: "savepoint" | "rollback" | "release";
12
+ readonly savepoint: string;
13
+ } | {
14
+ readonly operation: "add";
15
+ readonly table: QualifiedSqlName;
16
+ readonly column: string;
17
+ readonly definition: string;
18
+ };
19
+ /** Render pinned-session scratch statements; this helper never executes or manages a session. */
20
+ export declare function renderPostgresGeneratedColumnProbe({ dialect, probe }: {
21
+ readonly dialect: SqlDialect;
22
+ readonly probe: PostgresGeneratedColumnProbe;
23
+ }): RenderedQuery;
24
+ /** Approved stored-generated to ordinary conversion, retaining the present stored values. */
25
+ export declare function renderPostgresDropGeneratedExpression({ dialect, table, column }: {
26
+ readonly dialect: SqlDialect;
27
+ readonly table: QualifiedSqlName;
28
+ readonly column: string;
29
+ }): RenderedQuery;
30
+ //# sourceMappingURL=postgres-generated-columns.d.ts.map
@@ -0,0 +1,10 @@
1
+ import type { IndexExpressionProbeParams, IndexKeyOperand } from "../index-expressions.ts";
2
+ import type { RenderedQuery } from "../render.ts";
3
+ /** CREATE INDEX extraction is conservative; structured catalog rows remain authoritative. */
4
+ export declare function extractPostgresIndexKeys({ definition, index }: {
5
+ readonly definition: string;
6
+ readonly index: string;
7
+ }): readonly IndexKeyOperand[] | undefined;
8
+ /** Caller owns a fixed search_path and rollback savepoint; no populated user table is indexed. */
9
+ export declare function renderPostgresIndexExpressionProbe({ ...params }: IndexExpressionProbeParams): readonly RenderedQuery[];
10
+ //# sourceMappingURL=postgres-index-expressions.d.ts.map
@@ -1,10 +1,25 @@
1
1
  import type { QualifiedSqlName } from "@vibeorm/schema";
2
2
  import type { SqlDialect } from "../dialect.ts";
3
3
  import type { RenderedQuery } from "../render.ts";
4
+ /**
5
+ * Turn JIT compilation off for the rest of the current transaction and return
6
+ * the previous `jit` value as `previous` (read before the change). The bulk
7
+ * index-key read costs far above `jit_above_cost`, and compiling it takes longer
8
+ * than running it. Transaction-local: outside a transaction block it changes nothing.
9
+ */
10
+ export declare function renderPostgresJitSuspend(params: {
11
+ dialect: SqlDialect;
12
+ }): RenderedQuery;
13
+ /** Restore the transaction's `jit` value read by {@link renderPostgresJitSuspend}. */
14
+ export declare function renderPostgresJitRestore(params: {
15
+ dialect: SqlDialect;
16
+ previous: string;
17
+ }): RenderedQuery;
4
18
  /** Physical operands and unambiguous server-default evidence for actual type/access-method pairs. */
5
19
  export declare function renderPostgresIndexKeys(params: {
6
20
  dialect: SqlDialect;
7
21
  table?: QualifiedSqlName;
8
22
  namespaces?: readonly string[];
23
+ expressionMetadata?: boolean;
9
24
  }): RenderedQuery;
10
25
  //# sourceMappingURL=postgres-index-keys.d.ts.map
@@ -57,11 +57,26 @@ export declare function renderRlsPolicy(params: {
57
57
  policyName?: string;
58
58
  targetSchema?: "public" | "pg_temp";
59
59
  }): RenderedQuery;
60
- /** Every declared setting, parameterized in declaration order and transaction-local. */
60
+ /**
61
+ * Every declared setting, parameterized in declaration order and transaction-local.
62
+ *
63
+ * Optional parts travel in the SAME statement (statement-count pass, EPIC 1-2);
64
+ * without them the text is exactly the claims statement:
65
+ * - `role` (PGlite): binds the trusted runtime role FIRST, as {@link renderRlsRole} does;
66
+ * - `readOnly`: switches the transaction to READ ONLY after the claims (`$all` on a
67
+ * verified connection; PostgreSQL accepts this switch at any point of a transaction);
68
+ * - `backend`: reports the backend as `"backendPid"` / `"backendStart"` text columns,
69
+ * the readiness catalog read's own expressions (adapters that name no connection object).
70
+ *
71
+ * A context with zero keys is accepted only when one of these parts is present.
72
+ */
61
73
  export declare function renderRlsContext(params: {
62
74
  dialect: SqlDialect;
63
75
  context: RlsContextIR;
64
76
  values: Readonly<Record<string, string | null>>;
77
+ role?: string;
78
+ readOnly?: boolean;
79
+ backend?: boolean;
65
80
  }): RenderedQuery;
66
81
  /** First transaction statement on eligible PGlite only; server roles are login roles. */
67
82
  export declare function renderRlsRole(params: {
@@ -0,0 +1,25 @@
1
+ import type { FieldIR, QualifiedSqlName, RowRuleIR, SqlObjectIdentity } from "@vibeorm/schema";
2
+ import type { SqlDialect } from "../dialect.ts";
3
+ import type { RenderedRowRule } from "../row-rules.ts";
4
+ /** A finalized PostgreSQL comparison context, supplied by the canonical schema collector. */
5
+ export type PostgresRowRuleContext = {
6
+ readonly model: string;
7
+ readonly table: QualifiedSqlName;
8
+ readonly rule: RowRuleIR;
9
+ readonly functionIdentity: Extract<SqlObjectIdentity, {
10
+ readonly kind: "function";
11
+ }>;
12
+ readonly triggerIdentity: {
13
+ readonly kind: "trigger";
14
+ readonly table: QualifiedSqlName;
15
+ readonly name: string;
16
+ };
17
+ readonly protectedFields: readonly FieldIR[];
18
+ readonly gateField?: FieldIR;
19
+ };
20
+ /** Emit one ordinary invoker function and an unfiltered BEFORE row trigger; no live objects are taken over. */
21
+ export declare function renderPostgresRowRule({ dialect, context }: {
22
+ readonly dialect: SqlDialect;
23
+ readonly context: PostgresRowRuleContext;
24
+ }): RenderedRowRule;
25
+ //# sourceMappingURL=postgres-row-rules.d.ts.map
@@ -0,0 +1,18 @@
1
+ import type { ExtractedGeneratedColumn, GeneratedColumnDeclaration } from "../generated-columns.ts";
2
+ /** SQLite stored syntax; adding a stored column still requires the migrate rebuild path. */
3
+ export declare function renderSqliteGeneratedColumnClause({ generated }: {
4
+ readonly generated: GeneratedColumnDeclaration;
5
+ }): string;
6
+ /** Shared source-slice extraction for SQLite source and MySQL SHOW CREATE, not SQL equivalence. */
7
+ export declare function extractGenerationFromDefinition({ definition, column, backslashEscapes, mysql }: {
8
+ readonly definition: string;
9
+ readonly column: string;
10
+ readonly backslashEscapes?: boolean;
11
+ readonly mysql?: boolean;
12
+ }): ExtractedGeneratedColumn | undefined;
13
+ /** Recover the exact AS interior; omitted storage means virtual, never inferred stored. */
14
+ export declare function extractSqliteGeneratedColumn({ definition, column }: {
15
+ readonly definition: string;
16
+ readonly column: string;
17
+ }): ExtractedGeneratedColumn | undefined;
18
+ //# sourceMappingURL=sqlite-generated-columns.d.ts.map
@@ -0,0 +1,23 @@
1
+ import type { IndexKeyOperand } from "../index-expressions.ts";
2
+ import type { TableDefinitionToken } from "./sqlite-table-definition.ts";
3
+ /** Exact outer-key slices shared by CREATE INDEX and MySQL table-key clauses. */
4
+ export declare function extractIndexKeyList({ sql, open, close, tokens, wrapped, removeWrapper }: {
5
+ readonly sql: string;
6
+ readonly open: TableDefinitionToken;
7
+ readonly close: TableDefinitionToken;
8
+ readonly tokens: readonly TableDefinitionToken[];
9
+ readonly wrapped?: boolean;
10
+ readonly removeWrapper?: boolean;
11
+ }): readonly IndexKeyOperand[] | undefined;
12
+ /** Parse the CREATE INDEX header without interpreting any scalar expression. */
13
+ export declare function extractStandaloneIndexKeys({ definition, index, postgres }: {
14
+ readonly definition: string;
15
+ readonly index: string;
16
+ readonly postgres?: boolean;
17
+ }): readonly IndexKeyOperand[] | undefined;
18
+ /** SQLite source spelling is the evidence; CAST alone never proves a plain Decimal key. */
19
+ export declare function extractSqliteIndexKeys({ definition, index }: {
20
+ readonly definition: string;
21
+ readonly index: string;
22
+ }): readonly IndexKeyOperand[] | undefined;
23
+ //# sourceMappingURL=sqlite-index-expressions.d.ts.map
@@ -0,0 +1,8 @@
1
+ import type { SqlDialect } from "../dialect.ts";
2
+ import type { EventRowRuleContext, RenderedRowRule } from "../row-rules.ts";
3
+ /** Emit literal-only ABORT diagnostics and one unfiltered BEFORE trigger per event. */
4
+ export declare function renderSqliteRowRule({ dialect, context }: {
5
+ readonly dialect: SqlDialect;
6
+ readonly context: EventRowRuleContext;
7
+ }): RenderedRowRule;
8
+ //# sourceMappingURL=sqlite-row-rules.d.ts.map
@@ -8,12 +8,32 @@ export type TableDefinitionParts = {
8
8
  readonly clauses: readonly string[];
9
9
  readonly suffix: string;
10
10
  };
11
- /** Extract a CREATE TABLE list with a quote-aware delimiter walk, not an expression parser. */
12
- export declare function tableDefinitionParts(params: {
13
- sql: string;
11
+ /** A source token with balanced-parenthesis depth and original byte boundaries. */
12
+ export type TableDefinitionToken = {
13
+ readonly value: string;
14
+ readonly kind: "word" | "quoted" | "literal" | "symbol";
15
+ readonly start: number;
16
+ readonly end: number;
17
+ readonly depth: number;
18
+ };
19
+ /** Scan SQL delimiters without looking inside quoted strings, identifiers or comments. */
20
+ export declare function tableDefinitionTokens({ sql, backslashEscapes }: {
21
+ readonly sql: string;
22
+ readonly backslashEscapes?: boolean;
23
+ }): readonly TableDefinitionToken[];
24
+ /** Extract a CREATE TABLE list with a quote/comment-aware delimiter walk. */
25
+ export declare function tableDefinitionParts({ sql, backslashEscapes }: {
26
+ readonly sql: string;
27
+ readonly backslashEscapes?: boolean;
14
28
  }): TableDefinitionParts;
29
+ /** Read column names for SQLite source and MySQL SHOW CREATE using the same lexer. */
30
+ export declare function tableColumnClauses({ sql, backslashEscapes, mysql }: {
31
+ readonly sql: string;
32
+ readonly backslashEscapes?: boolean;
33
+ readonly mysql?: boolean;
34
+ }): readonly SqliteColumnClause[];
15
35
  /** Return column clauses without rewriting any SQL inside them. */
16
- export declare function sqliteColumnClauses(params: {
17
- sql: string;
36
+ export declare function sqliteColumnClauses({ sql }: {
37
+ readonly sql: string;
18
38
  }): readonly SqliteColumnClause[];
19
39
  //# sourceMappingURL=sqlite-table-definition.d.ts.map