@vibeorm/sql 5.1.0 → 5.3.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
@@ -45,6 +45,8 @@ export type ColRef = {
45
45
  * storage and read-back stay the exact text.
46
46
  */
47
47
  readonly castReal?: boolean;
48
+ /** Native enum order key through a scalar subquery: MySQL reads its member index, other dialects keep the column. */
49
+ readonly enumOrdinal?: true;
48
50
  /**
49
51
  * Order/distinct/compare cast, postgres `json` columns only (`Json @db.Json`,
50
52
  * release-3.0 D3): `json` has no ordering or equality operator, so ORDER BY,
@@ -93,6 +95,8 @@ export type AggRef = {
93
95
  } | {
94
96
  readonly fn: AggFn;
95
97
  readonly column: ColRef;
98
+ /** Native enum MIN/MAX: projections and HAVING return the database label; order keys keep its ordinal. */
99
+ readonly enumOrder?: "value" | "ordinal";
96
100
  };
97
101
  /**
98
102
  * One aggregate projection: `FN("col") AS "alias"`. The alias is part of the
@@ -463,18 +467,27 @@ export type OrderByAggregate = {
463
467
  };
464
468
  /**
465
469
  * ORDER BY a correlated scalar subquery — relation orderBy
466
- * (`orderBy: { posts: { _count: "desc" } }` → `(SELECT COUNT(*) …)` /
470
+ * (`orderBy: { posts: { _count: "desc" } }` → `(SELECT COUNT(*) …)`,
471
+ * `orderBy: { posts: { _max: { createdAt: "desc" } } }` → `(SELECT MAX(…) …)`,
467
472
  * `orderBy: { author: { email: "asc" } }` → `(SELECT "email" …)`).
468
473
  * Portable: all three dialects order by scalar subqueries. The subquery
469
474
  * correlates to the outer scope through table-qualified `cmpCol` predicates,
470
475
  * exactly like `exists`; its parameters merge into the statement's single
471
- * sequence in render order. NULLS placement is deliberately unsupported here
472
- * (emulating it would render the subquery twice and duplicate its params) —
473
- * missing relations sort at each engine's native NULL position.
476
+ * sequence in render order.
477
+ *
478
+ * `nulls` places the NULL a missing to-one row or a parent without related
479
+ * rows (under MIN/MAX/AVG/SUM) sorts as. Without it, NULL sorts at the
480
+ * engine's native position. Postgres and sqlite render `NULLS FIRST|LAST`;
481
+ * mysql renders the bare term when its native placement already matches, and
482
+ * otherwise a leading `(<subquery>) IS NULL` term — the subquery then renders
483
+ * twice and its parameters bind twice, in text order.
474
484
  */
475
485
  export type OrderBySubquery = {
476
486
  readonly select: SelectStatement;
487
+ /** A count behind a missing to-one row is zero, not NULL. */
488
+ readonly coalesce?: 0;
477
489
  readonly direction: "asc" | "desc";
490
+ readonly nulls?: "first" | "last";
478
491
  };
479
492
  export type OrderTerm = OrderByColumn | OrderByAggregate | OrderBySubquery;
480
493
  /**
@@ -519,6 +532,17 @@ export type LateralJoin = {
519
532
  /** The correlated child select — may reference outer columns via table-qualified refs. */
520
533
  readonly select: SelectStatement;
521
534
  };
535
+ /**
536
+ * One scalar subquery in a projection. The builder guarantees a single column
537
+ * and at most one row. Hidden sort values stay inside derived tables rather
538
+ * than becoming result fields. `coalesce` preserves a count across a missing
539
+ * to-one hop.
540
+ */
541
+ export type ScalarSubquery = {
542
+ readonly alias: string;
543
+ readonly select: SelectStatement;
544
+ readonly coalesce?: 0;
545
+ };
522
546
  /**
523
547
  * One correlated scalar `COUNT(*)` projected into a select list:
524
548
  * `(SELECT COUNT(*) FROM … WHERE <correlation>) AS "alias"` — the relation
@@ -765,6 +789,8 @@ export type SelectStatement = {
765
789
  readonly aggregates?: readonly AggSelect[];
766
790
  /** Correlated `COUNT(*)` subqueries, rendered after `aggregates` — see {@link CountSubquery}. */
