@vibeorm/sql 5.3.0 → 5.5.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 +65 -0
- package/dist/dialects/diagnostics.d.ts +25 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +115 -9
- package/dist/index.js.map +6 -6
- package/dist/query/index.js.map +1 -1
- package/package.json +2 -2
package/dist/ast.d.ts
CHANGED
|
@@ -868,10 +868,21 @@ export type SelectStatement = {
|
|
|
868
868
|
* The lock a locking read takes: one strength, one wait policy. Both members
|
|
869
869
|
* are REQUIRED here — defaulting happens once, in the query builder, so the
|
|
870
870
|
* rendered SQL is a function of the AST alone.
|
|
871
|
+
*
|
|
872
|
+
* `of` narrows the lock to named FROM items (`FOR UPDATE OF "Parent"`). The
|
|
873
|
+
* join relation strategy needs it: its statement also reads child rows through
|
|
874
|
+
* `LEFT JOIN LATERAL` subqueries, and an unnarrowed clause is refused by
|
|
875
|
+
* postgres ("FOR UPDATE is not allowed with aggregate functions", from the
|
|
876
|
+
* children's `json_agg`). With `of` naming the statement's own table only the
|
|
877
|
+
* parent rows are locked. Each name must be the statement's table alias, or its
|
|
878
|
+
* unqualified table name when it has no alias — the only FROM item the AST
|
|
879
|
+
* names; the renderer refuses anything else. Dialects without a locking-read
|
|
880
|
+
* clause render nothing, as for a lock without `of`.
|
|
871
881
|
*/
|
|
872
882
|
export type RowLock = {
|
|
873
883
|
readonly strength: RowLockStrength;
|
|
874
884
|
readonly wait: RowLockWaitPolicy;
|
|
885
|
+
readonly of?: readonly string[];
|
|
875
886
|
};
|
|
876
887
|
/**
|
|
877
888
|
* Sentinel VALUES cell: render the `DEFAULT` keyword instead of a placeholder
|
|
@@ -960,9 +971,28 @@ export type ReturningColumn = string | {
|
|
|
960
971
|
} | {
|
|
961
972
|
readonly name: string;
|
|
962
973
|
readonly transform: "jsonText";
|
|
974
|
+
} | CaptureReturningColumn;
|
|
975
|
+
/**
|
|
976
|
+
* A row-capture RETURNING column (`$transaction(…, { capture })`, dialects with
|
|
977
|
+
* the `rowCapture` capability: PostgreSQL 18, where RETURNING names the row
|
|
978
|
+
* before the statement `old` and the row after it `new`):
|
|
979
|
+
*
|
|
980
|
+
* - `"old"` / `"new"`: `old."col"::text AS "alias"` / `new."col"::text AS "alias"`,
|
|
981
|
+
* the value as text so every type survives exactly (`old` is all NULL for an
|
|
982
|
+
* inserted row, `new` for a deleted one);
|
|
983
|
+
* - `"inserted"`: `(old."col" IS NULL) AS "alias"` — the arm of an `INSERT …
|
|
984
|
+
* ON CONFLICT DO UPDATE` row, true when it was inserted (`col` must be a
|
|
985
|
+
* column no stored row holds NULL in: a primary-key column).
|
|
986
|
+
*/
|
|
987
|
+
export type CaptureReturningColumn = {
|
|
988
|
+
readonly capture: "old" | "new" | "inserted";
|
|
989
|
+
readonly name: string;
|
|
990
|
+
readonly alias: string;
|
|
963
991
|
};
|
|
964
992
|
/** The column name a {@link ReturningColumn} exposes in the result row. */
|
|
965
993
|
export declare function returningColumnName(column: ReturningColumn): string;
|
|
994
|
+
/** Is this RETURNING column a row-capture column ({@link CaptureReturningColumn})? */
|
|
995
|
+
export declare function isCaptureReturningColumn(column: ReturningColumn): column is CaptureReturningColumn;
|
|
966
996
|
export type InsertStatement = {
|
|
967
997
|
readonly kind: "insert";
|
|
968
998
|
readonly table: TableRef;
|
|
@@ -1319,6 +1349,41 @@ export type ChainResult = {
|
|
|
1319
1349
|
* it instead of counting result rows (effect `writeTally`).
|
|
1320
1350
|
*/
|
|
1321
1351
|
readonly tally?: ChainTally;
|
|
1352
|
+
/**
|
|
1353
|
+
* Row capture ({@link ChainCapture}): per captured data-modifying step, its
|
|
1354
|
+
* row count and its distinct before and after images, on every result row
|
|
1355
|
+
* (uncorrelated scalar subqueries, evaluated once). Needs the dialect's
|
|
1356
|
+
* `rowCapture` capability. Rendered after every other result column, so
|
|
1357
|
+
* their bound limits come last.
|
|
1358
|
+
*/
|
|
1359
|
+
readonly capture?: readonly ChainCapture[];
|
|
1360
|
+
};
|
|
1361
|
+
/**
|
|
1362
|
+
* One captured step of a chain ({@link ChainResult.capture}). Result columns,
|
|
1363
|
+
* `<alias>` being this entry's alias:
|
|
1364
|
+
*
|
|
1365
|
+
* - `<alias>_n`: `(SELECT count(*) FROM "step")`;
|
|
1366
|
+
* - `<alias>_i` (with `inserted`): the rows whose arm marker is true;
|
|
1367
|
+
* - `<alias>_b` / `<alias>_a` (with `before` / `after`): `(SELECT
|
|
1368
|
+
* array_agg("__vibe_i")::text FROM (SELECT DISTINCT ARRAY["c0", "c1"] AS
|
|
1369
|
+
* "__vibe_i" FROM "step" LIMIT $n) AS "__vibe_d")` — a two-dimensional text
|
|
1370
|
+
* array literal of at most `limit` distinct images, NULL when the step
|
|
1371
|
+
* returned no row. A before image skips the rows whose arm marker is true.
|
|
1372
|
+
*
|
|
1373
|
+
* `before` / `after` name columns the step RETURNS (its
|
|
1374
|
+
* {@link CaptureReturningColumn} aliases), in image order. `limit` is bound once.
|
|
1375
|
+
*/
|
|
1376
|
+
export type ChainCapture = {
|
|
1377
|
+
readonly step: string;
|
|
1378
|
+
readonly alias: string;
|
|
1379
|
+
/** The step's `"inserted"` arm-marker column, for an `INSERT … ON CONFLICT DO UPDATE` step. */
|
|
1380
|
+
readonly inserted?: string;
|
|
1381
|
+
/** With `inserted`: images of inserted rows only — `after` reads the rows whose marker is true. */
|
|
1382
|
+
readonly insertedOnly?: true;
|
|
1383
|
+
readonly before?: readonly string[];
|
|
1384
|
+
readonly after?: readonly string[];
|
|
1385
|
+
/** The most distinct images one image column returns: a positive safe integer, bound as a parameter. */
|
|
1386
|
+
readonly limit: number;
|
|
1322
1387
|
};
|
|
1323
1388
|
/**
|
|
1324
1389
|
* Fail the statement when `step` returned no row; `token` names the veto in the
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import type { SqlDialect } from "../dialect.ts";
|
|
2
2
|
import type { RenderedQuery } from "../render.ts";
|
|
3
|
-
import type { SqlStatement } from "../ast.ts";
|
|
3
|
+
import type { SqlStatement, TableRef } from "../ast.ts";
|
|
4
4
|
import type { PostgresExplainOptions, PostgresWorkloadQuery } from "./postgres-diagnostics.ts";
|
|
5
5
|
/** Fixed, parameterless diagnostics whose text never varies. */
|
|
6
6
|
export type FixedDiagnosticQuery = "postgresJsonWire" | "sqliteTriggers";
|
|
7
|
+
/** One table of row capture's readiness read: the table as write statements name it, and the columns capture returns. */
|
|
8
|
+
export type CaptureReadinessTable = {
|
|
9
|
+
readonly table: TableRef;
|
|
10
|
+
readonly columns: readonly string[];
|
|
11
|
+
};
|
|
7
12
|
/**
|
|
8
13
|
* Fixed diagnostic queries; execution and result interpretation belong to callers.
|
|
9
14
|
*
|
|
10
|
-
*
|
|
15
|
+
* Four variants, discriminated by `query`:
|
|
11
16
|
* - a fixed transport/catalog probe,
|
|
17
|
+
* - row capture's readiness read over the tables of one spec,
|
|
12
18
|
* - `postgresExplain`, which wraps another statement (EPIC 5),
|
|
13
19
|
* - a read-only PostgreSQL workload/catalog read (EPIC 5).
|
|
14
20
|
*/
|
|
15
21
|
export type DiagnosticStatement = {
|
|
16
22
|
readonly kind: "diagnostic";
|
|
17
23
|
readonly query: FixedDiagnosticQuery;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: "diagnostic";
|
|
26
|
+
readonly query: "postgresCaptureReadiness";
|
|
27
|
+
readonly tables: readonly CaptureReadinessTable[];
|
|
18
28
|
} | {
|
|
19
29
|
readonly kind: "diagnostic";
|
|
20
30
|
readonly query: "postgresExplain";
|
|
@@ -34,10 +44,22 @@ export declare function renderDiagnostic(params: {
|
|
|
34
44
|
dialect: SqlDialect;
|
|
35
45
|
query: FixedDiagnosticQuery;
|
|
36
46
|
}): RenderedQuery;
|
|
37
|
-
/** True for the
|
|
47
|
+
/** True for the fixed probes; everything else is the capture readiness read or an EPIC 5 PostgreSQL diagnostic. */
|
|
38
48
|
export declare function isFixedDiagnostic(params: {
|
|
39
49
|
query: DiagnosticStatement["query"];
|
|
40
50
|
}): params is {
|
|
41
51
|
query: FixedDiagnosticQuery;
|
|
42
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Row capture's readiness read (PostgreSQL): the server version, and for each
|
|
55
|
+
* captured table what would make its `RETURNING` change a write's outcome.
|
|
56
|
+
* One statement with one JSON parameter, bound as text and cast by the server
|
|
57
|
+
* (bun:sql re-encodes a parameter it resolves as JSON); `to_regclass` resolves
|
|
58
|
+
* each table from the same quoted name the write statements use
|
|
59
|
+
* (schema-qualified when the model has a namespace, else through `search_path`).
|
|
60
|
+
*/
|
|
61
|
+
export declare function renderCaptureReadiness(params: {
|
|
62
|
+
dialect: SqlDialect;
|
|
63
|
+
tables: readonly CaptureReadinessTable[];
|
|
64
|
+
}): RenderedQuery;
|
|
43
65
|
//# sourceMappingURL=diagnostics.d.ts.map
|
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, AdvisoryLocksStatement, AggFn, AggRef, AggSelect, ChainResult, ChainRowSource, ChainStatement, ChainStep, ChainStepStatement, ChainTally, ChainVeto, ColRef, ComparisonOp, DateTruncation, CountSubquery, ScalarSubquery, 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";
|
|
43
|
+
export type { AdvisoryLockKey, AdvisoryLockStatement, AdvisoryLocksStatement, AggFn, AggRef, AggSelect, ChainResult, ChainRowSource, ChainCapture, CaptureReturningColumn, ChainStatement, ChainStep, ChainStepStatement, ChainTally, ChainVeto, ColRef, ComparisonOp, DateTruncation, CountSubquery, ScalarSubquery, 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, isCaptureReturningColumn, 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, renderAdvisoryLocks, renderRowLockClause } from "./locks.ts";
|
|
47
47
|
export { JSON_TRANSPORT, projectionOutputName } from "./json-transport.ts";
|
|
@@ -76,7 +76,7 @@ export type { RlsContextStatement, RlsDisableStatement, RlsDropPolicyStatement,
|
|
|
76
76
|
export { containsPattern, endsWithPattern, escapeGlobPattern, escapeLikePattern, startsWithPattern, strictMatchCollationPrefilter, strictMatchPattern } from "./like.ts";
|
|
77
77
|
export { RLS_READINESS_SQL, NATIVE_RLS_PROBE_POLICY, renderRlsReadinessQuery, renderRlsProbeTable, renderRlsPolicyPath } from "./dialects/postgres-rls-readiness.ts";
|
|
78
78
|
export type { RlsReadinessQuery } from "./dialects/postgres-rls-readiness.ts";
|
|
79
|
-
export { isFixedDiagnostic, renderDiagnostic } from "./dialects/diagnostics.ts";
|
|
79
|
+
export { isFixedDiagnostic, renderCaptureReadiness, renderDiagnostic } from "./dialects/diagnostics.ts";
|
|
80
80
|
export { sqliteColumnClauses } from "./dialects/sqlite-table-definition.ts";
|
|
81
81
|
export type { SqliteColumnClause } from "./dialects/sqlite-table-definition.ts";
|
|
82
82
|
export { parsePostgresTypeName, renderPostgresTypeName, renderPostgresTypeIdentity } from "./dialects/postgres-type-identity.ts";
|
|
@@ -92,7 +92,7 @@ export { renderMigrationInventory, renderSqliteColumnInventory, renderSqliteFore
|
|
|
92
92
|
export { renderMigrationObjectBatch, renderMigrationObjectQuery, renderCheckProbe, renderMigrationNamespace, renderMigrationServerVersion, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlObjectBatch, renderMysqlObjectMembersBatch } from "./dialects/migration-objects.ts";
|
|
93
93
|
export type { MigrationObjectBatch, MysqlObjectBatch } from "./dialects/migration-objects.ts";
|
|
94
94
|
export type { SqliteIntrospectionPragma } from "./dialects/migration-inventory.ts";
|
|
95
|
-
export type { DiagnosticStatement, FixedDiagnosticQuery } from "./dialects/diagnostics.ts";
|
|
95
|
+
export type { CaptureReadinessTable, DiagnosticStatement, FixedDiagnosticQuery } from "./dialects/diagnostics.ts";
|
|
96
96
|
export { POSTGRES_DIAGNOSTIC_SQL, POSTGRES_WORKLOAD_LIMITS, renderPostgresExplain, renderPostgresWorkload, } from "./dialects/postgres-diagnostics.ts";
|
|
97
97
|
export type { PostgresExplainOptions, PostgresWorkloadQuery } from "./dialects/postgres-diagnostics.ts";
|
|
98
98
|
export { renderColumnStateQuery, renderConstraintStateQuery, renderIndexStateQuery, renderMigrationSessionRead, renderMigrationSessionRestore, renderMigrationSessionSet, renderRelationStateQuery, } from "./dialects/postgres-migration.ts";
|
package/dist/index.js
CHANGED
|
@@ -2894,7 +2894,10 @@ function isExcludedRef(value) {
|
|
|
2894
2894
|
return typeof value === "object" && value !== null && value.__excluded === true;
|
|
2895
2895
|
}
|
|
2896
2896
|
function returningColumnName(column) {
|
|
2897
|
-
return typeof column === "string" ? column : column.name;
|
|
2897
|
+
return typeof column === "string" ? column : ("capture" in column) ? column.alias : column.name;
|
|
2898
|
+
}
|
|
2899
|
+
function isCaptureReturningColumn(column) {
|
|
2900
|
+
return typeof column !== "string" && "capture" in column;
|
|
2898
2901
|
}
|
|
2899
2902
|
var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
|
|
2900
2903
|
function stepColumnRef(params) {
|
|
@@ -2960,6 +2963,12 @@ function renderRowLockClause(params) {
|
|
|
2960
2963
|
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
2961
2964
|
});
|
|
2962
2965
|
}
|
|
2966
|
+
if (lock.of !== undefined) {
|
|
2967
|
+
if (lock.of.length === 0)
|
|
2968
|
+
throw internalError({ message: "a row lock's `of` list cannot be empty" });
|
|
2969
|
+
const tables = lock.of.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
2970
|
+
return `${strengthText} OF ${tables}${waitText}`;
|
|
2971
|
+
}
|
|
2963
2972
|
return `${strengthText}${waitText}`;
|
|
2964
2973
|
}
|
|
2965
2974
|
var ADVISORY_KEY_BOUNDS = Object.freeze({
|
|
@@ -3646,7 +3655,7 @@ function renderSqliteJsonEquality(params) {
|
|
|
3646
3655
|
}
|
|
3647
3656
|
|
|
3648
3657
|
// packages/sql/src/dialects/diagnostics.ts
|
|
3649
|
-
import { unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3658
|
+
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3650
3659
|
var QUERIES = Object.freeze({
|
|
3651
3660
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
3652
3661
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -3659,7 +3668,23 @@ function renderDiagnostic(params) {
|
|
|
3659
3668
|
return { text: query.text, values: [], returning: "none" };
|
|
3660
3669
|
}
|
|
3661
3670
|
function isFixedDiagnostic(params) {
|
|
3662
|
-
return
|
|
3671
|
+
return Object.hasOwn(QUERIES, params.query);
|
|
3672
|
+
}
|
|
3673
|
+
var CAPTURE_READINESS_SQL = `SELECT pg_catalog.current_setting('server_version_num') AS "serverVersionNum", t."entry" AS "entry", c.oid IS NOT NULL AS "found", EXISTS (SELECT 1 FROM pg_catalog.pg_rewrite r WHERE r.ev_class = c.oid) AS "rules", COALESCE(pg_catalog.row_security_active(c.oid), false) AS "rowSecurity", (SELECT pg_catalog.array_agg(k.name)::text FROM pg_catalog.json_array_elements_text(t."columns") AS k(name) WHERE NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attribute a WHERE a.attrelid = c.oid AND a.attname = k.name AND a.attnum > 0 AND NOT a.attisdropped)) AS "missing", (SELECT pg_catalog.array_agg(k.name)::text FROM pg_catalog.json_array_elements_text(t."columns") AS k(name) JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attname = k.name AND a.attnum > 0 AND NOT a.attisdropped WHERE NOT pg_catalog.has_column_privilege(c.oid, a.attnum, 'SELECT')) AS "unreadable" FROM pg_catalog.json_to_recordset(%TABLES%::text::json) AS t("entry" integer, "table" text, "columns" json) LEFT JOIN pg_catalog.pg_class c ON c.oid = pg_catalog.to_regclass(t."table") ORDER BY t."entry"`;
|
|
3674
|
+
function renderCaptureReadiness(params) {
|
|
3675
|
+
const { dialect, tables } = params;
|
|
3676
|
+
if (dialect.name !== "postgres") {
|
|
3677
|
+
throw unsupportedCapability24({ dialect: dialect.name, feature: "postgresCaptureReadiness", hint: "row capture reads RETURNING old/new, PostgreSQL 18 or newer" });
|
|
3678
|
+
}
|
|
3679
|
+
const entries = tables.map((entry, index) => {
|
|
3680
|
+
const { name, namespace } = entry.table;
|
|
3681
|
+
if (namespace !== undefined)
|
|
3682
|
+
assertSqlNamespace2({ namespace, dialect: dialect.name });
|
|
3683
|
+
const relation = dialect.quoteIdent({ name });
|
|
3684
|
+
return { entry: index, table: namespace === undefined ? relation : `${dialect.quoteIdent({ name: namespace })}.${relation}`, columns: [...entry.columns] };
|
|
3685
|
+
});
|
|
3686
|
+
const collector = new ParamCollector(dialect);
|
|
3687
|
+
return { text: CAPTURE_READINESS_SQL.replace("%TABLES%", collector.add(JSON.stringify(entries))), values: collector.values, returning: "none" };
|
|
3663
3688
|
}
|
|
3664
3689
|
|
|
3665
3690
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
@@ -4084,7 +4109,7 @@ function renderRlsPolicyPath(params) {
|
|
|
4084
4109
|
}
|
|
4085
4110
|
|
|
4086
4111
|
// packages/sql/src/render.ts
|
|
4087
|
-
import { assertSqlNamespace as
|
|
4112
|
+
import { assertSqlNamespace as assertSqlNamespace3, internalError as internalError4, unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
|
|
4088
4113
|
|
|
4089
4114
|
// packages/sql/src/like.ts
|
|
4090
4115
|
function escapeLikePattern(params) {
|
|
@@ -4122,6 +4147,8 @@ function renderStatement(params) {
|
|
|
4122
4147
|
case "diagnostic": {
|
|
4123
4148
|
if (isFixedDiagnostic(statement))
|
|
4124
4149
|
return renderDiagnostic({ dialect, query: statement.query });
|
|
4150
|
+
if (statement.query === "postgresCaptureReadiness")
|
|
4151
|
+
return renderCaptureReadiness({ dialect, tables: statement.tables });
|
|
4125
4152
|
if (statement.query === "postgresExplain") {
|
|
4126
4153
|
return renderPostgresExplain({
|
|
4127
4154
|
dialect,
|
|
@@ -4240,7 +4267,7 @@ function renderProjection(params) {
|
|
|
4240
4267
|
}
|
|
4241
4268
|
function renderTable(dialect, table) {
|
|
4242
4269
|
if (table.namespace !== undefined)
|
|
4243
|
-
|
|
4270
|
+
assertSqlNamespace3({ namespace: table.namespace, dialect: dialect.name });
|
|
4244
4271
|
const relation = dialect.quoteIdent({ name: table.name });
|
|
4245
4272
|
const name = table.namespace === undefined ? relation : `${dialect.quoteIdent({ name: table.namespace })}.${relation}`;
|
|
4246
4273
|
if (table.alias === undefined)
|
|
@@ -4928,11 +4955,16 @@ function applyReturning(params) {
|
|
|
4928
4955
|
return { suffix: "", status: "none" };
|
|
4929
4956
|
}
|
|
4930
4957
|
if (!dialect.capabilities.returning) {
|
|
4958
|
+
const capture = requested.find(isCaptureReturningColumn);
|
|
4959
|
+
if (capture !== undefined)
|
|
4960
|
+
renderCaptureReturning({ dialect, column: capture });
|
|
4931
4961
|
return { suffix: "", status: "omitted" };
|
|
4932
4962
|
}
|
|
4933
4963
|
const columns = requested.map((column) => {
|
|
4934
4964
|
if (typeof column === "string")
|
|
4935
4965
|
return dialect.quoteIdent({ name: column });
|
|
4966
|
+
if ("capture" in column)
|
|
4967
|
+
return renderCaptureReturning({ dialect, column });
|
|
4936
4968
|
if ("transform" in column) {
|
|
4937
4969
|
const name = dialect.quoteIdent({ name: column.name });
|
|
4938
4970
|
return `${dialect.jsonTextProjection({ expression: name })} AS ${name}`;
|
|
@@ -4942,6 +4974,17 @@ function applyReturning(params) {
|
|
|
4942
4974
|
}).join(", ");
|
|
4943
4975
|
return { suffix: ` RETURNING ${columns}`, status: "native" };
|
|
4944
4976
|
}
|
|
4977
|
+
function renderCaptureReturning(params) {
|
|
4978
|
+
const { dialect, column } = params;
|
|
4979
|
+
if (!dialect.capabilities.rowCapture) {
|
|
4980
|
+
throw unsupportedCapability28({ dialect: dialect.name, feature: "row capture (RETURNING old./new.)", hint: "row capture needs PostgreSQL 18 or later; the runtime refuses $transaction({ capture }) on this dialect" });
|
|
4981
|
+
}
|
|
4982
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
4983
|
+
const alias = dialect.quoteIdent({ name: column.alias });
|
|
4984
|
+
if (column.capture === "inserted")
|
|
4985
|
+
return `(old.${name} IS NULL) AS ${alias}`;
|
|
4986
|
+
return `${textProjection({ dialect, expression: `${column.capture}.${name}` })} AS ${alias}`;
|
|
4987
|
+
}
|
|
4945
4988
|
function renderKeyTableJoin(params) {
|
|
4946
4989
|
const { dialect, join, collector } = params;
|
|
4947
4990
|
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
@@ -5136,6 +5179,13 @@ function renderSelect(params) {
|
|
|
5136
5179
|
text += ` OFFSET ${statement.offset}`;
|
|
5137
5180
|
}
|
|
5138
5181
|
if (statement.rowLock !== undefined) {
|
|
5182
|
+
const target = statement.table.alias ?? statement.table.name;
|
|
5183
|
+
if (statement.rowLock.of !== undefined && statement.rowLock.of.some((name) => name !== target)) {
|
|
5184
|
+
throw internalError4({ message: `a row lock's \`of\` list may name only the statement's own table "${target}"` });
|
|
5185
|
+
}
|
|
5186
|
+
if (statement.rowLock.of !== undefined && (statement.fromSelect !== undefined || statement.keyPage !== undefined)) {
|
|
5187
|
+
throw internalError4({ message: "a row lock's `of` list cannot name a derived table" });
|
|
5188
|
+
}
|
|
5139
5189
|
const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
|
|
5140
5190
|
if (clause !== null)
|
|
5141
5191
|
text += ` ${clause}`;
|
|
@@ -5451,8 +5501,18 @@ function renderChain(params) {
|
|
|
5451
5501
|
defined.add(step.name);
|
|
5452
5502
|
}
|
|
5453
5503
|
const result = statement.result;
|
|
5504
|
+
const captures = result.capture ?? [];
|
|
5505
|
+
if (captures.length > 0 && !dialect.capabilities.rowCapture) {
|
|
5506
|
+
throw unsupportedCapability28({ dialect: dialect.name, feature: "row capture (RETURNING old./new.)", hint: "row capture needs PostgreSQL 18 or later; the runtime refuses $transaction({ capture }) on this dialect" });
|
|
5507
|
+
}
|
|
5508
|
+
for (const alias of captureAliases({ captures })) {
|
|
5509
|
+
if (result.columns.includes(alias) || alias === result.verdict?.alias || alias === result.tally?.alias) {
|
|
5510
|
+
throw internalError4({ message: `chain capture alias "${alias}" collides with a result column` });
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5454
5513
|
if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
|
|
5455
|
-
|
|
5514
|
+
const tally = renderTallyColumn({ dialect, result, defined, collector });
|
|
5515
|
+
return { text: `WITH ${steps.join(", ")} SELECT ${[tally, ...renderChainCaptures({ dialect, captures, defined, collector })].join(", ")}`, values: collector.values, returning: "none" };
|
|
5456
5516
|
}
|
|
5457
5517
|
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
5458
5518
|
throw internalError4({ message: "a chain result needs at least one source step and one column" });
|
|
@@ -5462,9 +5522,11 @@ function renderChain(params) {
|
|
|
5462
5522
|
throw internalError4({ message: `chain result reads undefined step "${name}"` });
|
|
5463
5523
|
}
|
|
5464
5524
|
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
5465
|
-
const
|
|
5525
|
+
const guarded = result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0;
|
|
5526
|
+
const armCapture = guarded || captures.length === 0 ? "" : `, ${renderChainCaptures({ dialect, captures, defined, collector }).join(", ")}`;
|
|
5527
|
+
const union = result.rowsFrom.map((name) => `SELECT ${columnList}${armCapture} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
|
|
5466
5528
|
let select = union;
|
|
5467
|
-
if (
|
|
5529
|
+
if (guarded) {
|
|
5468
5530
|
const extra = [];
|
|
5469
5531
|
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
5470
5532
|
if (alias !== undefined && result.columns.includes(alias)) {
|
|
@@ -5479,6 +5541,7 @@ function renderChain(params) {
|
|
|
5479
5541
|
if (result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
5480
5542
|
extra.push(renderTallyColumn({ dialect, result, defined, collector }));
|
|
5481
5543
|
}
|
|
5544
|
+
extra.push(...renderChainCaptures({ dialect, captures, defined, collector }));
|
|
5482
5545
|
select = `SELECT ${[...projected, ...extra].join(", ")} FROM (SELECT 1) AS ${dialect.quoteIdent({ name: CHAIN_ONE_ALIAS })}` + ` LEFT JOIN (${union}) AS ${rows} ON TRUE`;
|
|
5483
5546
|
}
|
|
5484
5547
|
return { text: `WITH ${steps.join(", ")} ${select}`, values: collector.values, returning: "none" };
|
|
@@ -5498,6 +5561,47 @@ function renderChainStep(params) {
|
|
|
5498
5561
|
return renderDelete({ dialect, statement, collector }).text;
|
|
5499
5562
|
}
|
|
5500
5563
|
}
|
|
5564
|
+
function captureAliases(params) {
|
|
5565
|
+
const out = [];
|
|
5566
|
+
for (const entry of params.captures) {
|
|
5567
|
+
out.push(`${entry.alias}_n`);
|
|
5568
|
+
if (entry.inserted !== undefined)
|
|
5569
|
+
out.push(`${entry.alias}_i`);
|
|
5570
|
+
if (entry.before !== undefined && entry.before.length > 0)
|
|
5571
|
+
out.push(`${entry.alias}_b`);
|
|
5572
|
+
if (entry.after !== undefined && entry.after.length > 0)
|
|
5573
|
+
out.push(`${entry.alias}_a`);
|
|
5574
|
+
}
|
|
5575
|
+
return out;
|
|
5576
|
+
}
|
|
5577
|
+
function renderChainCaptures(params) {
|
|
5578
|
+
const { dialect, captures, defined, collector } = params;
|
|
5579
|
+
const quote = (name) => dialect.quoteIdent({ name });
|
|
5580
|
+
const out = [];
|
|
5581
|
+
for (const entry of captures) {
|
|
5582
|
+
if (!defined.has(entry.step))
|
|
5583
|
+
throw internalError4({ message: `chain capture reads undefined step "${entry.step}"` });
|
|
5584
|
+
const step = quote(entry.step);
|
|
5585
|
+
const marker = entry.inserted === undefined ? undefined : quote(entry.inserted);
|
|
5586
|
+
out.push(`(SELECT count(*) FROM ${step}) AS ${quote(`${entry.alias}_n`)}`);
|
|
5587
|
+
if (marker !== undefined)
|
|
5588
|
+
out.push(`(SELECT count(*) FROM ${step} WHERE ${marker}) AS ${quote(`${entry.alias}_i`)}`);
|
|
5589
|
+
const before = entry.before ?? [];
|
|
5590
|
+
const after = entry.after ?? [];
|
|
5591
|
+
if (before.length === 0 && after.length === 0)
|
|
5592
|
+
continue;
|
|
5593
|
+
if (!Number.isSafeInteger(entry.limit) || entry.limit < 1)
|
|
5594
|
+
throw internalError4({ message: `chain capture "${entry.alias}" needs a positive integer limit` });
|
|
5595
|
+
const limit = collector.add(entry.limit);
|
|
5596
|
+
const item = quote("__vibe_i");
|
|
5597
|
+
const image = (columns, filter) => `(SELECT array_agg(${item})::text FROM (SELECT DISTINCT ARRAY[${columns.map(quote).join(", ")}] AS ${item} FROM ${step}${filter} LIMIT ${limit}) AS ${quote("__vibe_d")})`;
|
|
5598
|
+
if (before.length > 0)
|
|
5599
|
+
out.push(`${image(before, marker === undefined ? "" : ` WHERE NOT ${marker}`)} AS ${quote(`${entry.alias}_b`)}`);
|
|
5600
|
+
if (after.length > 0)
|
|
5601
|
+
out.push(`${image(after, marker !== undefined && entry.insertedOnly === true ? ` WHERE ${marker}` : "")} AS ${quote(`${entry.alias}_a`)}`);
|
|
5602
|
+
}
|
|
5603
|
+
return out;
|
|
5604
|
+
}
|
|
5501
5605
|
function renderSelectCell(params) {
|
|
5502
5606
|
const { dialect, value, collector } = params;
|
|
5503
5607
|
if (isJsonParamCell(value))
|
|
@@ -6298,6 +6402,7 @@ export {
|
|
|
6298
6402
|
extractSqliteGeneratedColumn,
|
|
6299
6403
|
float32Codec,
|
|
6300
6404
|
getSqlDialect,
|
|
6405
|
+
isCaptureReturningColumn,
|
|
6301
6406
|
isDbNowMarker,
|
|
6302
6407
|
isExcludedRef,
|
|
6303
6408
|
isFixedDiagnostic,
|
|
@@ -6340,6 +6445,7 @@ export {
|
|
|
6340
6445
|
registerSqlHandleCheck,
|
|
6341
6446
|
renderAdvisoryLock,
|
|
6342
6447
|
renderAdvisoryLocks,
|
|
6448
|
+
renderCaptureReadiness,
|
|
6343
6449
|
renderCheckProbe,
|
|
6344
6450
|
renderColumnStateQuery,
|
|
6345
6451
|
renderConstraintOwnedIndexDrop,
|
|
@@ -6445,4 +6551,4 @@ export {
|
|
|
6445
6551
|
toPgArrayLiteral
|
|
6446
6552
|
};
|
|
6447
6553
|
|
|
6448
|
-
//# debugId=
|
|
6554
|
+
//# debugId=98E9DEF83EE89CED64756E2164756E21
|