@vibeorm/sql 5.10.0 → 5.11.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
@@ -611,6 +611,13 @@ export type LateralJoin = {
611
611
  * out. The row is the remaining columns in `select.columns` order.
612
612
  */
613
613
  readonly hidden?: readonly string[];
614
+ /**
615
+ * `jsonObject` laterals only: output names of `select.columns` the lateral
616
+ * exposes as typed columns of its own beside the JSON value (`"alias"."name"`
617
+ * in the outer scope). The outer statement reads a key of the row from them
618
+ * without parsing the JSON; they are SQL NULL when the lateral finds no row.
619
+ */
620
+ readonly expose?: readonly string[];
614
621
  /** The correlated child select — may reference outer columns via table-qualified refs. */
615
622
  readonly select: SelectStatement;
616
623
  };
@@ -777,6 +784,175 @@ export type RowNumberProjection = {
777
784
  readonly orderBy: readonly OrderTerm[];
778
785
  readonly alias: string;
779
786
  };
787
+ /**
788
+ * One window column of a select, rendered after `rowNumber`
789
+ * (`SelectStatement.windows`). Together the two kinds let a statement say
790
+ * which of its rows is the first to hold a key:
791
+ *
792
+ * - `ordinal`: `ROW_NUMBER() OVER ([ORDER BY …]) AS "alias"`, the row's
793
+ * position among the rows of its query level, in `orderBy`, or in the order
794
+ * the rows arrive when `orderBy` is empty (`OVER ()`).
795
+ * - `firstOrdinal`: `MIN(<ordinal>) OVER (PARTITION BY …) AS "alias"`, the
796
+ * smallest `ordinal` among the rows that share the partition columns. The
797
+ * database's own equality groups them (NULLs together), whatever the
798
+ * column's type or collation.
799
+ *
800
+ * A window is computed before the `ORDER BY` and `LIMIT` of its own query
801
+ * level: number a page one level outside the select that cuts it.
802
+ */
803
+ export type WindowColumn = {
804
+ readonly kind: "ordinal";
805
+ readonly alias: string;
806
+ readonly orderBy: readonly OrderTerm[];
807
+ } | {
808
+ readonly kind: "firstOrdinal";
809
+ readonly alias: string;
810
+ readonly ordinal: ColRef;
811
+ readonly partitionBy: readonly ColRef[];
812
+ };
813
+ /**
814
+ * One column of a `SelectStatement.fromRow`: an outer column under the name
815
+ * the row gives it.
816
+ */
817
+ export type RowSourceColumn = {
818
+ readonly column: ColRef;
819
+ readonly alias: string;
820
+ };
821
+ /**
822
+ * One common table expression of a select (`SelectStatement.with`):
823
+ * `"name" AS [MATERIALIZED] (<select>)`. `materialized` pins the table to one
824
+ * evaluation whatever the number of references: required when two readers
825
+ * must see the same rows of a select whose rows are numbered in arrival
826
+ * order.
827
+ */
828
+ export type CommonTable = {
829
+ readonly name: string;
830
+ readonly select: SelectStatement;
831
+ readonly materialized?: true;
832
+ };
833
+ /**
834
+ * The place of one value in a positional JSON row ({@link LateralJoin}
835
+ * `rowFormat: "positional"`): its index among the row's `width` values. A row
836
+ * wider than `JSON_TRANSPORT.chunkSize` is an array of chunks, so the renderer
837
+ * needs both numbers to address the value.
838
+ */
839
+ export type JsonSlot = {
840
+ readonly index: number;
841
+ readonly width: number;
842
+ };
843
+ /**
844
+ * One step of a walk through JSON a statement built: into one value of a
845
+ * positional row, or over every element of a list.
846
+ */
847
+ export type JsonStep = {
848
+ readonly kind: "slot";
849
+ readonly slot: JsonSlot;
850
+ } | {
851
+ readonly kind: "elements";
852
+ };
853
+ /**
854
+ * One key column of a {@link JsonKeys} source: `token` is the output name of
855
+ * the key's text as the JSON rows hold it (at `slot` of the row the walk
856
+ * ends at), `alias` the output name of the same key cast back to the
857
+ * column's own type (`member`, typed like a key-list member).
858
+ */
859
+ export type JsonKeyColumn = {
860
+ readonly slot: JsonSlot;
861
+ readonly token: string;
862
+ readonly alias: string;
863
+ readonly member: KeyListMember;
864
+ };
865
+ /**
866
+ * The distinct keys of the rows a JSON walk reaches, as a derived-table FROM
867
+ * (`SelectStatement.fromJsonKeys`). `source` is a JSON column of a common
868
+ * table of the statement (`source.table` names it); `steps` walk from that
869
+ * value to positional rows, each of which holds the text of its key columns
870
+ * in the slots `keys` name. Renders on PostgreSQL as
871
+ *
872
+ * `(SELECT "__vibe_k"."<token>", CAST("__vibe_k"."<token>" AS <type>) AS "<alias>"
873
+ * FROM (SELECT DISTINCT <row> ->> <slot> AS "<token>"
874
+ * FROM json_array_elements((SELECT json_agg(<list>) FROM "<table>" AS "__vibe_s" WHERE <list> IS NOT NULL))
875
+ * AS "__vibe_d0"("value")
876
+ * CROSS JOIN LATERAL json_array_elements("__vibe_d0"."value") AS "__vibe_e0"("value")) AS "__vibe_k")
877
+ * AS "<table.name>"`
878
+ *
879
+ * Each list on the way is read through ONE value that holds that level's
880
+ * lists of every row (an uncorrelated `json_agg`, planned once), and a
881
+ * second list on the way wraps the first walk the same way. PostgreSQL
882
+ * estimates every `json_array_elements` call at 100 rows whatever it reads:
883
+ * walked row by row, the estimate is rows x 100 per list on the way, and two
884
+ * lists over 2 000 rows were priced past the cost where the server's JIT
885
+ * compiler optimizes (measured: 249 ms against 94 ms with JIT off). Read
886
+ * through one value, each list on the way is 100 x 100 rows at any depth and
887
+ * any row count. The price: that one value holds a level's lists of the whole
888
+ * statement, so it is bound by PostgreSQL's 1 GB limit on a single value.
889
+ *
890
+ * The database groups the keys by their text and types each one from that
891
+ * text with the key-list's base types (`postgresKeyListElementType`), so the
892
+ * laterals joined to the derived table compare `child."fk" = "<table.name>"."<alias>"`
893
+ * under the column's own operator and index. A row the walk does not reach
894
+ * (a missing to-one, an empty list) contributes nothing or a NULL key.
895
+ * Needs the `sharedListDictionary` capability; never together with another
896
+ * FROM form, a `table.alias` or a `rowLock`.
897
+ */
898
+ export type JsonKeys = {
899
+ readonly source: ColRef;
900
+ readonly steps: readonly JsonStep[];
901
+ readonly keys: readonly JsonKeyColumn[];
902
+ };
903
+ /**
904
+ * The rows of a common table, read back through arrays, as a FROM
905
+ * (`SelectStatement.fromTableArrays`): `columns` of the table `source`, each
906
+ * under the output name `alias`, in the order of the column `orderBy`.
907
+ * Renders on PostgreSQL as
908
+ *
909
+ * `(SELECT array_agg("<source>"."<name>" ORDER BY "<source>"."<orderBy>") AS "__vibe_a0", …
910
+ * FROM "<source>") AS "__vibe_a"
911
+ * CROSS JOIN LATERAL unnest("__vibe_a"."__vibe_a0", …) AS "<table.name>"("<alias>", …)`
912
+ *
913
+ * It returns the rows a plain read of the table would, typed as the table's
914
+ * columns (no text on the way), and exists for two things the plain read
915
+ * does not give. The estimate: PostgreSQL expects 10 rows from `unnest` of an
916
+ * array it cannot see, while it expects one row per input row from a table
917
+ * grouped by a key it can trace to a unique column, and every lateral joined
918
+ * to the rows is priced once per expected row (measured: 20 000 rows of 10
919
+ * keys priced at 3.1 million against 129 000, which decides whether the
920
+ * server's JIT compiler runs). The order: laterals joined to the rows read
921
+ * their tables in `orderBy` order, not in a hash table's (measured: the same
922
+ * 20 000 index lookups in 440 ms against 1 066 ms). Needs the `carrierRows`
923
+ * capability on a dialect whose `lateralJson.carriedLists` is `"firstRow"`;
924
+ * never together with another FROM form, a `table.alias` or a `rowLock`.
925
+ */
926
+ export type TableArrays = {
927
+ readonly source: string;
928
+ readonly columns: readonly {
929
+ readonly name: string;
930
+ readonly alias: string;
931
+ }[];
932
+ readonly orderBy: string;
933
+ };
934
+ /**
935
+ * A JSON list a statement returns on its first row only
936
+ * (`SelectStatement.firstRowLists`): `list` describes the list as a
937
+ * `jsonArray` lateral does (its select, row format and column alias; the join
938
+ * alias is unused), `ordinal` is the statement's row ordinal (see
939
+ * {@link WindowColumn} `ordinal`). Renders as a projection:
940
+ *
941
+ * `CASE WHEN <ordinal> = 1 THEN (SELECT COALESCE(json_agg(<row>), '[]'::json)
942
+ * FROM (<list.select>) AS "__sub") END AS "<list.columnAlias>"`
943
+ *
944
+ * The select must not read the statement's own row: an uncorrelated scalar
945
+ * subquery is planned and costed once and runs once, when the first row asks
946
+ * for it. A lateral gated on the ordinal returns the same rows, but
947
+ * PostgreSQL prices it once per row of the statement (measured: 500 rows
948
+ * priced the list 500 times, the estimate passed `jit_above_cost` and the
949
+ * statement spent 100 ms compiling against 5 ms running). Every other row
950
+ * holds SQL NULL. Needs the `sharedListDictionary` capability.
951
+ */
952
+ export type FirstRowList = {
953
+ readonly ordinal: ColRef;
954
+ readonly list: LateralJoin;
955
+ };
780
956
  /**
781
957
  * A PER-KEY PAGE of one child table as a derived-table FROM
782
958
  * (`SelectStatement.keyPage`): for every listed key tuple, the child rows
@@ -869,6 +1045,14 @@ export type GroupingSets = {
869
1045
  };
870
1046
  export type SelectStatement = {
871
1047
  readonly kind: "select";
1048
+ /**
1049
+ * Common table expressions, rendered ahead of the select (`WITH a AS (…),
1050
+ * b AS (…) SELECT …`) and bound first, in order. The statement and the
1051
+ * tables after it read a table by its name as a plain `table`. Only on the
1052
+ * outermost select of a statement; needs the `sharedListDictionary`
1053
+ * capability. See {@link CommonTable}.
1054
+ */
1055
+ readonly with?: readonly CommonTable[];
872
1056
  readonly table: TableRef;
