@vibeorm/sql 5.2.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ast.d.ts CHANGED
@@ -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
@@ -1172,6 +1183,12 @@ export type AdvisoryLockStatement = {
1172
1183
  readonly wait: "wait" | "nowait";
1173
1184
  readonly key: AdvisoryLockKey;
1174
1185
  };
1186
+ /** An ordered set of transaction-scoped bigint advisory locks. */
1187
+ export type AdvisoryLocksStatement = {
1188
+ readonly kind: "advisoryLocks";
1189
+ readonly keys: readonly bigint[];
1190
+ readonly mode: AdvisoryLockMode;
1191
+ };
1175
1192
  /** The column an advisory try-lock projects its verdict into. */
1176
1193
  export declare const ADVISORY_LOCK_RESULT_COLUMN: string;
1177
1194
  /**
@@ -1290,6 +1307,7 @@ export type ChainStep = {
1290
1307
  * verdict-row outcome (the upsert fold always rolls it back).
1291
1308
  */
1292
1309
  export type ChainResult = {
1310
+ /** Both lists may be empty for a tally-only result: one count row, no model values. */
1293
1311
  readonly rowsFrom: readonly string[];
1294
1312
  readonly columns: readonly string[];
1295
1313
  readonly verdict?: {
@@ -1322,12 +1340,15 @@ export type ChainResult = {
1322
1340
  * `minRows` (EPIC W) makes the veto count-sensitive: the statement fails when
1323
1341
  * `step` returned FEWER rows than that (a multi-row INSERT a trigger shortened).
1324
1342
  * Omitted or 1 = "no row". A positive safe integer, rendered as SQL text.
1343
+ * `exactRows` instead requires equality, including zero. It is a nonnegative
1344
+ * safe integer and cannot combine with `minRows`.
1325
1345
  */
1326
1346
  export type ChainVeto = {
1327
1347
  readonly step: string;
1328
1348
  readonly token: string;
1329
1349
  readonly nonce: string;
1330
1350
  readonly minRows?: number;
1351
+ readonly exactRows?: number;
1331
1352
  };
1332
1353
  /** Count the RETURNING rows of `steps` into the result column `alias`. */
1333
1354
  export type ChainTally = {
@@ -1367,7 +1388,7 @@ export type ChainStatement = {
1367
1388
  readonly steps: readonly ChainStep[];
1368
1389
  readonly result: ChainResult;
1369
1390
  };
1370
- export type SqlStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | RlsEnableStatement | RlsForceStatement | RlsDisableStatement | RlsNoForceStatement | RlsDropPolicyStatement | RlsPolicyStatement | RlsContextStatement | RlsRoleStatement | AdvisoryLockStatement | ChainStatement;
1391
+ export type SqlStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | RlsEnableStatement | RlsForceStatement | RlsDisableStatement | RlsNoForceStatement | RlsDropPolicyStatement | RlsPolicyStatement | RlsContextStatement | RlsRoleStatement | AdvisoryLockStatement | AdvisoryLocksStatement | ChainStatement;
1371
1392
  /** Readiness statements remain distinct from application queries. */
1372
1393
  export type RlsReadinessStatement = {
1373
1394
  readonly kind: "rlsReadiness";
package/dist/index.d.ts CHANGED
@@ -40,10 +40,10 @@ 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, 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";
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
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";
45
45
  export type { AdvisoryLockFunctions, AdvisoryLockModeFunctions, RowLockSyntax, EmptyInsertSyntax, SqlDialect, } from "./dialect.ts";
46
- export { ADVISORY_KEY_BOUNDS, describeRowLock, renderAdvisoryLock, renderRowLockClause } from "./locks.ts";
46
+ export { ADVISORY_KEY_BOUNDS, describeRowLock, renderAdvisoryLock, renderAdvisoryLocks, renderRowLockClause } from "./locks.ts";
47
47
  export { JSON_TRANSPORT, projectionOutputName } from "./json-transport.ts";
48
48
  export { postgresDialect } from "./dialects/postgres.ts";
49
49
  export { sqliteDialect } from "./dialects/sqlite.ts";
package/dist/index.js CHANGED
@@ -2960,6 +2960,12 @@ function renderRowLockClause(params) {
2960
2960
  hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
2961
2961
  });
2962
2962
  }
2963
+ if (lock.of !== undefined) {
2964
+ if (lock.of.length === 0)
2965
+ throw internalError({ message: "a row lock's `of` list cannot be empty" });
2966
+ const tables = lock.of.map((name) => dialect.quoteIdent({ name })).join(", ");
2967
+ return `${strengthText} OF ${tables}${waitText}`;
2968
+ }
2963
2969
  return `${strengthText}${waitText}`;
2964
2970
  }
2965
2971
  var ADVISORY_KEY_BOUNDS = Object.freeze({
@@ -2990,6 +2996,28 @@ function renderAdvisoryLock(params) {
2990
2996
  returning: "none"
2991
2997
  };
2992
2998
  }
2999
+ function renderAdvisoryLocks(params) {
3000
+ const { dialect, keys, mode } = params;
3001
+ const functions = dialect.advisoryLockFunctions;
3002
+ if (functions === null)
3003
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "advisory locks" });
3004
+ if (mode !== "exclusive" && mode !== "shared")
3005
+ throw internalError({ message: "advisory lock mode must be exclusive or shared" });
3006
+ if (keys.some((key) => typeof key !== "bigint" || key < ADVISORY_KEY_BOUNDS.single.min || key > ADVISORY_KEY_BOUNDS.single.max)) {
3007
+ throw internalError({ message: "advisory lock keys must be signed 64-bit integers" });
3008
+ }
3009
+ const collector = new ParamCollector(dialect);
3010
+ const binding = collector.add(JSON.stringify(keys.map((key) => key.toString())));
3011
+ const fn = (mode === "exclusive" ? functions.exclusive : functions.shared).wait;
3012
+ const table = dialect.quoteIdent({ name: "__vibe_locks" });
3013
+ const key = dialect.quoteIdent({ name: "key" });
3014
+ const order = dialect.quoteIdent({ name: "ordinal" });
3015
+ return {
3016
+ text: `SELECT ${fn}(${table}.${key}${functions.keyCast.single}) FROM jsonb_array_elements_text(${binding}::text::jsonb) WITH ORDINALITY AS ${table}(${key}, ${order}) ORDER BY ${table}.${order}`,
3017
+ values: collector.values,
3018
+ returning: "none"
3019
+ };
3020
+ }
2993
3021
  function assertInt32(params) {
2994
3022
  const { value, part } = params;
2995
3023
  if (!Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
@@ -4111,6 +4139,8 @@ function renderStatement(params) {
4111
4139
  }
4112
4140
  case "select":
4113
4141
  return renderSelect({ dialect, statement, collector });
4142
+ case "advisoryLocks":
4143
+ return renderAdvisoryLocks({ dialect, keys: statement.keys, mode: statement.mode });
4114
4144
  case "advisoryLock":
4115
4145
  return renderAdvisoryLock({ dialect, statement, collector });
4116
4146
  case "insert":
@@ -5112,6 +5142,13 @@ function renderSelect(params) {
5112
5142
  text += ` OFFSET ${statement.offset}`;
5113
5143
  }
5114
5144
  if (statement.rowLock !== undefined) {
5145
+ const target = statement.table.alias ?? statement.table.name;
5146
+ if (statement.rowLock.of !== undefined && statement.rowLock.of.some((name) => name !== target)) {
5147
+ throw internalError4({ message: `a row lock's \`of\` list may name only the statement's own table "${target}"` });
5148
+ }
5149
+ if (statement.rowLock.of !== undefined && (statement.fromSelect !== undefined || statement.keyPage !== undefined)) {
5150
+ throw internalError4({ message: "a row lock's `of` list cannot name a derived table" });
5151
+ }
5115
5152
  const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
5116
5153
  if (clause !== null)
5117
5154
  text += ` ${clause}`;
@@ -5427,6 +5464,9 @@ function renderChain(params) {
5427
5464
  defined.add(step.name);
5428
5465
  }
5429
5466
  const result = statement.result;
5467
+ if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
5468
+ return { text: `WITH ${steps.join(", ")} SELECT ${renderTallyColumn({ dialect, result, defined, collector })}`, values: collector.values, returning: "none" };
5469
+ }
5430
5470
  if (result.rowsFrom.length === 0 || result.columns.length === 0) {
5431
5471
  throw internalError4({ message: "a chain result needs at least one source step and one column" });
5432
5472
  }
@@ -5525,7 +5565,10 @@ function renderTallyColumn(params) {
5525
5565
  const minRows = veto.minRows ?? 1;
5526
5566
  if (!Number.isSafeInteger(minRows) || minRows < 1)
5527
5567
  throw internalError4({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
5528
- const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
5568
+ if (veto.exactRows !== undefined && (!Number.isSafeInteger(veto.exactRows) || veto.exactRows < 0 || veto.minRows !== undefined)) {
5569
+ throw internalError4({ message: `chain veto "${veto.token}" needs a nonnegative safe integer exactRows without minRows` });
5570
+ }
5571
+ const when = veto.exactRows !== undefined ? `WHEN ${count(veto.step)} <> ${veto.exactRows}` : minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
5529
5572
  return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
5530
5573
  });
5531
5574
  return `CASE ${arms.join(" ")} ELSE ${sum} END AS ${dialect.quoteIdent({ name: tally.alias })}`;
@@ -6309,6 +6352,7 @@ export {
6309
6352
  recordSqlBindingCheck,
6310
6353
  registerSqlHandleCheck,
6311
6354
  renderAdvisoryLock,
6355
+ renderAdvisoryLocks,
6312
6356
  renderCheckProbe,
6313
6357
  renderColumnStateQuery,
6314
6358
  renderConstraintOwnedIndexDrop,
@@ -6414,4 +6458,4 @@ export {
6414
6458
  toPgArrayLiteral
6415
6459
  };
6416
6460
 
6417
- //# debugId=7A09C0BE2E07A46664756E2164756E21
6461
+ //# debugId=11C29D1889ACA76B64756E2164756E21