@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.
- package/dist/ast.d.ts +322 -9
- package/dist/codecs.d.ts +46 -0
- package/dist/dialect.d.ts +155 -5
- package/dist/dialects/migration-inventory.d.ts +58 -1
- package/dist/dialects/migration-objects.d.ts +36 -0
- package/dist/dialects/migration-visibility.d.ts +6 -1
- package/dist/dialects/mysql-check-probe.d.ts +1 -0
- package/dist/dialects/mysql-generated-columns.d.ts +43 -0
- package/dist/dialects/mysql-index-expressions.d.ts +20 -0
- package/dist/dialects/mysql-row-rules.d.ts +8 -0
- package/dist/dialects/mysql.d.ts +3 -2
- package/dist/dialects/postgres-generated-columns.d.ts +30 -0
- package/dist/dialects/postgres-index-expressions.d.ts +10 -0
- package/dist/dialects/postgres-index-keys.d.ts +15 -0
- package/dist/dialects/postgres-rls.d.ts +16 -1
- package/dist/dialects/postgres-row-rules.d.ts +25 -0
- package/dist/dialects/sqlite-generated-columns.d.ts +18 -0
- package/dist/dialects/sqlite-index-expressions.d.ts +23 -0
- package/dist/dialects/sqlite-row-rules.d.ts +8 -0
- package/dist/dialects/sqlite-table-definition.d.ts +25 -5
- package/dist/generated-columns.d.ts +18 -0
- package/dist/index-expressions.d.ts +61 -0
- package/dist/index.d.ts +23 -9
- package/dist/index.js +2662 -1161
- package/dist/index.js.map +35 -21
- package/dist/key-list.d.ts +83 -0
- package/dist/like.d.ts +17 -0
- package/dist/local-date.d.ts +9 -0
- package/dist/pg-array.d.ts +6 -0
- package/dist/predicate.d.ts +21 -7
- package/dist/query/index.js +59 -19
- package/dist/query/index.js.map +7 -7
- package/dist/query/state.d.ts +5 -0
- package/dist/query/types.d.ts +2 -0
- package/dist/row-rules.d.ts +57 -0
- package/dist/timestamp-identity.d.ts +8 -2
- package/package.json +2 -2
package/dist/ast.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { RlsReadinessQuery } from "./dialects/postgres-rls-readiness.ts";
|
|
|
10
10
|
* grows with the runtime in P2/P3. Every shape added here needs a catalog case
|
|
11
11
|
* (tests/catalog/cases.ts) in the same change.
|
|
12
12
|
*/
|
|
13
|
-
import type { AdvisoryLockMode, RlsContextIR, RowLockStrength, RowLockWaitPolicy, SchemaIR, SqlFragmentSpec } from "@vibeorm/schema";
|
|
13
|
+
import type { AdvisoryLockMode, RlsContextIR, RowLockStrength, RowLockWaitPolicy, ScalarType, SchemaIR, SqlFragmentSpec } from "@vibeorm/schema";
|
|
14
14
|
/** A column reference, optionally table-qualified. */
|
|
15
15
|
export type ColRef = {
|
|
16
16
|
readonly table?: string;
|
|
@@ -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')`.
|
|
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 —
|
|
@@ -88,6 +94,33 @@ export type AggSelect = {
|
|
|
88
94
|
*/
|
|
89
95
|
export type JsonFilterOp = "equals" | "string_contains" | "string_starts_with" | "string_ends_with" | "array_contains" | "array_starts_with" | "array_ends_with";
|
|
90
96
|
export type ComparisonOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte";
|
|
97
|
+
/**
|
|
98
|
+
* The column facts one member of a {@link SqlExpr} `keyList` tuple needs to
|
|
99
|
+
* bind exactly like the equality `"col" = $n` would. Postgres binds each
|
|
100
|
+
* member as ONE typed array (`$n::<element type>[]`), so it needs the
|
|
101
|
+
* column's element type: the declared native type without its modifier
|
|
102
|
+
* (`timestamptz`, never `timestamptz(3)` — a typmod cast would round
|
|
103
|
+
* `.1234` onto a stored `.123` that `=` does not match), `bpchar` for
|
|
104
|
+
* `char(n)`, `citext`, the enum's own type. MySQL needs to know a time-of-day
|
|
105
|
+
* member (`SqlDialect.timeOfDayInStrategy`); SQLite needs nothing.
|
|
106
|
+
*/
|
|
107
|
+
export type KeyListMember = {
|
|
108
|
+
/** The column's scalar type; `"Enum"` for an enum column. */
|
|
109
|
+
readonly type: ScalarType | "Enum";
|
|
110
|
+
/** The declared `@db.*` native type, verbatim (`VarChar(191)`, `Uuid`, `Timestamptz(3)`). */
|
|
111
|
+
readonly nativeType?: string;
|
|
112
|
+
/**
|
|
113
|
+
* A database-stored (native) enum's type: `namespace` qualifies it, absence
|
|
114
|
+
* resolves it through the search path like the statement's table. Absent on
|
|
115
|
+
* a string-stored enum, whose column is text.
|
|
116
|
+
*/
|
|
117
|
+
readonly enumType?: {
|
|
118
|
+
readonly namespace?: string;
|
|
119
|
+
readonly name: string;
|
|
120
|
+
};
|
|
121
|
+
/** A time-of-day column (`@db.Time(n)` / `@db.Timetz(n)`). */
|
|
122
|
+
readonly timeOfDay?: boolean;
|
|
123
|
+
};
|
|
91
124
|
export type SqlExpr = {
|
|
92
125
|
readonly kind: "composed";
|
|
93
126
|
readonly fragment: SqlFragment;
|
|
@@ -133,6 +166,68 @@ export type SqlExpr = {
|
|
|
133
166
|
/** Candidate tuples — every row carries exactly `columns.length` values. */
|
|
134
167
|
readonly rows: readonly (readonly unknown[])[];
|
|
135
168
|
readonly negate?: boolean;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* KEY-LIST membership — the public `where: { IN: [{ a, b }, …] }` filter:
|
|
172
|
+
* rows whose ordered tuple of column values equals one of `rows`, with
|
|
173
|
+
* semi-join semantics (a repeated tuple never repeats a result row). Renders
|
|
174
|
+
* per `SqlDialect.keyListStrategy`: postgres binds one typed ARRAY per
|
|
175
|
+
* member, `("a", "b") IN (SELECT * FROM unnest($1::int8[], $2::text[]))`,
|
|
176
|
+
* so the text and the planning cost stay constant however long the list is
|
|
177
|
+
* (an OR of 1 000 conjunctions planned in 114 ms on PGlite, 2 ms on
|
|
178
|
+
* postgres 18; the unnest form in 0.1–0.2 ms on both). A `DateTime` member
|
|
179
|
+
* without a declared native type switches postgres to the row-constructor
|
|
180
|
+
* list of untyped parameters (`keyListBindsUntypedRows`: the column may be a
|
|
181
|
+
* zone-less `timestamp`). MySQL renders the row-constructor list, SQLite
|
|
182
|
+
* `IN (VALUES …)`. Each member compares under the column's own type and
|
|
183
|
+
* collation, exactly like `"col" = $n`.
|
|
184
|
+
*
|
|
185
|
+
* An empty `rows` renders `1 = 0` (matches nothing, like an empty
|
|
186
|
+
* `inArray`). The runtime emits arity 1 as `inArray` instead, so a
|
|
187
|
+
* one-field key list keeps the scalar `in` SQL.
|
|
188
|
+
*/
|
|
189
|
+
| {
|
|
190
|
+
readonly kind: "keyList";
|
|
191
|
+
/** Ordered tuple columns (compare references); `rows[i][j]` binds to `columns[j]`. */
|
|
192
|
+
readonly columns: readonly ColRef[];
|
|
193
|
+
/** How each member binds, in column order — see {@link KeyListMember}. */
|
|
194
|
+
readonly members: readonly KeyListMember[];
|
|
195
|
+
/** Encoded candidate tuples, each exactly `columns.length` long; may repeat. */
|
|
196
|
+
readonly rows: readonly (readonly unknown[])[];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* ROW-VALUE comparison: `("a", "b") > ($1, $2)` — the keyset seek (D-01).
|
|
200
|
+
* SQL compares row values left to right, each pair with that column's own
|
|
201
|
+
* operator, affinity and collation, which is exactly the OR expansion
|
|
202
|
+
* `"a" > $1 OR ("a" = $2 AND "b" > $3)` when no member is NULL — and an
|
|
203
|
+
* index on `(a, b)` bounds it as one range (PostgreSQL `Index Cond: ROW(…) >
|
|
204
|
+
* ROW(…)`, SQLite `SEARCH … (a>?)`). A NULL member makes the comparison
|
|
205
|
+
* UNKNOWN as soon as the scan reaches it, so the runtime emits it only over
|
|
206
|
+
* non-null keys and values. Renders on every dialect (each has row
|
|
207
|
+
* constructors); whether the runtime emits it is
|
|
208
|
+
* `SqlDialect.keysetSeekStrategy`.
|
|
209
|
+
*/
|
|
210
|
+
| {
|
|
211
|
+
readonly kind: "rowCompare";
|
|
212
|
+
readonly op: "gt" | "gte" | "lt" | "lte";
|
|
213
|
+
/** Ordered row members (compare references); `values[i]` binds to `columns[i]`. */
|
|
214
|
+
readonly columns: readonly ColRef[];
|
|
215
|
+
/** Encoded values, exactly `columns.length` of them. */
|
|
216
|
+
readonly values: readonly unknown[];
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* SUBQUERY membership: `"id" IN (SELECT …)`, or the row-value form
|
|
220
|
+
* `("a", "b") IN (SELECT …)` for a composite key — the key-first `distinct`
|
|
221
|
+
* page on PostgreSQL (D-04). The subquery projects exactly `columns.length`
|
|
222
|
+
* columns and shares the statement's parameter sequence. MySQL refuses a
|
|
223
|
+
* `LIMIT` directly inside an `IN` subquery (error 1235), so the renderer
|
|
224
|
+
* refuses that pair there with `VIBE_UNSUPPORTED_CAPABILITY`.
|
|
225
|
+
*/
|
|
226
|
+
| {
|
|
227
|
+
readonly kind: "inSelect";
|
|
228
|
+
/** The tested columns, in the subquery's projection order. */
|
|
229
|
+
readonly columns: readonly ColRef[];
|
|
230
|
+
readonly select: SelectStatement;
|
|
136
231
|
} | {
|
|
137
232
|
readonly kind: "like";
|
|
138
233
|
readonly column: ColRef;
|
|
@@ -367,7 +462,11 @@ export type OrderTerm = OrderByColumn | OrderByAggregate | OrderBySubquery;
|
|
|
367
462
|
/**
|
|
368
463
|
* One `LEFT JOIN LATERAL (…) AS alias ON true` clause aggregating a correlated
|
|
369
464
|
* subquery into a single JSON value per outer row — the "join" relation-loading
|
|
370
|
-
* strategy (postgres/pglite
|
|
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.
|
|
371
470
|
*
|
|
372
471
|
* Renders (agg `jsonArray`):
|
|
373
472
|
* `LEFT JOIN LATERAL (SELECT COALESCE(json_agg(to_json("__sub".*)), '[]'::json)
|
|
@@ -435,6 +534,37 @@ export type InnerJoin = {
|
|
|
435
534
|
readonly right: ColRef;
|
|
436
535
|
}[];
|
|
437
536
|
};
|
|
537
|
+
/**
|
|
538
|
+
* One `LEFT JOIN (<select>) AS "alias" ON "alias"."k" = "outer"."c" [AND …]`
|
|
539
|
+
* clause — portable, equality ON pairs only. Built for the to-one relation
|
|
540
|
+
* `orderBy` (`orderBy: { author: { email: "asc" } }`): the target's key and
|
|
541
|
+
* sort columns ride a derived table under RESERVED aliases (`__vibe_ok0`,
|
|
542
|
+
* `__vibe_ov0`), so the outer statement's unqualified column names can never
|
|
543
|
+
* become ambiguous, and the outer ORDER BY names `"alias"."__vibe_ov0"`.
|
|
544
|
+
* Every engine merges such a derived table into a plain join (measured:
|
|
545
|
+
* postgres pulls it up into a hash/nested-loop left join, sqlite flattens it,
|
|
546
|
+
* mysql merges it), so the planner may hash-join instead of evaluating a
|
|
547
|
+
* correlated subquery per candidate row.
|
|
548
|
+
*
|
|
549
|
+
* The builder emits it only for links that match AT MOST ONE target row, so
|
|
550
|
+
* the join keeps the parent row count. The derived select must alias every
|
|
551
|
+
* projected column; its parameters (none today) join the statement's single
|
|
552
|
+
* sequence where the clause renders (after `keyTable`, before
|
|
553
|
+
* `lateralJoins`). A statement with a `rowLock` refuses it: postgres rejects
|
|
554
|
+
* FOR UPDATE on the nullable side of an outer join, and mysql would lock the
|
|
555
|
+
* joined rows too.
|
|
556
|
+
*/
|
|
557
|
+
export type LeftJoin = {
|
|
558
|
+
/** Derived-table alias (`__ord1`). */
|
|
559
|
+
readonly alias: string;
|
|
560
|
+
/** The derived table — every projected column carries an `alias`. */
|
|
561
|
+
readonly select: SelectStatement;
|
|
562
|
+
/** Equality pairs ANDed into the ON clause; never empty. */
|
|
563
|
+
readonly on: readonly {
|
|
564
|
+
readonly left: ColRef;
|
|
565
|
+
readonly right: ColRef;
|
|
566
|
+
}[];
|
|
567
|
+
};
|
|
438
568
|
/**
|
|
439
569
|
* How one member of a {@link KeyTableJoin} tuple binds inside the derived
|
|
440
570
|
* table. `value` binds the parameter as it is: it stays COERCIBLE, so a text
|
|
@@ -523,6 +653,82 @@ export type RowNumberProjection = {
|
|
|
523
653
|
readonly orderBy: readonly OrderTerm[];
|
|
524
654
|
readonly alias: string;
|
|
525
655
|
};
|
|
656
|
+
/**
|
|
657
|
+
* A PER-KEY PAGE of one child table as a derived-table FROM
|
|
658
|
+
* (`SelectStatement.keyPage`): for every listed key tuple, the child rows
|
|
659
|
+
* whose key columns equal it, ordered by `select.orderBy` and cut by
|
|
660
|
+
* `select.limit`/`select.offset` — each key's OWN page, plus a rank column
|
|
661
|
+
* (`rankAlias`) that orders the rows inside their key. The paged include's
|
|
662
|
+
* `ROW_NUMBER() OVER (PARTITION BY key …)` window returns the same rows but
|
|
663
|
+
* numbers EVERY child of every listed key first; this form reads one page of
|
|
664
|
+
* primary keys per key through an index on (key columns, order columns),
|
|
665
|
+
* then each page row once by its primary key.
|
|
666
|
+
*
|
|
667
|
+
* Rendered per `SqlDialect.keyPageStrategy`:
|
|
668
|
+
* - `lateral` (postgres): `unnest($1::<type>[], …) AS "__vibe_keys"
|
|
669
|
+
* ("__vibe_k0", …) CROSS JOIN LATERAL (SELECT <identity>, ROW_NUMBER() OVER
|
|
670
|
+
* (PARTITION BY <key> ORDER BY <order>) AS <rank> FROM <table> WHERE <key> =
|
|
671
|
+
* "__vibe_keys"."__vibe_k<i>" … ORDER BY <order> LIMIT … OFFSET …) AS
|
|
672
|
+
* "__vibe_ids" INNER JOIN <table> AS "__vibe_page" ON <identity> pairs`.
|
|
673
|
+
* - `inLimit` (sqlite, which has no LATERAL): the key tuples as `(VALUES …)`
|
|
674
|
+
* (columns `column1`, `column2`, …, as SQLite names a VALUES clause's
|
|
675
|
+
* columns) joined to `<table> AS "__vibe_page"` ON `(<identity>) IN (SELECT
|
|
676
|
+
* <identity> … WHERE <key> = "__vibe_keys"."column<i>" … ORDER BY … LIMIT …)`,
|
|
677
|
+
* the rank re-derived over the fetched page (`ROW_NUMBER() OVER (PARTITION
|
|
678
|
+
* BY <key> ORDER BY <order>)`): its values differ from the window's, its
|
|
679
|
+
* order inside a key does not.
|
|
680
|
+
* - `window` (mysql): refused with `VIBE_UNSUPPORTED_CAPABILITY` — measured
|
|
681
|
+
* no gain there; the runtime keeps the window.
|
|
682
|
+
*
|
|
683
|
+
* The caller guarantees what makes the page equal to the window's: the order
|
|
684
|
+
* is total inside a key (the identity columns are among the key and order
|
|
685
|
+
* columns), the key tuples are distinct and compare exactly (no folding
|
|
686
|
+
* collation). Members bind like the key-list filter's (`KeyListMember`).
|
|
687
|
+
*/
|
|
688
|
+
export type KeyPage = {
|
|
689
|
+
/** How each key member binds, in member order (postgres: one typed array each). */
|
|
690
|
+
readonly members: readonly KeyListMember[];
|
|
691
|
+
/** Encoded, distinct key tuples; never empty, each `members.length` long. */
|
|
692
|
+
readonly rows: readonly (readonly unknown[])[];
|
|
693
|
+
/** The child columns each key member equals, in member order (unqualified). */
|
|
694
|
+
readonly keyColumns: readonly ColRef[];
|
|
695
|
+
/** The child's primary-key columns (unqualified) — each page row is read once by them. */
|
|
696
|
+
readonly identity: readonly ColRef[];
|
|
697
|
+
/**
|
|
698
|
+
* ONE key's page: the child `table` (no alias), the projected `columns`
|
|
699
|
+
* (unqualified; transforms and aliases render as usual), an optional extra
|
|
700
|
+
* `where`, the non-empty `orderBy` (plain column terms), `limit`/`offset`.
|
|
701
|
+
* Any other member is refused.
|
|
702
|
+
*/
|
|
703
|
+
readonly select: SelectStatement;
|
|
704
|
+
/** Output name of the rank column. */
|
|
705
|
+
readonly rankAlias: string;
|
|
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
|
+
};
|
|
526
732
|
export type SelectStatement = {
|
|
527
733
|
readonly kind: "select";
|
|
528
734
|
readonly table: TableRef;
|
|
@@ -548,20 +754,56 @@ export type SelectStatement = {
|
|
|
548
754
|
* guarantees that arrangement.
|
|
549
755
|
*/
|
|
550
756
|
readonly distinctOn?: readonly ColRef[];
|
|
757
|
+
/**
|
|
758
|
+
* Plain `SELECT DISTINCT` over the whole projection — every dialect. The
|
|
759
|
+
* query builder sets it only where it returns exactly the rows its
|
|
760
|
+
* `ROW_NUMBER()` distinct emulation would (sqlite/mysql, value-exact keys,
|
|
761
|
+
* projection equal to the distinct fields); never together with
|
|
762
|
+
* `distinctOn` (the renderer refuses the pair).
|
|
763
|
+
*/
|
|
764
|
+
readonly distinct?: true;
|
|
551
765
|
/**
|
|
552
766
|
* Derived-table FROM: renders `FROM (<fromSelect>) AS "<table.name>"` —
|
|
553
767
|
* `table.name` is the subquery ALIAS and `table.alias` must be unset. Used
|
|
554
768
|
* by aggregate-over-a-subset shapes (`aggregate({ take, skip, orderBy })`).
|
|
555
769
|
*/
|
|
556
770
|
readonly fromSelect?: SelectStatement;
|
|
557
|
-
/** Lateral JSON-aggregation joins — postgres
|
|
771
|
+
/** Lateral JSON-aggregation joins — postgres and MySQL 8.0.14+, see {@link LateralJoin}. */
|
|
558
772
|
readonly lateralJoins?: readonly LateralJoin[];
|
|
559
773
|
/** Plain equality INNER JOINs — portable, see {@link InnerJoin}. */
|
|
560
774
|
readonly innerJoins?: readonly InnerJoin[];
|
|
561
775
|
/** Key-table join, rendered after `innerJoins` — mysql only, see {@link KeyTableJoin}. */
|
|
562
776
|
readonly keyTable?: KeyTableJoin;
|
|
777
|
+
/** Fix lane Q2 (D-03): derived-table LEFT JOINs, rendered after `keyTable` — see {@link LeftJoin}. */
|
|
778
|
+
readonly leftJoins?: readonly LeftJoin[];
|
|
563
779
|
/** ROW_NUMBER window projection — see {@link RowNumberProjection}. */
|
|
564
780
|
readonly rowNumber?: RowNumberProjection;
|
|
781
|
+
/**
|
|
782
|
+
* Fix lane Q1 (DB-01): derived-table FROM over a per-key page — renders
|
|
783
|
+
* `FROM (<per-key page>) AS "<table.name>"` like `fromSelect` (and never
|
|
784
|
+
* together with it); see {@link KeyPage}.
|
|
785
|
+
*/
|
|
786
|
+
readonly keyPage?: KeyPage;
|
|
787
|
+
/**
|
|
788
|
+
* Fix lane Q2 (round 3, DB-07): derived-table FROM over a `UNION ALL` of
|
|
789
|
+
* branch selects — renders `FROM (SELECT * FROM (<branch 0>) AS
|
|
790
|
+
* "__vibe_u0" UNION ALL SELECT * FROM (<branch 1>) AS "__vibe_u1" …) AS
|
|
791
|
+
* "<table.name>"`. Each branch keeps its own ORDER BY and LIMIT: wrapped in
|
|
792
|
+
* its own derived table, a limited branch is valid on all three engines
|
|
793
|
+
* (SQLite refuses ORDER BY/LIMIT on a bare compound member). At least two
|
|
794
|
+
* branches; each a plain `SELECT <columns> FROM <table> [WHERE] [ORDER BY]
|
|
795
|
+
* [LIMIT]` projecting the same output names in the same order; never
|
|
796
|
+
* together with `fromSelect`, `keyPage` or a `table.alias`. Parameters bind
|
|
797
|
+
* branch by branch, in text order.
|
|
798
|
+
*/
|
|
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;
|
|
565
807
|
readonly where?: SqlExpr;
|
|
566
808
|
readonly groupBy?: readonly ColRef[];
|
|
567
809
|
/** HAVING predicate — grouped selects; may contain `aggCmp` expressions. */
|
|
@@ -597,6 +839,19 @@ export type RowLock = {
|
|
|
597
839
|
export declare const SQL_DEFAULT_CELL: {
|
|
598
840
|
readonly __sqlDefault: true;
|
|
599
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;
|
|
600
855
|
/** Is this VALUES cell the {@link SQL_DEFAULT_CELL} sentinel? */
|
|
601
856
|
export declare function isSqlDefaultCell(value: unknown): boolean;
|
|
602
857
|
/**
|
|
@@ -674,6 +929,26 @@ export type InsertStatement = {
|
|
|
674
929
|
readonly onConflict?: OnConflictClause;
|
|
675
930
|
/** Rendered as RETURNING where the dialect supports it, else omitted+flagged. */
|
|
676
931
|
readonly returning?: readonly ReturningColumn[];
|
|
932
|
+
/**
|
|
933
|
+
* Bind the rows as ONE typed array per column instead of one parameter per
|
|
934
|
+
* value (`SqlDialect.bulkInsertForm` `"unnest"`, postgres): `INSERT INTO t
|
|
935
|
+
* ("a", "b") SELECT "c1", "c2" FROM unnest($1::text[], $2::int4[]) WITH
|
|
936
|
+
* ORDINALITY AS "__vibe_rows" ("c1", "c2", "ord") ORDER BY "ord"`. The text
|
|
937
|
+
* is the same for 2 rows and for 3 000, and the statement binds one parameter
|
|
938
|
+
* per column. `ORDER BY "ord"` keeps input order (sequence values, the
|
|
939
|
+
* `ON CONFLICT DO NOTHING` winner).
|
|
940
|
+
*
|
|
941
|
+
* One entry per column, in `columns` order: the column's DECLARED type facts
|
|
942
|
+
* (see {@link KeyListMember}; a `Json` entry names its storage through
|
|
943
|
+
* `nativeType`). Each array binds the BASE type (`varchar`, never
|
|
944
|
+
* `varchar(191)` — an explicit modifier cast truncates silently), so the
|
|
945
|
+
* INSERT's own assignment applies the column's length and precision exactly
|
|
946
|
+
* as it does for a VALUES parameter. `rows` stays row-major: the renderer
|
|
947
|
+
* transposes it. Every cell is a plain value, SQL NULL or a
|
|
948
|
+
* {@link JsonParamCell} — never `DEFAULT` or the database clock (the query
|
|
949
|
+
* builder keeps VALUES for those; the renderer refuses them).
|
|
950
|
+
*/
|
|
951
|
+
readonly unnest?: readonly KeyListMember[];
|
|
677
952
|
};
|
|
678
953
|
/** Conflicting rows are updated with these column values. */
|
|
679
954
|
export type OnConflictUpdate = {
|
|
@@ -705,11 +980,28 @@ export type OnConflictUpdate = {
|
|
|
705
980
|
};
|
|
706
981
|
/**
|
|
707
982
|
* Conflicting rows are skipped (`createMany({ skipDuplicates: true })`).
|
|
708
|
-
* An empty/absent target means "any conflict" — the mysql form
|
|
709
|
-
*
|
|
710
|
-
*
|
|
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.
|
|
711
987
|
*/
|
|
712
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;
|
|
713
1005
|
readonly columns?: readonly string[];
|
|
714
1006
|
/**
|
|
715
1007
|
* Partial-index target predicate, RAW SQL: `ON CONFLICT (cols) WHERE pred
|
|
@@ -727,7 +1019,13 @@ export type OnConflictClause = OnConflictUpdate | OnConflictDoNothing;
|
|
|
727
1019
|
export type UpdateStatement = {
|
|
728
1020
|
readonly kind: "update";
|
|
729
1021
|
readonly table: TableRef;
|
|
730
|
-
/**
|
|
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
|
+
*/
|
|
731
1029
|
readonly set: Readonly<Record<string, unknown>>;
|
|
732
1030
|
readonly where?: SqlExpr;
|
|
733
1031
|
readonly returning?: readonly ReturningColumn[];
|
|
@@ -777,6 +1075,12 @@ export type RlsContextStatement = {
|
|
|
777
1075
|
readonly kind: "rlsContext";
|
|
778
1076
|
readonly context: RlsContextIR;
|
|
779
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;
|
|
780
1084
|
};
|
|
781
1085
|
/** Trusted pre-existing PGlite execution role; never a request-supplied principal. */
|
|
782
1086
|
export type RlsRoleStatement = {
|
|
@@ -865,6 +1169,15 @@ export type InsertSelectStatement = {
|
|
|
865
1169
|
*/
|
|
866
1170
|
readonly source?: ChainRowSource;
|
|
867
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
|
+
};
|
|
868
1181
|
readonly returning?: readonly ReturningColumn[];
|
|
869
1182
|
};
|
|
870
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
|
|
@@ -122,6 +166,8 @@ export declare function parseDateTimeText(params: {
|
|
|
122
166
|
export declare function timeOfDayCodec(params: {
|
|
123
167
|
nativeType: string | undefined;
|
|
124
168
|
}): ScalarCodec | undefined;
|
|
169
|
+
/** Canonical calendar-day text only; a driver Date cannot recover the authored day. */
|
|
170
|
+
export declare const localDateCodec: ScalarCodec;
|
|
125
171
|
/**
|
|
126
172
|
* Parse stored JSON text at the centralized codec boundary. A supplied context
|
|
127
173
|
* marks a bound field: its error contains only that context, never the document
|