@vibeorm/sql 4.3.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.
package/dist/ast.d.ts CHANGED
@@ -22,7 +22,13 @@ export type ColRef = {
22
22
  * `castText` renders a dialect text cast and preserves `alias ?? name`,
23
23
  * including timestamp identity before driver Date truncation. PostgreSQL
24
24
  * uses `"col"::text`; SQLite/MySQL use CAST AS TEXT/CHAR. `encodeBase64`
25
- * is postgres-only and renders `encode("col", 'base64')`. Other users:
25
+ * is postgres-only and renders `encode("col", 'base64')`.
26
+ * `encodeArrayBase64` is postgres-only and renders
27
+ * `encode(array_send("col"), 'base64')` — a `bytea[]` column's binary array
28
+ * form (dimensions, NULL elements, raw element bytes) as base64: faithful
29
+ * whatever the session's `bytea_output`, keeping a NULL column (`NULL`), an
30
+ * empty array and NULL elements distinct (5.0 lane V; the lateral subselect's
31
+ * `Bytes[]` form, decoded by the runtime's JSON-transport plan). Other users:
26
32
  * the lateral subselect (SQL review B3: raw `to_jsonb` would ship BigInt/Decimal as float64 JSON
27
33
  * numbers and Bytes as `\x` hex text), and every row projection of a
28
34
  * `Float[]`/`Json[]` column (bun:sql's array parser refuses float arrays
@@ -30,7 +36,7 @@ export type ColRef = {
30
36
  * the runtime parses the array literal text itself). Never set on
31
37
  * filter/order refs.
32
38
  */
33
- readonly transform?: "castText" | "encodeBase64" | "jsonText";
39
+ readonly transform?: "castText" | "encodeBase64" | "encodeArrayBase64" | "jsonText";
34
40
  /**
35
41
  * Comparison/order/aggregate cast, sqlite Decimal only (SQL review B4):
36
42
  * Decimal stores as exact TEXT there and text comparison is lexicographic —
@@ -456,7 +462,11 @@ export type OrderTerm = OrderByColumn | OrderByAggregate | OrderBySubquery;
456
462
  /**
457
463
  * One `LEFT JOIN LATERAL (…) AS alias ON true` clause aggregating a correlated
458
464
  * subquery into a single JSON value per outer row — the "join" relation-loading
459
- * strategy (postgres/pglite only; gated on `capabilities.lateralJoin`).
465
+ * strategy (postgres/pglite, and MySQL 8.0.14+ since lane J; gated on
466
+ * `capabilities.lateralJoin` and rendered through `SqlDialect.lateralJson`).
467
+ * MySQL renders positional rows only (`JSON_ARRAY`, `JSON_ARRAYAGG`, `ON
468
+ * true`); its aggregate cannot order, so an ordered list level carries a
469
+ * `select.rowNumber` without partition — rendered as the row's last slot.
460
470
  *
461
471
  * Renders (agg `jsonArray`):
462
472
  * `LEFT JOIN LATERAL (SELECT COALESCE(json_agg(to_json("__sub".*)), '[]'::json)
@@ -694,6 +704,31 @@ export type KeyPage = {
694
704
  /** Output name of the rank column. */
695
705
  readonly rankAlias: string;
696
706
  };
707
+ /**
708
+ * Statement-count pass EPIC 4 (round-3 ST-07): bound key tuples as the FROM
709
+ * of a select (`SelectStatement.fromKeys`) — a derived table named
710
+ * `table.name` whose columns are `columns`, one row per tuple. The write
711
+ * follow-up puts it where the parent table would be: the find path's laterals
712
+ * correlate to `"<table.name>"."<column>"` exactly as over the table, but read
713
+ * the RETURNED key values instead of the parent row (a deleted row, or a
714
+ * native-RLS parent whose SELECT policy the per-relation loads never met).
715
+ *
716
+ * Renders where `SqlDialect.keyListStrategy` is `unnest` (postgres) as the
717
+ * typed form of a `VALUES` list — one array per column, typed like the
718
+ * key-list filter's members (`postgresKeyListElementType`), so the text does
719
+ * not depend on the tuple count: `FROM unnest($1::text[], $2::int4[]) AS
720
+ * "Post" ("id", "authorId")`. A `DateTime` member without a declared native
721
+ * type (`keyListBindsUntypedRows`) and every other dialect refuse with
722
+ * `VIBE_UNSUPPORTED_CAPABILITY` — those dialects have no lateral joins either.
723
+ */
724
+ export type KeyRows = {
725
+ /** Output column names of the derived table, in member order. */
726
+ readonly columns: readonly string[];
727
+ /** How each column binds, in column order — see {@link KeyListMember}. */
728
+ readonly members: readonly KeyListMember[];
729
+ /** Encoded tuples; never empty, each exactly `columns.length` long. */
730
+ readonly rows: readonly (readonly unknown[])[];
731
+ };
697
732
  export type SelectStatement = {
698
733
  readonly kind: "select";
699
734
  readonly table: TableRef;
@@ -733,7 +768,7 @@ export type SelectStatement = {
733
768
  * by aggregate-over-a-subset shapes (`aggregate({ take, skip, orderBy })`).
734
769
  */
735
770
  readonly fromSelect?: SelectStatement;
736
- /** Lateral JSON-aggregation joins — postgres-only, see {@link LateralJoin}. */
771
+ /** Lateral JSON-aggregation joins — postgres and MySQL 8.0.14+, see {@link LateralJoin}. */
737
772
  readonly lateralJoins?: readonly LateralJoin[];
738
773
  /** Plain equality INNER JOINs — portable, see {@link InnerJoin}. */
739
774
  readonly innerJoins?: readonly InnerJoin[];
@@ -762,6 +797,13 @@ export type SelectStatement = {
762
797
  * branch by branch, in text order.
763
798
  */
764
799
  readonly fromUnion?: readonly SelectStatement[];
800
+ /**
801
+ * Statement-count pass EPIC 4 (ST-07): derived-table FROM over bound key
802
+ * tuples, named `table.name` with the listed column names — see
803
+ * {@link KeyRows}. Never together with `fromSelect`, `keyPage`, `fromUnion`,
804
+ * a `table.alias` or a `rowLock`.
805
+ */
806
+ readonly fromKeys?: KeyRows;
765
807
  readonly where?: SqlExpr;
766
808
  readonly groupBy?: readonly ColRef[];
767
809
  /** HAVING predicate — grouped selects; may contain `aggCmp` expressions. */
@@ -797,6 +839,19 @@ export type RowLock = {
797
839
  export declare const SQL_DEFAULT_CELL: {
798
840
  readonly __sqlDefault: true;
799
841
  };
842
+ /**
843
+ * Sentinel update-arm cell: the column keeps its current value —
844
+ * `` `col` = `table`.`col` `` (V8, the insert-if-missing upsert). Valid only in
845
+ * an {@link OnConflictUpdate} `update` set, where MySQL's `ON DUPLICATE KEY
846
+ * UPDATE` needs at least one assignment: a self-assignment changes nothing on a
847
+ * duplicate and, unlike `INSERT IGNORE`, leaves every other error an error.
848
+ * Refused in a plain UPDATE's SET list.
849
+ */
850
+ export declare const SQL_KEEP_CELL: {
851
+ readonly __sqlKeep: true;
852
+ };
853
+ /** Is this update cell the {@link SQL_KEEP_CELL} sentinel? */
854
+ export declare function isSqlKeepCell(value: unknown): boolean;
800
855
  /** Is this VALUES cell the {@link SQL_DEFAULT_CELL} sentinel? */
801
856
  export declare function isSqlDefaultCell(value: unknown): boolean;
802
857
  /**
@@ -925,11 +980,28 @@ export type OnConflictUpdate = {
925
980
  };
926
981
  /**
927
982
  * Conflicting rows are skipped (`createMany({ skipDuplicates: true })`).
928
- * An empty/absent target means "any conflict" — the mysql form (INSERT IGNORE)
929
- * can only express that, so it ignores a bare `columns` target (documented
930
- * degradation) and REFUSES the refined targets below.
983
+ * An empty/absent target means "any conflict" — the mysql form
984
+ * (`ON DUPLICATE KEY UPDATE k = t.k`, dialect `doNothingForm:
985
+ * "duplicateKeyKeep"`) can only express that, so it ignores a bare `columns`
986
+ * target (documented degradation) and REFUSES the refined targets below.
931
987
  */
932
988
  export type OnConflictDoNothing = {
989
+ /**
990
+ * MySQL form only (`duplicateKeyKeep`): the column the duplicate arm assigns
991
+ * to itself (`` `k` = `t`.`k` ``, a no-op). Absent = the first inserted
992
+ * column; an all-default row (no column) must name one.
993
+ */
994
+ readonly keepColumn?: string;
995
+ /**
996
+ * MySQL form only, single-row statements: a decimal literal S (digits only).
997
+ * The keep assignment becomes `` `k` = IF(LAST_INSERT_ID(S), `t`.`k`, `t`.`k`) ``,
998
+ * so a skipped duplicate reports S as the statement's insert id (an inserted
999
+ * row reports its own id, or 0) — the exact skipped verdict that the
1000
+ * affected count cannot give with CLIENT_FOUND_ROWS (an unchanged duplicate
1001
+ * counts 1, like an insert). The builder picks S unequal to the row's
1002
+ * explicit AUTO_INCREMENT value.
1003
+ */
1004
+ readonly duplicateFlag?: string;
933
1005
  readonly columns?: readonly string[];
934
1006
  /**
935
1007
  * Partial-index target predicate, RAW SQL: `ON CONFLICT (cols) WHERE pred
@@ -947,7 +1019,13 @@ export type OnConflictClause = OnConflictUpdate | OnConflictDoNothing;
947
1019
  export type UpdateStatement = {
948
1020
  readonly kind: "update";
949
1021
  readonly table: TableRef;
950
- /** Column → parameter value, or an atomic {@link SetOperation}. */
1022
+ /**
1023
+ * Column → parameter value, or an atomic {@link SetOperation}. As a
1024
+ * {@link ChainStep} only (statement-maxx lane C3), a value may be a
1025
+ * {@link StepColumnRef} to an EARLIER step: `"col" = (SELECT "c" FROM
1026
+ * "step")` — the step must return at most one row (an owner resolved by a
1027
+ * unique where, or an owner INSERT of one row).
1028
+ */
951
1029
  readonly set: Readonly<Record<string, unknown>>;
952
1030
  readonly where?: SqlExpr;
953
1031
  readonly returning?: readonly ReturningColumn[];
@@ -997,6 +1075,12 @@ export type RlsContextStatement = {
997
1075
  readonly kind: "rlsContext";
998
1076
  readonly context: RlsContextIR;
999
1077
  readonly values: Readonly<Record<string, string | null>>;
1078
+ /** PGlite: bind this trusted runtime role first, in the same statement. */
1079
+ readonly role?: string;
1080
+ /** Switch the transaction to READ ONLY in the same statement, after the claims. */
1081
+ readonly readOnly?: boolean;
1082
+ /** Report the backend as `"backendPid"` / `"backendStart"` text columns. */
1083
+ readonly backend?: boolean;
1000
1084
  };
1001
1085
  /** Trusted pre-existing PGlite execution role; never a request-supplied principal. */
1002
1086
  export type RlsRoleStatement = {
@@ -1085,6 +1169,15 @@ export type InsertSelectStatement = {
1085
1169
  */
1086
1170
  readonly source?: ChainRowSource;
1087
1171
  readonly where?: SqlExpr;
1172
+ /**
1173
+ * `ON CONFLICT (<columns>) DO NOTHING` (statement-maxx lane C2): a row whose
1174
+ * conflict target already exists is skipped and not returned — the implicit
1175
+ * many-to-many join INSERT, where re-connecting an existing pair is a no-op.
1176
+ * Postgres form only (the chain renders nowhere else); at least one column.
1177
+ */
1178
+ readonly conflictDoNothing?: {
1179
+ readonly columns: readonly string[];
1180
+ };
1088
1181
  readonly returning?: readonly ReturningColumn[];
1089
1182
  };
1090
1183
  /**
package/dist/codecs.d.ts CHANGED
@@ -20,6 +20,7 @@
20
20
  * Database enums ride the String codec. String-backed enums use the field-bound
21
21
  * membership codec below; their wire values are still strings on every dialect.
22
22
  */
23
+ import { VibeError } from "@vibeorm/schema";
23
24
  import type { Dialect, ScalarType } from "@vibeorm/schema";
24
25
  /**
25
26
  * One scalar type's transport rules for one dialect. Both directions see only
@@ -66,6 +67,23 @@ export type WireFidelity = {
66
67
  readonly json?: "parsed" | "text";
67
68
  /** Int/Float columns arrive as JS numbers (`"native"`) or as text (`"text"`). */
68
69
  readonly numbers?: "native" | "text";
70
+ /**
71
+ * Bytes columns arrive as Node `Buffer` instances (`"buffer"`: node-postgres,
72
+ * bun:sql); absent means a plain `Uint8Array` (PGlite). Never prunes a
73
+ * decoder: the runtime's JSON-transport plan reads it, so a `"join"`
74
+ * relation child's bytes (base64 on the wire) materialize in the same class
75
+ * the driver hands the `"query"` strategy — value identity (5.0 lane V).
76
+ */
77
+ readonly bytes?: "buffer";
78
+ /**
79
+ * Zone-less date-time text (MySQL DATETIME / TIMESTAMP / DATE) is read as a
80
+ * UTC instant by the driver (`"utc"`: mysql2 with `timezone: "Z"`). Absent
81
+ * means another or an unknown zone (another `timezone` option, an injected
82
+ * pool). Never prunes a decoder: the runtime reads it to decide whether a
83
+ * dialect default of `"join"` keeps value identity — the JSON transport
84
+ * decodes that text as UTC (`lateralJson.requiresUtcDateTimeWire`, lane J).
85
+ */
86
+ readonly zoneLessDateTime?: "utc";
69
87
  };
70
88
  /**
71
89
  * Is this scalar type's DECODE a no-op under the declared wire fidelity?
@@ -113,6 +131,32 @@ export declare function decodeIsNoop(params: {
113
131
  export declare function parseDateTimeText(params: {
114
132
  text: string;
115
133
  }): Date | undefined;
134
+ /**
135
+ * PostgreSQL date/time text with a year of five or more digits or a ` BC` era,
136
+ * in the row text form and the JSON form: `12345-06-07 08:09:10.111+00`,
137
+ * `12345-06-07T08:09:10.111` (zone-less), `0044-03-15T12:00:00.5+00:00 BC`,
138
+ * `0044-03-15 BC` (`date`). ECMA-262 parses none of them (an expanded year
139
+ * needs `+012345`), and a value near the end of year 9999 reaches them
140
+ * through the session zone alone (`9999-12-31 23:59+00` prints as
141
+ * `10000-01-01 00:59+01` east of UTC). Built from its fields, by the rules of
142
+ * {@link parseDateTimeText}: the BC year astronomically (`1 BC` = year 0, the
143
+ * reading of node-postgres), zone-less text as UTC, the fraction truncated to
144
+ * milliseconds, an offset with seconds applied exactly.
145
+ *
146
+ * `undefined` for every other text — a 4-digit AD value keeps the parse it
147
+ * had — and for an instant outside the `Date` range. Exported for the adapters
148
+ * whose zone-less driver parsers fall back to it (one rule, rule 6).
149
+ */
150
+ export declare function parseExtendedDateTimeText(params: {
151
+ text: string;
152
+ }): Date | undefined;
153
+ /**
154
+ * The decode refusal of a non-finite or unparseable DateTime — one error for
155
+ * every DateTime codec. Exported so a statement that refuses such a value
156
+ * itself (the runtime's in-statement value guard, round-trip follow-up D1)
157
+ * reports exactly this error.
158
+ */
159
+ export declare function decodedDateTimeRefusal(): VibeError;
116
160
  /**
117
161
  * The time-of-day codec for a DateTime field's native type, or `undefined`
118
162
  * when the native type is not `time`/`timetz` (the DateTime table row
package/dist/dialect.d.ts CHANGED
@@ -167,6 +167,51 @@ export type SqlDialect = {
167
167
  * `ROW_NUMBER()` window (MySQL: a lateral page still read every child).
168
168
  */
169
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";
170
215
  /**
171
216
  * Fix lane Q1 (round 3, DB-03): how a positive relation filter that pins ONE
172
217
  * related row by a unique key renders. `exists`: the correlated `EXISTS` —
@@ -241,11 +286,14 @@ export type SqlDialect = {
241
286
  };
242
287
  /**
243
288
  * Skip-conflicting-rows form. `onConflictDoNothing` appends
244
- * `ON CONFLICT [(target)] DO NOTHING`; `insertIgnore` turns the statement into
245
- * `INSERT IGNORE INTO` — which ignores a wider class of errors than a pure
246
- * 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).
247
295
  */
248
- readonly doNothingForm: "onConflictDoNothing" | "insertIgnore";
296
+ readonly doNothingForm: "onConflictDoNothing" | "duplicateKeyKeep";
249
297
  /** Case-insensitive LIKE: native ILIKE or LOWER(col) LIKE LOWER(pattern). */
250
298
  readonly insensitiveLike: "ilike" | "lower";
251
299
  /**
@@ -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;
@@ -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;
@@ -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: {
package/dist/index.d.ts CHANGED
@@ -40,8 +40,8 @@ export type { ResultDecoder, ResultReadDecoder, ResultParameterEncoder, ResultSc
40
40
  * lives in this package; see the root sql-catalog.md for the reviewable surface.
41
41
  */
42
42
  export { compileExclusion, renderExclusionSetup, renderExclusionDdl, renderExclusionCatalog, renderExclusionProbe } from "./exclusion.ts";
43
- export type { AdvisoryLockKey, AdvisoryLockStatement, AggFn, AggRef, AggSelect, ChainResult, ChainRowSource, ChainStatement, ChainStep, ChainStepStatement, ChainTally, ChainVeto, ColRef, ComparisonOp, CountSubquery, DeleteStatement, ExcludedRef, InnerJoin, KeyListMember, KeyPage, KeyTableJoin, KeyTableMember, InsertSelectStatement, InsertStatement, ReturningColumn, JsonFilterOp, LateralJoin, LeftJoin, ListSetOperation, OnConflictClause, OnConflictDoNothing, OnConflictUpdate, OrderByAggregate, OrderByColumn, OrderBySubquery, OrderTerm, RowLock, RowNumberProjection, DbNowMarker, JsonParamCell, SelectExpr, SelectStatement, SetOperation, SetOperator, SqlExpr, SqlStatement, StepColumnRef, TableRef, UpdateStatement, } from "./ast.ts";
44
- export { ADVISORY_LOCK_RESULT_COLUMN, CHAIN_VETO_PREFIX, CHAIN_VETO_TOKEN_PATTERN, SQL_DEFAULT_CELL, chainVetoToken, dbNowMarker, excludedRef, isDbNowMarker, isExcludedRef, isJsonParamCell, isListSetOperation, isSetOperation, isSqlDefaultCell, isStepColumnRef, jsonParamCell, returningColumnName, stepColumnRef, } from "./ast.ts";
43
+ export type { AdvisoryLockKey, AdvisoryLockStatement, AggFn, AggRef, AggSelect, ChainResult, ChainRowSource, ChainStatement, ChainStep, ChainStepStatement, ChainTally, ChainVeto, ColRef, ComparisonOp, CountSubquery, DeleteStatement, ExcludedRef, InnerJoin, KeyListMember, KeyPage, KeyRows, KeyTableJoin, KeyTableMember, InsertSelectStatement, InsertStatement, ReturningColumn, JsonFilterOp, LateralJoin, LeftJoin, ListSetOperation, OnConflictClause, OnConflictDoNothing, OnConflictUpdate, OrderByAggregate, OrderByColumn, OrderBySubquery, OrderTerm, RowLock, RowNumberProjection, DbNowMarker, JsonParamCell, SelectExpr, SelectStatement, SetOperation, SetOperator, SqlExpr, SqlStatement, StepColumnRef, TableRef, UpdateStatement, } from "./ast.ts";
44
+ export { ADVISORY_LOCK_RESULT_COLUMN, CHAIN_VETO_PREFIX, CHAIN_VETO_TOKEN_PATTERN, SQL_DEFAULT_CELL, SQL_KEEP_CELL, chainVetoToken, dbNowMarker, excludedRef, isDbNowMarker, isExcludedRef, isJsonParamCell, isListSetOperation, isSetOperation, isSqlDefaultCell, isSqlKeepCell, isStepColumnRef, jsonParamCell, returningColumnName, stepColumnRef, } from "./ast.ts";
45
45
  export type { AdvisoryLockFunctions, AdvisoryLockModeFunctions, RowLockSyntax, EmptyInsertSyntax, SqlDialect, } from "./dialect.ts";
46
46
  export { ADVISORY_KEY_BOUNDS, describeRowLock, renderAdvisoryLock, renderRowLockClause } from "./locks.ts";
47
47
  export { JSON_TRANSPORT, projectionOutputName } from "./json-transport.ts";
@@ -64,7 +64,7 @@ export { renderRowRule } from "./row-rules.ts";
64
64
  export type { RenderedRowRule, RowRuleStatement, RowRuleTriggerPlacement } from "./row-rules.ts";
65
65
  export type { PredicateModel, PredicateUsage } from "./predicate.ts";
66
66
  export { DIALECT_CAPABILITIES, MYSQL_CAPABILITIES, POSTGRES_CAPABILITIES, SQLITE_CAPABILITIES, } from "./capabilities.ts";
67
- export { DIALECT_CODECS, MYSQL_CODECS, POSTGRES_CODECS, SQLITE_CODECS, decodeIsNoop, enumMembershipCodec, float32Codec, localDateCodec, parseDateTimeText, parseJsonText, postgresArrayElementFromText, shortestFloat32, timeOfDayCodec, } from "./codecs.ts";
67
+ export { DIALECT_CODECS, MYSQL_CODECS, POSTGRES_CODECS, SQLITE_CODECS, decodeIsNoop, decodedDateTimeRefusal, enumMembershipCodec, float32Codec, localDateCodec, parseDateTimeText, parseExtendedDateTimeText, parseJsonText, postgresArrayElementFromText, shortestFloat32, timeOfDayCodec, } from "./codecs.ts";
68
68
  export type { CodecTable, ScalarCodec, WireFidelity } from "./codecs.ts";
69
69
  export { renderExpr, renderFragment, renderProjection, renderStatement } from "./render.ts";
70
70
  export type { RenderedQuery } from "./render.ts";
@@ -87,9 +87,10 @@ export { renderMigrationHistoryQuery } from "./dialects/migration-history.ts";
87
87
  export { sqliteTableConstraints } from "./dialects/sqlite-constraints.ts";
88
88
  export type { SqliteConstraintClause } from "./dialects/sqlite-constraints.ts";
89
89
  export { renderMigrationVisibility } from "./dialects/migration-visibility.ts";
90
- export { renderMigrationInventory, renderSqliteColumnInventory, renderSqliteForeignKeyInventory, renderSqliteRebuildObjects } from "./dialects/migration-inventory.ts";
91
- export { renderMigrationObjectBatch, renderMigrationObjectQuery, renderCheckProbe, renderMigrationNamespace, renderMigrationServerVersion, renderMigrationDependencies, renderMysqlObjectMembers } from "./dialects/migration-objects.ts";
92
- export type { MigrationObjectBatch } from "./dialects/migration-objects.ts";
90
+ export { renderMigrationInventory, renderSqliteColumnInventory, renderSqliteForeignKeyInventory, renderSqliteRebuildObjects, renderSqliteColumnInventoryBatch, renderSqliteForeignKeyInventoryBatch, renderSqliteIntrospectionPragmaBatch } from "./dialects/migration-inventory.ts";
91
+ export { renderMigrationObjectBatch, renderMigrationObjectQuery, renderCheckProbe, renderMigrationNamespace, renderMigrationServerVersion, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlObjectBatch, renderMysqlObjectMembersBatch } from "./dialects/migration-objects.ts";
92
+ export type { MigrationObjectBatch, MysqlObjectBatch } from "./dialects/migration-objects.ts";
93
+ export type { SqliteIntrospectionPragma } from "./dialects/migration-inventory.ts";
93
94
  export type { DiagnosticStatement, FixedDiagnosticQuery } from "./dialects/diagnostics.ts";
94
95
  export { POSTGRES_DIAGNOSTIC_SQL, POSTGRES_WORKLOAD_LIMITS, renderPostgresExplain, renderPostgresWorkload, } from "./dialects/postgres-diagnostics.ts";
95
96
  export type { PostgresExplainOptions, PostgresWorkloadQuery } from "./dialects/postgres-diagnostics.ts";