873
1057
  /** Empty array renders as `SELECT *` (unless `selectExpr` replaces it). */
874
1058
  readonly columns: readonly ColRef[];
@@ -918,6 +1102,39 @@ export type SelectStatement = {
918
1102
  readonly leftJoins?: readonly LeftJoin[];
919
1103
  /** ROW_NUMBER window projection — see {@link RowNumberProjection}. */
920
1104
  readonly rowNumber?: RowNumberProjection;
1105
+ /** Window columns, rendered after `rowNumber` — see {@link WindowColumn}. */
1106
+ readonly windows?: readonly WindowColumn[];
1107
+ /** JSON lists of the first row, rendered after `windows` — see {@link FirstRowList}. */
1108
+ readonly firstRowLists?: readonly FirstRowList[];
1109
+ /**
1110
+ * Every column of the statement's FROM, ahead of the other projections:
1111
+ * `SELECT "<from>".*, …`. With `fromSelect` and an empty `columns` list (a
1112
+ * level that adds window columns to the level below it re-exposes that
1113
+ * level's columns without naming each one again), or over a common table
1114
+ * of the statement's own `with`, read under `table.alias ?? table.name`,
1115
+ * where the other projections follow it.
1116
+ */
1117
+ readonly allColumns?: true;
1118
+ /**
1119
+ * Derived-table FROM of exactly ONE row built from outer columns: renders
1120
+ * `FROM (SELECT <column> AS "<alias>", …) AS "<table.name>"`. Inside a
1121
+ * lateral it gives outer values the column names of a table row, so the
1122
+ * laterals joined to it correlate as they would to that table's row
1123
+ * (`child."fk" = "<table.name>"."id"`) and the table is not read again.
1124
+ * Needs the `carrierRows` capability; never together with `fromSelect`,
1125
+ * `keyPage`, `fromUnion`, `fromKeys`, a `table.alias` or a `rowLock`.
1126
+ */
1127
+ readonly fromRow?: readonly RowSourceColumn[];
1128
+ /**
1129
+ * Derived-table FROM over the distinct, typed keys of a JSON walk, named
1130
+ * `table.name` — see {@link JsonKeys}.
1131
+ */
1132
+ readonly fromJsonKeys?: JsonKeys;
1133
+ /**
1134
+ * FROM over the rows of a common table read back through arrays, named
1135
+ * `table.name` — see {@link TableArrays}.
1136
+ */
1137
+ readonly fromTableArrays?: TableArrays;
921
1138
  /**
922
1139
  * Fix lane Q1 (DB-01): derived-table FROM over a per-key page — renders
923
1140
  * `FROM (<per-key page>) AS "<table.name>"` like `fromSelect` (and never
package/dist/dialect.d.ts CHANGED
@@ -210,6 +210,55 @@ export type SqlDialect = {
210
210
  * where every adapter pins zone-less values to UTC (postgres).
211
211
  */
212
212
  readonly requiresUtcDateTimeWire: boolean;
213
+ /**
214
+ * How a dictionary of shared lists reads its distinct keys back out of
215
+ * the JSON the statement built (`SelectStatement.fromJsonKeys`).
216
+ * `"elements"`: one set-returning call per list on the way and a cast of
217
+ * the key text to the key-list's base type (postgres). `"jsonTable"`:
218
+ * one `JSON_TABLE` whose row path walks the lists, `JSON`-typed columns
219
+ * grouped as JSON and unquoted outside (MySQL; see
220
+ * `dialects/mysql-json-keys.ts` for why no other form is both right and
221
+ * indexed).
222
+ */
223
+ readonly keysFromJson: "elements" | "jsonTable";
224
+ /**
225
+ * How a common table is held to one evaluation (`CommonTable.materialized`):
226
+ * `"keyword"` — `AS MATERIALIZED (…)`; `"limit"` — the dialect has no such
227
+ * keyword and merges a common table into each reference unless its select
228
+ * is limited, so an unlimited select gets the largest `LIMIT` (MySQL).
229
+ */
230
+ readonly pinnedCommonTable: "keyword" | "limit";
231
+ /**
232
+ * Where a statement with carrier rows returns the lists it builds once
233
+ * per distinct target. `"firstRow"`: all of them as one JSON list on the
234
+ * statement's first row, built by an uncorrelated scalar subquery over the
235
+ * distinct keys (`SelectStatement.fromTableArrays`); PostgreSQL prices that
236
+ * subquery once, where it prices a lateral once per row of the statement
237
+ * and the estimate alone starts the JIT compiler from about 1 000 rows.
238
+ * `"carrierRow"`: each target's lists on the first row that reads it, from
239
+ * a lateral gated on that row (`SelectStatement.fromRow`); MySQL has no
240
+ * JIT compiler, runs the gated lateral once per target, and has no arrays
241
+ * to read the keys back through. The runtime builds `"carrierRow"` on a
242
+ * `"firstRow"` dialect too where a statement's root is proven to return
243
+ * a few rows: the estimate is bounded there and the statement is cheaper.
244
+ */
245
+ readonly carriedLists: "firstRow" | "carrierRow";
246
+ /**
247
+ * The server setting that caps one JSON value where the server can answer
248
+ * a larger one without failing the statement (MySQL `max_allowed_packet`,
249
+ * 64 MB by default). Measured on MySQL 26.7 with the statements the
250
+ * runtime builds: a carried row whose lists together pass the cap arrives
251
+ * as SQL NULL with warning 1301, a dictionary past it as its JSON text cut
252
+ * a little past the cap with the same warning, and one list past it fails
253
+ * the statement (errno 1301), which leaves a transaction open. The
254
+ * runtime reads a NULL or unparsable text where the statement always
255
+ * builds a JSON value as that overflow (`VIBE_RESULT_TOO_LARGE`), and the
256
+ * client answers a find that fails with that code under the `"query"`
257
+ * strategy. `null`: the server fails the statement itself and the error
258
+ * reaches the caller (PostgreSQL: 1 GB, and a failed statement aborts its
259
+ * transaction).
260
+ */
261
+ readonly silentSizeLimit: string | null;
213
262
  } | null;
