@vibeorm/sql 5.4.0 → 5.6.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 +79 -0
- package/dist/dialects/diagnostics.d.ts +25 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +135 -13
- 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;
|
|
@@ -1084,6 +1103,14 @@ export type OnConflictClause = OnConflictUpdate | OnConflictDoNothing;
|
|
|
1084
1103
|
export type UpdateStatement = {
|
|
1085
1104
|
readonly kind: "update";
|
|
1086
1105
|
readonly table: TableRef;
|
|
1106
|
+
/** The target's alias; required with {@link UpdateStatement.from}, where two relations are in scope. */
|
|
1107
|
+
readonly alias?: string;
|
|
1108
|
+
/**
|
|
1109
|
+
* A joined source: `UPDATE … FROM` on PostgreSQL/SQLite (its `on` AND-ed
|
|
1110
|
+
* before `where`), `UPDATE … JOIN … ON` on MySQL. A
|
|
1111
|
+
* {@link SourceColumnRef} set value reads its columns.
|
|
1112
|
+
*/
|
|
1113
|
+
readonly from?: UpdateSource;
|
|
1087
1114
|
/**
|
|
1088
1115
|
* Column → parameter value, or an atomic {@link SetOperation}. As a
|
|
1089
1116
|
* {@link ChainStep} only (statement-maxx lane C3), a value may be a
|
|
@@ -1208,6 +1235,23 @@ export declare function stepColumnRef(params: {
|
|
|
1208
1235
|
step: string;
|
|
1209
1236
|
column: string;
|
|
1210
1237
|
}): StepColumnRef;
|
|
1238
|
+
/** The joined relation of an {@link UpdateStatement}: its table, alias and join condition. */
|
|
1239
|
+
export type UpdateSource = {
|
|
1240
|
+
readonly table: TableRef;
|
|
1241
|
+
readonly alias: string;
|
|
1242
|
+
readonly on: SqlExpr;
|
|
1243
|
+
};
|
|
1244
|
+
/** An UPDATE set value read from the joined source's `column` (`UPDATE … FROM`). */
|
|
1245
|
+
export type SourceColumnRef = {
|
|
1246
|
+
readonly __sourceColumn: true;
|
|
1247
|
+
readonly column: string;
|
|
1248
|
+
};
|
|
1249
|
+
/** Read `column` of an {@link UpdateStatement}'s joined source as a set value. */
|
|
1250
|
+
export declare function sourceColumnRef(params: {
|
|
1251
|
+
column: string;
|
|
1252
|
+
}): SourceColumnRef;
|
|
1253
|
+
/** Is this UPDATE set value a {@link SourceColumnRef}? */
|
|
1254
|
+
export declare function isSourceColumnRef(value: unknown): value is SourceColumnRef;
|
|
1211
1255
|
/** Is this INSERT … SELECT cell a {@link StepColumnRef}? */
|
|
1212
1256
|
export declare function isStepColumnRef(value: unknown): value is StepColumnRef;
|
|
1213
1257
|
/**
|
|
@@ -1330,6 +1374,41 @@ export type ChainResult = {
|
|
|
1330
1374
|
* it instead of counting result rows (effect `writeTally`).
|
|
1331
1375
|
*/
|
|
1332
1376
|
readonly tally?: ChainTally;
|
|
1377
|
+
/**
|
|
1378
|
+
* Row capture ({@link ChainCapture}): per captured data-modifying step, its
|
|
1379
|
+
* row count and its distinct before and after images, on every result row
|
|
1380
|
+
* (uncorrelated scalar subqueries, evaluated once). Needs the dialect's
|
|
1381
|
+
* `rowCapture` capability. Rendered after every other result column, so
|
|
1382
|
+
* their bound limits come last.
|
|
1383
|
+
*/
|
|
1384
|
+
readonly capture?: readonly ChainCapture[];
|
|
1385
|
+
};
|
|
1386
|
+
/**
|
|
1387
|
+
* One captured step of a chain ({@link ChainResult.capture}). Result columns,
|
|
1388
|
+
* `<alias>` being this entry's alias:
|
|
1389
|
+
*
|
|
1390
|
+
* - `<alias>_n`: `(SELECT count(*) FROM "step")`;
|
|
1391
|
+
* - `<alias>_i` (with `inserted`): the rows whose arm marker is true;
|
|
1392
|
+
* - `<alias>_b` / `<alias>_a` (with `before` / `after`): `(SELECT
|
|
1393
|
+
* array_agg("__vibe_i")::text FROM (SELECT DISTINCT ARRAY["c0", "c1"] AS
|
|
1394
|
+
* "__vibe_i" FROM "step" LIMIT $n) AS "__vibe_d")` — a two-dimensional text
|
|
1395
|
+
* array literal of at most `limit` distinct images, NULL when the step
|
|
1396
|
+
* returned no row. A before image skips the rows whose arm marker is true.
|
|
1397
|
+
*
|
|
1398
|
+
* `before` / `after` name columns the step RETURNS (its
|
|
1399
|
+
* {@link CaptureReturningColumn} aliases), in image order. `limit` is bound once.
|
|
1400
|
+
*/
|
|
1401
|
+
export type ChainCapture = {
|
|
1402
|
+
readonly step: string;
|
|
1403
|
+
readonly alias: string;
|
|
1404
|
+
/** The step's `"inserted"` arm-marker column, for an `INSERT … ON CONFLICT DO UPDATE` step. */
|
|
1405
|
+
readonly inserted?: string;
|
|
1406
|
+
/** With `inserted`: images of inserted rows only — `after` reads the rows whose marker is true. */
|
|
1407
|
+
readonly insertedOnly?: true;
|
|
1408
|
+
readonly before?: readonly string[];
|
|
1409
|
+
readonly after?: readonly string[];
|
|
1410
|
+
/** The most distinct images one image column returns: a positive safe integer, bound as a parameter. */
|
|
1411
|
+
readonly limit: number;
|
|
1333
1412
|
};
|
|
1334
1413
|
/**
|
|
1335
1414
|
* 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, SourceColumnRef, UpdateSource, 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, isSourceColumnRef, sourceColumnRef, 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,12 +2894,24 @@ 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) {
|
|
2901
2904
|
return { __stepColumn: true, step: params.step, column: params.column };
|
|
2902
2905
|
}
|
|
2906
|
+
function sourceColumnRef(params) {
|
|
2907
|
+
return Object.freeze({ __sourceColumn: true, column: params.column });
|
|
2908
|
+
}
|
|
2909
|
+
function isSourceColumnRef(value) {
|
|
2910
|
+
if (typeof value !== "object" || value === null)
|
|
2911
|
+
return false;
|
|
2912
|
+
const candidate = value;
|
|
2913
|
+
return candidate.__sourceColumn === true && typeof candidate.column === "string" && Object.keys(value).length === 2;
|
|
2914
|
+
}
|
|
2903
2915
|
function isStepColumnRef(value) {
|
|
2904
2916
|
return typeof value === "object" && value !== null && value.__stepColumn === true;
|
|
2905
2917
|
}
|
|
@@ -3652,7 +3664,7 @@ function renderSqliteJsonEquality(params) {
|
|
|
3652
3664
|
}
|
|
3653
3665
|
|
|
3654
3666
|
// packages/sql/src/dialects/diagnostics.ts
|
|
3655
|
-
import { unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3667
|
+
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3656
3668
|
var QUERIES = Object.freeze({
|
|
3657
3669
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
3658
3670
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -3665,7 +3677,23 @@ function renderDiagnostic(params) {
|
|
|
3665
3677
|
return { text: query.text, values: [], returning: "none" };
|
|
3666
3678
|
}
|
|
3667
3679
|
function isFixedDiagnostic(params) {
|
|
3668
|
-
return
|
|
3680
|
+
return Object.hasOwn(QUERIES, params.query);
|
|
3681
|
+
}
|
|
3682
|
+
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"`;
|
|
3683
|
+
function renderCaptureReadiness(params) {
|
|
3684
|
+
const { dialect, tables } = params;
|
|
3685
|
+
if (dialect.name !== "postgres") {
|
|
3686
|
+
throw unsupportedCapability24({ dialect: dialect.name, feature: "postgresCaptureReadiness", hint: "row capture reads RETURNING old/new, PostgreSQL 18 or newer" });
|
|
3687
|
+
}
|
|
3688
|
+
const entries = tables.map((entry, index) => {
|
|
3689
|
+
const { name, namespace } = entry.table;
|
|
3690
|
+
if (namespace !== undefined)
|
|
3691
|
+
assertSqlNamespace2({ namespace, dialect: dialect.name });
|
|
3692
|
+
const relation = dialect.quoteIdent({ name });
|
|
3693
|
+
return { entry: index, table: namespace === undefined ? relation : `${dialect.quoteIdent({ name: namespace })}.${relation}`, columns: [...entry.columns] };
|
|
3694
|
+
});
|
|
3695
|
+
const collector = new ParamCollector(dialect);
|
|
3696
|
+
return { text: CAPTURE_READINESS_SQL.replace("%TABLES%", collector.add(JSON.stringify(entries))), values: collector.values, returning: "none" };
|
|
3669
3697
|
}
|
|
3670
3698
|
|
|
3671
3699
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
@@ -4090,7 +4118,7 @@ function renderRlsPolicyPath(params) {
|
|
|
4090
4118
|
}
|
|
4091
4119
|
|
|
4092
4120
|
// packages/sql/src/render.ts
|
|
4093
|
-
import { assertSqlNamespace as
|
|
4121
|
+
import { assertSqlNamespace as assertSqlNamespace3, internalError as internalError4, unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
|
|
4094
4122
|
|
|
4095
4123
|
// packages/sql/src/like.ts
|
|
4096
4124
|
function escapeLikePattern(params) {
|
|
@@ -4128,6 +4156,8 @@ function renderStatement(params) {
|
|
|
4128
4156
|
case "diagnostic": {
|
|
4129
4157
|
if (isFixedDiagnostic(statement))
|
|
4130
4158
|
return renderDiagnostic({ dialect, query: statement.query });
|
|
4159
|
+
if (statement.query === "postgresCaptureReadiness")
|
|
4160
|
+
return renderCaptureReadiness({ dialect, tables: statement.tables });
|
|
4131
4161
|
if (statement.query === "postgresExplain") {
|
|
4132
4162
|
return renderPostgresExplain({
|
|
4133
4163
|
dialect,
|
|
@@ -4246,7 +4276,7 @@ function renderProjection(params) {
|
|
|
4246
4276
|
}
|
|
4247
4277
|
function renderTable(dialect, table) {
|
|
4248
4278
|
if (table.namespace !== undefined)
|
|
4249
|
-
|
|
4279
|
+
assertSqlNamespace3({ namespace: table.namespace, dialect: dialect.name });
|
|
4250
4280
|
const relation = dialect.quoteIdent({ name: table.name });
|
|
4251
4281
|
const name = table.namespace === undefined ? relation : `${dialect.quoteIdent({ name: table.namespace })}.${relation}`;
|
|
4252
4282
|
if (table.alias === undefined)
|
|
@@ -4934,20 +4964,37 @@ function applyReturning(params) {
|
|
|
4934
4964
|
return { suffix: "", status: "none" };
|
|
4935
4965
|
}
|
|
4936
4966
|
if (!dialect.capabilities.returning) {
|
|
4967
|
+
const capture = requested.find(isCaptureReturningColumn);
|
|
4968
|
+
if (capture !== undefined)
|
|
4969
|
+
renderCaptureReturning({ dialect, column: capture });
|
|
4937
4970
|
return { suffix: "", status: "omitted" };
|
|
4938
4971
|
}
|
|
4972
|
+
const read = (name) => params.qualify === undefined ? name : `${params.qualify}.${name}`;
|
|
4939
4973
|
const columns = requested.map((column) => {
|
|
4940
4974
|
if (typeof column === "string")
|
|
4941
|
-
return dialect.quoteIdent({ name: column });
|
|
4975
|
+
return read(dialect.quoteIdent({ name: column }));
|
|
4976
|
+
if ("capture" in column)
|
|
4977
|
+
return renderCaptureReturning({ dialect, column });
|
|
4942
4978
|
if ("transform" in column) {
|
|
4943
4979
|
const name = dialect.quoteIdent({ name: column.name });
|
|
4944
|
-
return `${dialect.jsonTextProjection({ expression: name })} AS ${name}`;
|
|
4980
|
+
return `${dialect.jsonTextProjection({ expression: read(name) })} AS ${name}`;
|
|
4945
4981
|
}
|
|
4946
4982
|
const name = dialect.quoteIdent({ name: column.name });
|
|
4947
|
-
return `${textProjection({ dialect, expression: name })} AS ${name}`;
|
|
4983
|
+
return `${textProjection({ dialect, expression: read(name) })} AS ${name}`;
|
|
4948
4984
|
}).join(", ");
|
|
4949
4985
|
return { suffix: ` RETURNING ${columns}`, status: "native" };
|
|
4950
4986
|
}
|
|
4987
|
+
function renderCaptureReturning(params) {
|
|
4988
|
+
const { dialect, column } = params;
|
|
4989
|
+
if (!dialect.capabilities.rowCapture) {
|
|
4990
|
+
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" });
|
|
4991
|
+
}
|
|
4992
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
4993
|
+
const alias = dialect.quoteIdent({ name: column.alias });
|
|
4994
|
+
if (column.capture === "inserted")
|
|
4995
|
+
return `(old.${name} IS NULL) AS ${alias}`;
|
|
4996
|
+
return `${textProjection({ dialect, expression: `${column.capture}.${name}` })} AS ${alias}`;
|
|
4997
|
+
}
|
|
4951
4998
|
function renderKeyTableJoin(params) {
|
|
4952
4999
|
const { dialect, join, collector } = params;
|
|
4953
5000
|
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
@@ -5367,6 +5414,18 @@ function renderUpdate(params) {
|
|
|
5367
5414
|
if (assignments.length === 0) {
|
|
5368
5415
|
throw internalError4({ message: "update must set at least one column" });
|
|
5369
5416
|
}
|
|
5417
|
+
const source = statement.from;
|
|
5418
|
+
if (source !== undefined) {
|
|
5419
|
+
if (statement.alias === undefined || statement.alias === source.alias)
|
|
5420
|
+
throw internalError4({ message: "UPDATE \u2026 FROM needs distinct target and source aliases" });
|
|
5421
|
+
const target = dialect.quoteIdent({ name: statement.alias });
|
|
5422
|
+
const joined = dialect.quoteIdent({ name: source.alias });
|
|
5423
|
+
const assign = () => renderAssignments({ dialect, entries: assignments, collector, selfRef: target, source: joined, ...dialect.name === "mysql" ? { qualifyWrite: target } : {}, ...params.earlier === undefined ? {} : { steps: params.earlier } });
|
|
5424
|
+
const where = () => statement.where === undefined ? "" : ` AND ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
5425
|
+
const text = dialect.name === "mysql" ? `UPDATE ${renderTable(dialect, statement.table)} AS ${target} JOIN ${renderTable(dialect, source.table)} AS ${joined} ON ${renderExpr({ dialect, expr: source.on, collector })} SET ${assign()}${statement.where === undefined ? "" : ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`}` : `UPDATE ${renderTable(dialect, statement.table)} AS ${target} SET ${assign()} FROM ${renderTable(dialect, source.table)} AS ${joined} WHERE ${renderExpr({ dialect, expr: source.on, collector })}${where()}`;
|
|
5426
|
+
const returning = applyReturning({ dialect, requested: statement.returning, ...dialect.name === "postgres" ? { qualify: target } : {} });
|
|
5427
|
+
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5428
|
+
}
|
|
5370
5429
|
const setSql = renderAssignments({ dialect, entries: assignments, collector, ...params.earlier === undefined ? {} : { steps: params.earlier } });
|
|
5371
5430
|
let text = `UPDATE ${renderTable(dialect, statement.table)} SET ${setSql}`;
|
|
5372
5431
|
if (statement.where !== undefined) {
|
|
@@ -5386,6 +5445,11 @@ function renderAssignments(params) {
|
|
|
5386
5445
|
return entries.map(([name, value]) => {
|
|
5387
5446
|
const column = dialect.quoteIdent({ name });
|
|
5388
5447
|
const read = selfRef === undefined ? column : `${selfRef}.${column}`;
|
|
5448
|
+
if (isSourceColumnRef(value)) {
|
|
5449
|
+
if (params.source === undefined)
|
|
5450
|
+
throw internalError4({ message: `an UPDATE set value reads the joined source's column ${value.column}, but the statement joins no source` });
|
|
5451
|
+
return `${column} = ${params.source}.${dialect.quoteIdent({ name: value.column })}`;
|
|
5452
|
+
}
|
|
5389
5453
|
if (isStepColumnRef(value)) {
|
|
5390
5454
|
if (params.steps === undefined || !params.steps.has(value.step)) {
|
|
5391
5455
|
throw internalError4({ message: `an UPDATE set value reads step "${value.step}", which is not an earlier chain step` });
|
|
@@ -5429,7 +5493,7 @@ function renderAssignments(params) {
|
|
|
5429
5493
|
return `${column} = ${read} || ${collector.add(value.value)}`;
|
|
5430
5494
|
}
|
|
5431
5495
|
return `${column} = ${collector.add(value)}`;
|
|
5432
|
-
}).join(", ");
|
|
5496
|
+
}).map((assignment) => params.qualifyWrite === undefined ? assignment : `${params.qualifyWrite}.${assignment}`).join(", ");
|
|
5433
5497
|
}
|
|
5434
5498
|
function renderDelete(params) {
|
|
5435
5499
|
const { dialect, statement, collector } = params;
|
|
@@ -5464,8 +5528,18 @@ function renderChain(params) {
|
|
|
5464
5528
|
defined.add(step.name);
|
|
5465
5529
|
}
|
|
5466
5530
|
const result = statement.result;
|
|
5531
|
+
const captures = result.capture ?? [];
|
|
5532
|
+
if (captures.length > 0 && !dialect.capabilities.rowCapture) {
|
|
5533
|
+
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" });
|
|
5534
|
+
}
|
|
5535
|
+
for (const alias of captureAliases({ captures })) {
|
|
5536
|
+
if (result.columns.includes(alias) || alias === result.verdict?.alias || alias === result.tally?.alias) {
|
|
5537
|
+
throw internalError4({ message: `chain capture alias "${alias}" collides with a result column` });
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5467
5540
|
if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
|
|
5468
|
-
|
|
5541
|
+
const tally = renderTallyColumn({ dialect, result, defined, collector });
|
|
5542
|
+
return { text: `WITH ${steps.join(", ")} SELECT ${[tally, ...renderChainCaptures({ dialect, captures, defined, collector })].join(", ")}`, values: collector.values, returning: "none" };
|
|
5469
5543
|
}
|
|
5470
5544
|
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
5471
5545
|
throw internalError4({ message: "a chain result needs at least one source step and one column" });
|
|
@@ -5475,9 +5549,11 @@ function renderChain(params) {
|
|
|
5475
5549
|
throw internalError4({ message: `chain result reads undefined step "${name}"` });
|
|
5476
5550
|
}
|
|
5477
5551
|
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
5478
|
-
const
|
|
5552
|
+
const guarded = result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0;
|
|
5553
|
+
const armCapture = guarded || captures.length === 0 ? "" : `, ${renderChainCaptures({ dialect, captures, defined, collector }).join(", ")}`;
|
|
5554
|
+
const union = result.rowsFrom.map((name) => `SELECT ${columnList}${armCapture} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
|
|
5479
5555
|
let select = union;
|
|
5480
|
-
if (
|
|
5556
|
+
if (guarded) {
|
|
5481
5557
|
const extra = [];
|
|
5482
5558
|
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
5483
5559
|
if (alias !== undefined && result.columns.includes(alias)) {
|
|
@@ -5492,6 +5568,7 @@ function renderChain(params) {
|
|
|
5492
5568
|
if (result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
5493
5569
|
extra.push(renderTallyColumn({ dialect, result, defined, collector }));
|
|
5494
5570
|
}
|
|
5571
|
+
extra.push(...renderChainCaptures({ dialect, captures, defined, collector }));
|
|
5495
5572
|
select = `SELECT ${[...projected, ...extra].join(", ")} FROM (SELECT 1) AS ${dialect.quoteIdent({ name: CHAIN_ONE_ALIAS })}` + ` LEFT JOIN (${union}) AS ${rows} ON TRUE`;
|
|
5496
5573
|
}
|
|
5497
5574
|
return { text: `WITH ${steps.join(", ")} ${select}`, values: collector.values, returning: "none" };
|
|
@@ -5511,6 +5588,47 @@ function renderChainStep(params) {
|
|
|
5511
5588
|
return renderDelete({ dialect, statement, collector }).text;
|
|
5512
5589
|
}
|
|
5513
5590
|
}
|
|
5591
|
+
function captureAliases(params) {
|
|
5592
|
+
const out = [];
|
|
5593
|
+
for (const entry of params.captures) {
|
|
5594
|
+
out.push(`${entry.alias}_n`);
|
|
5595
|
+
if (entry.inserted !== undefined)
|
|
5596
|
+
out.push(`${entry.alias}_i`);
|
|
5597
|
+
if (entry.before !== undefined && entry.before.length > 0)
|
|
5598
|
+
out.push(`${entry.alias}_b`);
|
|
5599
|
+
if (entry.after !== undefined && entry.after.length > 0)
|
|
5600
|
+
out.push(`${entry.alias}_a`);
|
|
5601
|
+
}
|
|
5602
|
+
return out;
|
|
5603
|
+
}
|
|
5604
|
+
function renderChainCaptures(params) {
|
|
5605
|
+
const { dialect, captures, defined, collector } = params;
|
|
5606
|
+
const quote = (name) => dialect.quoteIdent({ name });
|
|
5607
|
+
const out = [];
|
|
5608
|
+
for (const entry of captures) {
|
|
5609
|
+
if (!defined.has(entry.step))
|
|
5610
|
+
throw internalError4({ message: `chain capture reads undefined step "${entry.step}"` });
|
|
5611
|
+
const step = quote(entry.step);
|
|
5612
|
+
const marker = entry.inserted === undefined ? undefined : quote(entry.inserted);
|
|
5613
|
+
out.push(`(SELECT count(*) FROM ${step}) AS ${quote(`${entry.alias}_n`)}`);
|
|
5614
|
+
if (marker !== undefined)
|
|
5615
|
+
out.push(`(SELECT count(*) FROM ${step} WHERE ${marker}) AS ${quote(`${entry.alias}_i`)}`);
|
|
5616
|
+
const before = entry.before ?? [];
|
|
5617
|
+
const after = entry.after ?? [];
|
|
5618
|
+
if (before.length === 0 && after.length === 0)
|
|
5619
|
+
continue;
|
|
5620
|
+
if (!Number.isSafeInteger(entry.limit) || entry.limit < 1)
|
|
5621
|
+
throw internalError4({ message: `chain capture "${entry.alias}" needs a positive integer limit` });
|
|
5622
|
+
const limit = collector.add(entry.limit);
|
|
5623
|
+
const item = quote("__vibe_i");
|
|
5624
|
+
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")})`;
|
|
5625
|
+
if (before.length > 0)
|
|
5626
|
+
out.push(`${image(before, marker === undefined ? "" : ` WHERE NOT ${marker}`)} AS ${quote(`${entry.alias}_b`)}`);
|
|
5627
|
+
if (after.length > 0)
|
|
5628
|
+
out.push(`${image(after, marker !== undefined && entry.insertedOnly === true ? ` WHERE ${marker}` : "")} AS ${quote(`${entry.alias}_a`)}`);
|
|
5629
|
+
}
|
|
5630
|
+
return out;
|
|
5631
|
+
}
|
|
5514
5632
|
function renderSelectCell(params) {
|
|
5515
5633
|
const { dialect, value, collector } = params;
|
|
5516
5634
|
if (isJsonParamCell(value))
|
|
@@ -6311,6 +6429,7 @@ export {
|
|
|
6311
6429
|
extractSqliteGeneratedColumn,
|
|
6312
6430
|
float32Codec,
|
|
6313
6431
|
getSqlDialect,
|
|
6432
|
+
isCaptureReturningColumn,
|
|
6314
6433
|
isDbNowMarker,
|
|
6315
6434
|
isExcludedRef,
|
|
6316
6435
|
isFixedDiagnostic,
|
|
@@ -6321,6 +6440,7 @@ export {
|
|
|
6321
6440
|
isReusableSqlCommand,
|
|
6322
6441
|
isReusableSqlQuery,
|
|
6323
6442
|
isSetOperation,
|
|
6443
|
+
isSourceColumnRef,
|
|
6324
6444
|
isSqlBindingCollector,
|
|
6325
6445
|
isSqlCommand,
|
|
6326
6446
|
isSqlDefaultCell,
|
|
@@ -6353,6 +6473,7 @@ export {
|
|
|
6353
6473
|
registerSqlHandleCheck,
|
|
6354
6474
|
renderAdvisoryLock,
|
|
6355
6475
|
renderAdvisoryLocks,
|
|
6476
|
+
renderCaptureReadiness,
|
|
6356
6477
|
renderCheckProbe,
|
|
6357
6478
|
renderColumnStateQuery,
|
|
6358
6479
|
renderConstraintOwnedIndexDrop,
|
|
@@ -6439,6 +6560,7 @@ export {
|
|
|
6439
6560
|
sealSqlInputScope,
|
|
6440
6561
|
shortestFloat32,
|
|
6441
6562
|
snapshotSqlValue,
|
|
6563
|
+
sourceColumnRef,
|
|
6442
6564
|
sql,
|
|
6443
6565
|
sqlInputContract,
|
|
6444
6566
|
sqlParamContract,
|
|
@@ -6458,4 +6580,4 @@ export {
|
|
|
6458
6580
|
toPgArrayLiteral
|
|
6459
6581
|
};
|
|
6460
6582
|
|
|
6461
|
-
//# debugId=
|
|
6583
|
+
//# debugId=FBE116988ABE557464756E2164756E21
|