@dbsp/adapter-pgsql 1.6.0 → 1.8.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/index.js CHANGED
@@ -663,9 +663,6 @@ function funcCall(name, args = [], options = {}) {
663
663
  function coalesceExpr(args) {
664
664
  return { CoalesceExpr: { args } };
665
665
  }
666
- function coalesce(...args) {
667
- return funcCall("coalesce", args);
668
- }
669
666
  function sortBy(expr, direction = "DEFAULT", nulls = "DEFAULT") {
670
667
  const sb = {
671
668
  node: expr,
@@ -4486,6 +4483,11 @@ var init_relation_filter = __esm({
4486
4483
 
4487
4484
  // src/subquery-emission.ts
4488
4485
  import { isParamIntent as isParamIntent5 } from "@dbsp/types";
4486
+ function isColumnOrderBy(orderBy) {
4487
+ return Array.isArray(orderBy) && orderBy.every(
4488
+ (item) => typeof item === "object" && item !== null && "column" in item && typeof item.column === "string"
4489
+ );
4490
+ }
4489
4491
  function assertNoDroppedDecisionModifiers(decision, use) {
4490
4492
  const d = decision;
4491
4493
  const unsupported = [];
@@ -4604,8 +4606,9 @@ function buildPredicateSubquerySelect(use, sourceIntent, decision, ctx, state, d
4604
4606
  ],
4605
4607
  ...whereClause && { whereClause }
4606
4608
  };
4607
- if (decision.orderBy && decision.orderBy.length > 0) {
4608
- stmt.sortClause = decision.orderBy.map(
4609
+ const orderBy = isColumnOrderBy(decision.orderBy) ? decision.orderBy : void 0;
4610
+ if (orderBy && orderBy.length > 0) {
4611
+ stmt.sortClause = orderBy.map(
4609
4612
  (o) => sortBy(
4610
4613
  columnRef(o.column, targetAlias, void 0, ctx.naming),
4611
4614
  o.direction ?? "ASC",
@@ -4856,10 +4859,7 @@ var init_where = __esm({
4856
4859
  hasRelationHandler,
4857
4860
  hasNoRelationHandler
4858
4861
  ];
4859
- allWhereHandlers = [
4860
- ...simpleWhereHandlers,
4861
- ...complexWhereHandlers
4862
- ];
4862
+ allWhereHandlers = [...simpleWhereHandlers, ...complexWhereHandlers];
4863
4863
  }
4864
4864
  });
4865
4865
 
@@ -5098,19 +5098,19 @@ var init_shared = __esm({
5098
5098
  });
5099
5099
 
5100
5100
  // src/handlers/include/json-agg.ts
5101
- import { toColumnList as toColumnList6 } from "@dbsp/types";
5101
+ import { resolveJsonAggOrderKey } from "@dbsp/types";
5102
+ function isJsonAggOrderBy(orderBy) {
5103
+ return Array.isArray(orderBy) && orderBy.every((item) => typeof item === "string");
5104
+ }
5102
5105
  function resolveJsonAggOrderBy(decision, targetTable, ctx) {
5103
5106
  const table = ctx.model?.getTable(targetTable);
5104
5107
  if (table) {
5105
- const pkColumns = toColumnList6(table.primaryKey);
5106
- const orderIntent = pkColumns.length > 0 ? { columns: pkColumns, fallback: false } : {
5107
- columns: table.columns.map((col) => col.name),
5108
- fallback: true
5109
- };
5110
- return orderIntent.columns.length > 0 ? orderIntent : void 0;
5108
+ const orderKey = resolveJsonAggOrderKey(table);
5109
+ return orderKey.columns.length > 0 ? orderKey : void 0;
5111
5110
  }
5112
- return decision.targetPrimaryKey && decision.targetPrimaryKey.length > 0 ? {
5113
- columns: decision.targetPrimaryKey,
5111
+ const decisionOrderBy = isJsonAggOrderBy(decision.orderBy) ? decision.orderBy : void 0;
5112
+ return decisionOrderBy && decisionOrderBy.length > 0 ? {
5113
+ columns: decisionOrderBy,
5114
5114
  fallback: decision.orderByFallback === true
5115
5115
  } : void 0;
5116
5116
  }
@@ -5210,7 +5210,7 @@ var init_json_agg = __esm({
5210
5210
  });
5211
5211
 
5212
5212
  // src/handlers/include/lateral.ts
5213
- import { toColumnList as toColumnList7 } from "@dbsp/types";
5213
+ import { toColumnList as toColumnList6 } from "@dbsp/types";
5214
5214
  function buildLateralTargets(columns, alias, ctx) {
5215
5215
  if (columns && columns.length > 0 && !(columns.length === 1 && columns[0] === "*")) {
5216
5216
  return columns.map((col) => ({
@@ -5312,7 +5312,7 @@ var init_lateral = __esm({
5312
5312
  lateralIncludeHandler = {
5313
5313
  strategy: "lateral",
5314
5314
  compile(decision, ctx, state) {
5315
- const sourceColumn = toColumnList7(decision.sourceColumn);
5315
+ const sourceColumn = toColumnList6(decision.sourceColumn);
5316
5316
  if (sourceColumn.length === 0) {
5317
5317
  throw new Error(
5318
5318
  "Missing required column 'sourceColumn' in lateral include"
@@ -5382,10 +5382,6 @@ __export(handlers_exports, {
5382
5382
  LOGICAL_OPERATORS: () => LOGICAL_OPERATORS,
5383
5383
  NULL_OPERATORS: () => NULL_OPERATORS,
5384
5384
  PATTERN_OPERATORS: () => PATTERN_OPERATORS,
5385
- aggregateExpressionHandlers: () => aggregateExpressionHandlers,
5386
- allExpressionHandlers: () => allExpressionHandlers,
5387
- allIncludeHandlers: () => allIncludeHandlers,
5388
- allWhereHandlers: () => allWhereHandlers,
5389
5385
  andHandler: () => andHandler,
5390
5386
  anyHandler: () => anyHandler,
5391
5387
  arithmeticHandler: () => arithmeticHandler,
@@ -5396,12 +5392,9 @@ __export(handlers_exports, {
5396
5392
  clearHandlers: () => clearHandlers,
5397
5393
  coalesceHandler: () => coalesceHandler,
5398
5394
  columnAliasHandler: () => columnAliasHandler,
5399
- columnExpressionHandlers: () => columnExpressionHandlers,
5400
5395
  columnHandler: () => columnHandler,
5401
5396
  comparisonHandler: () => comparisonHandler,
5402
5397
  compileExpressionIntent: () => compileExpressionIntent,
5403
- complexWhereHandlers: () => complexWhereHandlers,
5404
- conditionalExpressionHandlers: () => conditionalExpressionHandlers,
5405
5398
  countDistinctHandler: () => countDistinctHandler,
5406
5399
  countHandler: () => countHandler,
5407
5400
  createCompilerState: () => createCompilerState,
@@ -5438,7 +5431,6 @@ __export(handlers_exports, {
5438
5431
  jsonComparisonHandler: () => jsonComparisonHandler,
5439
5432
  jsonContainsHandler: () => jsonContainsHandler,
5440
5433
  jsonExistsHandler: () => jsonExistsHandler,
5441
- jsonExpressionHandlers: () => jsonExpressionHandlers,
5442
5434
  jsonExtractHandler: () => jsonExtractHandler,
5443
5435
  jsonPathExtractHandler: () => jsonPathExtractHandler,
5444
5436
  lagHandler: () => lagHandler,
@@ -5459,11 +5451,9 @@ __export(handlers_exports, {
5459
5451
  orHandler: () => orHandler,
5460
5452
  prefixedRelationColumnHandler: () => prefixedRelationColumnHandler,
5461
5453
  pseudoColumnHandler: () => pseudoColumnHandler,
5462
- pseudoExpressionHandlers: () => pseudoExpressionHandlers,
5463
5454
  rangeHandler: () => rangeHandler,
5464
5455
  rankHandler: () => rankHandler,
5465
5456
  rawExistsHandler: () => rawExistsHandler,
5466
- rawExpressionHandlers: () => rawExpressionHandlers,
5467
5457
  rawHandler: () => rawHandler,
5468
5458
  registerAllExpressionHandlers: () => registerAllExpressionHandlers,
5469
5459
  registerAllIncludeHandlers: () => registerAllIncludeHandlers,
@@ -5475,7 +5465,6 @@ __export(handlers_exports, {
5475
5465
  relationAliasHandler: () => relationAliasHandler,
5476
5466
  relationColumnHandler: () => relationColumnHandler,
5477
5467
  relationColumnsHandler: () => relationColumnsHandler,
5478
- relationExpressionHandlers: () => relationExpressionHandlers,
5479
5468
  relationFilterHandler: () => relationFilterHandler,
5480
5469
  relationStarHandler: () => relationStarHandler,
5481
5470
  rowNumberHandler: () => rowNumberHandler,
@@ -5485,8 +5474,7 @@ __export(handlers_exports, {
5485
5474
  singleHopPseudoHandler: () => singleHopPseudoHandler,
5486
5475
  sqlFunctionHandler: () => sqlFunctionHandler,
5487
5476
  starHandler: () => starHandler,
5488
- sumHandler: () => sumHandler,
5489
- windowExpressionHandlers: () => windowExpressionHandlers
5477
+ sumHandler: () => sumHandler
5490
5478
  });
5491
5479
  function registerWhereHandler(handler) {
5492
5480
  for (const op3 of handler.operators) {
@@ -6267,6 +6255,7 @@ function buildRecursiveScalarSubquery(config) {
6267
6255
  pkColumn,
6268
6256
  fkColumn,
6269
6257
  outerAlias,
6258
+ outerSeedColumn,
6270
6259
  isAncestors,
6271
6260
  maxDepth,
6272
6261
  selectColumn,
@@ -6277,6 +6266,9 @@ function buildRecursiveScalarSubquery(config) {
6277
6266
  const dbPk = naming.toDatabase(pkColumn);
6278
6267
  const dbFk = naming.toDatabase(fkColumn);
6279
6268
  const dbOuter = naming.toDatabase(outerAlias);
6269
+ const dbOuterSeed = naming.toDatabase(
6270
+ outerSeedColumn ?? (isAncestors ? fkColumn : pkColumn)
6271
+ );
6280
6272
  const innerAlias = "__n";
6281
6273
  const anchorSelect = {
6282
6274
  targetList: [
@@ -6345,7 +6337,7 @@ function buildRecursiveScalarSubquery(config) {
6345
6337
  ColumnRef: {
6346
6338
  fields: [
6347
6339
  { String: { sval: dbOuter } },
6348
- { String: { sval: dbFk } }
6340
+ { String: { sval: dbOuterSeed } }
6349
6341
  ]
6350
6342
  }
6351
6343
  }
@@ -6365,7 +6357,7 @@ function buildRecursiveScalarSubquery(config) {
6365
6357
  ColumnRef: {
6366
6358
  fields: [
6367
6359
  { String: { sval: dbOuter } },
6368
- { String: { sval: dbPk } }
6360
+ { String: { sval: dbOuterSeed } }
6369
6361
  ]
6370
6362
  }
6371
6363
  }
@@ -6429,15 +6421,7 @@ function buildRecursiveScalarSubquery(config) {
6429
6421
  }
6430
6422
  ],
6431
6423
  fromClause: [
6432
- // FROM __rc
6433
- {
6434
- RangeVar: {
6435
- relname: cteAlias,
6436
- inh: true,
6437
- relpersistence: "p"
6438
- }
6439
- },
6440
- // INNER JOIN table AS __n
6424
+ // FROM __rc INNER JOIN table AS __n
6441
6425
  {
6442
6426
  JoinExpr: {
6443
6427
  jointype: "JOIN_INNER",
@@ -6520,7 +6504,7 @@ function buildRecursiveScalarSubquery(config) {
6520
6504
  },
6521
6505
  integerNode(maxDepth)
6522
6506
  ),
6523
- // pk <> ALL(__visited) cycle detection
6507
+ // pk <> ALL(__visited) - cycle detection
6524
6508
  {
6525
6509
  A_Expr: {
6526
6510
  kind: "AEXPR_OP_ALL",
@@ -6565,20 +6549,35 @@ function buildRecursiveScalarSubquery(config) {
6565
6549
  targetList: [
6566
6550
  {
6567
6551
  ResTarget: {
6568
- val: coalesce(
6569
- funcCall("json_agg", [
6570
- {
6571
- ColumnRef: {
6572
- fields: [
6573
- { String: { sval: cteAlias } },
6574
- { String: { sval: dbSelectCol } }
6575
- ]
6552
+ val: coalesceExpr([
6553
+ funcCall(
6554
+ "json_agg",
6555
+ [
6556
+ {
6557
+ ColumnRef: {
6558
+ fields: [
6559
+ { String: { sval: cteAlias } },
6560
+ { String: { sval: dbSelectCol } }
6561
+ ]
6562
+ }
6576
6563
  }
6564
+ ],
6565
+ {
6566
+ orderBy: [
6567
+ sortBy({
6568
+ ColumnRef: {
6569
+ fields: [
6570
+ { String: { sval: cteAlias } },
6571
+ { String: { sval: "__depth" } }
6572
+ ]
6573
+ }
6574
+ })
6575
+ ]
6577
6576
  }
6578
- ]),
6577
+ ),
6579
6578
  // Empty array fallback: '[]'::json
6580
- typeCast(stringNode("[]"), "json")
6581
- )
6579
+ typeCast({ A_Const: { sval: { sval: "[]" } } }, "json")
6580
+ ])
6582
6581
  }
6583
6582
  }
6584
6583
  ],
@@ -6915,6 +6914,14 @@ var init_raw = __esm({
6915
6914
  });
6916
6915
 
6917
6916
  // src/handlers/expression/relation.ts
6917
+ function resolveRelationAlias(relation, state, options = {}) {
6918
+ if (options.resolveDottedLeaf && relation.includes(".")) {
6919
+ const segments = relation.split(".");
6920
+ const leaf = segments[segments.length - 1];
6921
+ return state.aliases.get(relation) ?? state.aliases.get(leaf) ?? leaf;
6922
+ }
6923
+ return state.aliases.get(relation) ?? relation;
6924
+ }
6918
6925
  var relationStarHandler, relationColumnHandler, relationColumnsHandler, relationAliasHandler, prefixedRelationColumnHandler;
6919
6926
  var init_relation = __esm({
6920
6927
  "src/handlers/expression/relation.ts"() {
@@ -6947,14 +6954,9 @@ var init_relation = __esm({
6947
6954
  if (!column) {
6948
6955
  throw new Error("Relation column handler requires column name");
6949
6956
  }
6950
- let alias;
6951
- if (relation.includes(".")) {
6952
- const segments = relation.split(".");
6953
- const leaf = segments[segments.length - 1];
6954
- alias = state.aliases.get(relation) ?? state.aliases.get(leaf) ?? leaf;
6955
- } else {
6956
- alias = state.aliases.get(relation) ?? relation;
6957
- }
6957
+ const alias = resolveRelationAlias(relation, state, {
6958
+ resolveDottedLeaf: true
6959
+ });
6958
6960
  if (column === "*") {
6959
6961
  return columnRefStar(alias, ctx.naming);
6960
6962
  }
@@ -6972,7 +6974,7 @@ var init_relation = __esm({
6972
6974
  if (!columns || columns.length === 0) {
6973
6975
  throw new Error("Relation columns handler requires columns array");
6974
6976
  }
6975
- const alias = state.aliases.get(relation) ?? relation;
6977
+ const alias = resolveRelationAlias(relation, state);
6976
6978
  const column = columns[0];
6977
6979
  const colRef = columnRef(column, alias, void 0, ctx.naming);
6978
6980
  const outputAlias = decision.alias;
@@ -6998,7 +7000,7 @@ var init_relation = __esm({
6998
7000
  if (!column) {
6999
7001
  throw new Error("Relation alias handler requires column name");
7000
7002
  }
7001
- const tableAlias = state.aliases.get(relation) ?? relation;
7003
+ const tableAlias = resolveRelationAlias(relation, state);
7002
7004
  const colRef = columnRef(column, tableAlias, void 0, ctx.naming);
7003
7005
  if (!outputAlias) {
7004
7006
  return colRef;
@@ -7037,6 +7039,11 @@ var init_relation = __esm({
7037
7039
  });
7038
7040
 
7039
7041
  // src/handlers/expression/window.ts
7042
+ function isWindowOrderBy(orderBy) {
7043
+ return Array.isArray(orderBy) && orderBy.every(
7044
+ (item) => typeof item === "object" && item !== null && "column" in item && typeof item.column === "string"
7045
+ );
7046
+ }
7040
7047
  function buildSortBy(column, direction, ctx) {
7041
7048
  const tableAlias = ctx.currentAlias ?? ctx.rootTable;
7042
7049
  const colRef = columnRef(column, tableAlias, void 0, ctx.naming);
@@ -7049,7 +7056,7 @@ function buildSortBy(column, direction, ctx) {
7049
7056
  }
7050
7057
  function buildWindowDef(decision, ctx) {
7051
7058
  const partition = decision.partition;
7052
- const orderBy = decision.orderBy;
7059
+ const orderBy = isWindowOrderBy(decision.orderBy) ? decision.orderBy : void 0;
7053
7060
  const frame = decision.frame;
7054
7061
  const tableAlias = ctx.currentAlias ?? ctx.rootTable;
7055
7062
  const windowDef = { frameOptions: WINDOW_FRAME_DEFAULT };
@@ -7264,15 +7271,8 @@ var init_expression = __esm({
7264
7271
  lastValueHandler,
7265
7272
  genericWindowHandler
7266
7273
  ];
7267
- rawExpressionHandlers = [
7268
- rawHandler,
7269
- sqlFunctionHandler,
7270
- literalHandler
7271
- ];
7272
- jsonExpressionHandlers = [
7273
- jsonExtractHandler,
7274
- jsonPathExtractHandler
7275
- ];
7274
+ rawExpressionHandlers = [rawHandler, sqlFunctionHandler, literalHandler];
7275
+ jsonExpressionHandlers = [jsonExtractHandler, jsonPathExtractHandler];
7276
7276
  pseudoExpressionHandlers = [
7277
7277
  pseudoColumnHandler,
7278
7278
  singleHopPseudoHandler,
@@ -7313,7 +7313,7 @@ import {
7313
7313
  isParamIntent as isParamIntent6,
7314
7314
  NQL_SELECT_SCALAR_FUNCTION_ALLOWLIST,
7315
7315
  NQL_SELECT_WINDOW_FUNCTION_ALLOWLIST,
7316
- toColumnList as toColumnList8
7316
+ toColumnList as toColumnList7
7317
7317
  } from "@dbsp/types";
7318
7318
  import {
7319
7319
  getNqlBindingRefName,
@@ -8379,6 +8379,7 @@ init_case_value();
8379
8379
  init_custom();
8380
8380
  init_expression();
8381
8381
  init_param_value();
8382
+ init_pseudo();
8382
8383
  init_window();
8383
8384
  init_include();
8384
8385
  init_shared();
@@ -8425,7 +8426,7 @@ function trustedRelationHasMultipleHops(relation) {
8425
8426
  if (typeof relation !== "string") return relation.length > 1;
8426
8427
  return relation.split(".").length > 1;
8427
8428
  }
8428
- function isJsonAggOrderBy(orderBy) {
8429
+ function isJsonAggOrderBy2(orderBy) {
8429
8430
  return Array.isArray(orderBy) && orderBy.every((item) => typeof item === "string");
8430
8431
  }
8431
8432
  function isExpressionOrderBy(orderBy) {
@@ -8434,8 +8435,12 @@ function isExpressionOrderBy(orderBy) {
8434
8435
  );
8435
8436
  }
8436
8437
  function mapToHandlerDecision(pd, rootTable, defaultPk, deriveFk) {
8437
- const jsonAggOrderBy = isJsonAggOrderBy(pd.orderBy) ? pd.orderBy : void 0;
8438
+ const jsonAggOrderBy = isJsonAggOrderBy2(pd.orderBy) ? pd.orderBy : void 0;
8438
8439
  const expressionOrderBy = isExpressionOrderBy(pd.orderBy) ? pd.orderBy : void 0;
8440
+ const handlerOrderBy = jsonAggOrderBy ?? expressionOrderBy?.map((o) => ({
8441
+ column: o.field,
8442
+ direction: o.direction?.toUpperCase() ?? "ASC"
8443
+ }));
8439
8444
  const derivedFkColumns = deriveFkColumns(
8440
8445
  pd,
8441
8446
  pd.sourceTable ?? rootTable,
@@ -8470,7 +8475,6 @@ function mapToHandlerDecision(pd, rootTable, defaultPk, deriveFk) {
8470
8475
  relationType: pd.relationType,
8471
8476
  foreignKey: pd.foreignKey,
8472
8477
  parentKey: pd.parentKey,
8473
- targetPrimaryKey: pd.targetPrimaryKey ?? jsonAggOrderBy,
8474
8478
  orderByFallback: pd.orderByFallback,
8475
8479
  dataType: pd.dataType,
8476
8480
  traversal: pd.traversal,
@@ -8486,10 +8490,7 @@ function mapToHandlerDecision(pd, rootTable, defaultPk, deriveFk) {
8486
8490
  include: pd.include?.map(
8487
8491
  (c) => mapToHandlerDecision(c, rootTable, defaultPk, deriveFk)
8488
8492
  ),
8489
- orderBy: expressionOrderBy?.map((o) => ({
8490
- column: o.field,
8491
- direction: o.direction?.toUpperCase() ?? "ASC"
8492
- })),
8493
+ orderBy: handlerOrderBy,
8493
8494
  partition: pd.partitionBy,
8494
8495
  jsonPath: pd.jsonPath,
8495
8496
  jsonMode: pd.jsonMode,
@@ -9048,8 +9049,11 @@ var PlanCompiler = class _PlanCompiler {
9048
9049
  isNqlBindingRoot(plan) {
9049
9050
  return hasBindingName(this.bindingNames, plan.rootTable, this.naming);
9050
9051
  }
9052
+ allocateBindingRelationAlias() {
9053
+ return `rc_${this.bindingRelationColumnSubqueryIndex++}`;
9054
+ }
9051
9055
  buildCorrelatedRelationRefs(fields, plan) {
9052
- const relatedAlias = `rc_${this.bindingRelationColumnSubqueryIndex++}`;
9056
+ const relatedAlias = this.allocateBindingRelationAlias();
9053
9057
  const relatedTable = rangeVar(
9054
9058
  fields.targetTable,
9055
9059
  relatedAlias,
@@ -9068,12 +9072,93 @@ var PlanCompiler = class _PlanCompiler {
9068
9072
  relatedColumn
9069
9073
  };
9070
9074
  }
9075
+ bindingRelationHasCompleteManyToManyProof(fields) {
9076
+ return (fields.relationType === "manyToMany" || fields.relationType === "belongsToMany") && fields.through !== void 0 && fields.throughSourceColumn !== void 0 && fields.throughTargetColumn !== void 0;
9077
+ }
9078
+ bindingRelationName(fields) {
9079
+ return typeof fields.relation === "string" ? fields.relation : fields.relation.join(".");
9080
+ }
9081
+ bindingRelationNameLooksRecursive(fields) {
9082
+ const relationName = this.bindingRelationName(fields).toLowerCase();
9083
+ return relationName === "ascendant" || relationName === "descendant";
9084
+ }
9085
+ compileBindingRecursiveRelationColumnSubquery(fields, plan, dialectCapabilities) {
9086
+ const recursive = fields.recursive;
9087
+ if (recursive === void 0) {
9088
+ throw new Error(
9089
+ `NQL binding recursive relation-column proof for '${plan.rootTable}' is missing recursive metadata.`
9090
+ );
9091
+ }
9092
+ if (fields.cardinality !== "many") {
9093
+ throw new Error(
9094
+ `NQL binding recursive relation-column proof for '${plan.rootTable}' must use cardinality 'many'.`
9095
+ );
9096
+ }
9097
+ if (fields.sourceColumn.length !== 1) {
9098
+ throw new Error(
9099
+ `NQL binding recursive relation-column proof for '${plan.rootTable}' must carry exactly one projected seed column.`
9100
+ );
9101
+ }
9102
+ if (dialectCapabilities?.supportsRecursiveCTE === false) {
9103
+ throw new Error(
9104
+ `Recursive NQL binding relation columns require a dialect with supportsRecursiveCTE; current dialect (${dialectCapabilities.name}) declared it unsupported.`
9105
+ );
9106
+ }
9107
+ assertDialectCapability(
9108
+ dialectCapabilities,
9109
+ "supportsJsonAgg",
9110
+ "JSON aggregation for NQL binding relation columns is"
9111
+ );
9112
+ const seedColumn = fields.sourceColumn[0];
9113
+ if (!seedColumn) {
9114
+ throw new Error(
9115
+ `NQL binding recursive relation-column proof for '${plan.rootTable}' is missing its projected seed column.`
9116
+ );
9117
+ }
9118
+ const relatedAlias = this.allocateBindingRelationAlias();
9119
+ return buildRecursiveScalarSubquery({
9120
+ cteAlias: `__${relatedAlias}`,
9121
+ table: fields.targetTable,
9122
+ pkColumn: recursive.targetKeyColumn,
9123
+ fkColumn: recursive.selfRefColumn,
9124
+ outerAlias: plan.rootTable,
9125
+ outerSeedColumn: seedColumn,
9126
+ isAncestors: recursive.direction === "up",
9127
+ maxDepth: recursive.maxDepth,
9128
+ selectColumn: fields.selectedColumn,
9129
+ ctx: this.createHandlerContext(plan, plan.rootTable)
9130
+ });
9131
+ }
9071
9132
  compileBindingRelationColumnSubquery(fields, plan, dialectCapabilities) {
9072
9133
  if (fields.selectedColumn === void 0) {
9073
9134
  throw new Error(
9074
9135
  `NQL binding relation-column proof for '${plan.rootTable}' is missing selectedColumn.`
9075
9136
  );
9076
9137
  }
9138
+ if (fields.recursive !== void 0) {
9139
+ return this.compileBindingRecursiveRelationColumnSubquery(
9140
+ fields,
9141
+ plan,
9142
+ dialectCapabilities
9143
+ );
9144
+ }
9145
+ if (this.bindingRelationNameLooksRecursive(fields)) {
9146
+ throw new Error(
9147
+ `NQL binding recursive relation-column proof for '${plan.rootTable}' is missing recursive metadata.`
9148
+ );
9149
+ }
9150
+ const isManyToManyRelation = fields.relationType === "manyToMany" || fields.relationType === "belongsToMany";
9151
+ const hasCompleteManyToManyProof = this.bindingRelationHasCompleteManyToManyProof(fields);
9152
+ if (isManyToManyRelation && (fields.cardinality !== "many" || !hasCompleteManyToManyProof)) {
9153
+ if (fields.cardinality !== "many") {
9154
+ throw new Error(
9155
+ `NQL binding relation-column proof for '${plan.rootTable}' is manyToMany but must use cardinality 'many' with complete junction proof (through, throughSourceColumn, throughTargetColumn) (ref-#192).`
9156
+ );
9157
+ }
9158
+ throw new Error(
9159
+ `NQL binding relation-column proof for '${plan.rootTable}' is manyToMany but missing complete junction proof (through, throughSourceColumn, throughTargetColumn) (ref-#192).`
9160
+ );
9161
+ }
9077
9162
  if (fields.cardinality === "one") {
9078
9163
  const { relatedAlias, relatedTable, relatedColumn } = this.buildCorrelatedRelationRefs(fields, plan);
9079
9164
  if (fields.hops.length === 0 && trustedRelationHasMultipleHops(fields.relation)) {
@@ -9152,9 +9237,14 @@ var PlanCompiler = class _PlanCompiler {
9152
9237
  };
9153
9238
  }
9154
9239
  if (fields.cardinality === "many") {
9155
- if (fields.relationType !== "hasMany") {
9240
+ if (fields.relationType !== "hasMany" && fields.relationType !== "manyToMany" && fields.relationType !== "belongsToMany") {
9156
9241
  throw new Error(
9157
- `NQL binding relation-column proof for '${plan.rootTable}' has cardinality 'many' but relationType '${fields.relationType ?? "unknown"}' is not supported; only hasMany can be aggregated (ref-#192).`
9242
+ `NQL binding relation-column proof for '${plan.rootTable}' has cardinality 'many' but relationType '${fields.relationType ?? "unknown"}' is not supported; only hasMany or manyToMany can be aggregated (ref-#192).`
9243
+ );
9244
+ }
9245
+ if ((fields.relationType === "manyToMany" || fields.relationType === "belongsToMany") && !hasCompleteManyToManyProof) {
9246
+ throw new Error(
9247
+ `NQL binding relation-column proof for '${plan.rootTable}' is manyToMany but missing complete junction proof (through, throughSourceColumn, throughTargetColumn) (ref-#192).`
9158
9248
  );
9159
9249
  }
9160
9250
  if (dialectCapabilities?.supportsJsonAgg === false) {
@@ -9163,6 +9253,37 @@ var PlanCompiler = class _PlanCompiler {
9163
9253
  );
9164
9254
  }
9165
9255
  const { relatedAlias, relatedTable, relatedColumn } = this.buildCorrelatedRelationRefs(fields, plan);
9256
+ const junctionAlias = hasCompleteManyToManyProof ? this.allocateBindingRelationAlias() : void 0;
9257
+ const handlerContext = this.createHandlerContext(plan);
9258
+ const fromNode = hasCompleteManyToManyProof ? innerJoin(
9259
+ relatedTable,
9260
+ rangeVar(
9261
+ fields.through,
9262
+ junctionAlias,
9263
+ this.schemaForRangeVar(plan, fields.through),
9264
+ this.naming
9265
+ ),
9266
+ buildKeyCorrelation(
9267
+ relatedAlias,
9268
+ fields.targetColumn,
9269
+ junctionAlias,
9270
+ [fields.throughTargetColumn],
9271
+ handlerContext
9272
+ )
9273
+ ) : relatedTable;
9274
+ const whereNode = hasCompleteManyToManyProof ? buildKeyCorrelation(
9275
+ junctionAlias,
9276
+ [fields.throughSourceColumn],
9277
+ plan.rootTable,
9278
+ fields.sourceColumn,
9279
+ handlerContext
9280
+ ) : buildKeyCorrelation(
9281
+ relatedAlias,
9282
+ fields.targetColumn,
9283
+ plan.rootTable,
9284
+ fields.sourceColumn,
9285
+ handlerContext
9286
+ );
9166
9287
  return {
9167
9288
  SubLink: {
9168
9289
  subLinkType: "EXPR_SUBLINK",
@@ -9185,14 +9306,8 @@ var PlanCompiler = class _PlanCompiler {
9185
9306
  }
9186
9307
  }
9187
9308
  ],
9188
- from: [relatedTable],
9189
- where: buildKeyCorrelation(
9190
- relatedAlias,
9191
- fields.targetColumn,
9192
- plan.rootTable,
9193
- fields.sourceColumn,
9194
- this.createHandlerContext(plan)
9195
- )
9309
+ from: [fromNode],
9310
+ where: whereNode
9196
9311
  })
9197
9312
  }
9198
9313
  };
@@ -10357,8 +10472,8 @@ var PlanCompiler = class _PlanCompiler {
10357
10472
  this.schemaForRangeVar(plan, decision.targetTable ?? ""),
10358
10473
  this.naming
10359
10474
  );
10360
- const sourceColumn = toColumnList8(decision.sourceColumn);
10361
- const targetColumn = toColumnList8(decision.targetColumn);
10475
+ const sourceColumn = toColumnList7(decision.sourceColumn);
10476
+ const targetColumn = toColumnList7(decision.targetColumn);
10362
10477
  if (sourceColumn.length === 0) {
10363
10478
  throw new Error("Missing required column 'sourceColumn' in compileJoin");
10364
10479
  }
@@ -10797,6 +10912,12 @@ function buildSequenceClause(verb, seqName, seq, includeCycleNoCycle = false) {
10797
10912
  }
10798
10913
  return `${parts.join(" ")};`;
10799
10914
  }
10915
+ function failSafeUnknownDownChange(kind) {
10916
+ return {
10917
+ sql: `-- WARNING: Cannot reverse unsupported SchemaChange kind "${kind}"`,
10918
+ destructive: true
10919
+ };
10920
+ }
10800
10921
  function isChangeSupported(kind, caps) {
10801
10922
  switch (kind) {
10802
10923
  case "create_enum":
@@ -11249,188 +11370,381 @@ function changeToUpSQL(change, schemaName) {
11249
11370
  function changeToDownSQL(change, schemaName) {
11250
11371
  switch (change.kind) {
11251
11372
  case "create_table":
11252
- return `DROP TABLE IF EXISTS ${qualifyTable(change.table, schemaName)} CASCADE;`;
11373
+ return {
11374
+ sql: `DROP TABLE IF EXISTS ${qualifyTable(change.table, schemaName)} CASCADE;`,
11375
+ destructive: true
11376
+ };
11253
11377
  case "drop_table":
11254
- return `-- WARNING: Cannot reverse drop_table "${change.table}" -- table data was lost`;
11378
+ return {
11379
+ sql: `-- WARNING: Cannot reverse drop_table "${change.table}" -- table data was lost`,
11380
+ destructive: true
11381
+ };
11255
11382
  case "add_column":
11256
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP COLUMN ${quoteIdent2(change.column, "alias")} CASCADE;`;
11383
+ return {
11384
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP COLUMN ${quoteIdent2(change.column, "alias")} CASCADE;`,
11385
+ destructive: true
11386
+ };
11257
11387
  case "drop_column":
11258
- return `-- WARNING: Cannot reverse drop_column "${change.table}"."${change.column}" -- column data was lost`;
11388
+ return {
11389
+ sql: `-- WARNING: Cannot reverse drop_column "${change.table}"."${change.column}" -- column data was lost`,
11390
+ destructive: true
11391
+ };
11259
11392
  case "alter_column_type": {
11260
11393
  const fromType = change.meta?.fromType;
11261
11394
  if (!fromType) {
11262
- return `-- WARNING: Cannot reverse alter_column_type "${change.table}"."${change.column}" -- missing migration metadata`;
11395
+ return {
11396
+ sql: `-- WARNING: Cannot reverse alter_column_type "${change.table}"."${change.column}" -- missing migration metadata`,
11397
+ destructive: true
11398
+ };
11263
11399
  }
11264
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} TYPE ${fromType};`;
11400
+ return {
11401
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} TYPE ${fromType};`,
11402
+ destructive: true
11403
+ };
11265
11404
  }
11266
11405
  case "alter_column_nullable": {
11267
11406
  const oldNullable = change.meta?.oldNullable;
11268
11407
  if (oldNullable === void 0) {
11269
- return `-- WARNING: Cannot reverse alter_column_nullable "${change.table}"."${change.column}" -- missing migration metadata`;
11408
+ return {
11409
+ sql: `-- WARNING: Cannot reverse alter_column_nullable "${change.table}"."${change.column}" -- missing migration metadata`,
11410
+ destructive: true
11411
+ };
11270
11412
  }
11271
11413
  const action = oldNullable ? "DROP NOT NULL" : "SET NOT NULL";
11272
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} ${action};`;
11414
+ return {
11415
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} ${action};`,
11416
+ // Allowlisted: restores the recorded prior nullability metadata.
11417
+ destructive: false
11418
+ };
11273
11419
  }
11274
11420
  case "alter_column_default": {
11275
11421
  const oldDefault = change.meta?.oldDefault;
11276
11422
  if (oldDefault === void 0) {
11277
- return `-- WARNING: Cannot reverse alter_column_default "${change.table}"."${change.column}" -- missing migration metadata`;
11423
+ return {
11424
+ sql: `-- WARNING: Cannot reverse alter_column_default "${change.table}"."${change.column}" -- missing migration metadata`,
11425
+ destructive: true
11426
+ };
11278
11427
  }
11279
11428
  if (oldDefault === null) {
11280
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} DROP DEFAULT;`;
11429
+ return {
11430
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} DROP DEFAULT;`,
11431
+ // Allowlisted: restores the recorded prior state of "no default".
11432
+ destructive: false
11433
+ };
11281
11434
  }
11282
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} SET DEFAULT ${formatDefault(oldDefault)};`;
11435
+ return {
11436
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} SET DEFAULT ${formatDefault(oldDefault)};`,
11437
+ // Allowlisted: restores the recorded prior default value.
11438
+ destructive: false
11439
+ };
11283
11440
  }
11284
11441
  case "add_primary_key": {
11285
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP CONSTRAINT IF EXISTS ${quoteIdent2(pkName(change.table), "alias")} CASCADE;`;
11442
+ return {
11443
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP CONSTRAINT IF EXISTS ${quoteIdent2(pkName(change.table), "alias")} CASCADE;`,
11444
+ destructive: true
11445
+ };
11446
+ }
11447
+ case "drop_primary_key": {
11448
+ const columns = change.meta?.columns;
11449
+ if (Array.isArray(columns) && columns.length > 0) {
11450
+ const pkCols = columns.map((n) => quoteIdent2(n, "alias")).join(", ");
11451
+ return {
11452
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} ADD CONSTRAINT ${quoteIdent2(pkName(change.table), "alias")} PRIMARY KEY (${pkCols});`,
11453
+ // Allowlisted: re-adds the dropped primary-key constraint from metadata.
11454
+ destructive: false
11455
+ };
11456
+ }
11457
+ return {
11458
+ sql: `-- WARNING: Cannot reverse drop_primary_key "${change.table}" -- columns unknown`,
11459
+ destructive: true
11460
+ };
11286
11461
  }
11287
- case "drop_primary_key":
11288
- return `-- WARNING: Cannot reverse drop_primary_key "${change.table}" -- columns unknown`;
11289
11462
  case "add_foreign_key": {
11290
11463
  const fk = change.meta?.fk;
11291
- if (!fk) return void 0;
11464
+ if (!fk) return { sql: void 0, destructive: true };
11292
11465
  const constraintName = quoteIdent2(
11293
11466
  fkName(change.table, fk.columns),
11294
11467
  "alias"
11295
11468
  );
11296
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP CONSTRAINT IF EXISTS ${constraintName} CASCADE;`;
11469
+ return {
11470
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP CONSTRAINT IF EXISTS ${constraintName} CASCADE;`,
11471
+ destructive: true
11472
+ };
11473
+ }
11474
+ case "drop_foreign_key": {
11475
+ const fk = change.meta?.fk;
11476
+ if (!fk) {
11477
+ return {
11478
+ sql: `-- WARNING: Cannot reverse drop_foreign_key "${change.table}" -- FK definition was lost`,
11479
+ destructive: true
11480
+ };
11481
+ }
11482
+ return {
11483
+ sql: generateAddFKSQL(change.table, fk, schemaName),
11484
+ // Allowlisted: re-adds the dropped foreign-key constraint from metadata.
11485
+ destructive: false
11486
+ };
11297
11487
  }
11298
- case "drop_foreign_key":
11299
- return `-- WARNING: Cannot reverse drop_foreign_key "${change.table}" -- FK definition was lost`;
11300
11488
  case "alter_foreign_key": {
11301
11489
  const oldFk = change.meta?.oldFk;
11302
- if (!oldFk) {
11303
- return `-- WARNING: Cannot reverse alter_foreign_key "${change.table}" -- missing migration metadata`;
11304
- }
11305
11490
  const fk = change.meta?.fk;
11491
+ if (!oldFk || !fk) {
11492
+ return {
11493
+ sql: `-- WARNING: Cannot reverse alter_foreign_key "${change.table}" -- missing migration metadata`,
11494
+ destructive: true
11495
+ };
11496
+ }
11306
11497
  const constraintName = quoteIdent2(
11307
11498
  fkName(change.table, fk.columns),
11308
11499
  "alias"
11309
11500
  );
11310
11501
  const drop = `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP CONSTRAINT IF EXISTS ${constraintName};`;
11311
11502
  const add = generateAddFKSQL(change.table, oldFk, schemaName);
11312
- return `${drop}
11313
- ${add}`;
11503
+ return { sql: `${drop}
11504
+ ${add}`, destructive: false };
11314
11505
  }
11315
11506
  case "create_index": {
11316
11507
  const idx = change.meta?.index;
11317
- if (!idx) return void 0;
11508
+ if (!idx) return { sql: void 0, destructive: true };
11318
11509
  const indexName = quoteIdent2(
11319
11510
  idxName(change.table, idx.columns, idx.name),
11320
11511
  "alias"
11321
11512
  );
11322
11513
  const schemaPrefix = schemaName ? `${quoteIdent2(schemaName, "alias")}.` : "";
11323
- return `DROP INDEX IF EXISTS ${schemaPrefix}${indexName};`;
11514
+ return {
11515
+ sql: `DROP INDEX IF EXISTS ${schemaPrefix}${indexName};`,
11516
+ destructive: true
11517
+ };
11518
+ }
11519
+ case "drop_index": {
11520
+ const idx = change.meta?.index;
11521
+ if (!idx) {
11522
+ return {
11523
+ sql: `-- WARNING: Cannot reverse drop_index "${change.table}" -- index definition was lost`,
11524
+ destructive: true
11525
+ };
11526
+ }
11527
+ return {
11528
+ sql: upCreateIndex(change, schemaName),
11529
+ // Allowlisted: re-creates the dropped index from metadata.
11530
+ destructive: false
11531
+ };
11324
11532
  }
11325
- case "drop_index":
11326
- return `-- WARNING: Cannot reverse drop_index "${change.table}" -- index definition was lost`;
11327
11533
  case "add_check_constraint": {
11328
11534
  const check = change.meta?.check;
11329
- if (!check) return void 0;
11330
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP CONSTRAINT IF EXISTS ${quoteIdent2(check.name, "alias")};`;
11535
+ if (!check) return { sql: void 0, destructive: true };
11536
+ return {
11537
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP CONSTRAINT IF EXISTS ${quoteIdent2(check.name, "alias")};`,
11538
+ destructive: true
11539
+ };
11331
11540
  }
11332
11541
  case "drop_check_constraint": {
11333
11542
  const check = change.meta?.check;
11334
- if (!check) return void 0;
11543
+ if (!check) return { sql: void 0, destructive: true };
11335
11544
  validateSqlExpression(
11336
11545
  check.expression,
11337
11546
  "migration check constraint (down)"
11338
11547
  );
11339
- return "DO $$ BEGIN ALTER TABLE " + qualifyTable(change.table, schemaName) + " ADD CONSTRAINT " + quoteIdent2(check.name, "alias") + " " + check.expression + "; EXCEPTION WHEN duplicate_object THEN NULL; END $$;";
11548
+ return {
11549
+ sql: "DO $$ BEGIN ALTER TABLE " + qualifyTable(change.table, schemaName) + " ADD CONSTRAINT " + quoteIdent2(check.name, "alias") + " " + check.expression + "; EXCEPTION WHEN duplicate_object THEN NULL; END $$;",
11550
+ // Allowlisted: re-adds the dropped CHECK constraint from metadata.
11551
+ destructive: false
11552
+ };
11340
11553
  }
11341
11554
  case "create_enum": {
11342
11555
  const enumDef = change.meta?.enum;
11343
- if (!enumDef) return void 0;
11556
+ if (!enumDef) return { sql: void 0, destructive: true };
11344
11557
  const enumName = schemaName ? `${quoteIdent2(schemaName, "alias")}.${quoteIdent2(enumDef.name, "alias")}` : quoteIdent2(enumDef.name, "alias");
11345
- return `DROP TYPE IF EXISTS ${enumName} CASCADE;`;
11558
+ return {
11559
+ sql: `DROP TYPE IF EXISTS ${enumName} CASCADE;`,
11560
+ destructive: true
11561
+ };
11346
11562
  }
11347
11563
  case "drop_enum": {
11348
11564
  const enumDef = change.meta?.enum;
11349
- if (!enumDef) return void 0;
11565
+ if (!enumDef) return { sql: void 0, destructive: true };
11350
11566
  const enumName = schemaName ? `${quoteIdent2(schemaName, "alias")}.${quoteIdent2(enumDef.name, "alias")}` : quoteIdent2(enumDef.name, "alias");
11351
11567
  const values = enumDef.values.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
11352
- return `CREATE TYPE ${enumName} AS ENUM (${values});`;
11568
+ return {
11569
+ sql: `CREATE TYPE ${enumName} AS ENUM (${values});`,
11570
+ // Allowlisted: re-creates the dropped enum type from metadata.
11571
+ destructive: false
11572
+ };
11353
11573
  }
11354
11574
  case "alter_enum_add_value":
11355
- return `-- ALTER TYPE ADD VALUE cannot be reversed in PostgreSQL`;
11575
+ return {
11576
+ sql: `-- ALTER TYPE ADD VALUE cannot be reversed in PostgreSQL`,
11577
+ destructive: true
11578
+ };
11356
11579
  case "alter_column_collation": {
11357
- return `-- WARNING: Cannot reverse alter_column_collation "${change.table}"."${change.column}" -- previous collation unknown`;
11580
+ return {
11581
+ sql: `-- WARNING: Cannot reverse alter_column_collation "${change.table}"."${change.column}" -- previous collation unknown`,
11582
+ destructive: true
11583
+ };
11358
11584
  }
11359
11585
  case "alter_column_identity": {
11360
11586
  const col = change.meta?.column;
11361
11587
  const prevIdentity = change.meta?.previousIdentity;
11362
- if (!col) return void 0;
11588
+ if (!col) return { sql: void 0, destructive: true };
11363
11589
  const table = qualifyTable(change.table, schemaName);
11364
11590
  const column = quoteIdent2(change.column, "alias");
11365
11591
  if (prevIdentity && !col.identity) {
11366
11592
  const gen2 = prevIdentity === "always" ? "ALWAYS" : "BY DEFAULT";
11367
- return `ALTER TABLE ${table} ALTER COLUMN ${column} ADD GENERATED ${gen2} AS IDENTITY;`;
11593
+ return {
11594
+ sql: `ALTER TABLE ${table} ALTER COLUMN ${column} ADD GENERATED ${gen2} AS IDENTITY;`,
11595
+ // Allowlisted: re-adds the previously recorded identity metadata.
11596
+ destructive: false
11597
+ };
11368
11598
  }
11369
11599
  if (!prevIdentity && col.identity) {
11370
- return `ALTER TABLE ${table} ALTER COLUMN ${column} DROP IDENTITY IF EXISTS;`;
11600
+ return {
11601
+ sql: `ALTER TABLE ${table} ALTER COLUMN ${column} DROP IDENTITY IF EXISTS;`,
11602
+ destructive: true
11603
+ };
11604
+ }
11605
+ if (!prevIdentity && !col.identity) {
11606
+ return {
11607
+ sql: `-- WARNING: Cannot reverse alter_column_identity "${change.table}"."${change.column}" -- missing previous identity metadata`,
11608
+ destructive: true
11609
+ };
11371
11610
  }
11372
11611
  const gen = prevIdentity === "always" ? "ALWAYS" : "BY DEFAULT";
11373
- return `ALTER TABLE ${table} ALTER COLUMN ${column} SET GENERATED ${gen};`;
11612
+ return {
11613
+ sql: `ALTER TABLE ${table} ALTER COLUMN ${column} SET GENERATED ${gen};`,
11614
+ // Allowlisted: restores the recorded prior identity generation mode.
11615
+ destructive: false
11616
+ };
11374
11617
  }
11375
11618
  case "add_comment": {
11376
11619
  const target = change.meta?.target;
11377
11620
  if (target === "table") {
11378
- return `COMMENT ON TABLE ${qualifyTable(change.table, schemaName)} IS NULL;`;
11621
+ return {
11622
+ sql: `COMMENT ON TABLE ${qualifyTable(change.table, schemaName)} IS NULL;`,
11623
+ destructive: true
11624
+ };
11379
11625
  }
11380
- return `COMMENT ON COLUMN ${qualifyTable(change.table, schemaName)}.${quoteIdent2(change.column, "alias")} IS NULL;`;
11626
+ return {
11627
+ sql: `COMMENT ON COLUMN ${qualifyTable(change.table, schemaName)}.${quoteIdent2(change.column, "alias")} IS NULL;`,
11628
+ destructive: true
11629
+ };
11381
11630
  }
11382
11631
  case "drop_comment": {
11383
- return `-- WARNING: Cannot reverse drop_comment "${change.table}"${change.column ? `."${change.column}"` : ""} -- comment text was lost`;
11632
+ const target = change.meta?.target;
11633
+ const comment = change.meta?.comment;
11634
+ if (typeof comment === "string") {
11635
+ const escaped = comment.replace(/'/g, "''");
11636
+ if (target === "table") {
11637
+ return {
11638
+ sql: `COMMENT ON TABLE ${qualifyTable(change.table, schemaName)} IS '${escaped}';`,
11639
+ // Allowlisted: re-adds the dropped table comment from metadata.
11640
+ destructive: false
11641
+ };
11642
+ }
11643
+ if (target === "column") {
11644
+ return {
11645
+ sql: `COMMENT ON COLUMN ${qualifyTable(change.table, schemaName)}.${quoteIdent2(change.column, "alias")} IS '${escaped}';`,
11646
+ // Allowlisted: re-adds the dropped column comment from metadata.
11647
+ destructive: false
11648
+ };
11649
+ }
11650
+ }
11651
+ return {
11652
+ sql: `-- WARNING: Cannot reverse drop_comment "${change.table}"${change.column ? `."${change.column}"` : ""} -- comment text was lost`,
11653
+ destructive: true
11654
+ };
11384
11655
  }
11385
11656
  case "create_extension": {
11386
11657
  const ext = change.meta?.extension;
11387
- if (ext == null) return void 0;
11388
- return `DROP EXTENSION IF EXISTS ${quoteExtensionName(ext)} CASCADE;`;
11658
+ if (ext == null) return { sql: void 0, destructive: true };
11659
+ return {
11660
+ sql: `DROP EXTENSION IF EXISTS ${quoteExtensionName(ext)} CASCADE;`,
11661
+ destructive: true
11662
+ };
11389
11663
  }
11390
11664
  case "drop_extension": {
11391
11665
  const ext = change.meta?.extension;
11392
- if (ext == null) return void 0;
11393
- return `CREATE EXTENSION IF NOT EXISTS ${quoteExtensionName(ext)};`;
11666
+ if (ext == null) return { sql: void 0, destructive: true };
11667
+ return {
11668
+ sql: `CREATE EXTENSION IF NOT EXISTS ${quoteExtensionName(ext)};`,
11669
+ // Allowlisted: re-creates the dropped extension from metadata.
11670
+ destructive: false
11671
+ };
11394
11672
  }
11395
11673
  case "create_sequence": {
11396
11674
  const seq = change.meta?.sequence;
11397
- if (!seq) return void 0;
11675
+ if (!seq) return { sql: void 0, destructive: true };
11398
11676
  const seqName = schemaName ? `${quoteIdent2(schemaName, "alias")}.${quoteIdent2(seq.name, "alias")}` : quoteIdent2(seq.name, "alias");
11399
- return `DROP SEQUENCE IF EXISTS ${seqName} CASCADE;`;
11677
+ return {
11678
+ sql: `DROP SEQUENCE IF EXISTS ${seqName} CASCADE;`,
11679
+ destructive: true
11680
+ };
11400
11681
  }
11401
11682
  case "alter_sequence": {
11402
11683
  const prevSeq = change.meta?.previousSequence;
11403
11684
  if (!prevSeq) {
11404
- return `-- WARNING: Cannot reverse alter_sequence "${change.table}" -- missing migration metadata`;
11685
+ return {
11686
+ sql: `-- WARNING: Cannot reverse alter_sequence "${change.table}" -- missing migration metadata`,
11687
+ destructive: true
11688
+ };
11405
11689
  }
11406
11690
  const seqName = schemaName ? `${quoteIdent2(schemaName, "alias")}.${quoteIdent2(prevSeq.name, "alias")}` : quoteIdent2(prevSeq.name, "alias");
11407
- return buildSequenceClause("ALTER SEQUENCE", seqName, prevSeq, true);
11691
+ return {
11692
+ sql: buildSequenceClause("ALTER SEQUENCE", seqName, prevSeq, true),
11693
+ // Allowlisted: restores the recorded prior sequence metadata.
11694
+ destructive: false
11695
+ };
11408
11696
  }
11409
11697
  case "drop_sequence": {
11410
11698
  const seq = change.meta?.sequence;
11411
- if (!seq) return void 0;
11699
+ if (!seq) return { sql: void 0, destructive: true };
11412
11700
  const seqName = schemaName ? `${quoteIdent2(schemaName, "alias")}.${quoteIdent2(seq.name, "alias")}` : quoteIdent2(seq.name, "alias");
11413
- return buildSequenceClause("CREATE SEQUENCE", seqName, seq);
11701
+ return {
11702
+ sql: buildSequenceClause("CREATE SEQUENCE", seqName, seq),
11703
+ // Allowlisted: re-creates the dropped sequence from metadata.
11704
+ destructive: false
11705
+ };
11414
11706
  }
11415
11707
  case "validate_constraint":
11416
- return `-- VALIDATE CONSTRAINT cannot be reversed in PostgreSQL (table: "${change.table}")`;
11708
+ return {
11709
+ sql: `-- VALIDATE CONSTRAINT cannot be reversed in PostgreSQL (table: "${change.table}")`,
11710
+ destructive: true
11711
+ };
11417
11712
  case "enable_rls":
11418
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} DISABLE ROW LEVEL SECURITY;`;
11713
+ return {
11714
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} DISABLE ROW LEVEL SECURITY;`,
11715
+ destructive: true
11716
+ };
11419
11717
  case "disable_rls":
11420
- return `ALTER TABLE ${qualifyTable(change.table, schemaName)} ENABLE ROW LEVEL SECURITY;`;
11718
+ return {
11719
+ sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} ENABLE ROW LEVEL SECURITY;`,
11720
+ // Allowlisted: re-enables the previously present RLS security control.
11721
+ destructive: false
11722
+ };
11421
11723
  case "create_policy": {
11422
11724
  const policy = change.meta?.policy;
11423
- if (!policy) return void 0;
11424
- return `DROP POLICY IF EXISTS ${quoteIdent2(policy.name, "alias")} ON ${qualifyTable(change.table, schemaName)};`;
11725
+ if (!policy) return { sql: void 0, destructive: true };
11726
+ return {
11727
+ sql: `DROP POLICY IF EXISTS ${quoteIdent2(policy.name, "alias")} ON ${qualifyTable(change.table, schemaName)};`,
11728
+ destructive: true
11729
+ };
11425
11730
  }
11426
11731
  case "drop_policy": {
11427
11732
  const policy = change.meta?.policy;
11428
- if (!policy) return void 0;
11429
- return buildPolicySQL(change.table, policy, schemaName);
11733
+ if (!policy) return { sql: void 0, destructive: true };
11734
+ return {
11735
+ sql: buildPolicySQL(change.table, policy, schemaName),
11736
+ // Allowlisted: re-creates the dropped RLS policy from metadata.
11737
+ destructive: false
11738
+ };
11430
11739
  }
11740
+ default:
11741
+ return failSafeUnknownDownChange(change.kind);
11431
11742
  }
11432
11743
  }
11433
11744
  function generateDownSQL(diff, options) {
11745
+ return generateDownMigrationSQL(diff, options).statements;
11746
+ }
11747
+ function generateDownMigrationSQL(diff, options) {
11434
11748
  const schemaName = options?.schemaName;
11435
11749
  const includeDestructive = options?.includeDestructive ?? true;
11436
11750
  const changes = includeDestructive ? diff.changes : diff.changes.filter((c) => !c.destructive);
@@ -11479,13 +11793,15 @@ function generateDownSQL(diff, options) {
11479
11793
  phases[phase].push(change);
11480
11794
  }
11481
11795
  const statements = [];
11796
+ let destructive = false;
11482
11797
  for (let i = phases.length - 1; i >= 0; i--) {
11483
11798
  for (const change of phases[i]) {
11484
- const sql = changeToDownSQL(change, schemaName);
11485
- if (sql) statements.push(sql);
11799
+ const down = changeToDownSQL(change, schemaName);
11800
+ destructive = down.destructive || destructive;
11801
+ if (down.sql) statements.push(down.sql);
11486
11802
  }
11487
11803
  }
11488
- return statements;
11804
+ return { statements, destructive };
11489
11805
  }
11490
11806
  function generateCreateTableSQL(table, schemaName) {
11491
11807
  const qualTable = qualifyTable(table.name, schemaName);
@@ -11778,29 +12094,36 @@ function generateCreatePolicy(tableName, policy, schemaName, naming) {
11778
12094
  // src/ddl/migration-file.ts
11779
12095
  function generateMigrationFile(diff, options) {
11780
12096
  const upStatements = generateMigrationSQL(diff, options);
11781
- const downStatements = generateDownSQL(diff, options);
12097
+ const downMigration = generateDownMigrationSQL(diff, options);
12098
+ const downStatements = downMigration.statements;
12099
+ const destructiveHeader = `-- dbsp:destructive: ${downMigration.destructive ? "true" : "false"}`;
11782
12100
  const header = options?.name ? `-- Migration: ${options.name}
11783
12101
  -- Generated by: dbsp migrate dev
11784
12102
  -- Date: ${(/* @__PURE__ */ new Date()).toISOString()}
12103
+ ${destructiveHeader}
11785
12104
 
11786
- ` : "";
12105
+ ` : `${destructiveHeader}
12106
+
12107
+ `;
11787
12108
  const upSection = upStatements.length > 0 ? `${upStatements.join(";\n\n")};
11788
12109
  ` : "";
11789
12110
  const downSection = downStatements.length > 0 ? `${downStatements.join(";\n\n")};
11790
12111
  ` : "";
11791
- return `${header}${upSection}
11792
- -- DOWN
12112
+ const separatorPrefix = upSection.length > 0 ? "\n" : "";
12113
+ return `${header}${upSection}${separatorPrefix}-- DOWN
11793
12114
 
11794
12115
  ${downSection}`;
11795
12116
  }
11796
12117
  function parseMigrationFile(content) {
12118
+ const destructive = parseDestructiveHeader(content);
11797
12119
  const separatorRegex = /^\s*-- DOWN\s*$/m;
11798
12120
  const match = separatorRegex.exec(content);
11799
12121
  if (!match) {
11800
12122
  return {
11801
12123
  upStatements: splitStatements(content),
11802
12124
  downStatements: [],
11803
- hasDown: false
12125
+ hasDown: false,
12126
+ destructive
11804
12127
  };
11805
12128
  }
11806
12129
  const upContent = content.slice(0, match.index);
@@ -11808,9 +12131,36 @@ function parseMigrationFile(content) {
11808
12131
  return {
11809
12132
  upStatements: splitStatements(upContent),
11810
12133
  downStatements: splitStatements(downContent),
11811
- hasDown: true
12134
+ hasDown: true,
12135
+ destructive
11812
12136
  };
11813
12137
  }
12138
+ function parseDestructiveHeader(content) {
12139
+ let destructive;
12140
+ let found = false;
12141
+ for (const line of content.split(/\r?\n/)) {
12142
+ const trimmed = line.trim();
12143
+ if (trimmed.length === 0) {
12144
+ continue;
12145
+ }
12146
+ if (/^--\s*DOWN\s*$/i.test(trimmed)) {
12147
+ break;
12148
+ }
12149
+ if (!trimmed.startsWith("--")) {
12150
+ break;
12151
+ }
12152
+ const match = /^--\s*dbsp:destructive:\s*(true|false)\s*$/i.exec(trimmed);
12153
+ if (!match) {
12154
+ continue;
12155
+ }
12156
+ if (found) {
12157
+ return void 0;
12158
+ }
12159
+ found = true;
12160
+ destructive = match[1]?.toLowerCase() === "true";
12161
+ }
12162
+ return found ? destructive : void 0;
12163
+ }
11814
12164
  function isDestructiveDown(downStatements) {
11815
12165
  const destructivePatterns = [
11816
12166
  /DROP\s+TABLE/i,
@@ -12933,6 +13283,7 @@ function vectorDims(column) {
12933
13283
  // src/introspection.ts
12934
13284
  init_assert_field();
12935
13285
  import { ModelIRImpl } from "@dbsp/core";
13286
+ import { buildRelationKeyFields } from "@dbsp/types";
12936
13287
  function parseExpressionsList(raw) {
12937
13288
  const results = [];
12938
13289
  let depth = 0;
@@ -13488,9 +13839,10 @@ function inferRelations(fksByConstraint, filteredTables) {
13488
13839
  for (const [, fk] of fksByConstraint) {
13489
13840
  if (!filteredSet.has(fk.source) || !filteredSet.has(fk.target)) continue;
13490
13841
  const belongsToName = deriveRelationName(fk.cols[0], fk.target);
13491
- const fkCol = fk.cols.length === 1 ? fk.cols[0] : fk.cols;
13492
- const refCol = fk.refs.length === 1 ? fk.refs[0] : fk.refs;
13493
- const keyOverride = Array.isArray(refCol) || refCol !== DEFAULT_PK_COLUMN ? refCol : void 0;
13842
+ const relationFk = {
13843
+ columns: fk.cols,
13844
+ references: { table: fk.target, columns: fk.refs }
13845
+ };
13494
13846
  const belongsToKey = `${fk.source}.${belongsToName}`;
13495
13847
  if (!relations.has(belongsToKey)) {
13496
13848
  relations.set(belongsToKey, {
@@ -13498,8 +13850,7 @@ function inferRelations(fksByConstraint, filteredTables) {
13498
13850
  type: "belongsTo",
13499
13851
  source: fk.source,
13500
13852
  target: fk.target,
13501
- foreignKey: fkCol,
13502
- ...keyOverride !== void 0 && { targetKey: keyOverride },
13853
+ ...buildRelationKeyFields(relationFk, "belongsTo"),
13503
13854
  cardinality: "one",
13504
13855
  optionality: "optional",
13505
13856
  includeStrategy: "auto",
@@ -13515,8 +13866,7 @@ function inferRelations(fksByConstraint, filteredTables) {
13515
13866
  type: "hasMany",
13516
13867
  source: fk.target,
13517
13868
  target: fk.source,
13518
- foreignKey: fkCol,
13519
- ...keyOverride !== void 0 && { sourceKey: keyOverride },
13869
+ ...buildRelationKeyFields(relationFk, "inverse"),
13520
13870
  cardinality: "many",
13521
13871
  optionality: "optional",
13522
13872
  includeStrategy: "auto",
@@ -14419,7 +14769,7 @@ import { getNqlBindingRefName as getNqlBindingRefName2, isNqlBindingRef as isNql
14419
14769
 
14420
14770
  // src/adapter-compiler-includes.ts
14421
14771
  init_ast_helpers();
14422
- import { toColumnList as toColumnList9 } from "@dbsp/types";
14772
+ import { toColumnList as toColumnList8 } from "@dbsp/types";
14423
14773
  init_handlers();
14424
14774
  function compileSubqueryInclude(info, parentIds, _options, deps) {
14425
14775
  const schemaName = deps.schemaName;
@@ -14431,7 +14781,7 @@ function compileSubqueryInclude(info, parentIds, _options, deps) {
14431
14781
  parameters: []
14432
14782
  };
14433
14783
  }
14434
- const fkColumns = toColumnList9(info.foreignKey);
14784
+ const fkColumns = toColumnList8(info.foreignKey);
14435
14785
  if (fkColumns.length === 0) {
14436
14786
  throw new Error(
14437
14787
  "Subquery include requires at least one foreignKey column."
@@ -14529,7 +14879,7 @@ function compileSubqueryIncludeManyToMany(info, parentIds, schemaName, state, de
14529
14879
  const throughTable = info.through;
14530
14880
  const throughSourceKey = info.throughSourceKey;
14531
14881
  const throughTargetKey = info.throughTargetKey;
14532
- const targetPkColumns = toColumnList9(info.sourceKey);
14882
+ const targetPkColumns = toColumnList8(info.sourceKey);
14533
14883
  if (targetPkColumns.length !== 1) {
14534
14884
  throw new Error(
14535
14885
  `Many-to-many subquery include requires a single-column target key; got ${JSON.stringify(targetPkColumns)}.`
@@ -14624,7 +14974,7 @@ import {
14624
14974
  POSTGRESQL_CAPABILITIES,
14625
14975
  plan as planFn
14626
14976
  } from "@dbsp/core";
14627
- import { toColumnList as toColumnList12 } from "@dbsp/types";
14977
+ import { toColumnList as toColumnList11 } from "@dbsp/types";
14628
14978
 
14629
14979
  // src/adapter-compiler-select.ts
14630
14980
  init_assert_field();
@@ -14632,7 +14982,7 @@ init_ast_helpers();
14632
14982
  init_binding_registry();
14633
14983
  init_compile_where();
14634
14984
  import { countDistinctRelationPathsByName } from "@dbsp/core";
14635
- import { toColumnList as toColumnList11 } from "@dbsp/types";
14985
+ import { toColumnList as toColumnList10 } from "@dbsp/types";
14636
14986
  init_compiler_utils();
14637
14987
  init_types();
14638
14988
  init_intent_to_decisions();
@@ -14642,7 +14992,7 @@ init_param_ref();
14642
14992
  init_assert_field();
14643
14993
  init_intent_to_decisions();
14644
14994
  import { deriveRelationPathFromIntentPath } from "@dbsp/core";
14645
- import { toColumnList as toColumnList10 } from "@dbsp/types";
14995
+ import { toColumnList as toColumnList9 } from "@dbsp/types";
14646
14996
  function findExistsIntents(where) {
14647
14997
  if (!where || typeof where !== "object") return [];
14648
14998
  const w = where;
@@ -14663,7 +15013,7 @@ function findExistsIntents(where) {
14663
15013
  function resolveRelation(model, sourceTable, relationName) {
14664
15014
  const rel = model.getRelation(`${sourceTable}.${relationName}`);
14665
15015
  if (!rel) return void 0;
14666
- const foreignKeyColumns = toColumnList10(rel.foreignKey);
15016
+ const foreignKeyColumns = toColumnList9(rel.foreignKey);
14667
15017
  const foreignKey = foreignKeyColumns.length > 0 ? foreignKeyColumns : void 0;
14668
15018
  const relationType = rel.type;
14669
15019
  return { target: rel.target, foreignKey, relationType };
@@ -14887,7 +15237,7 @@ function buildMultiHopExistsChain(hops, rootTable, outerOperator, innerCondition
14887
15237
  const hopRelations = [];
14888
15238
  for (const hop of hops) {
14889
15239
  const rel = model.getRelation(`${currentSource}.${hop}`);
14890
- const foreignKeyColumns = rel ? toColumnList10(rel.foreignKey) : [];
15240
+ const foreignKeyColumns = rel ? toColumnList9(rel.foreignKey) : [];
14891
15241
  const foreignKey = foreignKeyColumns.length > 0 ? foreignKeyColumns : void 0;
14892
15242
  const relationType = rel?.type === "belongsTo" ? "belongsTo" : rel?.type === "hasMany" || rel?.type === "hasOne" ? rel.type : void 0;
14893
15243
  const parentKey = relationType === "belongsTo" ? rel?.targetKey : rel?.sourceKey;
@@ -14998,7 +15348,7 @@ function enrichExistsStubsInConditions(conditions, sourceTable, model) {
14998
15348
  `exists('${relation}'): no relation '${relation}' is declared on table '${sourceTable}'. Use rawExists(subquery(...)) for an EXISTS over an undeclared or uncorrelated subquery.`
14999
15349
  );
15000
15350
  }
15001
- const foreignKeyColumns = toColumnList10(rel.foreignKey);
15351
+ const foreignKeyColumns = toColumnList9(rel.foreignKey);
15002
15352
  const foreignKey = foreignKeyColumns.length > 0 ? foreignKeyColumns : void 0;
15003
15353
  const relationType = rel.type === "belongsTo" ? "belongsTo" : rel.type === "hasMany" || rel.type === "hasOne" ? rel.type : void 0;
15004
15354
  const parentKey = relationType === "belongsTo" ? rel.targetKey : rel.sourceKey;
@@ -15058,7 +15408,7 @@ function buildEnrichedExistsDecision(d, matchingIntent, rootTable, model) {
15058
15408
  const relIR = model && context.relation ? model.getRelation(
15059
15409
  `${sourceTableForRelation}.${context.relation}`
15060
15410
  ) : void 0;
15061
- const foreignKeyColumns = relIR ? toColumnList10(relIR.foreignKey) : [];
15411
+ const foreignKeyColumns = relIR ? toColumnList9(relIR.foreignKey) : [];
15062
15412
  const foreignKey = foreignKeyColumns.length > 0 ? foreignKeyColumns : void 0;
15063
15413
  const relationType = relIR?.type === "belongsTo" ? "belongsTo" : relIR?.type === "hasMany" || relIR?.type === "hasOne" ? relIR.type : void 0;
15064
15414
  const parentKey = relationType === "belongsTo" ? relIR?.targetKey : relIR?.sourceKey;
@@ -15395,10 +15745,7 @@ function toIncludeDecision(d, choice, plan, defaultPk = DEFAULT_PK_COLUMN, deriv
15395
15745
  ...relationType && { relationType },
15396
15746
  foreignKey,
15397
15747
  parentKey,
15398
- ...context.targetPrimaryKey && context.targetPrimaryKey.length > 0 ? {
15399
- targetPrimaryKey: context.targetPrimaryKey,
15400
- orderBy: context.targetPrimaryKey
15401
- } : {},
15748
+ ...context.targetOrderKey && context.targetOrderKey.length > 0 ? { orderBy: context.targetOrderKey } : {},
15402
15749
  ...context.orderByFallback ? { orderByFallback: true } : {},
15403
15750
  ...context.intentPath && { intentPath: context.intentPath },
15404
15751
  ...limit != null && { limit }
@@ -15470,7 +15817,7 @@ function synthesizeMissingJoinDecisions(plan, coveredRelations, model, defaultPk
15470
15817
  (r) => r.name === alias || snakeToCamel(r.name) === alias
15471
15818
  );
15472
15819
  if (!rel) continue;
15473
- const rawFkColumns = toColumnList10(rel.foreignKey);
15820
+ const rawFkColumns = toColumnList9(rel.foreignKey);
15474
15821
  const rawFk = rawFkColumns.length > 0 ? rawFkColumns : [
15475
15822
  deriveFk(
15476
15823
  rel.type === "belongsTo" ? rel.target : sourceTable,
@@ -15498,7 +15845,7 @@ function synthesizeMissingJoinDecisions(plan, coveredRelations, model, defaultPk
15498
15845
  relationType: rel.type
15499
15846
  },
15500
15847
  foreignKey: rawFk,
15501
- parentKey: rel.type === "belongsTo" ? toColumnList10(rel.targetKey).length > 0 ? toColumnList10(rel.targetKey) : [defaultPk] : toColumnList10(rel.sourceKey).length > 0 ? toColumnList10(rel.sourceKey) : [defaultPk],
15848
+ parentKey: rel.type === "belongsTo" ? toColumnList9(rel.targetKey).length > 0 ? toColumnList9(rel.targetKey) : [defaultPk] : toColumnList9(rel.sourceKey).length > 0 ? toColumnList9(rel.sourceKey) : [defaultPk],
15502
15849
  columns,
15503
15850
  joinType: inc.join,
15504
15851
  ...conditions && { conditions }
@@ -15594,9 +15941,6 @@ function assertSafeTypeName(typeName, colIndex) {
15594
15941
  }
15595
15942
  }
15596
15943
  }
15597
- function bridgeLegacyDecisions(decisions) {
15598
- return decisions;
15599
- }
15600
15944
  function buildBatchValuesRangeFn(bv, startParamIndex, aliasOverride) {
15601
15945
  const params = [];
15602
15946
  let paramIdx = startParamIndex - 1;
@@ -15655,10 +15999,10 @@ function compileJoinIntents(joins, rootTable, schemaName, deps) {
15655
15999
  );
15656
16000
  }
15657
16001
  const isBelongsTo = rel.type === "belongsTo";
15658
- const rawFk = toColumnList11(rel.foreignKey);
16002
+ const rawFk = toColumnList10(rel.foreignKey);
15659
16003
  const fkColumns = rawFk.length > 0 ? rawFk : [deriveFk(isBelongsTo ? rootTable : rel.target, defaultPk)];
15660
- const sourceKey = toColumnList11(rel.sourceKey);
15661
- const targetKey = toColumnList11(rel.targetKey);
16004
+ const sourceKey = toColumnList10(rel.sourceKey);
16005
+ const targetKey = toColumnList10(rel.targetKey);
15662
16006
  const sourceColumn = isBelongsTo ? fkColumns : sourceKey.length > 0 ? sourceKey : [defaultPk];
15663
16007
  const targetColumn = isBelongsTo ? targetKey.length > 0 ? targetKey : [defaultPk] : fkColumns;
15664
16008
  const alias = intent.alias ?? intent.relation;
@@ -16003,7 +16347,7 @@ function compileSelect(plan, options, deps) {
16003
16347
  } else {
16004
16348
  simplifiedPlan = {
16005
16349
  rootTable: plan.rootTable,
16006
- decisions: bridgeLegacyDecisions(plan.decisions),
16350
+ decisions: plan.decisions,
16007
16351
  ...schemaName ? { schema: schemaName } : {}
16008
16352
  };
16009
16353
  }
@@ -16023,8 +16367,8 @@ function compileWithIncludes(plan, options, deps) {
16023
16367
  const relationName = ctx.includeAlias ?? ctx.relation;
16024
16368
  if (!relationName) continue;
16025
16369
  const rawFk = deriveForeignKey(ctx, deps.deriveFk, deps.defaultPk) ?? deps.defaultPk;
16026
- const fk = toColumnList11(rawFk);
16027
- const parentKey = toColumnList11(ctx.parentKey);
16370
+ const fk = toColumnList10(rawFk);
16371
+ const parentKey = toColumnList10(ctx.parentKey);
16028
16372
  const isBelongsTo = ctx.relationType === "belongsTo";
16029
16373
  const sourceKey = isBelongsTo ? fk : parentKey.length > 0 ? parentKey : [deps.defaultPk];
16030
16374
  const targetFk = isBelongsTo ? parentKey.length > 0 ? parentKey : [deps.defaultPk] : fk;
@@ -16090,7 +16434,7 @@ function prependSourceCte(sql, parameters, sourceName, sourceCte, naming) {
16090
16434
  };
16091
16435
  }
16092
16436
  function resolveMutationExistsForeignKey(foreignKey, relationName) {
16093
- const columns = toColumnList12(foreignKey);
16437
+ const columns = toColumnList11(foreignKey);
16094
16438
  if (columns.length === 0) return void 0;
16095
16439
  if (columns.some((column) => column.length === 0)) {
16096
16440
  throw new Error(
@@ -16110,13 +16454,13 @@ function resolveExistsRelation(sourceTable, relation, model) {
16110
16454
  return {
16111
16455
  targetTable,
16112
16456
  ...fk2 !== void 0 && { sourceColumn: fk2 },
16113
- targetColumn: toColumnList12(rel.targetKey).length > 0 ? toColumnList12(rel.targetKey) : ["id"]
16457
+ targetColumn: toColumnList11(rel.targetKey).length > 0 ? toColumnList11(rel.targetKey) : ["id"]
16114
16458
  };
16115
16459
  }
16116
16460
  const fk = resolveMutationExistsForeignKey(rel.foreignKey, relationName);
16117
16461
  return {
16118
16462
  targetTable,
16119
- sourceColumn: toColumnList12(rel.sourceKey).length > 0 ? toColumnList12(rel.sourceKey) : ["id"],
16463
+ sourceColumn: toColumnList11(rel.sourceKey).length > 0 ? toColumnList11(rel.sourceKey) : ["id"],
16120
16464
  ...fk !== void 0 && { targetColumn: fk }
16121
16465
  };
16122
16466
  }
@@ -18038,6 +18382,9 @@ function orderedNqlBindingNames(bundle) {
18038
18382
  }
18039
18383
  return names;
18040
18384
  }
18385
+ function runtimeBindingSourceTable(bundle, name) {
18386
+ return bundle.mutationBindings?.get(name)?.table ?? bundle.bindings?.get(name)?.from;
18387
+ }
18041
18388
  function mapRuntimeBindingColumnType(type) {
18042
18389
  switch (type) {
18043
18390
  case "string":
@@ -18090,13 +18437,13 @@ function resolveRuntimeBindingColumnType(bindingName, sourceTable, columnName) {
18090
18437
  );
18091
18438
  if (column === void 0) {
18092
18439
  throw new Error(
18093
- `NQL runtime binding '${bindingName}' cannot resolve projected column '${columnName}' on source mutation table '${sourceTable.name}'.`
18440
+ `NQL runtime binding '${bindingName}' cannot resolve projected column '${columnName}' on source table '${sourceTable.name}'.`
18094
18441
  );
18095
18442
  }
18096
18443
  const dbType = column.originalDbType?.trim() || mapRuntimeBindingColumnType(column.type);
18097
18444
  if (dbType === void 0 || dbType.trim() === "") {
18098
18445
  throw new Error(
18099
- `NQL runtime binding '${bindingName}' cannot resolve a PostgreSQL type for projected column '${columnName}' on source mutation table '${sourceTable.name}'.`
18446
+ `NQL runtime binding '${bindingName}' cannot resolve a PostgreSQL type for projected column '${columnName}' on source table '${sourceTable.name}'.`
18100
18447
  );
18101
18448
  }
18102
18449
  const typeName = dbType.trim();
@@ -18119,7 +18466,7 @@ function resolveRuntimeBindingColumnTypes(name, binding, model, sourceTableName)
18119
18466
  const sourceTable = findRuntimeBindingSourceTable(model, sourceTableName);
18120
18467
  if (sourceTable === void 0) {
18121
18468
  throw new Error(
18122
- `NQL runtime binding '${name}' cannot resolve source mutation table '${sourceTableName}' in the model.`
18469
+ `NQL runtime binding '${name}' cannot resolve source table '${sourceTableName}' in the model.`
18123
18470
  );
18124
18471
  }
18125
18472
  return binding.columns.map(
@@ -18136,6 +18483,73 @@ function assertRuntimeBindingValuesParameterCount(name, binding, parameterOffset
18136
18483
  `NQL runtime binding '${name}' would materialize ${valueParameterCount} VALUES parameters; limit is ${MAX_NQL_RUNTIME_BINDING_VALUES_PARAMETERS}. Current parameter offset is ${parameterOffset}, which would make ${totalParameterCount} total parameters before compiling the final statement.`
18137
18484
  );
18138
18485
  }
18486
+ function resolvePgTypeForColumnTypeInfo(bindingName, column, info) {
18487
+ if (info.kind === "aggregate" && info.fn !== "count") {
18488
+ throw new Error(
18489
+ `NQL runtime binding '${bindingName}' cannot resolve a PostgreSQL type for projected column '${column}': unsupported aggregate kind '${String(info.fn)}'.`
18490
+ );
18491
+ }
18492
+ const rawType = info.kind === "aggregate" ? "bigint" : info.originalDbType?.trim() || mapRuntimeBindingColumnType(info.type);
18493
+ if (rawType === void 0 || rawType.trim() === "") {
18494
+ throw new Error(
18495
+ `NQL runtime binding '${bindingName}' cannot resolve a PostgreSQL type for projected column '${column}'.`
18496
+ );
18497
+ }
18498
+ const typeName = rawType.trim();
18499
+ try {
18500
+ validateTypeName4(typeName);
18501
+ } catch (error) {
18502
+ const reason = error instanceof Error ? error.message : String(error);
18503
+ throw new Error(
18504
+ `NQL runtime binding '${bindingName}' cannot use PostgreSQL cast type for projected column '${column}': ${reason}`
18505
+ );
18506
+ }
18507
+ return typeName;
18508
+ }
18509
+ function resolveRuntimeBindingCteColumnTypes(name, binding) {
18510
+ const columnTypes = binding.columnTypes;
18511
+ if (columnTypes === void 0) {
18512
+ throw new Error(
18513
+ `NQL runtime binding '${name}' cannot resolve typed columns without a columnTypes map.`
18514
+ );
18515
+ }
18516
+ return binding.columns.map((column) => {
18517
+ const info = columnTypes[column];
18518
+ if (info === void 0) {
18519
+ throw new Error(
18520
+ `NQL runtime binding '${name}' is missing type info for projected column '${column}'.`
18521
+ );
18522
+ }
18523
+ return resolvePgTypeForColumnTypeInfo(name, column, info);
18524
+ });
18525
+ }
18526
+ function compileTypedNqlRuntimeBindingCte(name, binding, naming, parameterOffset, cteName, columnSql) {
18527
+ const pgTypes = resolveRuntimeBindingCteColumnTypes(name, binding);
18528
+ const anchorColumns = binding.columns.map(
18529
+ (column, columnIndex) => `CAST(NULL AS ${pgTypes[columnIndex]}) AS ${quoteIdent2(naming.toDatabase(column), "column")}`
18530
+ ).join(", ");
18531
+ const sourceAnchorSql = `SELECT ${anchorColumns} WHERE false`;
18532
+ if (binding.rows.length === 0) {
18533
+ return {
18534
+ cte: `${cteName} (${columnSql}) as (${sourceAnchorSql})`,
18535
+ parameters: []
18536
+ };
18537
+ }
18538
+ assertRuntimeBindingValuesParameterCount(name, binding, parameterOffset);
18539
+ const parameters = [];
18540
+ let nextParam = parameterOffset + 1;
18541
+ const valuesSql = binding.rows.map((row) => {
18542
+ const placeholders = binding.columns.map((column, columnIndex) => {
18543
+ parameters.push(row[column]);
18544
+ return `$${nextParam++}::${pgTypes[columnIndex]}`;
18545
+ });
18546
+ return `(${placeholders.join(", ")})`;
18547
+ }).join(", ");
18548
+ return {
18549
+ cte: `${cteName} (${columnSql}) as (${sourceAnchorSql} UNION ALL VALUES ${valuesSql})`,
18550
+ parameters
18551
+ };
18552
+ }
18139
18553
  function compileNqlRuntimeBindingCte(name, binding, naming, parameterOffset, sourceTable, schemaName, model) {
18140
18554
  if (binding.columns.length === 0) {
18141
18555
  throw new Error(
@@ -18144,9 +18558,19 @@ function compileNqlRuntimeBindingCte(name, binding, naming, parameterOffset, sou
18144
18558
  }
18145
18559
  const cteName = quoteIdent2(emittedBindName(name, naming), "alias");
18146
18560
  const columnSql = binding.columns.map((column) => quoteIdent2(naming.toDatabase(column), "column")).join(", ");
18561
+ if (binding.columnTypes !== void 0) {
18562
+ return compileTypedNqlRuntimeBindingCte(
18563
+ name,
18564
+ binding,
18565
+ naming,
18566
+ parameterOffset,
18567
+ cteName,
18568
+ columnSql
18569
+ );
18570
+ }
18147
18571
  if (sourceTable === void 0) {
18148
18572
  throw new Error(
18149
- `NQL runtime binding '${name}' cannot materialize a typed relation because its source mutation table is unavailable.`
18573
+ `NQL runtime binding '${name}' cannot materialize a typed relation because its source table is unavailable.`
18150
18574
  );
18151
18575
  }
18152
18576
  const projectedColumns = binding.columns.map((column) => quoteIdent2(naming.toDatabase(column), "column")).join(", ");
@@ -18421,7 +18845,7 @@ var PgsqlAdapter = class _PgsqlAdapter {
18421
18845
  runtimeBinding,
18422
18846
  naming,
18423
18847
  parameters.length,
18424
- bundle.mutationBindings?.get(name)?.table,
18848
+ runtimeBindingSourceTable(bundle, name),
18425
18849
  deps.schemaName,
18426
18850
  deps.model
18427
18851
  );