214
263
  /**
215
264
  * The relation strategy a client uses when neither the call nor the client
@@ -0,0 +1,26 @@
1
+ /**
2
+ * MySQL's text for the two pieces of a dictionary of shared lists that it
3
+ * cannot share with PostgreSQL: the distinct keys read back out of the JSON a
4
+ * statement built (`SelectStatement.fromJsonKeys`), and a common table that
5
+ * must be evaluated once (`CommonTable.materialized`).
6
+ *
7
+ * Every choice below was measured on MySQL 26.7 (500 roots, 1 000 keys):
8
+ *
9
+ * - A `VARCHAR` column of `JSON_TABLE` takes the CONNECTION's collation with
10
+ * implicit coercibility. Compared with a foreign-key column it fails
11
+ * (`ER_CANT_AGGREGATE_2COLLATIONS`) whenever the two collations differ, and
12
+ * mysql2 connects as `utf8mb4_unicode_ci` while MySQL creates columns as
13
+ * `utf8mb4_0900_ai_ci`.
14
+ * - The same column declared `COLLATE utf8mb4_bin` runs, but the binary
15
+ * collation wins the comparison and the foreign-key index is not used
16
+ * (627 ms against 25 ms).
17
+ * - `JSON_UNQUOTE(<a JSON value>)` is coercible like a bound literal, also
18
+ * through a materialized derived table, so the COLUMN's collation decides
19
+ * the comparison and its index serves it: the key matches its children
20
+ * exactly as `WHERE fk IN (?)` matches them.
21
+ * - `DISTINCT` on a `JSON` column compares the values exactly. On text under
22
+ * `utf8mb4_bin` it pads (`'a'` and `'a '` are one key), which would lose an
23
+ * entry where the key column's own collation does not pad.
24
+ */
25
+ export {};
26
+ //# sourceMappingURL=mysql-json-keys.d.ts.map
package/dist/index.d.ts CHANGED
@@ -48,7 +48,7 @@ export type { ResultDecoder, ResultReadDecoder, ResultParameterEncoder, ResultSc
48
48
  * lives in this package; see the root sql-catalog.md for the reviewable surface.
49
49
  */
50
50
  export { compileExclusion, renderExclusionSetup, renderExclusionDdl, renderExclusionCatalog, renderExclusionProbe } from "./exclusion.ts";
51
- export type { AdvisoryLockKey, AdvisoryLockStatement, AdvisoryLocksStatement, AggFn, AggRef, AggSelect, ChainResult, ChainRowSource, ChainCapture, CaptureReturningColumn, CaptureCascade, CaptureCascadeReturningColumn, CaptureCell, CaptureHop, OtherRowReturningColumn, ServerVersionReturningColumn, ChainStatement, ChainStep, ChainStepStatement, ChainTally, ChainVeto, ColRef, ComparisonOp, DateTruncation, GroupKeyExpression, GroupingSets, CountSubquery, ScalarSubquery, DeleteStatement, ExcludedRef, InnerJoin, KeyListMember, KeyPage, KeyRows, KeyTableJoin, KeyTableMember, InsertSelectStatement, InsertStatement, ReturningColumn, JsonFilterOp, LateralJoin, LeftJoin, ListSetOperation, OnConflictClause, OnConflictDoNothing, UnchangedColumn, UnchangedExactness, OnConflictUpdate, OrderByAggregate, OrderByColumn, OrderBySubquery, OrderTerm, RowLock, RowNumberProjection, DbNowMarker, JsonParamCell, SelectExpr, SelectStatement, SetOperation, SetOperator, SqlExpr, SqlStatement, StepColumnRef, SourceColumnRef, UpdateSource, UpdateTableSource, UpdateSelectSource, TableRef, UpdateStatement, } from "./ast.ts";
51
+ export type { AdvisoryLockKey, AdvisoryLockStatement, AdvisoryLocksStatement, AggFn, AggRef, AggSelect, ChainResult, ChainRowSource, ChainCapture, CaptureReturningColumn, CaptureCascade, CaptureCascadeReturningColumn, CaptureCell, CaptureHop, OtherRowReturningColumn, ServerVersionReturningColumn, ChainStatement, ChainStep, ChainStepStatement, ChainTally, ChainVeto, ColRef, ComparisonOp, DateTruncation, GroupKeyExpression, GroupingSets, CountSubquery, ScalarSubquery, DeleteStatement, ExcludedRef, InnerJoin, KeyListMember, KeyPage, KeyRows, KeyTableJoin, KeyTableMember, InsertSelectStatement, InsertStatement, ReturningColumn, JsonFilterOp, LateralJoin, LeftJoin, ListSetOperation, OnConflictClause, OnConflictDoNothing, UnchangedColumn, UnchangedExactness, OnConflictUpdate, OrderByAggregate, OrderByColumn, OrderBySubquery, OrderTerm, RowLock, RowNumberProjection, RowSourceColumn, CommonTable, FirstRowList, JsonKeyColumn, JsonKeys, TableArrays, JsonSlot, JsonStep, WindowColumn, DbNowMarker, JsonParamCell, SelectExpr, SelectStatement, SetOperation, SetOperator, SqlExpr, SqlStatement, StepColumnRef, SourceColumnRef, UpdateSource, UpdateTableSource, UpdateSelectSource, TableRef, UpdateStatement, } from "./ast.ts";
52
52
  export { ADVISORY_LOCK_RESULT_COLUMN, CHAIN_VETO_PREFIX, CHAIN_VETO_TOKEN_PATTERN, COALESCE_REF_NAME, SQL_DEFAULT_CELL, SQL_KEEP_CELL, chainVetoToken, coalesceRef, dbNowMarker, excludedRef, isDbNowMarker, isExcludedRef, isJsonParamCell, isListSetOperation, isSetOperation, isSqlDefaultCell, isSqlKeepCell, isStepColumnRef, isSourceColumnRef, sourceColumnRef, isCaptureReturningColumn, isOtherRowReturningColumn, jsonParamCell, returningColumnName, stepColumnRef, } from "./ast.ts";
53
53
  export type { AdvisoryLockFunctions, AdvisoryLockModeFunctions, RowLockSyntax, EmptyInsertSyntax, SqlDialect, } from "./dialect.ts";
54
54
  export { ADVISORY_KEY_BOUNDS, describeRowLock, renderAdvisoryLock, renderAdvisoryLocks, renderRowLockClause } from "./locks.ts";
@@ -58,7 +58,8 @@ export { sqliteDialect } from "./dialects/sqlite.ts";
58
58
  export { mysqlDialect } from "./dialects/mysql.ts";
59
59
  export { SQL_DIALECTS, getSqlDialect } from "./dialects/registry.ts";
60
60
  export { tableDefinitionParts } from "./dialects/sqlite-table-definition.ts";
61
- export { keyListBindsUntypedRows, postgresBulkInsertArrayCastOf, postgresKeyListElementTypeOf } from "./key-list.ts";
61
+ export { jsonKeyTokenCast, jsonKeysTyped, keyListBindsUntypedRows, mysqlJsonKeyTypeOf, postgresBulkInsertArrayCastOf, postgresKeyListElementTypeOf } from "./key-list.ts";
62
+ export type { MysqlJsonKeyType } from "./key-list.ts";
62
63
  export { renderPredicate, renderSchemaExpression } from "./predicate.ts";
63
64
  export { renderLocalDateCheck } from "./local-date.ts";
64
65
  export { isTimeZoneName, renderDateTruncation } from "./date-truncation.ts";