@vibeorm/sql 5.4.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 +54 -0
- package/dist/dialects/diagnostics.d.ts +25 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +102 -9
- package/dist/index.js.map +5 -5
- package/package.json +2 -2
package/dist/ast.d.ts
CHANGED
|
@@ -971,9 +971,28 @@ export type ReturningColumn = string | {
|
|
|
971
971
|
} | {
|
|
972
972
|
readonly name: string;
|
|
973
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;
|
|
974
991
|
};
|
|
975
992
|
/** The column name a {@link ReturningColumn} exposes in the result row. */
|
|
976
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;
|
|
977
996
|
export type InsertStatement = {
|
|
978
997
|
readonly kind: "insert";
|
|
979
998
|
readonly table: TableRef;
|
|
@@ -1330,6 +1349,41 @@ export type ChainResult = {
|
|
|
1330
1349
|
* it instead of counting result rows (effect `writeTally`).
|
|
1331
1350
|
*/
|
|
1332
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;
|
|
1333
1387
|
};
|
|
1334
1388
|
/**
|
|
1335
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) {
|
|
@@ -3652,7 +3655,7 @@ function renderSqliteJsonEquality(params) {
|
|
|
3652
3655
|
}
|
|
3653
3656
|
|
|
3654
3657
|
// packages/sql/src/dialects/diagnostics.ts
|
|
3655
|
-
import { unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3658
|
+
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3656
3659
|
var QUERIES = Object.freeze({
|
|
3657
3660
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
3658
3661
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -3665,7 +3668,23 @@ function renderDiagnostic(params) {
|
|
|
3665
3668
|
return { text: query.text, values: [], returning: "none" };
|
|
3666
3669
|
}
|
|
3667
3670
|
function isFixedDiagnostic(params) {
|
|
3668
|
-
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" };
|
|
3669
3688
|
}
|
|
3670
3689
|
|
|
3671
3690
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
@@ -4090,7 +4109,7 @@ function renderRlsPolicyPath(params) {
|
|
|
4090
4109
|
}
|
|
4091
4110
|
|
|
4092
4111
|
// packages/sql/src/render.ts
|
|
4093
|
-
import { assertSqlNamespace as
|
|
4112
|
+
import { assertSqlNamespace as assertSqlNamespace3, internalError as internalError4, unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
|
|
4094
4113
|
|
|
4095
4114
|
// packages/sql/src/like.ts
|
|
4096
4115
|
function escapeLikePattern(params) {
|
|
@@ -4128,6 +4147,8 @@ function renderStatement(params) {
|
|
|
4128
4147
|
case "diagnostic": {
|
|
4129
4148
|
if (isFixedDiagnostic(statement))
|
|
4130
4149
|
return renderDiagnostic({ dialect, query: statement.query });
|
|
4150
|
+
if (statement.query === "postgresCaptureReadiness")
|
|
4151
|
+
return renderCaptureReadiness({ dialect, tables: statement.tables });
|
|
4131
4152
|
if (statement.query === "postgresExplain") {
|
|
4132
4153
|
return renderPostgresExplain({
|
|
4133
4154
|
dialect,
|
|
@@ -4246,7 +4267,7 @@ function renderProjection(params) {
|
|
|
4246
4267
|
}
|
|
4247
4268
|
function renderTable(dialect, table) {
|
|
4248
4269
|
if (table.namespace !== undefined)
|
|
4249
|
-
|
|
4270
|
+
assertSqlNamespace3({ namespace: table.namespace, dialect: dialect.name });
|
|
4250
4271
|
const relation = dialect.quoteIdent({ name: table.name });
|
|
4251
4272
|
const name = table.namespace === undefined ? relation : `${dialect.quoteIdent({ name: table.namespace })}.${relation}`;
|
|
4252
4273
|
if (table.alias === undefined)
|
|
@@ -4934,11 +4955,16 @@ function applyReturning(params) {
|
|
|
4934
4955
|
return { suffix: "", status: "none" };
|
|
4935
4956
|
}
|
|
4936
4957
|
if (!dialect.capabilities.returning) {
|
|
4958
|
+
const capture = requested.find(isCaptureReturningColumn);
|
|
4959
|
+
if (capture !== undefined)
|
|
4960
|
+
renderCaptureReturning({ dialect, column: capture });
|
|
4937
4961
|
return { suffix: "", status: "omitted" };
|
|
4938
4962
|
}
|
|
4939
4963
|
const columns = requested.map((column) => {
|
|
4940
4964
|
if (typeof column === "string")
|
|
4941
4965
|
return dialect.quoteIdent({ name: column });
|
|
4966
|
+
if ("capture" in column)
|
|
4967
|
+
return renderCaptureReturning({ dialect, column });
|
|
4942
4968
|
if ("transform" in column) {
|
|
4943
4969
|
const name = dialect.quoteIdent({ name: column.name });
|
|
4944
4970
|
return `${dialect.jsonTextProjection({ expression: name })} AS ${name}`;
|
|
@@ -4948,6 +4974,17 @@ function applyReturning(params) {
|
|
|
4948
4974
|
}).join(", ");
|
|
4949
4975
|
return { suffix: ` RETURNING ${columns}`, status: "native" };
|
|
4950
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
|
+
}
|
|
4951
4988
|
function renderKeyTableJoin(params) {
|
|
4952
4989
|
const { dialect, join, collector } = params;
|
|
4953
4990
|
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
@@ -5464,8 +5501,18 @@ function renderChain(params) {
|
|
|
5464
5501
|
defined.add(step.name);
|
|
5465
5502
|
}
|
|
5466
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
|
+
}
|
|
5467
5513
|
if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
|
|
5468
|
-
|
|
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" };
|
|
5469
5516
|
}
|
|
5470
5517
|
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
5471
5518
|
throw internalError4({ message: "a chain result needs at least one source step and one column" });
|
|
@@ -5475,9 +5522,11 @@ function renderChain(params) {
|
|
|
5475
5522
|
throw internalError4({ message: `chain result reads undefined step "${name}"` });
|
|
5476
5523
|
}
|
|
5477
5524
|
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
5478
|
-
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 ");
|
|
5479
5528
|
let select = union;
|
|
5480
|
-
if (
|
|
5529
|
+
if (guarded) {
|
|
5481
5530
|
const extra = [];
|
|
5482
5531
|
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
5483
5532
|
if (alias !== undefined && result.columns.includes(alias)) {
|
|
@@ -5492,6 +5541,7 @@ function renderChain(params) {
|
|
|
5492
5541
|
if (result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
5493
5542
|
extra.push(renderTallyColumn({ dialect, result, defined, collector }));
|
|
5494
5543
|
}
|
|
5544
|
+
extra.push(...renderChainCaptures({ dialect, captures, defined, collector }));
|
|
5495
5545
|
select = `SELECT ${[...projected, ...extra].join(", ")} FROM (SELECT 1) AS ${dialect.quoteIdent({ name: CHAIN_ONE_ALIAS })}` + ` LEFT JOIN (${union}) AS ${rows} ON TRUE`;
|
|
5496
5546
|
}
|
|
5497
5547
|
return { text: `WITH ${steps.join(", ")} ${select}`, values: collector.values, returning: "none" };
|
|
@@ -5511,6 +5561,47 @@ function renderChainStep(params) {
|
|
|
5511
5561
|
return renderDelete({ dialect, statement, collector }).text;
|
|
5512
5562
|
}
|
|
5513
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
|
+
}
|
|
5514
5605
|
function renderSelectCell(params) {
|
|
5515
5606
|
const { dialect, value, collector } = params;
|
|
5516
5607
|
if (isJsonParamCell(value))
|
|
@@ -6311,6 +6402,7 @@ export {
|
|
|
6311
6402
|
extractSqliteGeneratedColumn,
|
|
6312
6403
|
float32Codec,
|
|
6313
6404
|
getSqlDialect,
|
|
6405
|
+
isCaptureReturningColumn,
|
|
6314
6406
|
isDbNowMarker,
|
|
6315
6407
|
isExcludedRef,
|
|
6316
6408
|
isFixedDiagnostic,
|
|
@@ -6353,6 +6445,7 @@ export {
|
|
|
6353
6445
|
registerSqlHandleCheck,
|
|
6354
6446
|
renderAdvisoryLock,
|
|
6355
6447
|
renderAdvisoryLocks,
|
|
6448
|
+
renderCaptureReadiness,
|
|
6356
6449
|
renderCheckProbe,
|
|
6357
6450
|
renderColumnStateQuery,
|
|
6358
6451
|
renderConstraintOwnedIndexDrop,
|
|
@@ -6458,4 +6551,4 @@ export {
|
|
|
6458
6551
|
toPgArrayLiteral
|
|
6459
6552
|
};
|
|
6460
6553
|
|
|
6461
|
-
//# debugId=
|
|
6554
|
+
//# debugId=98E9DEF83EE89CED64756E2164756E21
|