@dbsp/adapter-pgsql 1.2.1 → 1.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/index.js CHANGED
@@ -3651,6 +3651,27 @@ var init_range = __esm({
3651
3651
  }
3652
3652
  });
3653
3653
 
3654
+ // src/binding-registry.ts
3655
+ function emittedBindName(name, naming) {
3656
+ return naming.toDatabase(name);
3657
+ }
3658
+ function hasBindingName(bindingNames, name, naming) {
3659
+ return bindingNames?.has(emittedBindName(name, naming)) ?? false;
3660
+ }
3661
+ function schemaForFromName(schemaName, fromName, bindingNames, naming) {
3662
+ return hasBindingName(bindingNames, fromName, naming) ? void 0 : schemaName;
3663
+ }
3664
+ function withBindingName(bindingNames, name, naming) {
3665
+ const next = new Set(bindingNames ?? []);
3666
+ next.add(emittedBindName(name, naming));
3667
+ return next;
3668
+ }
3669
+ var init_binding_registry = __esm({
3670
+ "src/binding-registry.ts"() {
3671
+ "use strict";
3672
+ }
3673
+ });
3674
+
3654
3675
  // src/compile-where.ts
3655
3676
  function toHandlerContext(ctx) {
3656
3677
  return {
@@ -3659,11 +3680,12 @@ function toHandlerContext(ctx) {
3659
3680
  currentAlias: ctx.currentAlias ?? ctx.rootTable,
3660
3681
  maxRecursiveDepth: 100,
3661
3682
  ...ctx.schemaName !== void 0 && { schema: ctx.schemaName },
3683
+ ...ctx.bindingNames !== void 0 && { bindingNames: ctx.bindingNames },
3662
3684
  ...ctx.model !== void 0 && { model: ctx.model },
3663
3685
  ...ctx.outerTable !== void 0 && { outerAlias: ctx.outerTable }
3664
3686
  };
3665
3687
  }
3666
- function buildSubqueryFromIntent(intent, paramOffset, naming = identityNaming, schemaName, use = "rawExists") {
3688
+ function buildSubqueryFromIntent(intent, paramOffset, naming = identityNaming, schemaName, use = "rawExists", bindingNames) {
3667
3689
  assertNoUnsupportedSubqueryModifiers(intent, use);
3668
3690
  if (intent.where && containsOuterRef(intent.where)) {
3669
3691
  throw new Error(
@@ -3708,9 +3730,14 @@ function buildSubqueryFromIntent(intent, paramOffset, naming = identityNaming, s
3708
3730
  }
3709
3731
  const stmt = {
3710
3732
  targetList,
3711
- // Bug 3 fix: propagate schema name from outer context so schema-scoped
3712
- // queries generate "schema"."table" AS alias instead of bare "table" AS alias.
3713
- fromClause: [rangeVar(targetTable, innerAlias, schemaName, naming)]
3733
+ fromClause: [
3734
+ rangeVar(
3735
+ targetTable,
3736
+ innerAlias,
3737
+ schemaForFromName(schemaName, targetTable, bindingNames, naming),
3738
+ naming
3739
+ )
3740
+ ]
3714
3741
  };
3715
3742
  let paramCount = 0;
3716
3743
  let innerParameters = [];
@@ -3724,6 +3751,7 @@ function buildSubqueryFromIntent(intent, paramOffset, naming = identityNaming, s
3724
3751
  aliases: /* @__PURE__ */ new Map(),
3725
3752
  paramState: innerState,
3726
3753
  naming,
3754
+ ...bindingNames !== void 0 && { bindingNames },
3727
3755
  compileSubquery: (_nestedIntent, _nestedOffset) => {
3728
3756
  throw new Error(
3729
3757
  "buildSubqueryFromIntent: nested subquery not supported"
@@ -4101,6 +4129,7 @@ var init_compile_where = __esm({
4101
4129
  init_custom();
4102
4130
  init_handlers();
4103
4131
  init_assert_field();
4132
+ init_binding_registry();
4104
4133
  init_types();
4105
4134
  init_utils();
4106
4135
  init_intent_to_decisions();
@@ -4145,7 +4174,8 @@ var init_raw_exists = __esm({
4145
4174
  state.paramIndex,
4146
4175
  ctx.naming,
4147
4176
  ctx.schema,
4148
- "rawExists"
4177
+ "rawExists",
4178
+ ctx.bindingNames
4149
4179
  );
4150
4180
  if (innerParams) {
4151
4181
  for (const p of innerParams) {
@@ -4381,7 +4411,19 @@ function buildPredicateSubquerySelect(use, sourceIntent, decision, ctx, state, d
4381
4411
  }
4382
4412
  const stmt = {
4383
4413
  targetList: [{ ResTarget: { val: targetVal } }],
4384
- fromClause: [rangeVar(targetTable, targetAlias, ctx.schema, ctx.naming)],
4414
+ fromClause: [
4415
+ rangeVar(
4416
+ targetTable,
4417
+ targetAlias,
4418
+ schemaForFromName(
4419
+ ctx.schema,
4420
+ targetTable,
4421
+ ctx.bindingNames,
4422
+ ctx.naming
4423
+ ),
4424
+ ctx.naming
4425
+ )
4426
+ ],
4385
4427
  ...whereClause && { whereClause }
4386
4428
  };
4387
4429
  if (decision.orderBy && decision.orderBy.length > 0) {
@@ -4418,6 +4460,7 @@ var init_subquery_emission = __esm({
4418
4460
  "src/subquery-emission.ts"() {
4419
4461
  "use strict";
4420
4462
  init_ast_helpers();
4463
+ init_binding_registry();
4421
4464
  init_intent_to_decisions();
4422
4465
  init_param_intent();
4423
4466
  }
@@ -7050,6 +7093,7 @@ import {
7050
7093
  NQL_SELECT_SCALAR_FUNCTION_ALLOWLIST,
7051
7094
  NQL_SELECT_WINDOW_FUNCTION_ALLOWLIST
7052
7095
  } from "@dbsp/types";
7096
+ import { getNqlBindingRefName, isNqlBindingRef } from "@dbsp/types/internal";
7053
7097
 
7054
7098
  // src/pgsql-deparser.ts
7055
7099
  function deparse(node) {
@@ -8336,6 +8380,7 @@ var PlanCompiler = class _PlanCompiler {
8336
8380
  defaultPk;
8337
8381
  deriveFk;
8338
8382
  model;
8383
+ bindingNames;
8339
8384
  /** Mutable state shared with extracted condition/value compilation functions */
8340
8385
  state = {
8341
8386
  parameters: [],
@@ -8370,6 +8415,7 @@ var PlanCompiler = class _PlanCompiler {
8370
8415
  this.defaultPk = options.defaultPkColumnName ?? DEFAULT_PK_COLUMN;
8371
8416
  this.deriveFk = options.deriveFkColumnName ?? defaultFkDerivation;
8372
8417
  this.model = options.model ?? void 0;
8418
+ this.bindingNames = options.bindingNames;
8373
8419
  }
8374
8420
  /** Build immutable context for handler-based WHERE compilation */
8375
8421
  handlerCtx() {
@@ -8381,6 +8427,7 @@ var PlanCompiler = class _PlanCompiler {
8381
8427
  defaultPkColumnName: this.defaultPk,
8382
8428
  deriveFkColumnName: this.deriveFk,
8383
8429
  ...this.schema != null && { schema: this.schema },
8430
+ ...this.bindingNames != null && { bindingNames: this.bindingNames },
8384
8431
  ...this.model != null && { model: this.model }
8385
8432
  };
8386
8433
  }
@@ -8690,6 +8737,7 @@ var PlanCompiler = class _PlanCompiler {
8690
8737
  defaultPkColumnName: this.defaultPk,
8691
8738
  deriveFkColumnName: this.deriveFk,
8692
8739
  ...plan.schema ?? this.schema ? { schema: plan.schema ?? this.schema } : {},
8740
+ ...this.bindingNames != null && { bindingNames: this.bindingNames },
8693
8741
  ...this.model != null && { model: this.model },
8694
8742
  compileSubquery: (query, paramOffset) => this.compileExpressionSubquery(query, paramOffset),
8695
8743
  compileNqlSelectExpression: (value, handlerCtx, state) => this.compileNqlFunctionArg(value, handlerCtx, state)
@@ -8712,7 +8760,10 @@ var PlanCompiler = class _PlanCompiler {
8712
8760
  schema: this.schema
8713
8761
  },
8714
8762
  defaultPkColumnName: this.defaultPk,
8715
- deriveFkColumnName: this.deriveFk
8763
+ deriveFkColumnName: this.deriveFk,
8764
+ ...this.bindingNames !== void 0 && {
8765
+ bindingNames: this.bindingNames
8766
+ }
8716
8767
  });
8717
8768
  const innerPlan = {
8718
8769
  rootTable: query.from,
@@ -8731,14 +8782,14 @@ var PlanCompiler = class _PlanCompiler {
8731
8782
  return funcCall(functionName, argNodes);
8732
8783
  }
8733
8784
  compileNqlFunctionArg(arg, ctx, state) {
8785
+ if (isNqlBindingRef(arg)) {
8786
+ return buildColumnRef(getNqlBindingRefName(arg), ctx);
8787
+ }
8734
8788
  if (typeof arg === "string") {
8735
8789
  return buildColumnRef(arg, ctx);
8736
8790
  }
8737
8791
  if (typeof arg === "object" && arg !== null) {
8738
8792
  const record = arg;
8739
- if (typeof record.$ref === "string") {
8740
- return buildColumnRef(record.$ref, ctx);
8741
- }
8742
8793
  if (typeof record.kind !== "string") {
8743
8794
  const legacyKind = typeof record.$op === "string" ? `$op:${record.$op}` : typeof record.$fn === "string" ? `$fn:${record.$fn}` : "object";
8744
8795
  throw new UnhandledNqlSelectExpressionKindError(legacyKind);
@@ -9390,19 +9441,35 @@ var PlanCompiler = class _PlanCompiler {
9390
9441
  }
9391
9442
  return void 0;
9392
9443
  }
9444
+ /**
9445
+ * Compile a column reference that may use dotted 'relation.column' notation.
9446
+ */
9447
+ compileRelationAwareColumnRef(column, table) {
9448
+ const dot = column.lastIndexOf(".");
9449
+ if (dot !== -1) {
9450
+ const relation = column.slice(0, dot);
9451
+ const alias = this.resolvedJoinAliases().get(relation) ?? relation;
9452
+ return columnRef(column.slice(dot + 1), alias, void 0, this.naming);
9453
+ }
9454
+ return columnRef(column, table, void 0, this.naming);
9455
+ }
9393
9456
  /**
9394
9457
  * Compile a single groupBy decision into a ColumnRef AST node.
9395
9458
  * Supports dotted 'relation.column' notation for joined tables.
9396
9459
  */
9397
9460
  compileGroupByDecision(decision) {
9398
- const gbCol = decision.column;
9399
- const gbDot = gbCol.lastIndexOf(".");
9400
- if (gbDot !== -1) {
9401
- const relation = gbCol.slice(0, gbDot);
9402
- const alias = this.resolvedJoinAliases().get(relation) ?? relation;
9403
- return columnRef(gbCol.slice(gbDot + 1), alias, void 0, this.naming);
9404
- }
9405
- return columnRef(gbCol, decision.table, void 0, this.naming);
9461
+ return this.compileRelationAwareColumnRef(
9462
+ decision.column,
9463
+ decision.table
9464
+ );
9465
+ }
9466
+ /**
9467
+ * Compile a DISTINCT ON column into a ColumnRef AST node.
9468
+ * Mirrors GROUP BY relation-alias resolution while preserving unqualified
9469
+ * DISTINCT ON columns as unqualified references.
9470
+ */
9471
+ compileDistinctOnColumn(column) {
9472
+ return this.compileRelationAwareColumnRef(column, void 0);
9406
9473
  }
9407
9474
  /**
9408
9475
  * Assemble the final SelectStmt from all accumulated clause nodes.
@@ -9539,7 +9606,7 @@ var PlanCompiler = class _PlanCompiler {
9539
9606
  case "distinctOn":
9540
9607
  if (decision.columns && decision.columns.length > 0) {
9541
9608
  distinct = decision.columns.map(
9542
- (col) => columnRef(col, void 0, void 0, this.naming)
9609
+ (col) => this.compileDistinctOnColumn(col)
9543
9610
  );
9544
9611
  }
9545
9612
  break;
@@ -13139,6 +13206,7 @@ function matchGlob(pattern, value) {
13139
13206
 
13140
13207
  // src/mutations/mutation-compiler.ts
13141
13208
  init_ast_helpers();
13209
+ init_binding_registry();
13142
13210
  init_compiler_utils();
13143
13211
  init_handlers();
13144
13212
  init_param_intent();
@@ -13364,12 +13432,18 @@ function compileInsertFrom(config, ctx, state) {
13364
13432
  const _dbTargetTable = naming.toDatabase(config.targetTable);
13365
13433
  const dbSourceTable = naming.toDatabase(config.sourceTable);
13366
13434
  const sourceAlias = config.sourceTable;
13435
+ const sourceSchema = schemaForFromName(
13436
+ ctx.schema,
13437
+ config.sourceTable,
13438
+ ctx.bindingNames,
13439
+ naming
13440
+ );
13367
13441
  const dbColumns = config.columns?.map((c) => naming.toDatabase(c));
13368
13442
  let targetList;
13369
13443
  if (config.columns && config.columns.length > 0) {
13370
13444
  targetList = config.columns.map(
13371
13445
  (col) => resTarget(
13372
- columnRef(col, sourceAlias, ctx.schema, naming),
13446
+ columnRef(col, sourceAlias, sourceSchema, naming),
13373
13447
  naming.toDatabase(col)
13374
13448
  )
13375
13449
  );
@@ -13409,8 +13483,8 @@ function compileInsertFrom(config, ctx, state) {
13409
13483
  inh: true,
13410
13484
  relpersistence: "p"
13411
13485
  };
13412
- if (ctx.schema) {
13413
- sourceRelation.schemaname = naming.toDatabase(ctx.schema);
13486
+ if (sourceSchema) {
13487
+ sourceRelation.schemaname = naming.toDatabase(sourceSchema);
13414
13488
  }
13415
13489
  const selectQuery = {
13416
13490
  SelectStmt: {
@@ -13439,12 +13513,18 @@ function compileUpsertFrom(config, ctx, state) {
13439
13513
  const naming = ctx.naming;
13440
13514
  const dbSourceTable = naming.toDatabase(config.sourceTable);
13441
13515
  const sourceAlias = config.sourceTable;
13516
+ const sourceSchema = schemaForFromName(
13517
+ ctx.schema,
13518
+ config.sourceTable,
13519
+ ctx.bindingNames,
13520
+ naming
13521
+ );
13442
13522
  const dbColumns = config.columns?.map((c) => naming.toDatabase(c));
13443
13523
  let targetList;
13444
13524
  if (config.columns && config.columns.length > 0) {
13445
13525
  targetList = config.columns.map(
13446
13526
  (col) => resTarget(
13447
- columnRef(col, sourceAlias, ctx.schema, naming),
13527
+ columnRef(col, sourceAlias, sourceSchema, naming),
13448
13528
  naming.toDatabase(col)
13449
13529
  )
13450
13530
  );
@@ -13484,8 +13564,8 @@ function compileUpsertFrom(config, ctx, state) {
13484
13564
  inh: true,
13485
13565
  relpersistence: "p"
13486
13566
  };
13487
- if (ctx.schema) {
13488
- sourceRelation.schemaname = naming.toDatabase(ctx.schema);
13567
+ if (sourceSchema) {
13568
+ sourceRelation.schemaname = naming.toDatabase(sourceSchema);
13489
13569
  }
13490
13570
  const selectQuery = {
13491
13571
  SelectStmt: {
@@ -13630,6 +13710,28 @@ init_compiler_utils();
13630
13710
  init_handlers();
13631
13711
  init_param_intent();
13632
13712
  init_param_ref();
13713
+ function buildWhereClause(conditions, ctx, state) {
13714
+ if (!conditions || conditions.length === 0) return void 0;
13715
+ const dispatch = createWhereDispatcher();
13716
+ if (conditions.length === 1) {
13717
+ return dispatch(conditions[0], ctx, state);
13718
+ }
13719
+ const nodes = conditions.map((condition) => dispatch(condition, ctx, state));
13720
+ return {
13721
+ BoolExpr: { boolop: "AND_EXPR", args: nodes }
13722
+ };
13723
+ }
13724
+ function buildActionWhereClause(config, ctx, state) {
13725
+ if (config.actionWhereIntent) {
13726
+ if (!config.compileActionWhere) {
13727
+ throw new Error(
13728
+ "Upsert actionWhereIntent requires compileActionWhere callback"
13729
+ );
13730
+ }
13731
+ return config.compileActionWhere(config.actionWhereIntent, state);
13732
+ }
13733
+ return buildWhereClause(config.actionWhere, ctx, state);
13734
+ }
13633
13735
  function buildOnConflictClause(config, ctx, state) {
13634
13736
  const naming = ctx.naming;
13635
13737
  const action = config.conflictAction;
@@ -13643,18 +13745,12 @@ function buildOnConflictClause(config, ctx, state) {
13643
13745
  }))
13644
13746
  };
13645
13747
  if (config.conflictTarget.where && config.conflictTarget.where.length > 0) {
13646
- const dispatch = createWhereDispatcher();
13647
- const conditions = config.conflictTarget.where;
13648
- let whereClause;
13649
- if (conditions.length === 1) {
13650
- whereClause = dispatch(conditions[0], ctx, state);
13651
- } else {
13652
- const nodes = conditions.map((c) => dispatch(c, ctx, state));
13653
- whereClause = {
13654
- BoolExpr: { boolop: "AND_EXPR", args: nodes }
13655
- };
13656
- }
13657
- infer.whereClause = whereClause;
13748
+ const whereClause2 = buildWhereClause(
13749
+ config.conflictTarget.where,
13750
+ ctx,
13751
+ state
13752
+ );
13753
+ if (whereClause2) infer.whereClause = whereClause2;
13658
13754
  }
13659
13755
  } else if (config.conflictTarget.constraint) {
13660
13756
  infer = {
@@ -13700,10 +13796,12 @@ function buildOnConflictClause(config, ctx, state) {
13700
13796
  }
13701
13797
  };
13702
13798
  });
13799
+ const whereClause = buildActionWhereClause(config, ctx, state);
13703
13800
  return {
13704
13801
  action: "ONCONFLICT_UPDATE",
13705
13802
  ...infer && { infer },
13706
- targetList
13803
+ targetList,
13804
+ ...whereClause && { whereClause }
13707
13805
  };
13708
13806
  }
13709
13807
  function compileUpsert(config, ctx, state) {
@@ -13841,7 +13939,8 @@ init_naming_plugin();
13841
13939
  init_param_ref();
13842
13940
 
13843
13941
  // src/pgsql-adapter.ts
13844
- import { POSTGRESQL_CAPABILITIES, plan as planFn } from "@dbsp/core";
13942
+ import { POSTGRESQL_CAPABILITIES as POSTGRESQL_CAPABILITIES2, plan as planFn2 } from "@dbsp/core";
13943
+ import { getNqlBindingRefName as getNqlBindingRefName2, isNqlBindingRef as isNqlBindingRef2 } from "@dbsp/types/internal";
13845
13944
 
13846
13945
  // src/adapter-compiler-includes.ts
13847
13946
  init_ast_helpers();
@@ -14027,449 +14126,97 @@ function compileSubqueryIncludeManyToMany(info, parentIds, schemaName, state, de
14027
14126
  }
14028
14127
 
14029
14128
  // src/adapter-compiler-mutations.ts
14129
+ import {
14130
+ InvalidOperationError as InvalidOperationError3,
14131
+ isSqlRaw as isSqlRaw2,
14132
+ POSTGRESQL_CAPABILITIES,
14133
+ plan as planFn
14134
+ } from "@dbsp/core";
14135
+
14136
+ // src/adapter-compiler-select.ts
14137
+ init_assert_field();
14138
+ init_ast_helpers();
14030
14139
  init_compile_where();
14140
+ import { countDistinctRelationPathsByName } from "@dbsp/core";
14031
14141
  init_compiler_utils();
14032
- import { InvalidOperationError as InvalidOperationError3, isSqlRaw as isSqlRaw2 } from "@dbsp/core";
14033
- init_handlers();
14034
- function whereIntentAsDecision(where) {
14035
- return where;
14036
- }
14037
- function resolveExistsRelation(sourceTable, relation, model) {
14038
- if (!model) return { targetTable: relation };
14039
- const rel = model.getRelation(`${sourceTable}.${relation}`);
14040
- if (!rel) return { targetTable: relation };
14041
- const targetTable = rel.target;
14042
- if (rel.type === "belongsTo") {
14043
- const fk = typeof rel.foreignKey === "string" ? rel.foreignKey : rel.foreignKey?.[0];
14044
- return {
14045
- targetTable,
14046
- ...fk !== void 0 && { sourceColumn: fk },
14047
- targetColumn: "id"
14048
- };
14049
- }
14050
- return { targetTable };
14051
- }
14052
- function resolveExistsIntent(where, sourceTable, deps) {
14142
+ init_types();
14143
+ init_intent_to_decisions();
14144
+ init_param_ref();
14145
+
14146
+ // src/plan-decision-extractor.ts
14147
+ init_assert_field();
14148
+ init_intent_to_decisions();
14149
+ import { deriveRelationPathFromIntentPath } from "@dbsp/core";
14150
+ function findExistsIntents(where) {
14151
+ if (!where || typeof where !== "object") return [];
14053
14152
  const w = where;
14054
- const kind = w.kind;
14055
- if (kind === "and" || kind === "or") {
14056
- const conditions = w.conditions;
14057
- if (!conditions) return where;
14058
- const enriched = conditions.map(
14059
- (c) => resolveExistsIntent(c, sourceTable, deps)
14060
- );
14061
- const changed = enriched.some((c, i) => c !== conditions[i]);
14062
- return changed ? { ...w, conditions: enriched } : where;
14063
- }
14064
- if (kind === "not") {
14065
- const condition = w.condition;
14066
- if (!condition) return where;
14067
- const enriched = resolveExistsIntent(condition, sourceTable, deps);
14068
- return enriched !== condition ? { ...w, condition: enriched } : where;
14153
+ if (w.kind === "exists" || w.kind === "notExists" || w.kind === "relationFilter") {
14154
+ return [w];
14069
14155
  }
14070
- if (kind !== "exists" && kind !== "notExists") return where;
14071
- const relation = w.relation;
14072
- const resolved = resolveExistsRelation(sourceTable, relation, deps.model);
14073
- if (resolved.targetTable === relation && !resolved.sourceColumn) return where;
14074
- return {
14075
- ...w,
14076
- targetTable: resolved.targetTable,
14077
- ...resolved.sourceColumn !== void 0 && {
14078
- sourceColumn: resolved.sourceColumn
14079
- },
14080
- ...resolved.targetColumn !== void 0 && {
14081
- targetColumn: resolved.targetColumn
14156
+ const results = [];
14157
+ if (w.conditions && Array.isArray(w.conditions)) {
14158
+ for (const c of w.conditions) {
14159
+ results.push(...findExistsIntents(c));
14082
14160
  }
14083
- };
14161
+ }
14162
+ if (w.condition) {
14163
+ results.push(...findExistsIntents(w.condition));
14164
+ }
14165
+ return results;
14084
14166
  }
14085
- function getColumnTypes(tableName, columns, deps) {
14086
- if (!deps.model) return void 0;
14087
- const table = deps.model.getTable(tableName);
14088
- if (!table) return void 0;
14089
- let result;
14090
- for (const col of columns) {
14091
- const columnIR = table.columns.find((c) => c.name === col);
14092
- if (columnIR) {
14093
- result ??= {};
14094
- result[col] = columnIR.originalDbType ?? columnIR.type;
14167
+ function resolveRelation(model, sourceTable, relationName) {
14168
+ const rel = model.getRelation(`${sourceTable}.${relationName}`);
14169
+ if (!rel) return void 0;
14170
+ const foreignKey = typeof rel.foreignKey === "string" ? rel.foreignKey : rel.foreignKey?.[0];
14171
+ const relationType = rel.type;
14172
+ return { target: rel.target, foreignKey, relationType };
14173
+ }
14174
+ function resolveIncludeAlias(context) {
14175
+ return context.relation ?? context.includeAlias;
14176
+ }
14177
+ function resolveIncludeByPath(includes, intentPath, relationName) {
14178
+ if (!includes) return void 0;
14179
+ if (intentPath) {
14180
+ const indexPattern = /include\[(\d+)\]/g;
14181
+ let current = includes;
14182
+ let resolved;
14183
+ let execResult = indexPattern.exec(intentPath);
14184
+ while (execResult !== null) {
14185
+ const idx = parseInt(execResult[1], 10);
14186
+ const item = current[idx];
14187
+ if (!item) break;
14188
+ resolved = item;
14189
+ current = item.include ?? [];
14190
+ execResult = indexPattern.exec(intentPath);
14095
14191
  }
14192
+ if (resolved) return resolved;
14096
14193
  }
14097
- return result;
14194
+ return includes.find(
14195
+ (i) => i.relation === relationName || i.via === relationName
14196
+ );
14098
14197
  }
14099
- function compileInsert2(intent, options, deps) {
14100
- const schemaName = deps.schemaName;
14101
- const ctx = {
14102
- naming: deps.naming,
14103
- rootTable: intent.table,
14104
- ...schemaName !== void 0 && { schema: schemaName },
14105
- maxRecursiveDepth: 100
14106
- };
14107
- const state = createCompilerState();
14108
- const firstRow = intent.values?.[0] ?? {};
14109
- const columns = Object.keys(firstRow);
14110
- const rows = intent.values ?? [];
14111
- const values = rows.map((row) => columns.map((col) => row[col]));
14112
- const columnTypes = getColumnTypes(intent.table, columns, deps);
14113
- const config = {
14114
- table: intent.table,
14115
- columns,
14116
- values,
14117
- ...intent.returning && { returning: [...intent.returning] },
14118
- ...columnTypes && { columnTypes }
14119
- };
14120
- const maxBatchSize = options?.maxBatchSize;
14121
- if (maxBatchSize !== void 0 && values.length > maxBatchSize) {
14122
- throw new InvalidOperationError3(
14123
- "insert",
14124
- `Batch size ${values.length} exceeds maxBatchSize ${maxBatchSize}`
14125
- );
14198
+ function deriveForeignKey(context, deriveFk = defaultFkDerivation, defaultPk = DEFAULT_PK_COLUMN) {
14199
+ const fk = context.foreignKey ?? context.sourceFK;
14200
+ if (fk) return fk;
14201
+ if (!context.relationType) return void 0;
14202
+ if (context.relationType === "belongsTo") {
14203
+ return context.target ? deriveFk(context.target, defaultPk) : void 0;
14126
14204
  }
14127
- const batchThreshold = options?.batchThreshold ?? 50;
14128
- const useUnnest = values.length > 0 && (batchThreshold === 0 || values.length > batchThreshold);
14129
- const ast = useUnnest ? compileUnnestInsert(config, ctx, state) : compileInsert(config, ctx, state);
14130
- const sql = deparseQuoted(ast);
14131
- return {
14132
- sql,
14133
- parameters: state.parameters
14134
- };
14205
+ return context.sourceTable ? deriveFk(context.sourceTable, defaultPk) : void 0;
14135
14206
  }
14136
- function compileInsertFrom2(intent, _options, deps) {
14137
- const schemaName = deps.schemaName;
14138
- const ctx = {
14139
- naming: deps.naming,
14140
- rootTable: intent.source,
14141
- ...schemaName !== void 0 && { schema: schemaName },
14142
- maxRecursiveDepth: 100
14143
- };
14144
- const state = createCompilerState();
14145
- const config = {
14146
- targetTable: intent.table,
14147
- sourceTable: intent.source,
14148
- ...intent.columns && { columns: [...intent.columns] },
14149
- ...intent.where && { where: [whereIntentAsDecision(intent.where)] },
14150
- ...intent.limit !== void 0 && { limit: intent.limit },
14151
- ...intent.returning && { returning: [...intent.returning] }
14152
- };
14153
- const ast = compileInsertFrom(config, ctx, state);
14154
- const sql = deparseQuoted(ast);
14155
- return {
14156
- sql,
14157
- parameters: state.parameters
14207
+ function mapComparisonOperator(op3) {
14208
+ const map = {
14209
+ eq: "=",
14210
+ neq: "!=",
14211
+ gt: ">",
14212
+ gte: ">=",
14213
+ lt: "<",
14214
+ lte: "<=",
14215
+ like: "LIKE",
14216
+ ilike: "ILIKE",
14217
+ isDistinctFrom: "IS DISTINCT FROM"
14158
14218
  };
14159
- }
14160
- function compileUpdate2(intent, _options, deps) {
14161
- const schemaName = deps.schemaName;
14162
- const ctx = {
14163
- naming: deps.naming,
14164
- rootTable: intent.table,
14165
- ...schemaName !== void 0 && { schema: schemaName },
14166
- maxRecursiveDepth: 100
14167
- };
14168
- const state = createCompilerState();
14169
- const setColumns = Object.keys(intent.set ?? {});
14170
- const columnTypes = getColumnTypes(intent.table, setColumns, deps);
14171
- const config = {
14172
- table: intent.table,
14173
- set: Object.entries(intent.set ?? {}).map(([column, value]) => ({
14174
- column,
14175
- value
14176
- })),
14177
- ...intent.where && { where: [whereIntentAsDecision(intent.where)] },
14178
- ...intent.returning && { returning: [...intent.returning] },
14179
- ...columnTypes && { columnTypes }
14180
- };
14181
- const ast = compileUpdate(config, ctx, state);
14182
- const sql = deparseQuoted(ast);
14183
- return {
14184
- sql,
14185
- parameters: state.parameters
14186
- };
14187
- }
14188
- function compileBatchUpdate(intent, _options, deps) {
14189
- const schemaName = deps.schemaName;
14190
- const ctx = {
14191
- naming: deps.naming,
14192
- rootTable: intent.table,
14193
- ...schemaName !== void 0 && { schema: schemaName },
14194
- maxRecursiveDepth: 100
14195
- };
14196
- const state = createCompilerState();
14197
- if (intent.updates.length === 0) {
14198
- throw new InvalidOperationError3(
14199
- "update",
14200
- "batchSet requires at least one row"
14201
- );
14202
- }
14203
- const allColumns = Object.keys(intent.updates[0]);
14204
- const matchColumns = [...intent.matchColumns];
14205
- for (const mc of matchColumns) {
14206
- if (!allColumns.includes(mc)) {
14207
- throw new InvalidOperationError3(
14208
- "update",
14209
- `Match column "${mc}" not found in update data. Each row must include the match column(s).`
14210
- );
14211
- }
14212
- }
14213
- const values = intent.updates.map((row) => allColumns.map((col) => row[col]));
14214
- validateBatchCardinality(allColumns, values);
14215
- const columnArrays = transposeToColumnArrays(allColumns, values);
14216
- const columnTypes = getColumnTypes(intent.table, allColumns, deps);
14217
- const scalarSet = intent.scalarSet ? Object.entries(intent.scalarSet).map(([column, value]) => ({
14218
- column,
14219
- value
14220
- })) : void 0;
14221
- let whereGuard;
14222
- if (intent.where) {
14223
- const whereCtx = {
14224
- rootTable: intent.table,
14225
- aliases: /* @__PURE__ */ new Map(),
14226
- paramState: state,
14227
- naming: deps.naming,
14228
- ...schemaName !== void 0 && { schemaName },
14229
- ...deps.model !== void 0 && { model: deps.model },
14230
- compileSubquery: (sqIntent, paramOffset) => buildSubqueryFromIntent(
14231
- sqIntent,
14232
- paramOffset,
14233
- deps.naming,
14234
- schemaName,
14235
- "rawExists"
14236
- )
14237
- };
14238
- whereGuard = compileWhereIntent(intent.where, whereCtx);
14239
- }
14240
- const config = {
14241
- table: intent.table,
14242
- matchColumns,
14243
- allColumns,
14244
- columnArrays,
14245
- ...scalarSet && { scalarSet },
14246
- ...intent.returning && { returning: [...intent.returning] },
14247
- ...columnTypes && { columnTypes },
14248
- ...whereGuard !== void 0 && { whereGuard }
14249
- };
14250
- const ast = compileUnnestUpdate(config, ctx, state);
14251
- const sql = deparseQuoted(ast);
14252
- return {
14253
- sql,
14254
- parameters: state.parameters
14255
- };
14256
- }
14257
- function compileDelete2(intent, options, deps) {
14258
- const schemaName = deps.schemaName;
14259
- const resolvedModel = options?.model ?? deps.model;
14260
- const ctx = {
14261
- naming: deps.naming,
14262
- rootTable: intent.table,
14263
- ...schemaName !== void 0 && { schema: schemaName },
14264
- maxRecursiveDepth: 100,
14265
- ...resolvedModel !== void 0 && { model: resolvedModel }
14266
- };
14267
- const state = createCompilerState();
14268
- const resolvedWhere = intent.where ? resolveExistsIntent(intent.where, intent.table, deps) : void 0;
14269
- const config = {
14270
- table: intent.table,
14271
- ...resolvedWhere && { where: [whereIntentAsDecision(resolvedWhere)] },
14272
- ...intent.returning && { returning: [...intent.returning] }
14273
- };
14274
- const ast = compileDelete(config, ctx, state);
14275
- const sql = deparseQuoted(ast);
14276
- return {
14277
- sql,
14278
- parameters: state.parameters
14279
- };
14280
- }
14281
- function compileUpsert2(intent, options, deps) {
14282
- const schemaName = deps.schemaName;
14283
- const ctx = {
14284
- naming: deps.naming,
14285
- rootTable: intent.table,
14286
- ...schemaName !== void 0 && { schema: schemaName },
14287
- maxRecursiveDepth: 100
14288
- };
14289
- const state = createCompilerState();
14290
- const firstRow = intent.values?.[0] ?? {};
14291
- const rawExprs = {};
14292
- const scalarSet = {};
14293
- if (intent.action.type === "doUpdate" && intent.action.set) {
14294
- for (const [key, val] of Object.entries(intent.action.set)) {
14295
- if (isSqlRaw2(val)) {
14296
- rawExprs[key] = val.sql;
14297
- } else {
14298
- scalarSet[key] = val;
14299
- }
14300
- }
14301
- }
14302
- const hasScalarSet = Object.keys(scalarSet).length > 0;
14303
- const mergedFirstRow = hasScalarSet ? { ...firstRow, ...scalarSet } : firstRow;
14304
- const columns = Object.keys(mergedFirstRow);
14305
- const values = (intent.values ?? []).map((row) => {
14306
- const mergedRow = hasScalarSet ? { ...row, ...scalarSet } : row;
14307
- return columns.map((col) => mergedRow[col]);
14308
- });
14309
- const conflictTarget = {};
14310
- if ("columns" in intent.onConflict) {
14311
- conflictTarget.columns = [...intent.onConflict.columns];
14312
- } else if ("constraint" in intent.onConflict) {
14313
- conflictTarget.constraint = intent.onConflict.constraint;
14314
- }
14315
- const conflictAction = intent.action.type === "doNothing" ? "nothing" : "update";
14316
- let updateColumns;
14317
- if (intent.action.type === "doUpdate") {
14318
- if (intent.action.set) {
14319
- updateColumns = Object.keys(intent.action.set);
14320
- } else {
14321
- const conflictCols = "columns" in intent.onConflict ? intent.onConflict.columns : [];
14322
- updateColumns = columns.filter((col) => !conflictCols.includes(col));
14323
- }
14324
- }
14325
- const columnTypes = getColumnTypes(intent.table, columns, deps);
14326
- const hasRawExprs = Object.keys(rawExprs).length > 0;
14327
- const config = {
14328
- table: intent.table,
14329
- columns,
14330
- values,
14331
- conflictTarget,
14332
- conflictAction,
14333
- ...updateColumns && { updateColumns },
14334
- ...intent.returning && { returning: [...intent.returning] },
14335
- ...columnTypes && { columnTypes },
14336
- ...hasRawExprs && { updateExpressions: rawExprs }
14337
- };
14338
- const maxBatchSize = options?.maxBatchSize;
14339
- if (maxBatchSize !== void 0 && values.length > maxBatchSize) {
14340
- throw new InvalidOperationError3(
14341
- "upsert",
14342
- `Batch size ${values.length} exceeds maxBatchSize ${maxBatchSize}`
14343
- );
14344
- }
14345
- const batchThreshold = options?.batchThreshold ?? 50;
14346
- const useUnnest = values.length > 0 && (batchThreshold === 0 || values.length > batchThreshold);
14347
- const ast = useUnnest ? compileUnnestUpsert(config, ctx, state) : compileUpsert(config, ctx, state);
14348
- const sql = deparseQuoted(ast);
14349
- return {
14350
- sql,
14351
- parameters: state.parameters
14352
- };
14353
- }
14354
- function compileUpsertFrom2(intent, options, deps) {
14355
- const schemaName = deps.schemaName;
14356
- const ctx = {
14357
- naming: deps.naming,
14358
- rootTable: intent.source,
14359
- ...schemaName !== void 0 && { schema: schemaName },
14360
- maxRecursiveDepth: 100
14361
- };
14362
- const state = createCompilerState();
14363
- let columns;
14364
- if (intent.columns) {
14365
- columns = [...intent.columns];
14366
- } else if (options?.model) {
14367
- const targetTable = options.model.getTable(intent.table);
14368
- if (targetTable) {
14369
- columns = targetTable.columns.map((c) => c.name);
14370
- }
14371
- }
14372
- const config = {
14373
- targetTable: intent.table,
14374
- sourceTable: intent.source,
14375
- conflictColumns: [...intent.conflictColumns],
14376
- ...columns && { columns },
14377
- ...intent.where && { where: [whereIntentAsDecision(intent.where)] },
14378
- ...intent.limit !== void 0 && { limit: intent.limit },
14379
- ...intent.returning && { returning: [...intent.returning] }
14380
- };
14381
- const ast = compileUpsertFrom(config, ctx, state);
14382
- const sql = deparseQuoted(ast);
14383
- return {
14384
- sql,
14385
- parameters: state.parameters
14386
- };
14387
- }
14388
-
14389
- // src/adapter-compiler-select.ts
14390
- init_assert_field();
14391
- init_ast_helpers();
14392
- init_compile_where();
14393
- import { countDistinctRelationPathsByName } from "@dbsp/core";
14394
- init_compiler_utils();
14395
- init_types();
14396
- init_intent_to_decisions();
14397
- init_param_ref();
14398
-
14399
- // src/plan-decision-extractor.ts
14400
- init_assert_field();
14401
- init_intent_to_decisions();
14402
- import { deriveRelationPathFromIntentPath } from "@dbsp/core";
14403
- function findExistsIntents(where) {
14404
- if (!where || typeof where !== "object") return [];
14405
- const w = where;
14406
- if (w.kind === "exists" || w.kind === "notExists" || w.kind === "relationFilter") {
14407
- return [w];
14408
- }
14409
- const results = [];
14410
- if (w.conditions && Array.isArray(w.conditions)) {
14411
- for (const c of w.conditions) {
14412
- results.push(...findExistsIntents(c));
14413
- }
14414
- }
14415
- if (w.condition) {
14416
- results.push(...findExistsIntents(w.condition));
14417
- }
14418
- return results;
14419
- }
14420
- function resolveRelation(model, sourceTable, relationName) {
14421
- const rel = model.getRelation(`${sourceTable}.${relationName}`);
14422
- if (!rel) return void 0;
14423
- const foreignKey = typeof rel.foreignKey === "string" ? rel.foreignKey : rel.foreignKey?.[0];
14424
- const relationType = rel.type;
14425
- return { target: rel.target, foreignKey, relationType };
14426
- }
14427
- function resolveIncludeAlias(context) {
14428
- return context.relation ?? context.includeAlias;
14429
- }
14430
- function resolveIncludeByPath(includes, intentPath, relationName) {
14431
- if (!includes) return void 0;
14432
- if (intentPath) {
14433
- const indexPattern = /include\[(\d+)\]/g;
14434
- let current = includes;
14435
- let resolved;
14436
- let execResult = indexPattern.exec(intentPath);
14437
- while (execResult !== null) {
14438
- const idx = parseInt(execResult[1], 10);
14439
- const item = current[idx];
14440
- if (!item) break;
14441
- resolved = item;
14442
- current = item.include ?? [];
14443
- execResult = indexPattern.exec(intentPath);
14444
- }
14445
- if (resolved) return resolved;
14446
- }
14447
- return includes.find(
14448
- (i) => i.relation === relationName || i.via === relationName
14449
- );
14450
- }
14451
- function deriveForeignKey(context, deriveFk = defaultFkDerivation, defaultPk = DEFAULT_PK_COLUMN) {
14452
- const fk = context.foreignKey ?? context.sourceFK;
14453
- if (fk) return fk;
14454
- if (!context.relationType) return void 0;
14455
- if (context.relationType === "belongsTo") {
14456
- return context.target ? deriveFk(context.target, defaultPk) : void 0;
14457
- }
14458
- return context.sourceTable ? deriveFk(context.sourceTable, defaultPk) : void 0;
14459
- }
14460
- function mapComparisonOperator(op3) {
14461
- const map = {
14462
- eq: "=",
14463
- neq: "!=",
14464
- gt: ">",
14465
- gte: ">=",
14466
- lt: "<",
14467
- lte: "<=",
14468
- like: "LIKE",
14469
- ilike: "ILIKE",
14470
- isDistinctFrom: "IS DISTINCT FROM"
14471
- };
14472
- return map[op3] ?? "=";
14219
+ return map[op3] ?? "=";
14473
14220
  }
14474
14221
  function convertWhereToDecisions(where, table) {
14475
14222
  if (!where || typeof where !== "object") return [];
@@ -15489,288 +15236,754 @@ function compileJoinIntents(joins, rootTable, schemaName, deps) {
15489
15236
  });
15490
15237
  }
15491
15238
  }
15492
- return results;
15239
+ return results;
15240
+ }
15241
+ function stripJoinColumnsForAggregation(decisions, intent) {
15242
+ const isAggregateOnly = intent.select && "type" in intent.select && intent.select.type === "aggregate" && !("fields" in intent.select && intent.select.fields);
15243
+ const isDistinct = intent.distinct === true;
15244
+ const hasGroupBy = intent.groupBy && intent.groupBy.length > 0;
15245
+ const hasExplicitColumns = intent.select && "type" in intent.select && intent.select.type === "expressions";
15246
+ if (isAggregateOnly || isDistinct || hasGroupBy || hasExplicitColumns) {
15247
+ for (const d of decisions) {
15248
+ if (d.type === "includeStrategy" && d.choice === "join") {
15249
+ d.columns = [];
15250
+ }
15251
+ }
15252
+ }
15253
+ }
15254
+ function buildRelationColumnsMap(decisions, includedRelations) {
15255
+ const map = /* @__PURE__ */ new Map();
15256
+ for (const d of decisions) {
15257
+ if (!(d.type === "selectRelationColumn" && d.relation && d.column))
15258
+ continue;
15259
+ const col = d.column;
15260
+ const alias = d.alias;
15261
+ const fullRelation = d.relation;
15262
+ const rootRelation = fullRelation.split(".")[0] ?? "";
15263
+ if (!includedRelations.has(rootRelation)) continue;
15264
+ const mapKey = fullRelation;
15265
+ if (col === "*") {
15266
+ map.set(mapKey, [{ col: "*" }]);
15267
+ continue;
15268
+ }
15269
+ const existing = map.get(mapKey);
15270
+ if (existing) {
15271
+ if (existing.length === 1 && existing[0]?.col === "*") continue;
15272
+ if (!existing.some((e) => e.col === col)) {
15273
+ existing.push({ col, ...alias !== void 0 && { alias } });
15274
+ }
15275
+ } else {
15276
+ map.set(mapKey, [{ col, ...alias !== void 0 && { alias } }]);
15277
+ }
15278
+ }
15279
+ return map;
15280
+ }
15281
+ function injectAndValidateRelationColumns(enrichedUnifiedDecisions, relationColumnsMap, model) {
15282
+ if (relationColumnsMap.size === 0) return;
15283
+ for (const d of enrichedUnifiedDecisions) {
15284
+ if (d.type === "includeStrategy" && d.relationName) {
15285
+ const mapKey = d.relationPath ?? d.relationName;
15286
+ const entries = mapKey ? relationColumnsMap.get(mapKey) : void 0;
15287
+ if (entries) {
15288
+ const mut = d;
15289
+ mut.columns = entries.map((e) => e.col);
15290
+ const aliasMap = {};
15291
+ for (const { col, alias } of entries) {
15292
+ if (alias) aliasMap[col] = alias;
15293
+ }
15294
+ if (Object.keys(aliasMap).length > 0) {
15295
+ mut.columnAliases = aliasMap;
15296
+ }
15297
+ }
15298
+ }
15299
+ }
15300
+ if (!model) return;
15301
+ for (const d of enrichedUnifiedDecisions) {
15302
+ if (d.type === "includeStrategy" && d.columns && d.targetTable && !(d.columns.length === 1 && d.columns[0] === "*")) {
15303
+ const targetTable = model.getTable(d.targetTable);
15304
+ if (targetTable) {
15305
+ const validColumnNames = new Set(
15306
+ targetTable.columns.map((c) => c.name)
15307
+ );
15308
+ const invalid = d.columns.filter(
15309
+ (c) => !validColumnNames.has(c)
15310
+ );
15311
+ if (invalid.length > 0) {
15312
+ throw new Error(
15313
+ `Unknown column(s) ${invalid.map((c) => `'${c}'`).join(", ")} in relation '${d.relationName}' (table '${d.targetTable}'). Available: ${[...validColumnNames].join(", ")}`
15314
+ );
15315
+ }
15316
+ }
15317
+ }
15318
+ }
15319
+ }
15320
+ function applyJoinHydrationPrefixes(decisions) {
15321
+ const usages = [];
15322
+ for (const d of decisions) {
15323
+ if (d.type !== "includeStrategy" || d.choice !== "join" || !d.relationName) {
15324
+ continue;
15325
+ }
15326
+ const relationName = d.relationName;
15327
+ const relationPath = d.relationPath ?? relationName;
15328
+ usages.push({ relationName, relationPath });
15329
+ }
15330
+ const pathCountsByRelation = countDistinctRelationPathsByName(usages);
15331
+ for (const d of decisions) {
15332
+ if (d.type !== "includeStrategy" || d.choice !== "join" || !d.relationName) {
15333
+ continue;
15334
+ }
15335
+ const relationName = d.relationName;
15336
+ const relationPath = d.relationPath ?? relationName;
15337
+ const usesFullPath = (pathCountsByRelation.get(relationName) ?? 0) > 1;
15338
+ d.hydrationPrefix = usesFullPath ? relationPath : relationName;
15339
+ }
15340
+ }
15341
+ function enrichRangeDecisions(allDecisions, model, rootTable) {
15342
+ if (!model) return;
15343
+ for (let i = 0; i < allDecisions.length; i++) {
15344
+ const d = allDecisions[i];
15345
+ if (d && d.type === "where" && (d.operator === "contains" || d.operator === "containedBy" || d.operator === "overlaps")) {
15346
+ const tableName = d.table || rootTable;
15347
+ const table = model.getTable(tableName);
15348
+ if (table) {
15349
+ const col = table.columns.find((c) => c.name === d.column);
15350
+ if (col?.type.endsWith("range")) {
15351
+ allDecisions[i] = { ...d, dataType: col.type };
15352
+ }
15353
+ }
15354
+ }
15355
+ }
15356
+ }
15357
+ function buildSimplifiedPlanReport(plan, allDecisions, schemaName) {
15358
+ const bvFromSource = plan.intent?.batchValuesSource;
15359
+ const batchValuesFromFields = bvFromSource ? (() => {
15360
+ const { rangeFunction, params } = buildBatchValuesRangeFn(
15361
+ bvFromSource,
15362
+ 1
15363
+ );
15364
+ return {
15365
+ batchValuesFromNode: rangeFunction,
15366
+ batchValuesFromParams: params
15367
+ };
15368
+ })() : {};
15369
+ return {
15370
+ rootTable: plan.rootTable,
15371
+ decisions: allDecisions,
15372
+ ...schemaName ? { schema: schemaName } : {},
15373
+ ...plan.intent?.existsWrap ? { existsWrap: true } : {},
15374
+ ...plan.intent?.lock ? { lock: plan.intent.lock } : {},
15375
+ ...batchValuesFromFields
15376
+ };
15377
+ }
15378
+ function compileSelect(plan, options, deps) {
15379
+ const schemaName = deps.schemaName;
15380
+ const resolvedModelForCompiler = options?.model ?? deps.model;
15381
+ const compilerOptions = {
15382
+ naming: deps.naming,
15383
+ ...schemaName && { schema: schemaName },
15384
+ defaultPkColumnName: deps.defaultPk,
15385
+ deriveFkColumnName: deps.deriveFk,
15386
+ ...deps.bindingNames !== void 0 && {
15387
+ bindingNames: deps.bindingNames
15388
+ },
15389
+ ...resolvedModelForCompiler != null && {
15390
+ model: resolvedModelForCompiler
15391
+ }
15392
+ };
15393
+ const execIntent = plan.executableIntent ?? plan.intent;
15394
+ const planForCompilation = plan.executableIntent !== void 0 ? { ...plan, intent: plan.executableIntent } : plan;
15395
+ let simplifiedPlan;
15396
+ if (execIntent) {
15397
+ let decisions = intentToDecisions(execIntent, plan.rootTable);
15398
+ const resolvedModel = options?.model ?? deps.model;
15399
+ if (resolvedModel) {
15400
+ decisions = convertDottedFieldsToExists(
15401
+ decisions,
15402
+ plan.rootTable,
15403
+ resolvedModel
15404
+ );
15405
+ }
15406
+ enrichExistsDecisionsInPlace(
15407
+ decisions,
15408
+ planForCompilation,
15409
+ options?.model ?? deps.model
15410
+ );
15411
+ const unifiedIncludeDecisions = extractAllIncludeDecisions(
15412
+ planForCompilation,
15413
+ deps.defaultPk,
15414
+ deps.deriveFk
15415
+ );
15416
+ const coveredByPlanner = new Set(
15417
+ unifiedIncludeDecisions.filter((d) => d.type === "includeStrategy").map((d) => d.relationName).filter(Boolean)
15418
+ );
15419
+ const synthesizedModel = options?.model ?? deps.model;
15420
+ const synthesizedJoins = synthesizedModel ? synthesizeMissingJoinDecisions(
15421
+ planForCompilation,
15422
+ coveredByPlanner,
15423
+ synthesizedModel,
15424
+ deps.defaultPk,
15425
+ deps.deriveFk
15426
+ ) : [];
15427
+ const allUnifiedIncludeDecisions = synthesizedJoins.length > 0 ? [...unifiedIncludeDecisions, ...synthesizedJoins] : unifiedIncludeDecisions;
15428
+ const enrichedUnifiedDecisions = [
15429
+ ...allUnifiedIncludeDecisions
15430
+ ];
15431
+ stripJoinColumnsForAggregation(enrichedUnifiedDecisions, execIntent);
15432
+ applyJoinHydrationPrefixes(enrichedUnifiedDecisions);
15433
+ const includedRelations = new Set(
15434
+ enrichedUnifiedDecisions.filter((d) => d.type === "includeStrategy").map((d) => d.relationName).filter(Boolean)
15435
+ );
15436
+ if (includedRelations.size > 0) {
15437
+ const relationColumnsMap = buildRelationColumnsMap(
15438
+ decisions,
15439
+ includedRelations
15440
+ );
15441
+ injectAndValidateRelationColumns(
15442
+ enrichedUnifiedDecisions,
15443
+ relationColumnsMap,
15444
+ options?.model ?? deps.model
15445
+ );
15446
+ }
15447
+ const deduplicatedDecisions = includedRelations.size > 0 ? decisions.filter((d) => {
15448
+ if (d.type === "selectRelationColumn" && d.relation) {
15449
+ const rel = d.relation;
15450
+ const rootRelation = rel.split(".")[0] ?? rel;
15451
+ if (includedRelations.has(rootRelation)) {
15452
+ return false;
15453
+ }
15454
+ }
15455
+ return true;
15456
+ }) : decisions;
15457
+ const joinIntentDecisions = execIntent?.joins && execIntent.joins.length > 0 ? compileJoinIntents(
15458
+ execIntent.joins,
15459
+ plan.rootTable,
15460
+ schemaName,
15461
+ deps
15462
+ ) : [];
15463
+ const allDecisions = [
15464
+ ...deduplicatedDecisions,
15465
+ ...enrichedUnifiedDecisions,
15466
+ ...joinIntentDecisions
15467
+ ];
15468
+ const rangeModel = options?.model ?? deps.model;
15469
+ enrichRangeDecisions(allDecisions, rangeModel, plan.rootTable);
15470
+ simplifiedPlan = buildSimplifiedPlanReport(
15471
+ planForCompilation,
15472
+ allDecisions,
15473
+ schemaName
15474
+ );
15475
+ } else {
15476
+ simplifiedPlan = {
15477
+ rootTable: plan.rootTable,
15478
+ decisions: bridgeLegacyDecisions(plan.decisions),
15479
+ ...schemaName ? { schema: schemaName } : {}
15480
+ };
15481
+ }
15482
+ const result = compilePlan(simplifiedPlan, compilerOptions);
15483
+ return {
15484
+ sql: result.sql,
15485
+ parameters: result.parameters
15486
+ };
15493
15487
  }
15494
- function stripJoinColumnsForAggregation(decisions, intent) {
15495
- const isAggregateOnly = intent.select && "type" in intent.select && intent.select.type === "aggregate" && !("fields" in intent.select && intent.select.fields);
15496
- const isDistinct = intent.distinct === true;
15497
- const hasGroupBy = intent.groupBy && intent.groupBy.length > 0;
15498
- const hasExplicitColumns = intent.select && "type" in intent.select && intent.select.type === "expressions";
15499
- if (isAggregateOnly || isDistinct || hasGroupBy || hasExplicitColumns) {
15500
- for (const d of decisions) {
15501
- if (d.type === "includeStrategy" && d.choice === "join") {
15502
- d.columns = [];
15503
- }
15488
+ function compileWithIncludes(plan, options, deps) {
15489
+ const main = compileSelect(plan, options, deps);
15490
+ const subqueryIncludes = [];
15491
+ for (const d of plan.decisions) {
15492
+ if (d.type !== "include-strategy" || d.choice !== "subquery") continue;
15493
+ const ctx = d.context;
15494
+ if (!ctx.target) continue;
15495
+ const relationName = ctx.includeAlias ?? ctx.relation;
15496
+ if (!relationName) continue;
15497
+ const rawFk = deriveForeignKey(ctx, deps.deriveFk, deps.defaultPk) ?? deps.defaultPk;
15498
+ const fk = Array.isArray(rawFk) ? rawFk[0] : rawFk;
15499
+ const isBelongsTo = ctx.relationType === "belongsTo";
15500
+ const sourceKey = isBelongsTo ? fk : "id";
15501
+ const targetFk = isBelongsTo ? "id" : fk;
15502
+ const includeIntent = plan.intent?.include?.find(
15503
+ (i) => i.relation === relationName || i.relation === ctx.includeAlias
15504
+ );
15505
+ const entry = {
15506
+ relationName,
15507
+ targetTable: ctx.target,
15508
+ foreignKey: targetFk,
15509
+ sourceKey,
15510
+ sourceTable: ctx.sourceTable ?? plan.rootTable
15511
+ };
15512
+ if (typeof ctx.relationType === "string") {
15513
+ entry.relationType = ctx.relationType;
15504
15514
  }
15505
- }
15506
- }
15507
- function buildRelationColumnsMap(decisions, includedRelations) {
15508
- const map = /* @__PURE__ */ new Map();
15509
- for (const d of decisions) {
15510
- if (!(d.type === "selectRelationColumn" && d.relation && d.column))
15511
- continue;
15512
- const col = d.column;
15513
- const alias = d.alias;
15514
- const fullRelation = d.relation;
15515
- const rootRelation = fullRelation.split(".")[0] ?? "";
15516
- if (!includedRelations.has(rootRelation)) continue;
15517
- const mapKey = fullRelation;
15518
- if (col === "*") {
15519
- map.set(mapKey, [{ col: "*" }]);
15520
- continue;
15515
+ if (includeIntent?.select != null) {
15516
+ entry.select = includeIntent.select;
15521
15517
  }
15522
- const existing = map.get(mapKey);
15523
- if (existing) {
15524
- if (existing.length === 1 && existing[0]?.col === "*") continue;
15525
- if (!existing.some((e) => e.col === col)) {
15526
- existing.push({ col, ...alias !== void 0 && { alias } });
15527
- }
15528
- } else {
15529
- map.set(mapKey, [{ col, ...alias !== void 0 && { alias } }]);
15518
+ if (includeIntent?.where != null) {
15519
+ entry.where = includeIntent.where;
15530
15520
  }
15521
+ subqueryIncludes.push(entry);
15531
15522
  }
15532
- return map;
15523
+ return { main, subqueryIncludes };
15533
15524
  }
15534
- function injectAndValidateRelationColumns(enrichedUnifiedDecisions, relationColumnsMap, model) {
15535
- if (relationColumnsMap.size === 0) return;
15536
- for (const d of enrichedUnifiedDecisions) {
15537
- if (d.type === "includeStrategy" && d.relationName) {
15538
- const mapKey = d.relationPath ?? d.relationName;
15539
- const entries = mapKey ? relationColumnsMap.get(mapKey) : void 0;
15540
- if (entries) {
15541
- const mut = d;
15542
- mut.columns = entries.map((e) => e.col);
15543
- const aliasMap = {};
15544
- for (const { col, alias } of entries) {
15545
- if (alias) aliasMap[col] = alias;
15546
- }
15547
- if (Object.keys(aliasMap).length > 0) {
15548
- mut.columnAliases = aliasMap;
15549
- }
15550
- }
15551
- }
15525
+
15526
+ // src/adapter-compiler-mutations.ts
15527
+ init_binding_registry();
15528
+ init_compile_where();
15529
+ init_compiler_utils();
15530
+ init_handlers();
15531
+ function whereIntentAsDecision(where) {
15532
+ return where;
15533
+ }
15534
+ function renumberSqlParams(sql, offset) {
15535
+ if (offset === 0) return sql;
15536
+ return sql.replace(/\$(\d+)/g, (_match, num) => {
15537
+ return `$${Number.parseInt(num, 10) + offset}`;
15538
+ });
15539
+ }
15540
+ function compileSourceQueryCte(operation, sourceName, sourceQuery, options, deps) {
15541
+ const model = deps.model;
15542
+ if (model === void 0) {
15543
+ throw new Error(
15544
+ `${operation} with sourceQuery requires a model to emit the source CTE for '${sourceName}'.`
15545
+ );
15552
15546
  }
15553
- if (!model) return;
15554
- for (const d of enrichedUnifiedDecisions) {
15555
- if (d.type === "includeStrategy" && d.columns && d.targetTable && !(d.columns.length === 1 && d.columns[0] === "*")) {
15556
- const targetTable = model.getTable(d.targetTable);
15557
- if (targetTable) {
15558
- const validColumnNames = new Set(
15559
- targetTable.columns.map((c) => c.name)
15560
- );
15561
- const invalid = d.columns.filter(
15562
- (c) => !validColumnNames.has(c)
15563
- );
15564
- if (invalid.length > 0) {
15565
- throw new Error(
15566
- `Unknown column(s) ${invalid.map((c) => `'${c}'`).join(", ")} in relation '${d.relationName}' (table '${d.targetTable}'). Available: ${[...validColumnNames].join(", ")}`
15567
- );
15568
- }
15569
- }
15547
+ const sourcePlan = planFn(sourceQuery, model, {
15548
+ dialectCapabilities: POSTGRESQL_CAPABILITIES
15549
+ });
15550
+ return compileSelect(sourcePlan, options, deps);
15551
+ }
15552
+ function prependSourceCte(sql, parameters, sourceName, sourceCte, naming) {
15553
+ if (sourceCte === void 0) {
15554
+ return { sql, parameters };
15555
+ }
15556
+ const cteParamCount = sourceCte.parameters.length;
15557
+ const sourceCteName = emittedBindName(sourceName, naming);
15558
+ return {
15559
+ sql: `WITH ${quoteIdent2(sourceCteName, "alias")} as (${sourceCte.sql}) ${renumberSqlParams(sql, cteParamCount)}`,
15560
+ parameters: [...sourceCte.parameters, ...parameters]
15561
+ };
15562
+ }
15563
+ function resolveExistsRelation(sourceTable, relation, model) {
15564
+ if (!model) return { targetTable: relation };
15565
+ const rel = model.getRelation(`${sourceTable}.${relation}`);
15566
+ if (!rel) return { targetTable: relation };
15567
+ const targetTable = rel.target;
15568
+ if (rel.type === "belongsTo") {
15569
+ const fk2 = typeof rel.foreignKey === "string" ? rel.foreignKey : rel.foreignKey?.[0];
15570
+ return {
15571
+ targetTable,
15572
+ ...fk2 !== void 0 && { sourceColumn: fk2 },
15573
+ targetColumn: "id"
15574
+ };
15575
+ }
15576
+ const fk = typeof rel.foreignKey === "string" ? rel.foreignKey : rel.foreignKey?.[0];
15577
+ return {
15578
+ targetTable,
15579
+ sourceColumn: rel.sourceKey ?? "id",
15580
+ ...fk !== void 0 && { targetColumn: fk }
15581
+ };
15582
+ }
15583
+ function resolveExistsIntent(where, sourceTable, deps) {
15584
+ const w = where;
15585
+ const kind = w.kind;
15586
+ if (kind === "and" || kind === "or") {
15587
+ const conditions = w.conditions;
15588
+ if (!conditions) return where;
15589
+ const enriched = conditions.map(
15590
+ (c) => resolveExistsIntent(c, sourceTable, deps)
15591
+ );
15592
+ const changed = enriched.some((c, i) => c !== conditions[i]);
15593
+ return changed ? { ...w, conditions: enriched } : where;
15594
+ }
15595
+ if (kind === "not") {
15596
+ const condition = w.condition;
15597
+ if (!condition) return where;
15598
+ const enriched = resolveExistsIntent(condition, sourceTable, deps);
15599
+ return enriched !== condition ? { ...w, condition: enriched } : where;
15600
+ }
15601
+ if (kind !== "exists" && kind !== "notExists") return where;
15602
+ const relation = w.relation;
15603
+ const resolved = resolveExistsRelation(sourceTable, relation, deps.model);
15604
+ if (resolved.targetTable === relation && !resolved.sourceColumn) return where;
15605
+ return {
15606
+ ...w,
15607
+ targetTable: resolved.targetTable,
15608
+ ...resolved.sourceColumn !== void 0 && {
15609
+ sourceColumn: resolved.sourceColumn
15610
+ },
15611
+ ...resolved.targetColumn !== void 0 && {
15612
+ targetColumn: resolved.targetColumn
15613
+ }
15614
+ };
15615
+ }
15616
+ function getColumnTypes(tableName, columns, deps) {
15617
+ if (!deps.model) return void 0;
15618
+ const table = deps.model.getTable(tableName);
15619
+ if (!table) return void 0;
15620
+ let result;
15621
+ for (const col of columns) {
15622
+ const columnIR = table.columns.find((c) => c.name === col);
15623
+ if (columnIR) {
15624
+ result ??= {};
15625
+ result[col] = columnIR.originalDbType ?? columnIR.type;
15570
15626
  }
15571
15627
  }
15628
+ return result;
15629
+ }
15630
+ function compileUpsertActionWhere(where, table, state, deps, schemaName) {
15631
+ const whereCtx = {
15632
+ rootTable: table,
15633
+ aliases: /* @__PURE__ */ new Map(),
15634
+ paramState: state,
15635
+ naming: deps.naming,
15636
+ ...schemaName !== void 0 && { schemaName },
15637
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15638
+ ...deps.model !== void 0 && { model: deps.model },
15639
+ compileSubquery: (sqIntent, paramOffset) => buildSubqueryFromIntent(
15640
+ sqIntent,
15641
+ paramOffset,
15642
+ deps.naming,
15643
+ schemaName,
15644
+ "rawExists",
15645
+ deps.bindingNames
15646
+ )
15647
+ };
15648
+ return compileWhereIntent(where, whereCtx);
15649
+ }
15650
+ function compileInsert2(intent, options, deps) {
15651
+ const schemaName = deps.schemaName;
15652
+ const ctx = {
15653
+ naming: deps.naming,
15654
+ rootTable: intent.table,
15655
+ ...schemaName !== void 0 && { schema: schemaName },
15656
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15657
+ maxRecursiveDepth: 100
15658
+ };
15659
+ const state = createCompilerState();
15660
+ const firstRow = intent.values?.[0] ?? {};
15661
+ const columns = Object.keys(firstRow);
15662
+ const rows = intent.values ?? [];
15663
+ const values = rows.map((row) => columns.map((col) => row[col]));
15664
+ const columnTypes = getColumnTypes(intent.table, columns, deps);
15665
+ const config = {
15666
+ table: intent.table,
15667
+ columns,
15668
+ values,
15669
+ ...intent.returning && { returning: [...intent.returning] },
15670
+ ...columnTypes && { columnTypes }
15671
+ };
15672
+ const maxBatchSize = options?.maxBatchSize;
15673
+ if (maxBatchSize !== void 0 && values.length > maxBatchSize) {
15674
+ throw new InvalidOperationError3(
15675
+ "insert",
15676
+ `Batch size ${values.length} exceeds maxBatchSize ${maxBatchSize}`
15677
+ );
15678
+ }
15679
+ const batchThreshold = options?.batchThreshold ?? 50;
15680
+ const useUnnest = values.length > 0 && (batchThreshold === 0 || values.length > batchThreshold);
15681
+ const ast = useUnnest ? compileUnnestInsert(config, ctx, state) : compileInsert(config, ctx, state);
15682
+ const sql = deparseQuoted(ast);
15683
+ return {
15684
+ sql,
15685
+ parameters: state.parameters
15686
+ };
15687
+ }
15688
+ function compileInsertFrom2(intent, options, deps) {
15689
+ const schemaName = deps.schemaName;
15690
+ const sourceCte = intent.sourceQuery !== void 0 && !hasBindingName(deps.bindingNames, intent.source, deps.naming) ? compileSourceQueryCte(
15691
+ "compileInsertFrom",
15692
+ intent.source,
15693
+ intent.sourceQuery,
15694
+ options,
15695
+ deps
15696
+ ) : void 0;
15697
+ const bindingNames = intent.sourceQuery !== void 0 ? withBindingName(deps.bindingNames, intent.source, deps.naming) : deps.bindingNames;
15698
+ const ctx = {
15699
+ naming: deps.naming,
15700
+ rootTable: intent.source,
15701
+ ...schemaName !== void 0 && { schema: schemaName },
15702
+ ...bindingNames !== void 0 && { bindingNames },
15703
+ maxRecursiveDepth: 100
15704
+ };
15705
+ const state = createCompilerState();
15706
+ const config = {
15707
+ targetTable: intent.table,
15708
+ sourceTable: intent.source,
15709
+ ...intent.columns && { columns: [...intent.columns] },
15710
+ ...intent.where && { where: [whereIntentAsDecision(intent.where)] },
15711
+ ...intent.limit !== void 0 && { limit: intent.limit },
15712
+ ...intent.returning && { returning: [...intent.returning] }
15713
+ };
15714
+ const ast = compileInsertFrom(config, ctx, state);
15715
+ const sql = deparseQuoted(ast);
15716
+ return prependSourceCte(
15717
+ sql,
15718
+ state.parameters,
15719
+ intent.source,
15720
+ sourceCte,
15721
+ deps.naming
15722
+ );
15572
15723
  }
15573
- function applyJoinHydrationPrefixes(decisions) {
15574
- const usages = [];
15575
- for (const d of decisions) {
15576
- if (d.type !== "includeStrategy" || d.choice !== "join" || !d.relationName) {
15577
- continue;
15578
- }
15579
- const relationName = d.relationName;
15580
- const relationPath = d.relationPath ?? relationName;
15581
- usages.push({ relationName, relationPath });
15724
+ function compileUpdate2(intent, options, deps) {
15725
+ const schemaName = deps.schemaName;
15726
+ const resolvedModel = options?.model ?? deps.model;
15727
+ const ctx = {
15728
+ naming: deps.naming,
15729
+ rootTable: intent.table,
15730
+ ...schemaName !== void 0 && { schema: schemaName },
15731
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15732
+ ...resolvedModel !== void 0 && { model: resolvedModel },
15733
+ maxRecursiveDepth: 100
15734
+ };
15735
+ const state = createCompilerState();
15736
+ const setColumns = Object.keys(intent.set ?? {});
15737
+ const columnTypes = getColumnTypes(intent.table, setColumns, deps);
15738
+ const resolvedWhere = intent.where ? resolveExistsIntent(intent.where, intent.table, deps) : void 0;
15739
+ const config = {
15740
+ table: intent.table,
15741
+ set: Object.entries(intent.set ?? {}).map(([column, value]) => ({
15742
+ column,
15743
+ value
15744
+ })),
15745
+ ...resolvedWhere && { where: [whereIntentAsDecision(resolvedWhere)] },
15746
+ ...intent.returning && { returning: [...intent.returning] },
15747
+ ...columnTypes && { columnTypes }
15748
+ };
15749
+ const ast = compileUpdate(config, ctx, state);
15750
+ const sql = deparseQuoted(ast);
15751
+ return {
15752
+ sql,
15753
+ parameters: state.parameters
15754
+ };
15755
+ }
15756
+ function compileBatchUpdate(intent, _options, deps) {
15757
+ const schemaName = deps.schemaName;
15758
+ const ctx = {
15759
+ naming: deps.naming,
15760
+ rootTable: intent.table,
15761
+ ...schemaName !== void 0 && { schema: schemaName },
15762
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15763
+ maxRecursiveDepth: 100
15764
+ };
15765
+ const state = createCompilerState();
15766
+ if (intent.updates.length === 0) {
15767
+ throw new InvalidOperationError3(
15768
+ "update",
15769
+ "batchSet requires at least one row"
15770
+ );
15582
15771
  }
15583
- const pathCountsByRelation = countDistinctRelationPathsByName(usages);
15584
- for (const d of decisions) {
15585
- if (d.type !== "includeStrategy" || d.choice !== "join" || !d.relationName) {
15586
- continue;
15772
+ const allColumns = Object.keys(intent.updates[0]);
15773
+ const matchColumns = [...intent.matchColumns];
15774
+ for (const mc of matchColumns) {
15775
+ if (!allColumns.includes(mc)) {
15776
+ throw new InvalidOperationError3(
15777
+ "update",
15778
+ `Match column "${mc}" not found in update data. Each row must include the match column(s).`
15779
+ );
15587
15780
  }
15588
- const relationName = d.relationName;
15589
- const relationPath = d.relationPath ?? relationName;
15590
- const usesFullPath = (pathCountsByRelation.get(relationName) ?? 0) > 1;
15591
- d.hydrationPrefix = usesFullPath ? relationPath : relationName;
15592
15781
  }
15593
- }
15594
- function enrichRangeDecisions(allDecisions, model, rootTable) {
15595
- if (!model) return;
15596
- for (let i = 0; i < allDecisions.length; i++) {
15597
- const d = allDecisions[i];
15598
- if (d && d.type === "where" && (d.operator === "contains" || d.operator === "containedBy" || d.operator === "overlaps")) {
15599
- const tableName = d.table || rootTable;
15600
- const table = model.getTable(tableName);
15601
- if (table) {
15602
- const col = table.columns.find((c) => c.name === d.column);
15603
- if (col?.type.endsWith("range")) {
15604
- allDecisions[i] = { ...d, dataType: col.type };
15605
- }
15606
- }
15607
- }
15782
+ const values = intent.updates.map((row) => allColumns.map((col) => row[col]));
15783
+ validateBatchCardinality(allColumns, values);
15784
+ const columnArrays = transposeToColumnArrays(allColumns, values);
15785
+ const columnTypes = getColumnTypes(intent.table, allColumns, deps);
15786
+ const scalarSet = intent.scalarSet ? Object.entries(intent.scalarSet).map(([column, value]) => ({
15787
+ column,
15788
+ value
15789
+ })) : void 0;
15790
+ let whereGuard;
15791
+ if (intent.where) {
15792
+ const whereCtx = {
15793
+ rootTable: intent.table,
15794
+ aliases: /* @__PURE__ */ new Map(),
15795
+ paramState: state,
15796
+ naming: deps.naming,
15797
+ ...schemaName !== void 0 && { schemaName },
15798
+ ...deps.bindingNames !== void 0 && {
15799
+ bindingNames: deps.bindingNames
15800
+ },
15801
+ ...deps.model !== void 0 && { model: deps.model },
15802
+ compileSubquery: (sqIntent, paramOffset) => buildSubqueryFromIntent(
15803
+ sqIntent,
15804
+ paramOffset,
15805
+ deps.naming,
15806
+ schemaName,
15807
+ "rawExists",
15808
+ deps.bindingNames
15809
+ )
15810
+ };
15811
+ whereGuard = compileWhereIntent(intent.where, whereCtx);
15608
15812
  }
15813
+ const config = {
15814
+ table: intent.table,
15815
+ matchColumns,
15816
+ allColumns,
15817
+ columnArrays,
15818
+ ...scalarSet && { scalarSet },
15819
+ ...intent.returning && { returning: [...intent.returning] },
15820
+ ...columnTypes && { columnTypes },
15821
+ ...whereGuard !== void 0 && { whereGuard }
15822
+ };
15823
+ const ast = compileUnnestUpdate(config, ctx, state);
15824
+ const sql = deparseQuoted(ast);
15825
+ return {
15826
+ sql,
15827
+ parameters: state.parameters
15828
+ };
15609
15829
  }
15610
- function buildSimplifiedPlanReport(plan, allDecisions, schemaName) {
15611
- const bvFromSource = plan.intent?.batchValuesSource;
15612
- const batchValuesFromFields = bvFromSource ? (() => {
15613
- const { rangeFunction, params } = buildBatchValuesRangeFn(
15614
- bvFromSource,
15615
- 1
15616
- );
15617
- return {
15618
- batchValuesFromNode: rangeFunction,
15619
- batchValuesFromParams: params
15620
- };
15621
- })() : {};
15830
+ function compileDelete2(intent, options, deps) {
15831
+ const schemaName = deps.schemaName;
15832
+ const resolvedModel = options?.model ?? deps.model;
15833
+ const ctx = {
15834
+ naming: deps.naming,
15835
+ rootTable: intent.table,
15836
+ ...schemaName !== void 0 && { schema: schemaName },
15837
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15838
+ maxRecursiveDepth: 100,
15839
+ ...resolvedModel !== void 0 && { model: resolvedModel }
15840
+ };
15841
+ const state = createCompilerState();
15842
+ const resolvedWhere = intent.where ? resolveExistsIntent(intent.where, intent.table, deps) : void 0;
15843
+ const config = {
15844
+ table: intent.table,
15845
+ ...resolvedWhere && { where: [whereIntentAsDecision(resolvedWhere)] },
15846
+ ...intent.returning && { returning: [...intent.returning] }
15847
+ };
15848
+ const ast = compileDelete(config, ctx, state);
15849
+ const sql = deparseQuoted(ast);
15622
15850
  return {
15623
- rootTable: plan.rootTable,
15624
- decisions: allDecisions,
15625
- ...schemaName ? { schema: schemaName } : {},
15626
- ...plan.intent?.existsWrap ? { existsWrap: true } : {},
15627
- ...plan.intent?.lock ? { lock: plan.intent.lock } : {},
15628
- ...batchValuesFromFields
15851
+ sql,
15852
+ parameters: state.parameters
15629
15853
  };
15630
15854
  }
15631
- function compileSelect(plan, options, deps) {
15855
+ function compileUpsert2(intent, options, deps) {
15632
15856
  const schemaName = deps.schemaName;
15633
- const resolvedModelForCompiler = options?.model ?? deps.model;
15634
- const compilerOptions = {
15857
+ const ctx = {
15635
15858
  naming: deps.naming,
15636
- ...schemaName && { schema: schemaName },
15637
- defaultPkColumnName: deps.defaultPk,
15638
- deriveFkColumnName: deps.deriveFk,
15639
- ...resolvedModelForCompiler != null && {
15640
- model: resolvedModelForCompiler
15641
- }
15859
+ rootTable: intent.table,
15860
+ ...schemaName !== void 0 && { schema: schemaName },
15861
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15862
+ ...deps.model !== void 0 && { model: deps.model },
15863
+ maxRecursiveDepth: 100
15642
15864
  };
15643
- const execIntent = plan.executableIntent ?? plan.intent;
15644
- const planForCompilation = plan.executableIntent !== void 0 ? { ...plan, intent: plan.executableIntent } : plan;
15645
- let simplifiedPlan;
15646
- if (execIntent) {
15647
- let decisions = intentToDecisions(execIntent, plan.rootTable);
15648
- const resolvedModel = options?.model ?? deps.model;
15649
- if (resolvedModel) {
15650
- decisions = convertDottedFieldsToExists(
15651
- decisions,
15652
- plan.rootTable,
15653
- resolvedModel
15654
- );
15865
+ const state = createCompilerState();
15866
+ const firstRow = intent.values?.[0] ?? {};
15867
+ const rawExprs = {};
15868
+ const scalarSet = {};
15869
+ if (intent.action.type === "doUpdate" && intent.action.set) {
15870
+ for (const [key, val] of Object.entries(intent.action.set)) {
15871
+ if (isSqlRaw2(val)) {
15872
+ rawExprs[key] = val.sql;
15873
+ } else {
15874
+ scalarSet[key] = val;
15875
+ }
15655
15876
  }
15656
- enrichExistsDecisionsInPlace(
15657
- decisions,
15658
- planForCompilation,
15659
- options?.model ?? deps.model
15660
- );
15661
- const unifiedIncludeDecisions = extractAllIncludeDecisions(
15662
- planForCompilation,
15663
- deps.defaultPk,
15664
- deps.deriveFk
15665
- );
15666
- const coveredByPlanner = new Set(
15667
- unifiedIncludeDecisions.filter((d) => d.type === "includeStrategy").map((d) => d.relationName).filter(Boolean)
15668
- );
15669
- const synthesizedModel = options?.model ?? deps.model;
15670
- const synthesizedJoins = synthesizedModel ? synthesizeMissingJoinDecisions(
15671
- planForCompilation,
15672
- coveredByPlanner,
15673
- synthesizedModel,
15674
- deps.defaultPk,
15675
- deps.deriveFk
15676
- ) : [];
15677
- const allUnifiedIncludeDecisions = synthesizedJoins.length > 0 ? [...unifiedIncludeDecisions, ...synthesizedJoins] : unifiedIncludeDecisions;
15678
- const enrichedUnifiedDecisions = [
15679
- ...allUnifiedIncludeDecisions
15680
- ];
15681
- stripJoinColumnsForAggregation(enrichedUnifiedDecisions, execIntent);
15682
- applyJoinHydrationPrefixes(enrichedUnifiedDecisions);
15683
- const includedRelations = new Set(
15684
- enrichedUnifiedDecisions.filter((d) => d.type === "includeStrategy").map((d) => d.relationName).filter(Boolean)
15685
- );
15686
- if (includedRelations.size > 0) {
15687
- const relationColumnsMap = buildRelationColumnsMap(
15688
- decisions,
15689
- includedRelations
15690
- );
15691
- injectAndValidateRelationColumns(
15692
- enrichedUnifiedDecisions,
15693
- relationColumnsMap,
15694
- options?.model ?? deps.model
15695
- );
15877
+ }
15878
+ const hasScalarSet = Object.keys(scalarSet).length > 0;
15879
+ const mergedFirstRow = hasScalarSet ? { ...firstRow, ...scalarSet } : firstRow;
15880
+ const columns = Object.keys(mergedFirstRow);
15881
+ const values = (intent.values ?? []).map((row) => {
15882
+ const mergedRow = hasScalarSet ? { ...row, ...scalarSet } : row;
15883
+ return columns.map((col) => mergedRow[col]);
15884
+ });
15885
+ const conflictTarget = {};
15886
+ if ("columns" in intent.onConflict) {
15887
+ conflictTarget.columns = [...intent.onConflict.columns];
15888
+ } else if ("constraint" in intent.onConflict) {
15889
+ conflictTarget.constraint = intent.onConflict.constraint;
15890
+ }
15891
+ const conflictAction = intent.action.type === "doNothing" ? "nothing" : "update";
15892
+ let updateColumns;
15893
+ if (intent.action.type === "doUpdate") {
15894
+ if (intent.action.set) {
15895
+ updateColumns = Object.keys(intent.action.set);
15896
+ } else {
15897
+ const conflictCols = "columns" in intent.onConflict ? intent.onConflict.columns : [];
15898
+ updateColumns = columns.filter((col) => !conflictCols.includes(col));
15696
15899
  }
15697
- const deduplicatedDecisions = includedRelations.size > 0 ? decisions.filter((d) => {
15698
- if (d.type === "selectRelationColumn" && d.relation) {
15699
- const rel = d.relation;
15700
- const rootRelation = rel.split(".")[0] ?? rel;
15701
- if (includedRelations.has(rootRelation)) {
15702
- return false;
15703
- }
15704
- }
15705
- return true;
15706
- }) : decisions;
15707
- const joinIntentDecisions = execIntent?.joins && execIntent.joins.length > 0 ? compileJoinIntents(
15708
- execIntent.joins,
15709
- plan.rootTable,
15710
- schemaName,
15711
- deps
15712
- ) : [];
15713
- const allDecisions = [
15714
- ...deduplicatedDecisions,
15715
- ...enrichedUnifiedDecisions,
15716
- ...joinIntentDecisions
15717
- ];
15718
- const rangeModel = options?.model ?? deps.model;
15719
- enrichRangeDecisions(allDecisions, rangeModel, plan.rootTable);
15720
- simplifiedPlan = buildSimplifiedPlanReport(
15721
- planForCompilation,
15722
- allDecisions,
15723
- schemaName
15900
+ }
15901
+ const columnTypes = getColumnTypes(intent.table, columns, deps);
15902
+ const hasRawExprs = Object.keys(rawExprs).length > 0;
15903
+ const actionWhere = intent.action.type === "doUpdate" && intent.action.where ? intent.action.where : void 0;
15904
+ const resolvedActionWhere = actionWhere ? resolveExistsIntent(actionWhere, intent.table, deps) : void 0;
15905
+ const config = {
15906
+ table: intent.table,
15907
+ columns,
15908
+ values,
15909
+ conflictTarget,
15910
+ conflictAction,
15911
+ ...updateColumns && { updateColumns },
15912
+ ...intent.returning && { returning: [...intent.returning] },
15913
+ ...columnTypes && { columnTypes },
15914
+ ...hasRawExprs && { updateExpressions: rawExprs },
15915
+ ...resolvedActionWhere && {
15916
+ actionWhereIntent: resolvedActionWhere,
15917
+ compileActionWhere: (where, paramState) => compileUpsertActionWhere(
15918
+ where,
15919
+ intent.table,
15920
+ paramState,
15921
+ deps,
15922
+ schemaName
15923
+ )
15924
+ }
15925
+ };
15926
+ const maxBatchSize = options?.maxBatchSize;
15927
+ if (maxBatchSize !== void 0 && values.length > maxBatchSize) {
15928
+ throw new InvalidOperationError3(
15929
+ "upsert",
15930
+ `Batch size ${values.length} exceeds maxBatchSize ${maxBatchSize}`
15724
15931
  );
15725
- } else {
15726
- simplifiedPlan = {
15727
- rootTable: plan.rootTable,
15728
- decisions: bridgeLegacyDecisions(plan.decisions),
15729
- ...schemaName ? { schema: schemaName } : {}
15730
- };
15731
15932
  }
15732
- const result = compilePlan(simplifiedPlan, compilerOptions);
15933
+ const batchThreshold = options?.batchThreshold ?? 50;
15934
+ const useUnnest = values.length > 0 && (batchThreshold === 0 || values.length > batchThreshold);
15935
+ const ast = useUnnest ? compileUnnestUpsert(config, ctx, state) : compileUpsert(config, ctx, state);
15936
+ const sql = deparseQuoted(ast);
15733
15937
  return {
15734
- sql: result.sql,
15735
- parameters: result.parameters
15938
+ sql,
15939
+ parameters: state.parameters
15736
15940
  };
15737
15941
  }
15738
- function compileWithIncludes(plan, options, deps) {
15739
- const main = compileSelect(plan, options, deps);
15740
- const subqueryIncludes = [];
15741
- for (const d of plan.decisions) {
15742
- if (d.type !== "include-strategy" || d.choice !== "subquery") continue;
15743
- const ctx = d.context;
15744
- if (!ctx.target) continue;
15745
- const relationName = ctx.includeAlias ?? ctx.relation;
15746
- if (!relationName) continue;
15747
- const rawFk = deriveForeignKey(ctx, deps.deriveFk, deps.defaultPk) ?? deps.defaultPk;
15748
- const fk = Array.isArray(rawFk) ? rawFk[0] : rawFk;
15749
- const isBelongsTo = ctx.relationType === "belongsTo";
15750
- const sourceKey = isBelongsTo ? fk : "id";
15751
- const targetFk = isBelongsTo ? "id" : fk;
15752
- const includeIntent = plan.intent?.include?.find(
15753
- (i) => i.relation === relationName || i.relation === ctx.includeAlias
15754
- );
15755
- const entry = {
15756
- relationName,
15757
- targetTable: ctx.target,
15758
- foreignKey: targetFk,
15759
- sourceKey,
15760
- sourceTable: ctx.sourceTable ?? plan.rootTable
15761
- };
15762
- if (typeof ctx.relationType === "string") {
15763
- entry.relationType = ctx.relationType;
15764
- }
15765
- if (includeIntent?.select != null) {
15766
- entry.select = includeIntent.select;
15767
- }
15768
- if (includeIntent?.where != null) {
15769
- entry.where = includeIntent.where;
15942
+ function compileUpsertFrom2(intent, options, deps) {
15943
+ const schemaName = deps.schemaName;
15944
+ const sourceCte = intent.sourceQuery !== void 0 && !hasBindingName(deps.bindingNames, intent.source, deps.naming) ? compileSourceQueryCte(
15945
+ "compileUpsertFrom",
15946
+ intent.source,
15947
+ intent.sourceQuery,
15948
+ options,
15949
+ deps
15950
+ ) : void 0;
15951
+ const bindingNames = intent.sourceQuery !== void 0 ? withBindingName(deps.bindingNames, intent.source, deps.naming) : deps.bindingNames;
15952
+ const ctx = {
15953
+ naming: deps.naming,
15954
+ rootTable: intent.source,
15955
+ ...schemaName !== void 0 && { schema: schemaName },
15956
+ ...bindingNames !== void 0 && { bindingNames },
15957
+ maxRecursiveDepth: 100
15958
+ };
15959
+ const state = createCompilerState();
15960
+ let columns;
15961
+ if (intent.columns) {
15962
+ columns = [...intent.columns];
15963
+ } else if (options?.model) {
15964
+ const targetTable = options.model.getTable(intent.table);
15965
+ if (targetTable) {
15966
+ columns = targetTable.columns.map((c) => c.name);
15770
15967
  }
15771
- subqueryIncludes.push(entry);
15772
15968
  }
15773
- return { main, subqueryIncludes };
15969
+ const config = {
15970
+ targetTable: intent.table,
15971
+ sourceTable: intent.source,
15972
+ conflictColumns: [...intent.conflictColumns],
15973
+ ...columns && { columns },
15974
+ ...intent.where && { where: [whereIntentAsDecision(intent.where)] },
15975
+ ...intent.limit !== void 0 && { limit: intent.limit },
15976
+ ...intent.returning && { returning: [...intent.returning] }
15977
+ };
15978
+ const ast = compileUpsertFrom(config, ctx, state);
15979
+ const sql = deparseQuoted(ast);
15980
+ return prependSourceCte(
15981
+ sql,
15982
+ state.parameters,
15983
+ intent.source,
15984
+ sourceCte,
15985
+ deps.naming
15986
+ );
15774
15987
  }
15775
15988
 
15776
15989
  // src/adapter-compiler-recursive.ts
@@ -16859,6 +17072,7 @@ function buildRecursiveAnchorWhere(where, tableAlias, deps, state) {
16859
17072
  // src/pgsql-adapter.ts
16860
17073
  init_assert_field();
16861
17074
  init_ast_helpers();
17075
+ init_binding_registry();
16862
17076
 
16863
17077
  // src/ddl/index-operations.ts
16864
17078
  init_validate();
@@ -17025,9 +17239,9 @@ function compileLeafOrBranch(intent, compileFn) {
17025
17239
  const result = compileFn(intent);
17026
17240
  return { sql: result.sql, parameters: result.parameters };
17027
17241
  }
17028
- function createLeafCompileFn(adapter, model, planFn2, options) {
17242
+ function createLeafCompileFn(adapter, model, planFn3, options) {
17029
17243
  return (query) => {
17030
- const planReport = planFn2(query, model, {
17244
+ const planReport = planFn3(query, model, {
17031
17245
  dialectCapabilities: adapter.dialectCapabilities
17032
17246
  });
17033
17247
  return adapter.compile(planReport, { ...options, model });
@@ -17149,7 +17363,7 @@ function mapFetchDirection(direction) {
17149
17363
 
17150
17364
  // src/pgsql-adapter.ts
17151
17365
  init_validate();
17152
- function renumberSqlParams(sql, offset) {
17366
+ function renumberSqlParams2(sql, offset) {
17153
17367
  if (offset === 0) return sql;
17154
17368
  return sql.replace(/\$(\d+)/g, (_match, num) => {
17155
17369
  return `$${Number.parseInt(num, 10) + offset}`;
@@ -17158,6 +17372,79 @@ function renumberSqlParams(sql, offset) {
17158
17372
  function isCompiledNqlQuery(input) {
17159
17373
  return !("intent" in input) && ("query" in input || "cteQuery" in input || "mutation" in input || "setOperation" in input || "bindings" in input || "mutationBindings" in input);
17160
17374
  }
17375
+ function formatGuardPathSegment(key) {
17376
+ return /^[A-Za-z_$][\w$]*$/.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
17377
+ }
17378
+ function findNqlBindingRefMarker(value, path, seen = /* @__PURE__ */ new WeakSet()) {
17379
+ if (value === null || typeof value !== "object") {
17380
+ return void 0;
17381
+ }
17382
+ if (isNqlBindingRef2(value)) {
17383
+ return {
17384
+ ref: getNqlBindingRefName2(value),
17385
+ path
17386
+ };
17387
+ }
17388
+ if (seen.has(value)) {
17389
+ return void 0;
17390
+ }
17391
+ seen.add(value);
17392
+ if (Array.isArray(value)) {
17393
+ for (let i = 0; i < value.length; i++) {
17394
+ const found = findNqlBindingRefMarker(value[i], `${path}[${i}]`, seen);
17395
+ if (found) return found;
17396
+ }
17397
+ return void 0;
17398
+ }
17399
+ const record = value;
17400
+ for (const key of Object.keys(record)) {
17401
+ const found = findNqlBindingRefMarker(
17402
+ record[key],
17403
+ `${path}${formatGuardPathSegment(key)}`,
17404
+ seen
17405
+ );
17406
+ if (found) return found;
17407
+ }
17408
+ return void 0;
17409
+ }
17410
+ function guardCompiledQuery(query, context) {
17411
+ const found = findNqlBindingRefMarker(query.parameters, "parameters");
17412
+ if (found) {
17413
+ throw new Error(
17414
+ `NQL binding reference marker '${found.ref}' survived into emitted SQL parameters at ${found.path} while compiling ${context}. Binding references must resolve to CTE subqueries before SQL emission.`
17415
+ );
17416
+ }
17417
+ return query;
17418
+ }
17419
+ function guardCompileResultWithIncludes(result, context) {
17420
+ return {
17421
+ ...result,
17422
+ main: guardCompiledQuery(result.main, context)
17423
+ };
17424
+ }
17425
+ function findPhysicalTableNameCollision(model, bindingName, naming) {
17426
+ for (const [modelTableName, table] of model.tables) {
17427
+ if (bindingName === table.name) return table.name;
17428
+ if (bindingName === modelTableName) return table.name;
17429
+ const emittedTableName = naming.toDatabase(table.name);
17430
+ if (bindingName === emittedTableName) return emittedTableName;
17431
+ const emittedModelTableName = naming.toDatabase(modelTableName);
17432
+ if (bindingName === emittedModelTableName) return emittedModelTableName;
17433
+ }
17434
+ return void 0;
17435
+ }
17436
+ function findDuplicateEmittedNqlBindingName(bindingNames, naming) {
17437
+ const seen = /* @__PURE__ */ new Map();
17438
+ for (const bindingName of bindingNames) {
17439
+ const emittedName = emittedBindName(bindingName, naming);
17440
+ const originalName = seen.get(emittedName);
17441
+ if (originalName !== void 0 && originalName !== bindingName) {
17442
+ return { originalName, duplicateName: bindingName, emittedName };
17443
+ }
17444
+ seen.set(emittedName, bindingName);
17445
+ }
17446
+ return void 0;
17447
+ }
17161
17448
  var PgsqlAdapter = class _PgsqlAdapter {
17162
17449
  pool;
17163
17450
  client;
@@ -17224,17 +17511,19 @@ var PgsqlAdapter = class _PgsqlAdapter {
17224
17511
  ...overrides
17225
17512
  };
17226
17513
  }
17227
- buildCompileDeps(options) {
17514
+ buildCompileDeps(options, bindingNames) {
17228
17515
  if (options?.schemaName) {
17229
17516
  validateIdentifier(options.schemaName, "schema");
17230
17517
  }
17518
+ const naming = options?.naming ?? this.naming;
17231
17519
  return {
17232
- naming: this.naming,
17520
+ naming,
17233
17521
  // `||` (not `??`): empty string is treated as "no override" and falls back to this.schemaName (which may be a configured schema or undefined)
17234
17522
  schemaName: options?.schemaName || this.schemaName,
17235
17523
  model: options?.model ?? this.model,
17236
17524
  defaultPk: this.defaultPk,
17237
- deriveFk: this.deriveFk
17525
+ deriveFk: this.deriveFk,
17526
+ ...bindingNames !== void 0 && { bindingNames }
17238
17527
  };
17239
17528
  }
17240
17529
  requireNqlCompileModel(options) {
@@ -17246,7 +17535,24 @@ var PgsqlAdapter = class _PgsqlAdapter {
17246
17535
  }
17247
17536
  return model;
17248
17537
  }
17249
- compileNqlMutation(bundle, options) {
17538
+ assertNqlBindingNamesDisjointFromTables(bindingNames, options) {
17539
+ if (bindingNames === void 0 || bindingNames.size === 0) return;
17540
+ const model = this.requireNqlCompileModel(options);
17541
+ const naming = this.buildCompileDeps(options, bindingNames).naming;
17542
+ for (const bindingName of bindingNames) {
17543
+ const physicalTableName = findPhysicalTableNameCollision(
17544
+ model,
17545
+ bindingName,
17546
+ naming
17547
+ );
17548
+ if (physicalTableName !== void 0) {
17549
+ throw new Error(
17550
+ `NQL binding '${bindingName}' collides with physical table name '${physicalTableName}'. NQL binding names must be disjoint from model table names.`
17551
+ );
17552
+ }
17553
+ }
17554
+ }
17555
+ compileNqlMutation(bundle, options, bindingNames) {
17250
17556
  const mutation = bundle.mutation;
17251
17557
  if (mutation === void 0) {
17252
17558
  throw new Error("NQL bundle did not contain a mutation intent.");
@@ -17256,84 +17562,124 @@ var PgsqlAdapter = class _PgsqlAdapter {
17256
17562
  return compileInsert2(
17257
17563
  mutation,
17258
17564
  options,
17259
- this.buildCompileDeps(options)
17565
+ this.buildCompileDeps(options, bindingNames)
17260
17566
  );
17261
17567
  case "insert_from":
17262
17568
  return compileInsertFrom2(
17263
17569
  mutation,
17264
17570
  options,
17265
- this.buildCompileDeps(options)
17571
+ this.buildCompileDeps(options, bindingNames)
17266
17572
  );
17267
17573
  case "update":
17268
17574
  return compileUpdate2(
17269
17575
  mutation,
17270
17576
  options,
17271
- this.buildCompileDeps(options)
17577
+ this.buildCompileDeps(options, bindingNames)
17272
17578
  );
17273
17579
  case "delete":
17274
17580
  return compileDelete2(
17275
17581
  mutation,
17276
17582
  options,
17277
- this.buildCompileDeps(options)
17583
+ this.buildCompileDeps(options, bindingNames)
17278
17584
  );
17279
17585
  case "upsert":
17280
17586
  return compileUpsert2(
17281
17587
  mutation,
17282
17588
  options,
17283
- this.buildCompileDeps(options)
17589
+ this.buildCompileDeps(options, bindingNames)
17284
17590
  );
17285
17591
  case "upsert_from":
17286
17592
  return compileUpsertFrom2(
17287
17593
  mutation,
17288
17594
  options,
17289
- this.buildCompileDeps(options)
17595
+ this.buildCompileDeps(options, bindingNames)
17290
17596
  );
17291
17597
  }
17292
17598
  throw new Error(
17293
17599
  `Unsupported NQL mutation type: ${mutation.type}`
17294
17600
  );
17295
17601
  }
17296
- compileNqlBundleLeaf(bundle, options) {
17602
+ compileNqlBundleLeaf(bundle, options, bindingNames) {
17297
17603
  if (bundle.query !== void 0) {
17604
+ const naming = this.buildCompileDeps(options, bindingNames).naming;
17605
+ if (hasBindingName(bindingNames, bundle.query.from, naming)) {
17606
+ throw new Error(
17607
+ `NQL binding '${bundle.query.from}' cannot be used as the final FROM source yet. Use the binding from a mutation source or WHERE IN predicate, or select from a real table.`
17608
+ );
17609
+ }
17298
17610
  const model = this.requireNqlCompileModel(options);
17299
- const planReport = planFn(bundle.query, model, {
17611
+ const planReport = planFn2(bundle.query, model, {
17300
17612
  dialectCapabilities: this.dialectCapabilities
17301
17613
  });
17302
- return compileSelect(
17303
- planReport,
17304
- options,
17305
- this.buildCompileDeps(options)
17614
+ return guardCompiledQuery(
17615
+ compileSelect(
17616
+ planReport,
17617
+ options,
17618
+ this.buildCompileDeps(options, bindingNames)
17619
+ ),
17620
+ "NQL query"
17306
17621
  );
17307
17622
  }
17308
17623
  if (bundle.cteQuery !== void 0) {
17309
- return compileCteQuery(
17310
- bundle.cteQuery,
17311
- options,
17312
- this.buildCompileDeps(options)
17624
+ return guardCompiledQuery(
17625
+ compileCteQuery(
17626
+ bundle.cteQuery,
17627
+ options,
17628
+ this.buildCompileDeps(options, bindingNames)
17629
+ ),
17630
+ "NQL CTE query"
17313
17631
  );
17314
17632
  }
17315
17633
  if (bundle.setOperation !== void 0) {
17316
17634
  const model = this.requireNqlCompileModel(options);
17317
- return this.compileSetOperation(
17318
- bundle.setOperation,
17319
- model,
17320
- options
17635
+ return guardCompiledQuery(
17636
+ this.compileSetOperationWithBindings(
17637
+ bundle.setOperation,
17638
+ model,
17639
+ options,
17640
+ bindingNames
17641
+ ),
17642
+ "NQL set operation"
17321
17643
  );
17322
17644
  }
17323
17645
  if (bundle.mutation !== void 0) {
17324
- return this.compileNqlMutation(bundle, options);
17646
+ return guardCompiledQuery(
17647
+ this.compileNqlMutation(
17648
+ bundle,
17649
+ options,
17650
+ bindingNames
17651
+ ),
17652
+ "NQL mutation"
17653
+ );
17325
17654
  }
17326
17655
  throw new Error("NQL bundle did not contain a compilable intent.");
17327
17656
  }
17328
17657
  compileNqlBundle(bundle, options) {
17329
17658
  const ctes = [];
17330
17659
  const parameters = [];
17660
+ const naming = this.buildCompileDeps(options).naming;
17661
+ const duplicateEmittedBinding = bundle.bindings !== void 0 ? findDuplicateEmittedNqlBindingName(bundle.bindings.keys(), naming) : void 0;
17662
+ if (duplicateEmittedBinding !== void 0) {
17663
+ throw new Error(
17664
+ `NQL bindings '${duplicateEmittedBinding.originalName}' and '${duplicateEmittedBinding.duplicateName}' emit to duplicate CTE name '${duplicateEmittedBinding.emittedName}'. NQL binding names must be unique after database naming.`
17665
+ );
17666
+ }
17667
+ const bindingNames = bundle.bindings !== void 0 ? new Set(
17668
+ [...bundle.bindings.keys()].map(
17669
+ (name) => emittedBindName(name, naming)
17670
+ )
17671
+ ) : void 0;
17672
+ this.assertNqlBindingNamesDisjointFromTables(bindingNames, options);
17331
17673
  for (const [name, queryIntent] of bundle.bindings ?? []) {
17332
- const cteName = quoteIdent2(name, "alias");
17674
+ const cteName = quoteIdent2(emittedBindName(name, naming), "alias");
17333
17675
  const bindingBundle = bundle.mutationBindings?.has(name) ? { mutation: bundle.mutationBindings.get(name) } : { query: queryIntent };
17334
- const compiled2 = this.compileNqlBundleLeaf(bindingBundle, options);
17676
+ const compiled2 = this.compileNqlBundleLeaf(
17677
+ bindingBundle,
17678
+ options,
17679
+ bindingNames
17680
+ );
17335
17681
  ctes.push(
17336
- `${cteName} as (${renumberSqlParams(compiled2.sql, parameters.length)})`
17682
+ `${cteName} as (${renumberSqlParams2(compiled2.sql, parameters.length)})`
17337
17683
  );
17338
17684
  parameters.push(...compiled2.parameters);
17339
17685
  }
@@ -17346,14 +17692,21 @@ var PgsqlAdapter = class _PgsqlAdapter {
17346
17692
  setOperation: bundle.setOperation
17347
17693
  }
17348
17694
  };
17349
- const compiled = this.compileNqlBundleLeaf(leafBundle, options);
17695
+ const compiled = this.compileNqlBundleLeaf(
17696
+ leafBundle,
17697
+ options,
17698
+ bindingNames
17699
+ );
17350
17700
  if (ctes.length === 0) {
17351
- return compiled;
17701
+ return guardCompiledQuery(compiled, "NQL bundle");
17352
17702
  }
17353
- return {
17354
- sql: `WITH ${ctes.join(", ")} ${renumberSqlParams(compiled.sql, parameters.length)}`,
17355
- parameters: [...parameters, ...compiled.parameters]
17356
- };
17703
+ return guardCompiledQuery(
17704
+ {
17705
+ sql: `WITH ${ctes.join(", ")} ${renumberSqlParams2(compiled.sql, parameters.length)}`,
17706
+ parameters: [...parameters, ...compiled.parameters]
17707
+ },
17708
+ "NQL bundle"
17709
+ );
17357
17710
  }
17358
17711
  /**
17359
17712
  * Returns the pool/client executor, or throws if in compile-only mode.
@@ -17373,7 +17726,7 @@ var PgsqlAdapter = class _PgsqlAdapter {
17373
17726
  }
17374
17727
  /** PostgreSQL dialect capabilities for planner strategy selection */
17375
17728
  get dialectCapabilities() {
17376
- return POSTGRESQL_CAPABILITIES;
17729
+ return POSTGRESQL_CAPABILITIES2;
17377
17730
  }
17378
17731
  /**
17379
17732
  * DB column casing convention used by this adapter.
@@ -17397,16 +17750,18 @@ var PgsqlAdapter = class _PgsqlAdapter {
17397
17750
  if (isCompiledNqlQuery(plan)) {
17398
17751
  return this.compileNqlBundle(plan, options);
17399
17752
  }
17400
- return compileSelect(plan, options, this.buildCompileDeps(options));
17753
+ return guardCompiledQuery(
17754
+ compileSelect(plan, options, this.buildCompileDeps(options)),
17755
+ "select plan"
17756
+ );
17401
17757
  }
17402
17758
  /**
17403
17759
  * Compile a plan with includes, returning subquery include metadata (DX-033).
17404
17760
  */
17405
17761
  compileWithIncludes(plan, options) {
17406
- return compileWithIncludes(
17407
- plan,
17408
- options,
17409
- this.buildCompileDeps(options)
17762
+ return guardCompileResultWithIncludes(
17763
+ compileWithIncludes(plan, options, this.buildCompileDeps(options)),
17764
+ "select plan with includes"
17410
17765
  );
17411
17766
  }
17412
17767
  /**
@@ -17419,11 +17774,14 @@ var PgsqlAdapter = class _PgsqlAdapter {
17419
17774
  * @returns Compiled query for fetching related records
17420
17775
  */
17421
17776
  compileSubqueryInclude(info, parentIds, options) {
17422
- return compileSubqueryInclude(
17423
- info,
17424
- parentIds,
17425
- options,
17426
- this.buildCompileDeps(options)
17777
+ return guardCompiledQuery(
17778
+ compileSubqueryInclude(
17779
+ info,
17780
+ parentIds,
17781
+ options,
17782
+ this.buildCompileDeps(options)
17783
+ ),
17784
+ "subquery include"
17427
17785
  );
17428
17786
  }
17429
17787
  /**
@@ -17467,7 +17825,10 @@ var PgsqlAdapter = class _PgsqlAdapter {
17467
17825
  targetList: [{ ResTarget: { val: node } }]
17468
17826
  });
17469
17827
  const sql = deparseQuoted(ast);
17470
- return { sql, parameters: state.parameters };
17828
+ return guardCompiledQuery(
17829
+ { sql, parameters: state.parameters },
17830
+ "select expression"
17831
+ );
17471
17832
  }
17472
17833
  /**
17473
17834
  * Compile an insert intent to executable SQL.
@@ -17477,24 +17838,29 @@ var PgsqlAdapter = class _PgsqlAdapter {
17477
17838
  * - rows > batchThreshold OR batchThreshold === 0: SELECT unnest($1::type[]),...
17478
17839
  */
17479
17840
  compileInsert(intent, options) {
17480
- return compileInsert2(intent, options, this.buildCompileDeps(options));
17841
+ return guardCompiledQuery(
17842
+ compileInsert2(intent, options, this.buildCompileDeps(options)),
17843
+ "insert"
17844
+ );
17481
17845
  }
17482
17846
  /**
17483
17847
  * Compile an insert-from intent to executable SQL (NQL-ALIGN).
17484
17848
  * INSERT INTO target (cols) SELECT cols FROM source WHERE ... LIMIT ... RETURNING ...
17485
17849
  */
17486
17850
  compileInsertFrom(intent, options) {
17487
- return compileInsertFrom2(
17488
- intent,
17489
- options,
17490
- this.buildCompileDeps(options)
17851
+ return guardCompiledQuery(
17852
+ compileInsertFrom2(intent, options, this.buildCompileDeps(options)),
17853
+ "insert from"
17491
17854
  );
17492
17855
  }
17493
17856
  /**
17494
17857
  * Compile an update intent to executable SQL.
17495
17858
  */
17496
17859
  compileUpdate(intent, options) {
17497
- return compileUpdate2(intent, options, this.buildCompileDeps(options));
17860
+ return guardCompiledQuery(
17861
+ compileUpdate2(intent, options, this.buildCompileDeps(options)),
17862
+ "update"
17863
+ );
17498
17864
  }
17499
17865
  /**
17500
17866
  * Compile a batch update intent to executable SQL using unnest FROM strategy (BATCH-001).
@@ -17506,33 +17872,37 @@ var PgsqlAdapter = class _PgsqlAdapter {
17506
17872
  * [RETURNING ...]
17507
17873
  */
17508
17874
  compileBatchUpdate(intent, options) {
17509
- return compileBatchUpdate(
17510
- intent,
17511
- options,
17512
- this.buildCompileDeps(options)
17875
+ return guardCompiledQuery(
17876
+ compileBatchUpdate(intent, options, this.buildCompileDeps(options)),
17877
+ "batch update"
17513
17878
  );
17514
17879
  }
17515
17880
  /**
17516
17881
  * Compile a delete intent to executable SQL.
17517
17882
  */
17518
17883
  compileDelete(intent, options) {
17519
- return compileDelete2(intent, options, this.buildCompileDeps(options));
17884
+ return guardCompiledQuery(
17885
+ compileDelete2(intent, options, this.buildCompileDeps(options)),
17886
+ "delete"
17887
+ );
17520
17888
  }
17521
17889
  /**
17522
17890
  * Compile an upsert intent to executable SQL (DX-026).
17523
17891
  */
17524
17892
  compileUpsert(intent, options) {
17525
- return compileUpsert2(intent, options, this.buildCompileDeps(options));
17893
+ return guardCompiledQuery(
17894
+ compileUpsert2(intent, options, this.buildCompileDeps(options)),
17895
+ "upsert"
17896
+ );
17526
17897
  }
17527
17898
  /**
17528
17899
  * Compile an upsert-from intent to executable SQL (NQL-BIND).
17529
17900
  * INSERT INTO target SELECT ... FROM source ON CONFLICT (cols) DO UPDATE SET ...
17530
17901
  */
17531
17902
  compileUpsertFrom(intent, options) {
17532
- return compileUpsertFrom2(
17533
- intent,
17534
- options,
17535
- this.buildCompileDeps(options)
17903
+ return guardCompiledQuery(
17904
+ compileUpsertFrom2(intent, options, this.buildCompileDeps(options)),
17905
+ "upsert from"
17536
17906
  );
17537
17907
  }
17538
17908
  /**
@@ -17540,11 +17910,14 @@ var PgsqlAdapter = class _PgsqlAdapter {
17540
17910
  * Supports adjacency-list and edge-table traversal modes.
17541
17911
  */
17542
17912
  compileRecursive(report, model, options) {
17543
- return compileRecursive(
17544
- report,
17545
- model,
17546
- options,
17547
- this.buildCompileDeps(options)
17913
+ return guardCompiledQuery(
17914
+ compileRecursive(
17915
+ report,
17916
+ model,
17917
+ options,
17918
+ this.buildCompileDeps(options)
17919
+ ),
17920
+ "recursive query"
17548
17921
  );
17549
17922
  }
17550
17923
  /**
@@ -17555,18 +17928,42 @@ var PgsqlAdapter = class _PgsqlAdapter {
17555
17928
  * to start after CTE params and prepend WITH clause.
17556
17929
  */
17557
17930
  compileCteQuery(intent, options) {
17558
- return compileCteQuery(intent, options, this.buildCompileDeps(options));
17931
+ return guardCompiledQuery(
17932
+ compileCteQuery(intent, options, this.buildCompileDeps(options)),
17933
+ "CTE query"
17934
+ );
17559
17935
  }
17560
17936
  /**
17561
17937
  * Compile a set operation (UNION / INTERSECT / EXCEPT) to SQL.
17562
17938
  */
17563
17939
  compileSetOperation(intent, model, options) {
17564
- const compileFn = createLeafCompileFn(this, model, planFn, options);
17940
+ return guardCompiledQuery(
17941
+ this.compileSetOperationWithBindings(intent, model, options),
17942
+ "set operation"
17943
+ );
17944
+ }
17945
+ compileSetOperationWithBindings(intent, model, options, bindingNames) {
17946
+ const compileFn = createLeafCompileFn(
17947
+ {
17948
+ dialectCapabilities: this.dialectCapabilities,
17949
+ compile: (planReport, leafOptions) => compileSelect(
17950
+ planReport,
17951
+ leafOptions,
17952
+ this.buildCompileDeps(leafOptions, bindingNames)
17953
+ )
17954
+ },
17955
+ model,
17956
+ planFn2,
17957
+ options
17958
+ );
17565
17959
  const result = compileSetOperation(intent, compileFn);
17566
- return {
17567
- sql: result.sql,
17568
- parameters: result.parameters
17569
- };
17960
+ return guardCompiledQuery(
17961
+ {
17962
+ sql: result.sql,
17963
+ parameters: result.parameters
17964
+ },
17965
+ "set operation"
17966
+ );
17570
17967
  }
17571
17968
  /**
17572
17969
  * Create a dump for observability.