767
791
  readonly countSubqueries?: readonly CountSubquery[];
792
+ /** Scalar values projected after count subqueries and before a row number. */
793
+ readonly scalarSubqueries?: readonly ScalarSubquery[];
768
794
  /**
769
795
  * `SELECT DISTINCT ON (cols)` — postgres only (`capabilities.distinctOn`;
770
796
  * other dialects refuse at render time and the runtime emulates client-side
@@ -1146,6 +1172,12 @@ export type AdvisoryLockStatement = {
1146
1172
  readonly wait: "wait" | "nowait";
1147
1173
  readonly key: AdvisoryLockKey;
1148
1174
  };
1175
+ /** An ordered set of transaction-scoped bigint advisory locks. */
1176
+ export type AdvisoryLocksStatement = {
1177
+ readonly kind: "advisoryLocks";
1178
+ readonly keys: readonly bigint[];
1179
+ readonly mode: AdvisoryLockMode;
1180
+ };
1149
1181
  /** The column an advisory try-lock projects its verdict into. */
1150
1182
  export declare const ADVISORY_LOCK_RESULT_COLUMN: string;
1151
1183
  /**
@@ -1264,6 +1296,7 @@ export type ChainStep = {
1264
1296
  * verdict-row outcome (the upsert fold always rolls it back).
1265
1297
  */
1266
1298
  export type ChainResult = {
1299
+ /** Both lists may be empty for a tally-only result: one count row, no model values. */
1267
1300
  readonly rowsFrom: readonly string[];
1268
1301
  readonly columns: readonly string[];
1269
1302
  readonly verdict?: {
@@ -1296,12 +1329,15 @@ export type ChainResult = {
1296
1329
  * `minRows` (EPIC W) makes the veto count-sensitive: the statement fails when
1297
1330
  * `step` returned FEWER rows than that (a multi-row INSERT a trigger shortened).
1298
1331
  * Omitted or 1 = "no row". A positive safe integer, rendered as SQL text.
1332
+ * `exactRows` instead requires equality, including zero. It is a nonnegative
1333
+ * safe integer and cannot combine with `minRows`.
1299
1334
  */
1300
1335
  export type ChainVeto = {
1301
1336
  readonly step: string;
1302
1337
  readonly token: string;
1303
1338
  readonly nonce: string;
1304
1339
  readonly minRows?: number;
1340
+ readonly exactRows?: number;
1305
1341
  };
1306
1342
  /** Count the RETURNING rows of `steps` into the result column `alias`. */
1307
1343
  export type ChainTally = {
@@ -1341,7 +1377,7 @@ export type ChainStatement = {
1341
1377
  readonly steps: readonly ChainStep[];
1342
1378
  readonly result: ChainResult;
1343
1379
  };
1344
- export type SqlStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | RlsEnableStatement | RlsForceStatement | RlsDisableStatement | RlsNoForceStatement | RlsDropPolicyStatement | RlsPolicyStatement | RlsContextStatement | RlsRoleStatement | AdvisoryLockStatement | ChainStatement;
1380
+ export type SqlStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | RlsEnableStatement | RlsForceStatement | RlsDisableStatement | RlsNoForceStatement | RlsDropPolicyStatement | RlsPolicyStatement | RlsContextStatement | RlsRoleStatement | AdvisoryLockStatement | AdvisoryLocksStatement | ChainStatement;
1345
1381
  /** Readiness statements remain distinct from application queries. */
1346
1382
  export type RlsReadinessStatement = {
1347
1383
  readonly kind: "rlsReadiness";
package/dist/dialect.d.ts CHANGED
@@ -446,6 +446,16 @@ export type SqlDialect = {
446
446
  quoteStringLiteral(params: {
447
447
  value: string;
448
448
  }): string;
449
+ /** Keep a native enum's ordering through a scalar subquery that may lose its physical enum type. */
450
+ renderEnumOrdinal(params: {
451
+ column: string;
452
+ }): string;
453
+ /** Render native enum MIN/MAX in database declaration order, returning its label or an order key. */
454
+ renderEnumMinMax(params: {
455
+ fn: "min" | "max";
456
+ column: string;
457
+ result: "value" | "ordinal";
458
+ }): string;
449
459
  /** Positional placeholder for the 1-based parameter index ($n or ?). */
450
460
  placeholder(params: {
451
461
  index: number;
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, 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
@@ -2402,6 +2402,12 @@ var sqliteDialect = {
2402
2402
  quoteStringLiteral({ value }) {
2403
2403
  return `'${value.replaceAll("'", "''")}'`;
2404
2404
  },
2405
+ renderEnumOrdinal({ column }) {
2406
+ return column;
2407
+ },
2408
+ renderEnumMinMax({ fn, column }) {
2409
+ return `${fn === "min" ? "MIN" : "MAX"}(${column})`;
2410
+ },
2405
2411
  placeholder() {
2406
2412
  return "?";
2407
2413
  },
@@ -2984,6 +2990,28 @@ function renderAdvisoryLock(params) {
2984
2990
  returning: "none"
2985
2991
  };
2986
2992
  }
2993
+ function renderAdvisoryLocks(params) {
2994
+ const { dialect, keys, mode } = params;
2995
+ const functions = dialect.advisoryLockFunctions;
2996
+ if (functions === null)
2997
+ throw unsupportedCapability15({ dialect: dialect.name, feature: "advisory locks" });
2998
+ if (mode !== "exclusive" && mode !== "shared")
2999
+ throw internalError({ message: "advisory lock mode must be exclusive or shared" });
3000
+ if (keys.some((key) => typeof key !== "bigint" || key < ADVISORY_KEY_BOUNDS.single.min || key > ADVISORY_KEY_BOUNDS.single.max)) {
3001
+ throw internalError({ message: "advisory lock keys must be signed 64-bit integers" });
3002
+ }
3003
+ const collector = new ParamCollector(dialect);
3004
+ const binding = collector.add(JSON.stringify(keys.map((key) => key.toString())));
3005
+ const fn = (mode === "exclusive" ? functions.exclusive : functions.shared).wait;
3006
+ const table = dialect.quoteIdent({ name: "__vibe_locks" });
3007
+ const key = dialect.quoteIdent({ name: "key" });
3008
+ const order = dialect.quoteIdent({ name: "ordinal" });
3009
+ return {
3010
+ 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}`,
3011
+ values: collector.values,
3012
+ returning: "none"
3013
+ };
3014
+ }
2987
3015
  function assertInt32(params) {
2988
3016
  const { value, part } = params;
2989
3017
  if (!Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
@@ -3075,6 +3103,12 @@ var postgresDialect = {
3075
3103
  quoteStringLiteral({ value }) {
3076
3104
  return `'${value.replaceAll("'", "''")}'`;
3077
3105
  },
3106
+ renderEnumOrdinal({ column }) {
3107
+ return column;
3108
+ },
3109
+ renderEnumMinMax({ fn, column }) {
3110
+ return `${fn === "min" ? "MIN" : "MAX"}(${column})`;
3111
+ },
3078
3112
  placeholder({ index }) {
3079
3113
  return `$${index}`;
3080
3114
  },
@@ -3148,6 +3182,15 @@ var mysqlDialect = {
3148
3182
  quoteStringLiteral({ value }) {
3149
3183
  return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "''")}'`;
3150
3184
  },
3185
+ renderEnumOrdinal({ column }) {
3186
+ return `${column} + 0`;
3187
+ },
3188
+ renderEnumMinMax({ fn, column, result }) {
3189
+ const aggregate = fn === "min" ? "MIN" : "MAX";
3190
+ if (result === "ordinal")
3191
+ return `${aggregate}(${column} + 0)`;
3192
+ return `SUBSTRING(${aggregate}(CONCAT(LPAD(${column} + 0, 5, '0'), ${column})), 6)`;
3193
+ },
3151
3194
  placeholder() {
3152
3195
  return "?";
3153
3196
  },
@@ -4090,6 +4133,8 @@ function renderStatement(params) {
4090
4133
  }
4091
4134
  case "select":
4092
4135
  return renderSelect({ dialect, statement, collector });
4136
+ case "advisoryLocks":
4137
+ return renderAdvisoryLocks({ dialect, keys: statement.keys, mode: statement.mode });
4093
4138
  case "advisoryLock":
4094
4139
  return renderAdvisoryLock({ dialect, statement, collector });
4095
4140
  case "insert":
@@ -4129,6 +4174,8 @@ function quoteCol(dialect, column) {
4129
4174
  const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
4130
4175
  if (column.truncate !== undefined)
4131
4176
  return renderDateTruncation({ dialect, reference: ref, truncation: column.truncate });
4177
+ if (column.enumOrdinal === true)
4178
+ return dialect.renderEnumOrdinal({ column: ref });
4132
4179
  if (column.castReal === true) {
4133
4180
  if (dialect.name !== "sqlite") {
4134
4181
  throw internalError4({ message: "ColRef.castReal is a sqlite Decimal construct" });
@@ -4229,7 +4276,11 @@ var AGG_FN_SQL = {
4229
4276
  function renderAggRef(dialect, agg) {
4230
4277
  if (agg.fn === "countAll")
4231
4278
  return "COUNT(*)";
4232
- return `${AGG_FN_SQL[agg.fn]}(${quoteCol(dialect, agg.column)})`;
4279
+ const column = quoteCol(dialect, agg.column);
4280
+ if ((agg.fn === "min" || agg.fn === "max") && agg.enumOrder !== undefined) {
4281
+ return dialect.renderEnumMinMax({ fn: agg.fn, column, result: agg.enumOrder });
4282
+ }
4283
+ return `${AGG_FN_SQL[agg.fn]}(${column})`;
4233
4284
  }
4234
4285
  function assertScalarArrays(dialect, feature) {
4235
4286
  if (dialect.capabilities.scalarArrays)
@@ -4770,23 +4821,20 @@ function renderOrderTerm(params) {
4770
4821
  if ("agg" in term) {
4771
4822
  return `${renderAggRef(dialect, term.agg)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
4772
4823
  }
4773
- if ("select" in term) {
4774
- const sub = renderSelect({ dialect, statement: term.select, collector });
4775
- return `(${sub.text}) ${term.direction === "asc" ? "ASC" : "DESC"}`;
4776
- }
4777
- const column = quoteCol(dialect, term.column);
4824
+ const expression = "select" in term ? () => renderScalarSubquery({ dialect, subquery: term, collector }) : () => quoteCol(dialect, term.column);
4778
4825
  const direction = term.direction === "asc" ? "ASC" : "DESC";
4779
4826
  if (term.nulls === undefined) {
4780
- return `${column} ${direction}`;
4827
+ return `${expression()} ${direction}`;
4781
4828
  }
4782
4829
  if (dialect.orderByNullsMode === "native") {
4783
- return `${column} ${direction} NULLS ${term.nulls === "first" ? "FIRST" : "LAST"}`;
4830
+ return `${expression()} ${direction} NULLS ${term.nulls === "first" ? "FIRST" : "LAST"}`;
4784
4831
  }
4785
4832
  if (term.nulls === "first" === (term.direction === "asc")) {
4786
- return `${column} ${direction}`;
4833
+ return `${expression()} ${direction}`;
4787
4834
  }
4788
- const nullsTerm = term.nulls === "first" ? `${column} IS NULL DESC` : `${column} IS NULL`;
4789
- return `${nullsTerm}, ${column} ${direction}`;
4835
+ const tested = expression();
4836
+ const nullsTerm = term.nulls === "first" ? `${tested} IS NULL DESC` : `${tested} IS NULL`;
4837
+ return `${nullsTerm}, ${expression()} ${direction}`;
4790
4838
  }
4791
4839
  function renderSelectExpr(params) {
4792
4840
  const { dialect, expr } = params;
@@ -4837,7 +4885,7 @@ function renderLateralJoin(params) {
4837
4885
  const json = lateralJsonOf({ dialect });
4838
4886
  let aggOrder = "";
4839
4887
  if (json.order === "inAggregate" && join.agg === "jsonArray" && join.select.orderBy !== undefined && join.select.orderBy.length > 0) {
4840
- const projected = new Set(join.select.columns.filter((column) => column.transform === undefined && column.castReal !== true).map((column) => column.name));
4888
+ const projected = new Set(join.select.columns.filter((column) => column.transform === undefined && column.castReal !== true).map((column) => column.name).concat((join.select.scalarSubqueries ?? []).map((subquery) => subquery.alias)));
4841
4889
  const terms = join.select.orderBy;
4842
4890
  const columnTerms = terms.filter((term) => ("column" in term));
4843
4891
  if (columnTerms.length === terms.length && columnTerms.every((term) => projected.has(term.column.name))) {
@@ -4863,12 +4911,17 @@ function renderLateralJoin(params) {
4863
4911
  function renderCountSubquery(params) {
4864
4912
  const { dialect, subquery, collector } = params;
4865
4913
  const select = subquery.select;
4866
- if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.distinct !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined) {
4914
+ if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.distinct !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined || select.scalarSubqueries !== undefined) {
4867
4915
  throw internalError4({ message: "count subquery must be one ungrouped COUNT(*)" });
4868
4916
  }
4869
4917
  const inner = renderSelect({ dialect, statement: select, collector });
4870
4918
  return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
4871
4919
  }
4920
+ function renderScalarSubquery(params) {
4921
+ const { dialect, subquery, collector } = params;
4922
+ const value = `(${renderSelect({ dialect, statement: subquery.select, collector }).text})`;
4923
+ return subquery.coalesce === 0 ? `COALESCE(${value}, 0)` : value;
4924
+ }
4872
4925
  function applyReturning(params) {
4873
4926
  const { dialect, requested } = params;
4874
4927
  if (requested === undefined || requested.length === 0) {
@@ -4977,6 +5030,11 @@ function renderSelect(params) {
4977
5030
  columnList.push(renderCountSubquery({ dialect, subquery, collector }));
4978
5031
  }
4979
5032
  }
5033
+ if (statement.scalarSubqueries !== undefined) {
5034
+ for (const subquery of statement.scalarSubqueries) {
5035
+ columnList.push(`${renderScalarSubquery({ dialect, subquery, collector })} AS ${dialect.quoteIdent({ name: subquery.alias })}`);
5036
+ }
5037
+ }
4980
5038
  if (statement.rowNumber !== undefined) {
4981
5039
  const rowNumber = statement.rowNumber;
4982
5040
  if (rowNumber.partitionBy.length === 0 && rowNumber.orderBy.length === 0) {
@@ -5393,6 +5451,9 @@ function renderChain(params) {
5393
5451
  defined.add(step.name);
5394
5452
  }
5395
5453
  const result = statement.result;
5454
+ if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
5455
+ return { text: `WITH ${steps.join(", ")} SELECT ${renderTallyColumn({ dialect, result, defined, collector })}`, values: collector.values, returning: "none" };
5456
+ }
5396
5457
  if (result.rowsFrom.length === 0 || result.columns.length === 0) {
5397
5458
  throw internalError4({ message: "a chain result needs at least one source step and one column" });
5398
5459
  }
@@ -5491,7 +5552,10 @@ function renderTallyColumn(params) {
5491
5552
  const minRows = veto.minRows ?? 1;
5492
5553
  if (!Number.isSafeInteger(minRows) || minRows < 1)
5493
5554
  throw internalError4({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
5494
- const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
5555
+ if (veto.exactRows !== undefined && (!Number.isSafeInteger(veto.exactRows) || veto.exactRows < 0 || veto.minRows !== undefined)) {
5556
+ throw internalError4({ message: `chain veto "${veto.token}" needs a nonnegative safe integer exactRows without minRows` });
5557
+ }
5558
+ 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}`;
5495
5559
  return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
5496
5560
  });
5497
5561
  return `CASE ${arms.join(" ")} ELSE ${sum} END AS ${dialect.quoteIdent({ name: tally.alias })}`;
@@ -6275,6 +6339,7 @@ export {
6275
6339
  recordSqlBindingCheck,
6276
6340
  registerSqlHandleCheck,
6277
6341
  renderAdvisoryLock,
6342
+ renderAdvisoryLocks,
6278
6343
  renderCheckProbe,
6279
6344
  renderColumnStateQuery,
6280
6345
  renderConstraintOwnedIndexDrop,
@@ -6380,4 +6445,4 @@ export {
6380
6445
  toPgArrayLiteral
6381
6446
  };
6382
6447
 
6383
- //# debugId=DB7441716F57EC8864756E2164756E21
6448
+ //# debugId=A6FF0AC9CFF5903864756E2164756E21