@dbsp/adapter-pgsql 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -949,6 +949,42 @@ var init_ast_helpers = __esm({
949
949
  }
950
950
  });
951
951
 
952
+ // src/binding-registry.ts
953
+ function emittedBindName(name, naming) {
954
+ return naming.toDatabase(name);
955
+ }
956
+ function hasBindingName(bindingNames, name, naming) {
957
+ return bindingNames?.has(emittedBindName(name, naming)) ?? false;
958
+ }
959
+ function schemaForFromName(schemaName, fromName, bindingNames, naming) {
960
+ return hasBindingName(bindingNames, fromName, naming) ? void 0 : schemaName;
961
+ }
962
+ function withBindingName(bindingNames, name, naming) {
963
+ const next = new Set(bindingNames ?? []);
964
+ next.add(emittedBindName(name, naming));
965
+ return next;
966
+ }
967
+ var init_binding_registry = __esm({
968
+ "src/binding-registry.ts"() {
969
+ "use strict";
970
+ }
971
+ });
972
+
973
+ // src/dialect-capabilities.ts
974
+ function supportsDialectCapability(capabilities, flag) {
975
+ return capabilities?.[flag] !== false;
976
+ }
977
+ function assertDialectCapability(capabilities, flag, feature) {
978
+ if (!supportsDialectCapability(capabilities, flag)) {
979
+ throw new Error(`${feature} not supported by this adapter`);
980
+ }
981
+ }
982
+ var init_dialect_capabilities = __esm({
983
+ "src/dialect-capabilities.ts"() {
984
+ "use strict";
985
+ }
986
+ });
987
+
952
988
  // src/param-ref.ts
953
989
  function validateParamRef(paramRef) {
954
990
  const errors = [];
@@ -2813,6 +2849,7 @@ var init_param_intent = __esm({
2813
2849
  });
2814
2850
 
2815
2851
  // src/handlers/where/utils.ts
2852
+ import { validateTypeName as validateTypeName3 } from "@dbsp/core";
2816
2853
  import { isFieldRef, isParamIntent as isParamIntent3 } from "@dbsp/types";
2817
2854
  function buildColumnRef(column, ctx) {
2818
2855
  if (column.includes(".")) {
@@ -2874,7 +2911,11 @@ function resolveColumnPgType(columnName, ctx) {
2874
2911
  if (!table) return void 0;
2875
2912
  const column = table.columns.find((c) => c.name === columnName);
2876
2913
  if (!column) return void 0;
2877
- if (column.originalDbType) return validateDbTypeName(column.originalDbType);
2914
+ if (column.originalDbType) {
2915
+ const typeName = column.originalDbType.trim();
2916
+ validateTypeName3(typeName);
2917
+ return typeName;
2918
+ }
2878
2919
  return void 0;
2879
2920
  }
2880
2921
  var init_utils = __esm({
@@ -2883,7 +2924,6 @@ var init_utils = __esm({
2883
2924
  init_ast_helpers();
2884
2925
  init_param_intent();
2885
2926
  init_param_ref();
2886
- init_validate();
2887
2927
  init_types();
2888
2928
  init_param_intent();
2889
2929
  }
@@ -2915,6 +2955,7 @@ var init_any = __esm({
2915
2955
  "src/handlers/where/any.ts"() {
2916
2956
  "use strict";
2917
2957
  init_compiler_utils();
2958
+ init_dialect_capabilities();
2918
2959
  init_param_intent();
2919
2960
  init_param_ref();
2920
2961
  init_types();
@@ -2922,6 +2963,11 @@ var init_any = __esm({
2922
2963
  anyHandler = {
2923
2964
  operators: [COLLECTION_OPERATORS.ANY],
2924
2965
  compile(decision, ctx, state) {
2966
+ assertDialectCapability(
2967
+ ctx.dialectCapabilities,
2968
+ "supportsArrayType",
2969
+ "ANY array operator is"
2970
+ );
2925
2971
  const column = decision.column;
2926
2972
  if (!column) {
2927
2973
  throw new Error("ANY handler requires a column");
@@ -3117,6 +3163,9 @@ function buildCorrelation(sourceAlias, sourceColumn, targetAlias, targetColumn,
3117
3163
  const right = columnRef(targetColumn, targetAlias, void 0, ctx.naming);
3118
3164
  return eqExpr(left, right);
3119
3165
  }
3166
+ function schemaForExistsFromName(ctx, fromName) {
3167
+ return schemaForFromName(ctx.schema, fromName, ctx.bindingNames, ctx.naming);
3168
+ }
3120
3169
  function buildExistsSubquery(decision, ctx, state, dispatch) {
3121
3170
  const relation = decision.relation;
3122
3171
  const targetTable = decision.targetTable ?? relation;
@@ -3160,7 +3209,7 @@ function buildExistsSubquery(decision, ctx, state, dispatch) {
3160
3209
  let fromNode = rangeVar(
3161
3210
  targetTable,
3162
3211
  targetAlias,
3163
- ctx.schema,
3212
+ schemaForExistsFromName(ctx, targetTable),
3164
3213
  ctx.naming
3165
3214
  );
3166
3215
  const includeDecisions = decision.include;
@@ -3219,7 +3268,7 @@ function buildExistsSubquery(decision, ctx, state, dispatch) {
3219
3268
  const joinRangeVar = rangeVar(
3220
3269
  joinTargetTable,
3221
3270
  joinAlias,
3222
- ctx.schema,
3271
+ schemaForExistsFromName(ctx, joinTargetTable),
3223
3272
  ctx.naming
3224
3273
  );
3225
3274
  fromNode = joinExpr(joinType, fromNode, joinRangeVar, joinQuals);
@@ -3245,6 +3294,7 @@ var init_exists = __esm({
3245
3294
  "use strict";
3246
3295
  init_assert_field();
3247
3296
  init_ast_helpers();
3297
+ init_binding_registry();
3248
3298
  existsHandler = {
3249
3299
  operators: ["exists", "some"],
3250
3300
  compile(decision, ctx, state, dispatch) {
@@ -3315,6 +3365,21 @@ function createNotInExpr(columnNode, state, values, columnType) {
3315
3365
  }
3316
3366
  };
3317
3367
  }
3368
+ function createLiteralInListExpr(columnNode, state, values, negated) {
3369
+ const paramRefs = values.map((value) => {
3370
+ state.paramIndex++;
3371
+ state.parameters.push(value);
3372
+ return createParamRef(state.paramIndex);
3373
+ });
3374
+ return {
3375
+ A_Expr: {
3376
+ kind: "AEXPR_IN",
3377
+ name: [{ String: { sval: negated ? "<>" : "=" } }],
3378
+ lexpr: columnNode,
3379
+ rexpr: { List: { items: paramRefs } }
3380
+ }
3381
+ };
3382
+ }
3318
3383
  function isWholeArrayParamList(value) {
3319
3384
  return value.length === 1 && isParamIntent4(value[0]) && Array.isArray(value[0].value);
3320
3385
  }
@@ -3330,6 +3395,7 @@ var init_in = __esm({
3330
3395
  "src/handlers/where/in.ts"() {
3331
3396
  "use strict";
3332
3397
  init_ast_helpers();
3398
+ init_dialect_capabilities();
3333
3399
  init_param_intent();
3334
3400
  init_param_ref();
3335
3401
  init_types();
@@ -3358,7 +3424,11 @@ var init_in = __esm({
3358
3424
  }
3359
3425
  const columnNode = buildColumnRef(column, ctx);
3360
3426
  const columnType = resolveColumnPgType(column, ctx);
3361
- if (operator === COLLECTION_OPERATORS.NOT_IN || operator === "notIn") {
3427
+ const isNotIn = operator === COLLECTION_OPERATORS.NOT_IN || operator === "notIn";
3428
+ if (!supportsDialectCapability(ctx.dialectCapabilities, "supportsArrayType")) {
3429
+ return createLiteralInListExpr(columnNode, state, values, isNotIn);
3430
+ }
3431
+ if (isNotIn) {
3362
3432
  return createNotInExpr(columnNode, state, values, columnType);
3363
3433
  }
3364
3434
  return createInExpr(columnNode, state, values, columnType);
@@ -3372,10 +3442,16 @@ var jsonContainsHandler, jsonExistsHandler, jsonComparisonHandler;
3372
3442
  var init_json = __esm({
3373
3443
  "src/handlers/where/json.ts"() {
3374
3444
  "use strict";
3445
+ init_dialect_capabilities();
3375
3446
  init_utils();
3376
3447
  jsonContainsHandler = {
3377
3448
  operators: ["jsonContains", "jsonContainedBy"],
3378
3449
  compile(decision, ctx, state) {
3450
+ assertDialectCapability(
3451
+ ctx.dialectCapabilities,
3452
+ "supportsJsonOperators",
3453
+ "JSON operators are"
3454
+ );
3379
3455
  const column = decision.column;
3380
3456
  if (!column) {
3381
3457
  throw new Error("JSON contains handler requires a column");
@@ -3396,6 +3472,11 @@ var init_json = __esm({
3396
3472
  jsonExistsHandler = {
3397
3473
  operators: ["jsonExists"],
3398
3474
  compile(decision, ctx, state) {
3475
+ assertDialectCapability(
3476
+ ctx.dialectCapabilities,
3477
+ "supportsJsonOperators",
3478
+ "JSON operators are"
3479
+ );
3399
3480
  const column = decision.column;
3400
3481
  if (!column) {
3401
3482
  throw new Error("JSON exists handler requires a column");
@@ -3415,6 +3496,11 @@ var init_json = __esm({
3415
3496
  jsonComparisonHandler = {
3416
3497
  operators: ["jsonComparison"],
3417
3498
  compile(decision, ctx, state) {
3499
+ assertDialectCapability(
3500
+ ctx.dialectCapabilities,
3501
+ "supportsJsonOperators",
3502
+ "JSON operators are"
3503
+ );
3418
3504
  const column = decision.column;
3419
3505
  if (!column) {
3420
3506
  throw new Error("JSON comparison handler requires a column");
@@ -3592,6 +3678,7 @@ var RANGE_OP_MAP, rangeHandler;
3592
3678
  var init_range = __esm({
3593
3679
  "src/handlers/where/range.ts"() {
3594
3680
  "use strict";
3681
+ init_dialect_capabilities();
3595
3682
  init_param_intent();
3596
3683
  init_param_ref();
3597
3684
  init_types();
@@ -3604,6 +3691,11 @@ var init_range = __esm({
3604
3691
  rangeHandler = {
3605
3692
  operators: ["contains", "containedBy", "overlaps"],
3606
3693
  compile(decision, ctx, state) {
3694
+ assertDialectCapability(
3695
+ ctx.dialectCapabilities,
3696
+ "supportsRangeTypes",
3697
+ "Range operators are"
3698
+ );
3607
3699
  const column = decision.column;
3608
3700
  if (!column) {
3609
3701
  throw new Error("Range handler requires a column");
@@ -3659,11 +3751,15 @@ function toHandlerContext(ctx) {
3659
3751
  currentAlias: ctx.currentAlias ?? ctx.rootTable,
3660
3752
  maxRecursiveDepth: 100,
3661
3753
  ...ctx.schemaName !== void 0 && { schema: ctx.schemaName },
3754
+ ...ctx.dialectCapabilities !== void 0 && {
3755
+ dialectCapabilities: ctx.dialectCapabilities
3756
+ },
3757
+ ...ctx.bindingNames !== void 0 && { bindingNames: ctx.bindingNames },
3662
3758
  ...ctx.model !== void 0 && { model: ctx.model },
3663
3759
  ...ctx.outerTable !== void 0 && { outerAlias: ctx.outerTable }
3664
3760
  };
3665
3761
  }
3666
- function buildSubqueryFromIntent(intent, paramOffset, naming = identityNaming, schemaName, use = "rawExists") {
3762
+ function buildSubqueryFromIntent(intent, paramOffset, naming = identityNaming, schemaName, use = "rawExists", bindingNames, dialectCapabilities) {
3667
3763
  assertNoUnsupportedSubqueryModifiers(intent, use);
3668
3764
  if (intent.where && containsOuterRef(intent.where)) {
3669
3765
  throw new Error(
@@ -3708,9 +3804,14 @@ function buildSubqueryFromIntent(intent, paramOffset, naming = identityNaming, s
3708
3804
  }
3709
3805
  const stmt = {
3710
3806
  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)]
3807
+ fromClause: [
3808
+ rangeVar(
3809
+ targetTable,
3810
+ innerAlias,
3811
+ schemaForFromName(schemaName, targetTable, bindingNames, naming),
3812
+ naming
3813
+ )
3814
+ ]
3714
3815
  };
3715
3816
  let paramCount = 0;
3716
3817
  let innerParameters = [];
@@ -3724,6 +3825,9 @@ function buildSubqueryFromIntent(intent, paramOffset, naming = identityNaming, s
3724
3825
  aliases: /* @__PURE__ */ new Map(),
3725
3826
  paramState: innerState,
3726
3827
  naming,
3828
+ ...schemaName !== void 0 && { schemaName },
3829
+ ...dialectCapabilities !== void 0 && { dialectCapabilities },
3830
+ ...bindingNames !== void 0 && { bindingNames },
3727
3831
  compileSubquery: (_nestedIntent, _nestedOffset) => {
3728
3832
  throw new Error(
3729
3833
  "buildSubqueryFromIntent: nested subquery not supported"
@@ -4101,6 +4205,7 @@ var init_compile_where = __esm({
4101
4205
  init_custom();
4102
4206
  init_handlers();
4103
4207
  init_assert_field();
4208
+ init_binding_registry();
4104
4209
  init_types();
4105
4210
  init_utils();
4106
4211
  init_intent_to_decisions();
@@ -4145,7 +4250,9 @@ var init_raw_exists = __esm({
4145
4250
  state.paramIndex,
4146
4251
  ctx.naming,
4147
4252
  ctx.schema,
4148
- "rawExists"
4253
+ "rawExists",
4254
+ ctx.bindingNames,
4255
+ ctx.dialectCapabilities
4149
4256
  );
4150
4257
  if (innerParams) {
4151
4258
  for (const p of innerParams) {
@@ -4381,7 +4488,19 @@ function buildPredicateSubquerySelect(use, sourceIntent, decision, ctx, state, d
4381
4488
  }
4382
4489
  const stmt = {
4383
4490
  targetList: [{ ResTarget: { val: targetVal } }],
4384
- fromClause: [rangeVar(targetTable, targetAlias, ctx.schema, ctx.naming)],
4491
+ fromClause: [
4492
+ rangeVar(
4493
+ targetTable,
4494
+ targetAlias,
4495
+ schemaForFromName(
4496
+ ctx.schema,
4497
+ targetTable,
4498
+ ctx.bindingNames,
4499
+ ctx.naming
4500
+ ),
4501
+ ctx.naming
4502
+ )
4503
+ ],
4385
4504
  ...whereClause && { whereClause }
4386
4505
  };
4387
4506
  if (decision.orderBy && decision.orderBy.length > 0) {
@@ -4418,6 +4537,7 @@ var init_subquery_emission = __esm({
4418
4537
  "src/subquery-emission.ts"() {
4419
4538
  "use strict";
4420
4539
  init_ast_helpers();
4540
+ init_binding_registry();
4421
4541
  init_intent_to_decisions();
4422
4542
  init_param_intent();
4423
4543
  }
@@ -5941,11 +6061,17 @@ var init_json2 = __esm({
5941
6061
  "src/handlers/expression/json.ts"() {
5942
6062
  "use strict";
5943
6063
  init_ast_helpers();
6064
+ init_dialect_capabilities();
5944
6065
  init_param_intent();
5945
6066
  init_utils();
5946
6067
  jsonExtractHandler = {
5947
6068
  types: ["jsonExtract"],
5948
6069
  compile(decision, ctx, state) {
6070
+ assertDialectCapability(
6071
+ ctx.dialectCapabilities,
6072
+ "supportsJsonOperators",
6073
+ "JSON operators are"
6074
+ );
5949
6075
  const column = decision.column;
5950
6076
  if (!column) {
5951
6077
  throw new Error("JSON extract handler requires a column");
@@ -5973,6 +6099,11 @@ var init_json2 = __esm({
5973
6099
  jsonPathExtractHandler = {
5974
6100
  types: ["jsonPathExtract"],
5975
6101
  compile(decision, ctx, state) {
6102
+ assertDialectCapability(
6103
+ ctx.dialectCapabilities,
6104
+ "supportsJsonOperators",
6105
+ "JSON operators are"
6106
+ );
5976
6107
  const column = decision.column;
5977
6108
  if (!column) {
5978
6109
  throw new Error("JSON path extract handler requires a column");
@@ -7044,12 +7175,14 @@ init_ast_helpers();
7044
7175
  // src/compiler.ts
7045
7176
  init_assert_field();
7046
7177
  init_ast_helpers();
7178
+ init_binding_registry();
7047
7179
  import { InvalidOperationError as InvalidOperationError2 } from "@dbsp/core";
7048
7180
  import {
7049
7181
  isParamIntent as isParamIntent6,
7050
7182
  NQL_SELECT_SCALAR_FUNCTION_ALLOWLIST,
7051
7183
  NQL_SELECT_WINDOW_FUNCTION_ALLOWLIST
7052
7184
  } from "@dbsp/types";
7185
+ import { getNqlBindingRefName, isNqlBindingRef } from "@dbsp/types/internal";
7053
7186
 
7054
7187
  // src/pgsql-deparser.ts
7055
7188
  function deparse(node) {
@@ -8104,6 +8237,7 @@ function deparseQuoted(ast) {
8104
8237
  }
8105
8238
 
8106
8239
  // src/compiler.ts
8240
+ init_dialect_capabilities();
8107
8241
  init_case_value();
8108
8242
  init_custom();
8109
8243
  init_expression();
@@ -8336,6 +8470,8 @@ var PlanCompiler = class _PlanCompiler {
8336
8470
  defaultPk;
8337
8471
  deriveFk;
8338
8472
  model;
8473
+ dialectCapabilities;
8474
+ bindingNames;
8339
8475
  /** Mutable state shared with extracted condition/value compilation functions */
8340
8476
  state = {
8341
8477
  parameters: [],
@@ -8370,6 +8506,24 @@ var PlanCompiler = class _PlanCompiler {
8370
8506
  this.defaultPk = options.defaultPkColumnName ?? DEFAULT_PK_COLUMN;
8371
8507
  this.deriveFk = options.deriveFkColumnName ?? defaultFkDerivation;
8372
8508
  this.model = options.model ?? void 0;
8509
+ this.dialectCapabilities = options.dialectCapabilities;
8510
+ this.bindingNames = options.bindingNames;
8511
+ }
8512
+ childCompilerOptions(overrides = {}) {
8513
+ return {
8514
+ naming: this.naming,
8515
+ ...this.schema !== void 0 && { schema: this.schema },
8516
+ defaultPkColumnName: this.defaultPk,
8517
+ deriveFkColumnName: this.deriveFk,
8518
+ ...this.model !== void 0 && { model: this.model },
8519
+ ...this.dialectCapabilities !== void 0 && {
8520
+ dialectCapabilities: this.dialectCapabilities
8521
+ },
8522
+ ...this.bindingNames !== void 0 && {
8523
+ bindingNames: this.bindingNames
8524
+ },
8525
+ ...overrides
8526
+ };
8373
8527
  }
8374
8528
  /** Build immutable context for handler-based WHERE compilation */
8375
8529
  handlerCtx() {
@@ -8381,6 +8535,10 @@ var PlanCompiler = class _PlanCompiler {
8381
8535
  defaultPkColumnName: this.defaultPk,
8382
8536
  deriveFkColumnName: this.deriveFk,
8383
8537
  ...this.schema != null && { schema: this.schema },
8538
+ ...this.dialectCapabilities != null && {
8539
+ dialectCapabilities: this.dialectCapabilities
8540
+ },
8541
+ ...this.bindingNames != null && { bindingNames: this.bindingNames },
8384
8542
  ...this.model != null && { model: this.model }
8385
8543
  };
8386
8544
  }
@@ -8690,6 +8848,10 @@ var PlanCompiler = class _PlanCompiler {
8690
8848
  defaultPkColumnName: this.defaultPk,
8691
8849
  deriveFkColumnName: this.deriveFk,
8692
8850
  ...plan.schema ?? this.schema ? { schema: plan.schema ?? this.schema } : {},
8851
+ ...this.dialectCapabilities != null && {
8852
+ dialectCapabilities: this.dialectCapabilities
8853
+ },
8854
+ ...this.bindingNames != null && { bindingNames: this.bindingNames },
8693
8855
  ...this.model != null && { model: this.model },
8694
8856
  compileSubquery: (query, paramOffset) => this.compileExpressionSubquery(query, paramOffset),
8695
8857
  compileNqlSelectExpression: (value, handlerCtx, state) => this.compileNqlFunctionArg(value, handlerCtx, state)
@@ -8705,15 +8867,16 @@ var PlanCompiler = class _PlanCompiler {
8705
8867
  joins: []
8706
8868
  };
8707
8869
  }
8870
+ schemaForRangeVar(plan, table) {
8871
+ return schemaForFromName(
8872
+ plan.schema ?? this.schema,
8873
+ table,
8874
+ this.bindingNames,
8875
+ this.naming
8876
+ );
8877
+ }
8708
8878
  compileExpressionSubquery(query, paramOffset) {
8709
- const innerCompiler = new _PlanCompiler({
8710
- naming: this.naming,
8711
- ...this.schema !== void 0 && {
8712
- schema: this.schema
8713
- },
8714
- defaultPkColumnName: this.defaultPk,
8715
- deriveFkColumnName: this.deriveFk
8716
- });
8879
+ const innerCompiler = new _PlanCompiler(this.childCompilerOptions());
8717
8880
  const innerPlan = {
8718
8881
  rootTable: query.from,
8719
8882
  decisions: intentToDecisions(query, query.from)
@@ -8731,14 +8894,14 @@ var PlanCompiler = class _PlanCompiler {
8731
8894
  return funcCall(functionName, argNodes);
8732
8895
  }
8733
8896
  compileNqlFunctionArg(arg, ctx, state) {
8897
+ if (isNqlBindingRef(arg)) {
8898
+ return buildColumnRef(getNqlBindingRefName(arg), ctx);
8899
+ }
8734
8900
  if (typeof arg === "string") {
8735
8901
  return buildColumnRef(arg, ctx);
8736
8902
  }
8737
8903
  if (typeof arg === "object" && arg !== null) {
8738
8904
  const record = arg;
8739
- if (typeof record.$ref === "string") {
8740
- return buildColumnRef(record.$ref, ctx);
8741
- }
8742
8905
  if (typeof record.kind !== "string") {
8743
8906
  const legacyKind = typeof record.$op === "string" ? `$op:${record.$op}` : typeof record.$fn === "string" ? `$fn:${record.$fn}` : "object";
8744
8907
  throw new UnhandledNqlSelectExpressionKindError(legacyKind);
@@ -9053,24 +9216,7 @@ var PlanCompiler = class _PlanCompiler {
9053
9216
  const ctx = {
9054
9217
  ...this.createHandlerContext(plan, plan.rootTable),
9055
9218
  compileSubquery(query, paramOffset) {
9056
- const innerCompiler = new _PlanCompiler({
9057
- naming: outerThis.naming,
9058
- ...outerThis.schema !== void 0 && {
9059
- schema: outerThis.schema
9060
- },
9061
- defaultPkColumnName: outerThis.defaultPk,
9062
- deriveFkColumnName: outerThis.deriveFk
9063
- });
9064
- const innerPlan = {
9065
- rootTable: query.from,
9066
- decisions: intentToDecisions(query, query.from)
9067
- };
9068
- const innerResult = innerCompiler.compile(innerPlan);
9069
- const renumbered = renumberParamRefsInAst(
9070
- innerResult.ast,
9071
- paramOffset
9072
- );
9073
- return { ast: renumbered, parameters: innerResult.parameters };
9219
+ return outerThis.compileExpressionSubquery(query, paramOffset);
9074
9220
  }
9075
9221
  };
9076
9222
  const state = this.createHandlerState();
@@ -9256,13 +9402,13 @@ var PlanCompiler = class _PlanCompiler {
9256
9402
  const targetRV = rangeVar(
9257
9403
  pj.table,
9258
9404
  pj.alias,
9259
- plan.schema ?? this.schema,
9405
+ this.schemaForRangeVar(plan, pj.table),
9260
9406
  this.naming
9261
9407
  );
9262
9408
  const base = from.length > 0 ? from[0] : rangeVar(
9263
9409
  plan.rootTable,
9264
9410
  void 0,
9265
- plan.schema ?? this.schema,
9411
+ this.schemaForRangeVar(plan, plan.rootTable),
9266
9412
  this.naming
9267
9413
  );
9268
9414
  from[0] = pj.type === "LEFT JOIN" ? leftJoin(base, targetRV, pj.on) : innerJoin(base, targetRV, pj.on);
@@ -9271,7 +9417,7 @@ var PlanCompiler = class _PlanCompiler {
9271
9417
  const base = from.length > 0 ? from[0] : rangeVar(
9272
9418
  plan.rootTable,
9273
9419
  void 0,
9274
- plan.schema ?? this.schema,
9420
+ this.schemaForRangeVar(plan, plan.rootTable),
9275
9421
  this.naming
9276
9422
  );
9277
9423
  const joinExpr2 = rawJoin;
@@ -9296,7 +9442,7 @@ var PlanCompiler = class _PlanCompiler {
9296
9442
  plan.batchValuesFromNode ? plan.batchValuesFromNode : rangeVar(
9297
9443
  plan.rootTable,
9298
9444
  void 0,
9299
- plan.schema ?? this.schema,
9445
+ this.schemaForRangeVar(plan, plan.rootTable),
9300
9446
  this.naming
9301
9447
  )
9302
9448
  ];
@@ -9390,19 +9536,35 @@ var PlanCompiler = class _PlanCompiler {
9390
9536
  }
9391
9537
  return void 0;
9392
9538
  }
9539
+ /**
9540
+ * Compile a column reference that may use dotted 'relation.column' notation.
9541
+ */
9542
+ compileRelationAwareColumnRef(column, table) {
9543
+ const dot = column.lastIndexOf(".");
9544
+ if (dot !== -1) {
9545
+ const relation = column.slice(0, dot);
9546
+ const alias = this.resolvedJoinAliases().get(relation) ?? relation;
9547
+ return columnRef(column.slice(dot + 1), alias, void 0, this.naming);
9548
+ }
9549
+ return columnRef(column, table, void 0, this.naming);
9550
+ }
9393
9551
  /**
9394
9552
  * Compile a single groupBy decision into a ColumnRef AST node.
9395
9553
  * Supports dotted 'relation.column' notation for joined tables.
9396
9554
  */
9397
9555
  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);
9556
+ return this.compileRelationAwareColumnRef(
9557
+ decision.column,
9558
+ decision.table
9559
+ );
9560
+ }
9561
+ /**
9562
+ * Compile a DISTINCT ON column into a ColumnRef AST node.
9563
+ * Mirrors GROUP BY relation-alias resolution while preserving unqualified
9564
+ * DISTINCT ON columns as unqualified references.
9565
+ */
9566
+ compileDistinctOnColumn(column) {
9567
+ return this.compileRelationAwareColumnRef(column, void 0);
9406
9568
  }
9407
9569
  /**
9408
9570
  * Assemble the final SelectStmt from all accumulated clause nodes.
@@ -9427,6 +9589,18 @@ var PlanCompiler = class _PlanCompiler {
9427
9589
  options.withClause = { ctes: this.pendingCtes, recursive: false };
9428
9590
  }
9429
9591
  if (plan.lock) {
9592
+ assertDialectCapability(
9593
+ this.dialectCapabilities,
9594
+ "supportsRowLevelLocks",
9595
+ "Row-level locks are"
9596
+ );
9597
+ if (plan.lock.waitPolicy !== "block") {
9598
+ assertDialectCapability(
9599
+ this.dialectCapabilities,
9600
+ "supportsLockWaitPolicies",
9601
+ "Lock wait policies are"
9602
+ );
9603
+ }
9430
9604
  const mapped = mapLockToAst(plan.lock);
9431
9605
  const hasJoins = this.rawJoins.length > 0;
9432
9606
  options.lockingClause = {
@@ -9436,7 +9610,7 @@ var PlanCompiler = class _PlanCompiler {
9436
9610
  rangeVar(
9437
9611
  plan.rootTable,
9438
9612
  void 0,
9439
- plan.schema ?? this.schema,
9613
+ this.schemaForRangeVar(plan, plan.rootTable),
9440
9614
  this.naming
9441
9615
  )
9442
9616
  ]
@@ -9539,7 +9713,7 @@ var PlanCompiler = class _PlanCompiler {
9539
9713
  case "distinctOn":
9540
9714
  if (decision.columns && decision.columns.length > 0) {
9541
9715
  distinct = decision.columns.map(
9542
- (col) => columnRef(col, void 0, void 0, this.naming)
9716
+ (col) => this.compileDistinctOnColumn(col)
9543
9717
  );
9544
9718
  }
9545
9719
  break;
@@ -9816,13 +9990,13 @@ var PlanCompiler = class _PlanCompiler {
9816
9990
  const baseTable = larg ?? rangeVar(
9817
9991
  plan.rootTable,
9818
9992
  void 0,
9819
- plan.schema ?? this.schema,
9993
+ this.schemaForRangeVar(plan, plan.rootTable),
9820
9994
  this.naming
9821
9995
  );
9822
9996
  const targetTable = rangeVar(
9823
9997
  decision.targetTable ?? "",
9824
9998
  decision.alias,
9825
- plan.schema ?? this.schema,
9999
+ this.schemaForRangeVar(plan, decision.targetTable ?? ""),
9826
10000
  this.naming
9827
10001
  );
9828
10002
  const onCondition = eqExpr(
@@ -13139,6 +13313,7 @@ function matchGlob(pattern, value) {
13139
13313
 
13140
13314
  // src/mutations/mutation-compiler.ts
13141
13315
  init_ast_helpers();
13316
+ init_binding_registry();
13142
13317
  init_compiler_utils();
13143
13318
  init_handlers();
13144
13319
  init_param_intent();
@@ -13364,12 +13539,18 @@ function compileInsertFrom(config, ctx, state) {
13364
13539
  const _dbTargetTable = naming.toDatabase(config.targetTable);
13365
13540
  const dbSourceTable = naming.toDatabase(config.sourceTable);
13366
13541
  const sourceAlias = config.sourceTable;
13542
+ const sourceSchema = schemaForFromName(
13543
+ ctx.schema,
13544
+ config.sourceTable,
13545
+ ctx.bindingNames,
13546
+ naming
13547
+ );
13367
13548
  const dbColumns = config.columns?.map((c) => naming.toDatabase(c));
13368
13549
  let targetList;
13369
13550
  if (config.columns && config.columns.length > 0) {
13370
13551
  targetList = config.columns.map(
13371
13552
  (col) => resTarget(
13372
- columnRef(col, sourceAlias, ctx.schema, naming),
13553
+ columnRef(col, sourceAlias, sourceSchema, naming),
13373
13554
  naming.toDatabase(col)
13374
13555
  )
13375
13556
  );
@@ -13409,8 +13590,8 @@ function compileInsertFrom(config, ctx, state) {
13409
13590
  inh: true,
13410
13591
  relpersistence: "p"
13411
13592
  };
13412
- if (ctx.schema) {
13413
- sourceRelation.schemaname = naming.toDatabase(ctx.schema);
13593
+ if (sourceSchema) {
13594
+ sourceRelation.schemaname = naming.toDatabase(sourceSchema);
13414
13595
  }
13415
13596
  const selectQuery = {
13416
13597
  SelectStmt: {
@@ -13439,12 +13620,18 @@ function compileUpsertFrom(config, ctx, state) {
13439
13620
  const naming = ctx.naming;
13440
13621
  const dbSourceTable = naming.toDatabase(config.sourceTable);
13441
13622
  const sourceAlias = config.sourceTable;
13623
+ const sourceSchema = schemaForFromName(
13624
+ ctx.schema,
13625
+ config.sourceTable,
13626
+ ctx.bindingNames,
13627
+ naming
13628
+ );
13442
13629
  const dbColumns = config.columns?.map((c) => naming.toDatabase(c));
13443
13630
  let targetList;
13444
13631
  if (config.columns && config.columns.length > 0) {
13445
13632
  targetList = config.columns.map(
13446
13633
  (col) => resTarget(
13447
- columnRef(col, sourceAlias, ctx.schema, naming),
13634
+ columnRef(col, sourceAlias, sourceSchema, naming),
13448
13635
  naming.toDatabase(col)
13449
13636
  )
13450
13637
  );
@@ -13484,8 +13671,8 @@ function compileUpsertFrom(config, ctx, state) {
13484
13671
  inh: true,
13485
13672
  relpersistence: "p"
13486
13673
  };
13487
- if (ctx.schema) {
13488
- sourceRelation.schemaname = naming.toDatabase(ctx.schema);
13674
+ if (sourceSchema) {
13675
+ sourceRelation.schemaname = naming.toDatabase(sourceSchema);
13489
13676
  }
13490
13677
  const selectQuery = {
13491
13678
  SelectStmt: {
@@ -13630,6 +13817,28 @@ init_compiler_utils();
13630
13817
  init_handlers();
13631
13818
  init_param_intent();
13632
13819
  init_param_ref();
13820
+ function buildWhereClause(conditions, ctx, state) {
13821
+ if (!conditions || conditions.length === 0) return void 0;
13822
+ const dispatch = createWhereDispatcher();
13823
+ if (conditions.length === 1) {
13824
+ return dispatch(conditions[0], ctx, state);
13825
+ }
13826
+ const nodes = conditions.map((condition) => dispatch(condition, ctx, state));
13827
+ return {
13828
+ BoolExpr: { boolop: "AND_EXPR", args: nodes }
13829
+ };
13830
+ }
13831
+ function buildActionWhereClause(config, ctx, state) {
13832
+ if (config.actionWhereIntent) {
13833
+ if (!config.compileActionWhere) {
13834
+ throw new Error(
13835
+ "Upsert actionWhereIntent requires compileActionWhere callback"
13836
+ );
13837
+ }
13838
+ return config.compileActionWhere(config.actionWhereIntent, state);
13839
+ }
13840
+ return buildWhereClause(config.actionWhere, ctx, state);
13841
+ }
13633
13842
  function buildOnConflictClause(config, ctx, state) {
13634
13843
  const naming = ctx.naming;
13635
13844
  const action = config.conflictAction;
@@ -13643,18 +13852,12 @@ function buildOnConflictClause(config, ctx, state) {
13643
13852
  }))
13644
13853
  };
13645
13854
  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;
13855
+ const whereClause2 = buildWhereClause(
13856
+ config.conflictTarget.where,
13857
+ ctx,
13858
+ state
13859
+ );
13860
+ if (whereClause2) infer.whereClause = whereClause2;
13658
13861
  }
13659
13862
  } else if (config.conflictTarget.constraint) {
13660
13863
  infer = {
@@ -13700,10 +13903,12 @@ function buildOnConflictClause(config, ctx, state) {
13700
13903
  }
13701
13904
  };
13702
13905
  });
13906
+ const whereClause = buildActionWhereClause(config, ctx, state);
13703
13907
  return {
13704
13908
  action: "ONCONFLICT_UPDATE",
13705
13909
  ...infer && { infer },
13706
- targetList
13910
+ targetList,
13911
+ ...whereClause && { whereClause }
13707
13912
  };
13708
13913
  }
13709
13914
  function compileUpsert(config, ctx, state) {
@@ -13841,7 +14046,12 @@ init_naming_plugin();
13841
14046
  init_param_ref();
13842
14047
 
13843
14048
  // src/pgsql-adapter.ts
13844
- import { POSTGRESQL_CAPABILITIES, plan as planFn } from "@dbsp/core";
14049
+ import {
14050
+ POSTGRESQL_CAPABILITIES as POSTGRESQL_CAPABILITIES2,
14051
+ plan as planFn2,
14052
+ validateTypeName as validateTypeName4
14053
+ } from "@dbsp/core";
14054
+ import { getNqlBindingRefName as getNqlBindingRefName2, isNqlBindingRef as isNqlBindingRef2 } from "@dbsp/types/internal";
13845
14055
 
13846
14056
  // src/adapter-compiler-includes.ts
13847
14057
  init_ast_helpers();
@@ -14027,426 +14237,75 @@ function compileSubqueryIncludeManyToMany(info, parentIds, schemaName, state, de
14027
14237
  }
14028
14238
 
14029
14239
  // src/adapter-compiler-mutations.ts
14240
+ import {
14241
+ InvalidOperationError as InvalidOperationError3,
14242
+ isSqlRaw as isSqlRaw2,
14243
+ POSTGRESQL_CAPABILITIES,
14244
+ plan as planFn
14245
+ } from "@dbsp/core";
14246
+
14247
+ // src/adapter-compiler-select.ts
14248
+ init_assert_field();
14249
+ init_ast_helpers();
14250
+ init_binding_registry();
14030
14251
  init_compile_where();
14252
+ import { countDistinctRelationPathsByName } from "@dbsp/core";
14031
14253
  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) {
14254
+ init_types();
14255
+ init_intent_to_decisions();
14256
+ init_param_ref();
14257
+
14258
+ // src/plan-decision-extractor.ts
14259
+ init_assert_field();
14260
+ init_intent_to_decisions();
14261
+ import { deriveRelationPathFromIntentPath } from "@dbsp/core";
14262
+ function findExistsIntents(where) {
14263
+ if (!where || typeof where !== "object") return [];
14053
14264
  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;
14265
+ if (w.kind === "exists" || w.kind === "notExists" || w.kind === "relationFilter") {
14266
+ return [w];
14069
14267
  }
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
14268
+ const results = [];
14269
+ if (w.conditions && Array.isArray(w.conditions)) {
14270
+ for (const c of w.conditions) {
14271
+ results.push(...findExistsIntents(c));
14082
14272
  }
14083
- };
14273
+ }
14274
+ if (w.condition) {
14275
+ results.push(...findExistsIntents(w.condition));
14276
+ }
14277
+ return results;
14084
14278
  }
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;
14279
+ function resolveRelation(model, sourceTable, relationName) {
14280
+ const rel = model.getRelation(`${sourceTable}.${relationName}`);
14281
+ if (!rel) return void 0;
14282
+ const foreignKey = typeof rel.foreignKey === "string" ? rel.foreignKey : rel.foreignKey?.[0];
14283
+ const relationType = rel.type;
14284
+ return { target: rel.target, foreignKey, relationType };
14285
+ }
14286
+ function resolveIncludeAlias(context) {
14287
+ return context.relation ?? context.includeAlias;
14288
+ }
14289
+ function resolveIncludeByPath(includes, intentPath, relationName) {
14290
+ if (!includes) return void 0;
14291
+ if (intentPath) {
14292
+ const indexPattern = /include\[(\d+)\]/g;
14293
+ let current = includes;
14294
+ let resolved;
14295
+ let execResult = indexPattern.exec(intentPath);
14296
+ while (execResult !== null) {
14297
+ const idx = parseInt(execResult[1], 10);
14298
+ const item = current[idx];
14299
+ if (!item) break;
14300
+ resolved = item;
14301
+ current = item.include ?? [];
14302
+ execResult = indexPattern.exec(intentPath);
14095
14303
  }
14304
+ if (resolved) return resolved;
14096
14305
  }
14097
- return result;
14098
- }
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
- );
14126
- }
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
- };
14135
- }
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
14158
- };
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
- );
14306
+ return includes.find(
14307
+ (i) => i.relation === relationName || i.via === relationName
14308
+ );
14450
14309
  }
14451
14310
  function deriveForeignKey(context, deriveFk = defaultFkDerivation, defaultPk = DEFAULT_PK_COLUMN) {
14452
14311
  const fk = context.foreignKey ?? context.sourceFK;
@@ -14707,1070 +14566,1599 @@ function enrichExistsStubsInConditions(conditions, sourceTable, model) {
14707
14566
  }
14708
14567
  hopCheckSource = hopRel.target;
14709
14568
  }
14710
- const mode = d.operator === "notExists" ? "none" : d.operator === "every" ? "every" : "some";
14711
- const isEvery = mode === "every";
14712
- const outerOperator = d.operator === "notExists" || mode === "none" || isEvery ? "notExists" : "exists";
14713
- const finalTarget = hopCheckSource;
14714
- let rawInnerConditions;
14715
- if (rawWhere) {
14716
- const c = convertWhereToDecisions(rawWhere, finalTarget);
14569
+ const mode = d.operator === "notExists" ? "none" : d.operator === "every" ? "every" : "some";
14570
+ const isEvery = mode === "every";
14571
+ const outerOperator = d.operator === "notExists" || mode === "none" || isEvery ? "notExists" : "exists";
14572
+ const finalTarget = hopCheckSource;
14573
+ let rawInnerConditions;
14574
+ if (rawWhere) {
14575
+ const c = convertWhereToDecisions(rawWhere, finalTarget);
14576
+ if (c.length > 0) {
14577
+ enrichExistsStubsInConditions(c, finalTarget, model);
14578
+ rawInnerConditions = c;
14579
+ }
14580
+ }
14581
+ if (isEvery && !rawInnerConditions) {
14582
+ const firstHop = hops[0];
14583
+ const firstRel = model.getRelation(`${sourceTable}.${firstHop}`);
14584
+ conditions[i] = {
14585
+ type: "where",
14586
+ operator: "every",
14587
+ targetTable: firstRel?.target ?? firstHop,
14588
+ sourceTable
14589
+ };
14590
+ continue;
14591
+ }
14592
+ const innerConditions = isEvery && rawInnerConditions ? [
14593
+ {
14594
+ type: "logical",
14595
+ operator: "not",
14596
+ conditions: rawInnerConditions
14597
+ }
14598
+ ] : rawInnerConditions;
14599
+ const enriched = buildMultiHopExistsChain(
14600
+ hops,
14601
+ sourceTable,
14602
+ outerOperator,
14603
+ innerConditions,
14604
+ model
14605
+ );
14606
+ conditions[i] = enriched;
14607
+ } else {
14608
+ const relation = hops[0];
14609
+ const rel = model.getRelation(`${sourceTable}.${relation}`);
14610
+ if (!rel) {
14611
+ throw new Error(
14612
+ `exists('${relation}'): no relation '${relation}' is declared on table '${sourceTable}'. Use rawExists(subquery(...)) for an EXISTS over an undeclared or uncorrelated subquery.`
14613
+ );
14614
+ }
14615
+ const foreignKey = typeof rel.foreignKey === "string" ? rel.foreignKey : rel.foreignKey?.[0];
14616
+ const relationType = rel.type === "belongsTo" ? "belongsTo" : rel.type === "hasMany" || rel.type === "hasOne" ? rel.type : void 0;
14617
+ const parentKey = relationType === "belongsTo" ? rel.targetKey : rel.sourceKey;
14618
+ const targetTable = rel.target;
14619
+ let innerConditions;
14620
+ if (rawWhere) {
14621
+ const c = convertWhereToDecisions(rawWhere, targetTable);
14622
+ if (c.length > 0) innerConditions = c;
14623
+ }
14624
+ const includeDecisions = rawInclude ? Object.entries(rawInclude).map(([rel2, opts]) => ({
14625
+ type: "existsInclude",
14626
+ relation: rel2,
14627
+ joinType: opts.join ?? "inner"
14628
+ })) : void 0;
14629
+ const enriched = {
14630
+ type: "where",
14631
+ operator: d.operator,
14632
+ targetTable,
14633
+ sourceTable,
14634
+ ...foreignKey ? { foreignKey } : {},
14635
+ ...relationType ? { relationType } : {},
14636
+ ...parentKey ? { parentKey } : {},
14637
+ ...innerConditions ? { conditions: innerConditions } : {},
14638
+ ...includeDecisions ? { include: includeDecisions } : {}
14639
+ };
14640
+ conditions[i] = enriched;
14641
+ if (innerConditions && innerConditions.length > 0) {
14642
+ enrichExistsStubsInConditions(innerConditions, targetTable, model);
14643
+ }
14644
+ }
14645
+ } else if ((d.type === "whereAnd" || d.type === "whereOr" || d.type === "whereNot") && d.conditions) {
14646
+ enrichExistsStubsInConditions(
14647
+ d.conditions,
14648
+ sourceTable,
14649
+ model
14650
+ );
14651
+ }
14652
+ }
14653
+ }
14654
+ function buildEnrichedExistsDecision(d, matchingIntent, rootTable, model) {
14655
+ const context = d.context;
14656
+ const targetTable = context.target;
14657
+ let conditions;
14658
+ if (matchingIntent?.where) {
14659
+ const nestedDecisions = convertWhereToDecisions(
14660
+ matchingIntent.where,
14661
+ targetTable
14662
+ );
14663
+ if (nestedDecisions.length > 0) {
14664
+ conditions = nestedDecisions;
14665
+ if (model) {
14666
+ enrichExistsStubsInConditions(conditions, targetTable, model);
14667
+ }
14668
+ }
14669
+ }
14670
+ const sourceTableForRelation = context.sourceTable || rootTable;
14671
+ const relIR = model && context.relation ? model.getRelation(
14672
+ `${sourceTableForRelation}.${context.relation}`
14673
+ ) : void 0;
14674
+ const foreignKey = relIR ? typeof relIR.foreignKey === "string" ? relIR.foreignKey : relIR.foreignKey?.[0] : void 0;
14675
+ const relationType = relIR?.type === "belongsTo" ? "belongsTo" : relIR?.type === "hasMany" || relIR?.type === "hasOne" ? relIR.type : void 0;
14676
+ const parentKey = relationType === "belongsTo" ? relIR?.targetKey : relIR?.sourceKey;
14677
+ let operator = "exists";
14678
+ if (matchingIntent?.kind === "notExists") {
14679
+ operator = "notExists";
14680
+ } else if (matchingIntent?.kind === "relationFilter") {
14681
+ const mode = matchingIntent.mode ?? "some";
14682
+ if (mode === "none") operator = "notExists";
14683
+ else if (mode === "every") operator = "every";
14684
+ }
14685
+ const includeIntent = matchingIntent?.include;
14686
+ const includeDecisions = includeIntent ? Object.entries(includeIntent).map(([rel, opts]) => ({
14687
+ type: "existsInclude",
14688
+ relation: rel,
14689
+ joinType: opts.join ?? "inner"
14690
+ })) : void 0;
14691
+ const relationName = context.relation;
14692
+ return {
14693
+ type: "where",
14694
+ operator,
14695
+ targetTable,
14696
+ // sourceTable: the table this EXISTS is resolved FROM (the relation's source).
14697
+ // Required by propagateExistsConditions's (sourceTable, relationName) identity
14698
+ // guard — without it the guard is a no-op and cross-source propagation can occur
14699
+ // when two relations share a name but originate from different tables.
14700
+ sourceTable: sourceTableForRelation,
14701
+ ...foreignKey ? { foreignKey } : {},
14702
+ // relationType is required so deriveFkColumns (called by mapToHandlerDecision) can
14703
+ // set sourceColumn/targetColumn in the correct direction for the EXISTS correlation.
14704
+ ...relationType ? { relationType } : {},
14705
+ // parentKey provides the explicit PK override for non-default PK columns.
14706
+ ...parentKey ? { parentKey } : {},
14707
+ ...conditions ? { conditions } : {},
14708
+ ...d.choice === "join" ? { choice: "join" } : {},
14709
+ ...relationName ? { relationName } : {},
14710
+ ...includeDecisions ? { include: includeDecisions } : {}
14711
+ };
14712
+ }
14713
+ function collectNestedExistsTargets(where, insideExists, sourceTable, model, out) {
14714
+ if (!where || typeof where !== "object") return;
14715
+ const w = where;
14716
+ if (w.kind === "exists" || w.kind === "notExists" || w.kind === "relationFilter") {
14717
+ if (insideExists) {
14718
+ const rawRelation = w.relation;
14719
+ const hops = Array.isArray(rawRelation) ? rawRelation : typeof rawRelation === "string" ? [rawRelation] : [];
14720
+ if (hops.length === 1) {
14721
+ const singleHop = hops[0] ?? "";
14722
+ const rel = model.getRelation(`${sourceTable}.${singleHop}`);
14723
+ const resolvedTarget = rel ? rel.target : singleHop;
14724
+ out.add(`${sourceTable}:${resolvedTarget}`);
14725
+ } else if (hops.length > 1) {
14726
+ let penultimate = sourceTable;
14727
+ for (let hi = 0; hi < hops.length - 1; hi++) {
14728
+ const hr = model.getRelation(`${penultimate}.${hops[hi] ?? ""}`);
14729
+ penultimate = hr ? hr.target : hops[hi] ?? penultimate;
14730
+ }
14731
+ out.add(`${penultimate}:${hops.join(".")}`);
14732
+ const lastHop = hops[hops.length - 1] ?? "";
14733
+ const lastRel = model.getRelation(`${penultimate}.${lastHop}`);
14734
+ const finalTarget = lastRel ? lastRel.target : lastHop;
14735
+ out.add(`${penultimate}:${finalTarget}`);
14736
+ }
14737
+ let nextSource = sourceTable;
14738
+ if (hops.length >= 1) {
14739
+ let cur = sourceTable;
14740
+ for (const hop of hops) {
14741
+ const rel = model.getRelation(`${cur}.${hop}`);
14742
+ cur = rel ? rel.target : hop;
14743
+ }
14744
+ nextSource = cur;
14745
+ }
14746
+ if (w.where) {
14747
+ collectNestedExistsTargets(w.where, true, nextSource, model, out);
14748
+ }
14749
+ } else {
14750
+ const rawRelation = w.relation;
14751
+ const hops = Array.isArray(rawRelation) ? rawRelation : typeof rawRelation === "string" ? [rawRelation] : [];
14752
+ let nextSource = sourceTable;
14753
+ if (hops.length >= 1) {
14754
+ let cur = sourceTable;
14755
+ for (const hop of hops) {
14756
+ const rel = model.getRelation(`${cur}.${hop}`);
14757
+ cur = rel ? rel.target : hop;
14758
+ }
14759
+ nextSource = cur;
14760
+ }
14761
+ if (w.where) {
14762
+ collectNestedExistsTargets(w.where, true, nextSource, model, out);
14763
+ }
14764
+ }
14765
+ return;
14766
+ }
14767
+ if (w.conditions && Array.isArray(w.conditions)) {
14768
+ for (const c of w.conditions) {
14769
+ collectNestedExistsTargets(c, insideExists, sourceTable, model, out);
14770
+ }
14771
+ }
14772
+ if (w.condition) {
14773
+ collectNestedExistsTargets(
14774
+ w.condition,
14775
+ insideExists,
14776
+ sourceTable,
14777
+ model,
14778
+ out
14779
+ );
14780
+ }
14781
+ }
14782
+ function collectExistsStubs(decisions, out) {
14783
+ decisions.forEach((d, i) => {
14784
+ if (!d) return;
14785
+ if (d.type === "where" && (d.operator === "exists" || d.operator === "notExists" || d.operator === "every")) {
14786
+ if (d.relationName) return;
14787
+ out.push({ decision: d, container: decisions, index: i });
14788
+ } else if ((d.type === "whereAnd" || d.type === "whereOr" || d.type === "whereNot") && d.conditions) {
14789
+ collectExistsStubs(d.conditions, out);
14790
+ }
14791
+ });
14792
+ }
14793
+ function normalizeStubRelation(targetTable) {
14794
+ if (Array.isArray(targetTable) && targetTable.length > 0)
14795
+ return targetTable[targetTable.length - 1];
14796
+ if (typeof targetTable === "string") return targetTable;
14797
+ return void 0;
14798
+ }
14799
+ function normalizeStubRelationPath(targetTable) {
14800
+ if (Array.isArray(targetTable) && targetTable.length > 0)
14801
+ return targetTable.join(".");
14802
+ if (typeof targetTable === "string") return targetTable;
14803
+ return void 0;
14804
+ }
14805
+ function enrichExistsDecisionsInPlace(decisions, plan, model) {
14806
+ const stubs = [];
14807
+ collectExistsStubs(decisions, stubs);
14808
+ const nestedExistsTargets = /* @__PURE__ */ new Set();
14809
+ if (plan.intent?.where && model) {
14810
+ collectNestedExistsTargets(
14811
+ plan.intent.where,
14812
+ false,
14813
+ plan.rootTable,
14814
+ model,
14815
+ nestedExistsTargets
14816
+ );
14817
+ }
14818
+ const filterDecisions = plan.decisions.filter(
14819
+ (d) => d.type === "filter-strategy" && (d.choice === "exists" || d.choice === "notExists" || d.choice === "join")
14820
+ );
14821
+ const placedDecisions = [];
14822
+ const consumedStubIndices = /* @__PURE__ */ new Set();
14823
+ if (filterDecisions.length > 0) {
14824
+ const existsIntents = plan.intent?.where ? findExistsIntents(plan.intent.where) : [];
14825
+ for (const d of filterDecisions) {
14826
+ const context = d.context;
14827
+ if (!context.target) continue;
14828
+ const contextRelationPath = context.relationPath;
14829
+ const contextSourceTableForIntent = context.sourceTable;
14830
+ const isNestedRelation = contextSourceTableForIntent !== void 0 && contextSourceTableForIntent !== plan.rootTable && !contextRelationPath;
14831
+ const matchIdx = isNestedRelation ? -1 : existsIntents.findIndex((i) => {
14832
+ if (contextRelationPath) {
14833
+ const intentPath = Array.isArray(i.relation) ? i.relation.join(".") : typeof i.relation === "string" ? i.relation : void 0;
14834
+ return intentPath === contextRelationPath;
14835
+ }
14836
+ const rel = Array.isArray(i.relation) ? i.relation.length > 0 ? i.relation[i.relation.length - 1] : void 0 : typeof i.relation === "string" ? i.relation : void 0;
14837
+ return rel === context.relation || rel === context.target || rel === context.includeAlias;
14838
+ });
14839
+ const matchingIntent = matchIdx >= 0 ? existsIntents[matchIdx] : void 0;
14840
+ if (matchIdx >= 0) existsIntents.splice(matchIdx, 1);
14841
+ const isMultiHop = matchingIntent && Array.isArray(matchingIntent.relation) && matchingIntent.relation.length > 1 && model;
14842
+ let enriched;
14843
+ if (isMultiHop && matchingIntent && model) {
14844
+ const mode = matchingIntent.mode ?? "some";
14845
+ const isEvery = matchingIntent.kind !== "notExists" && mode === "every";
14846
+ const outerOperator = matchingIntent.kind === "notExists" || mode === "none" || isEvery ? "notExists" : "exists";
14847
+ const lastHopTarget = context.target;
14848
+ const rawInnerConditions = matchingIntent.where ? (() => {
14849
+ const c = convertWhereToDecisions(
14850
+ matchingIntent.where,
14851
+ lastHopTarget
14852
+ );
14717
14853
  if (c.length > 0) {
14718
- enrichExistsStubsInConditions(c, finalTarget, model);
14719
- rawInnerConditions = c;
14854
+ enrichExistsStubsInConditions(c, lastHopTarget, model);
14720
14855
  }
14721
- }
14856
+ return c.length > 0 ? c : void 0;
14857
+ })() : void 0;
14722
14858
  if (isEvery && !rawInnerConditions) {
14723
- const firstHop = hops[0];
14724
- const firstRel = model.getRelation(`${sourceTable}.${firstHop}`);
14725
- conditions[i] = {
14726
- type: "where",
14727
- operator: "every",
14728
- targetTable: firstRel?.target ?? firstHop,
14729
- sourceTable
14730
- };
14731
- continue;
14859
+ enriched = buildEnrichedExistsDecision(
14860
+ d,
14861
+ matchingIntent,
14862
+ plan.rootTable,
14863
+ model
14864
+ );
14865
+ } else {
14866
+ const innerConditions = isEvery && rawInnerConditions ? [
14867
+ {
14868
+ type: "logical",
14869
+ operator: "not",
14870
+ conditions: rawInnerConditions
14871
+ }
14872
+ ] : rawInnerConditions;
14873
+ enriched = buildMultiHopExistsChain(
14874
+ matchingIntent.relation,
14875
+ plan.rootTable,
14876
+ outerOperator,
14877
+ innerConditions,
14878
+ model
14879
+ );
14732
14880
  }
14733
- const innerConditions = isEvery && rawInnerConditions ? [
14734
- {
14735
- type: "logical",
14736
- operator: "not",
14737
- conditions: rawInnerConditions
14738
- }
14739
- ] : rawInnerConditions;
14740
- const enriched = buildMultiHopExistsChain(
14741
- hops,
14742
- sourceTable,
14743
- outerOperator,
14744
- innerConditions,
14881
+ } else {
14882
+ enriched = buildEnrichedExistsDecision(
14883
+ d,
14884
+ matchingIntent,
14885
+ plan.rootTable,
14745
14886
  model
14746
14887
  );
14747
- conditions[i] = enriched;
14748
- } else {
14749
- const relation = hops[0];
14750
- const rel = model.getRelation(`${sourceTable}.${relation}`);
14751
- if (!rel) {
14752
- throw new Error(
14753
- `exists('${relation}'): no relation '${relation}' is declared on table '${sourceTable}'. Use rawExists(subquery(...)) for an EXISTS over an undeclared or uncorrelated subquery.`
14754
- );
14888
+ }
14889
+ const contextSourceTable = context.sourceTable;
14890
+ const stubIdx = stubs.findIndex((s, idx) => {
14891
+ if (consumedStubIndices.has(idx)) return false;
14892
+ if (contextRelationPath) {
14893
+ const stubPath = normalizeStubRelationPath(s.decision.targetTable);
14894
+ return stubPath === contextRelationPath;
14755
14895
  }
14756
- const foreignKey = typeof rel.foreignKey === "string" ? rel.foreignKey : rel.foreignKey?.[0];
14757
- const relationType = rel.type === "belongsTo" ? "belongsTo" : rel.type === "hasMany" || rel.type === "hasOne" ? rel.type : void 0;
14758
- const parentKey = relationType === "belongsTo" ? rel.targetKey : rel.sourceKey;
14759
- const targetTable = rel.target;
14760
- let innerConditions;
14761
- if (rawWhere) {
14762
- const c = convertWhereToDecisions(rawWhere, targetTable);
14763
- if (c.length > 0) innerConditions = c;
14896
+ if (contextSourceTable !== void 0 && contextSourceTable !== plan.rootTable) {
14897
+ return false;
14764
14898
  }
14765
- const includeDecisions = rawInclude ? Object.entries(rawInclude).map(([rel2, opts]) => ({
14766
- type: "existsInclude",
14767
- relation: rel2,
14768
- joinType: opts.join ?? "inner"
14769
- })) : void 0;
14770
- const enriched = {
14771
- type: "where",
14772
- operator: d.operator,
14773
- targetTable,
14774
- sourceTable,
14775
- ...foreignKey ? { foreignKey } : {},
14776
- ...relationType ? { relationType } : {},
14777
- ...parentKey ? { parentKey } : {},
14778
- ...innerConditions ? { conditions: innerConditions } : {},
14779
- ...includeDecisions ? { include: includeDecisions } : {}
14780
- };
14781
- conditions[i] = enriched;
14782
- if (innerConditions && innerConditions.length > 0) {
14783
- enrichExistsStubsInConditions(innerConditions, targetTable, model);
14899
+ const stubRelation = normalizeStubRelation(s.decision.targetTable);
14900
+ return stubRelation === context.relation || stubRelation === context.target || stubRelation === context.includeAlias;
14901
+ });
14902
+ if (stubIdx >= 0) {
14903
+ const stub = stubs[stubIdx];
14904
+ if (stub) {
14905
+ stub.container[stub.index] = enriched;
14906
+ consumedStubIndices.add(stubIdx);
14907
+ placedDecisions.push(enriched);
14908
+ }
14909
+ } else {
14910
+ const srcForKey = context.sourceTable ?? "";
14911
+ const targetName = context.target;
14912
+ const relationPath = contextRelationPath;
14913
+ const compoundTarget = targetName ? `${srcForKey}:${targetName}` : "";
14914
+ const compoundPath = relationPath ? `${srcForKey}:${relationPath}` : "";
14915
+ if (compoundTarget && nestedExistsTargets.has(compoundTarget) || compoundPath && nestedExistsTargets.has(compoundPath)) {
14916
+ continue;
14784
14917
  }
14918
+ decisions.push(enriched);
14919
+ placedDecisions.push(enriched);
14785
14920
  }
14786
- } else if ((d.type === "whereAnd" || d.type === "whereOr" || d.type === "whereNot") && d.conditions) {
14787
- enrichExistsStubsInConditions(
14788
- d.conditions,
14789
- sourceTable,
14790
- model
14921
+ }
14922
+ }
14923
+ for (let i = 0; i < stubs.length; i++) {
14924
+ if (consumedStubIndices.has(i)) continue;
14925
+ const stub = stubs[i];
14926
+ if (!stub) continue;
14927
+ const rawTargetTable = stub.decision.targetTable;
14928
+ const relationPath = Array.isArray(rawTargetTable) ? rawTargetTable : [rawTargetTable];
14929
+ const displayName = Array.isArray(rawTargetTable) ? rawTargetTable.join(".") : String(rawTargetTable ?? "");
14930
+ if (!displayName) continue;
14931
+ if (!model) {
14932
+ throw new Error(
14933
+ `exists('${displayName}'): cannot resolve relation '${displayName}' on table '${plan.rootTable}' \u2014 no model is configured. Use rawExists(subquery(...)) for an EXISTS over an uncorrelated or undeclared subquery.`
14934
+ );
14935
+ }
14936
+ let currentSource = plan.rootTable;
14937
+ let allHopsDeclared = true;
14938
+ for (const hop of relationPath) {
14939
+ if (!hop) {
14940
+ allHopsDeclared = false;
14941
+ break;
14942
+ }
14943
+ const rel = model.getRelation(`${currentSource}.${hop}`);
14944
+ if (!rel) {
14945
+ allHopsDeclared = false;
14946
+ break;
14947
+ }
14948
+ currentSource = rel.target;
14949
+ }
14950
+ if (!allHopsDeclared) {
14951
+ throw new Error(
14952
+ `exists('${displayName}'): no relation '${displayName}' is declared on table '${plan.rootTable}'. Use rawExists(subquery(...)) for an EXISTS over an undeclared or uncorrelated subquery.`
14791
14953
  );
14792
14954
  }
14793
14955
  }
14956
+ return placedDecisions;
14957
+ }
14958
+ function extractAllIncludeDecisions(plan, defaultPk = DEFAULT_PK_COLUMN, deriveFk = defaultFkDerivation) {
14959
+ const includeDecisions = plan.decisions.filter(
14960
+ (d) => d.type === "include-strategy"
14961
+ );
14962
+ if (includeDecisions.length === 0) return [];
14963
+ const treeStrategies = /* @__PURE__ */ new Set(["json_agg", "subquery", "lateral"]);
14964
+ const treeDecisions = [];
14965
+ const flatDecisions = [];
14966
+ for (const d of includeDecisions) {
14967
+ const choice = d.choice;
14968
+ if (treeStrategies.has(choice)) {
14969
+ const converted = toIncludeDecision(d, choice, plan, defaultPk, deriveFk);
14970
+ if (converted) treeDecisions.push(converted);
14971
+ } else if (choice === "join") {
14972
+ const converted = toJoinIncludeDecision(d, plan, defaultPk, deriveFk);
14973
+ if (converted) flatDecisions.push(converted);
14974
+ } else if (choice === "cte") {
14975
+ const converted = toIncludeDecision(d, choice, plan, defaultPk, deriveFk);
14976
+ if (converted) flatDecisions.push(converted);
14977
+ }
14978
+ }
14979
+ const builtTree = buildIncludeTree(treeDecisions);
14980
+ return [...builtTree, ...flatDecisions];
14981
+ }
14982
+ function toIncludeDecision(d, choice, plan, defaultPk = DEFAULT_PK_COLUMN, deriveFk = defaultFkDerivation) {
14983
+ const context = d.context;
14984
+ const relationName = resolveIncludeAlias(context);
14985
+ if (!context.target || !relationName) return void 0;
14986
+ const foreignKey = deriveForeignKey(context, deriveFk, defaultPk) ?? defaultPk;
14987
+ const relationType = context.relationType;
14988
+ const effectiveChoice = choice === "subquery" ? "json_agg" : choice;
14989
+ const includeIntent = resolveIncludeByPath(
14990
+ plan.intent?.include,
14991
+ context.intentPath,
14992
+ relationName
14993
+ );
14994
+ const limit = includeIntent?.limit;
14995
+ return {
14996
+ type: "includeStrategy",
14997
+ choice: effectiveChoice,
14998
+ relationName,
14999
+ relationPath: deriveRelationPathFromIntentPath(
15000
+ Array.isArray(plan.intent?.include) ? plan.intent.include : void 0,
15001
+ context.intentPath,
15002
+ relationName
15003
+ ) ?? relationName,
15004
+ targetTable: context.target,
15005
+ ...context.sourceTable && { sourceTable: context.sourceTable },
15006
+ ...relationType && { relationType },
15007
+ foreignKey: Array.isArray(foreignKey) ? foreignKey[0] : foreignKey,
15008
+ parentKey: defaultPk,
15009
+ ...context.intentPath && { intentPath: context.intentPath },
15010
+ ...limit != null && { limit }
15011
+ };
14794
15012
  }
14795
- function buildEnrichedExistsDecision(d, matchingIntent, rootTable, model) {
15013
+ function toJoinIncludeDecision(d, plan, defaultPk = DEFAULT_PK_COLUMN, deriveFk = defaultFkDerivation) {
14796
15014
  const context = d.context;
14797
- const targetTable = context.target;
15015
+ const relationName = context.relation ?? context.includeAlias;
15016
+ if (!context.target || !relationName) return void 0;
15017
+ const intentPath = context?.intentPath;
15018
+ const relationPath = deriveRelationPathFromIntentPath(
15019
+ Array.isArray(plan.intent?.include) ? plan.intent.include : void 0,
15020
+ intentPath,
15021
+ relationName
15022
+ ) ?? relationName;
15023
+ const includeIntent = resolveIncludeByPath(
15024
+ plan.intent?.include,
15025
+ intentPath,
15026
+ relationName
15027
+ );
15028
+ let columns = [defaultPk];
15029
+ if (includeIntent?.select?.type === "fields" && includeIntent.select.fields) {
15030
+ const fields = includeIntent.select.fields.filter((f) => f !== defaultPk);
15031
+ columns = [defaultPk, ...fields];
15032
+ }
15033
+ const foreignKey = deriveForeignKey(context, deriveFk, defaultPk) ?? defaultPk;
15034
+ const relationType = context.relationType;
14798
15035
  let conditions;
14799
- if (matchingIntent?.where) {
14800
- const nestedDecisions = convertWhereToDecisions(
14801
- matchingIntent.where,
14802
- targetTable
15036
+ if (includeIntent?.where) {
15037
+ const converted = convertWhereToDecisions(
15038
+ includeIntent.where,
15039
+ relationName
14803
15040
  );
14804
- if (nestedDecisions.length > 0) {
14805
- conditions = nestedDecisions;
14806
- if (model) {
14807
- enrichExistsStubsInConditions(conditions, targetTable, model);
14808
- }
15041
+ if (converted.length > 0) {
15042
+ conditions = converted;
14809
15043
  }
14810
15044
  }
14811
- const sourceTableForRelation = context.sourceTable || rootTable;
14812
- const relIR = model && context.relation ? model.getRelation(
14813
- `${sourceTableForRelation}.${context.relation}`
14814
- ) : void 0;
14815
- const foreignKey = relIR ? typeof relIR.foreignKey === "string" ? relIR.foreignKey : relIR.foreignKey?.[0] : void 0;
14816
- const relationType = relIR?.type === "belongsTo" ? "belongsTo" : relIR?.type === "hasMany" || relIR?.type === "hasOne" ? relIR.type : void 0;
14817
- const parentKey = relationType === "belongsTo" ? relIR?.targetKey : relIR?.sourceKey;
14818
- let operator = "exists";
14819
- if (matchingIntent?.kind === "notExists") {
14820
- operator = "notExists";
14821
- } else if (matchingIntent?.kind === "relationFilter") {
14822
- const mode = matchingIntent.mode ?? "some";
14823
- if (mode === "none") operator = "notExists";
14824
- else if (mode === "every") operator = "every";
14825
- }
14826
- const includeIntent = matchingIntent?.include;
14827
- const includeDecisions = includeIntent ? Object.entries(includeIntent).map(([rel, opts]) => ({
14828
- type: "existsInclude",
14829
- relation: rel,
14830
- joinType: opts.join ?? "inner"
14831
- })) : void 0;
14832
- const relationName = context.relation;
15045
+ const joinType = d.joinType;
14833
15046
  return {
14834
- type: "where",
14835
- operator,
14836
- targetTable,
14837
- // sourceTable: the table this EXISTS is resolved FROM (the relation's source).
14838
- // Required by propagateExistsConditions's (sourceTable, relationName) identity
14839
- // guard without it the guard is a no-op and cross-source propagation can occur
14840
- // when two relations share a name but originate from different tables.
14841
- sourceTable: sourceTableForRelation,
14842
- ...foreignKey ? { foreignKey } : {},
14843
- // relationType is required so deriveFkColumns (called by mapToHandlerDecision) can
14844
- // set sourceColumn/targetColumn in the correct direction for the EXISTS correlation.
14845
- ...relationType ? { relationType } : {},
14846
- // parentKey provides the explicit PK override for non-default PK columns.
14847
- ...parentKey ? { parentKey } : {},
14848
- ...conditions ? { conditions } : {},
14849
- ...d.choice === "join" ? { choice: "join" } : {},
14850
- ...relationName ? { relationName } : {},
14851
- ...includeDecisions ? { include: includeDecisions } : {}
15047
+ type: "includeStrategy",
15048
+ choice: "join",
15049
+ relationName,
15050
+ relationPath,
15051
+ targetTable: context.target,
15052
+ ...context.sourceTable && { sourceTable: context.sourceTable },
15053
+ ...relationType && { relationType },
15054
+ foreignKey: Array.isArray(foreignKey) ? foreignKey[0] : foreignKey,
15055
+ parentKey: defaultPk,
15056
+ columns,
15057
+ ...joinType && { joinType },
15058
+ ...conditions && { conditions }
14852
15059
  };
14853
15060
  }
14854
- function collectNestedExistsTargets(where, insideExists, sourceTable, model, out) {
14855
- if (!where || typeof where !== "object") return;
14856
- const w = where;
14857
- if (w.kind === "exists" || w.kind === "notExists" || w.kind === "relationFilter") {
14858
- if (insideExists) {
14859
- const rawRelation = w.relation;
14860
- const hops = Array.isArray(rawRelation) ? rawRelation : typeof rawRelation === "string" ? [rawRelation] : [];
14861
- if (hops.length === 1) {
14862
- const singleHop = hops[0] ?? "";
14863
- const rel = model.getRelation(`${sourceTable}.${singleHop}`);
14864
- const resolvedTarget = rel ? rel.target : singleHop;
14865
- out.add(`${sourceTable}:${resolvedTarget}`);
14866
- } else if (hops.length > 1) {
14867
- let penultimate = sourceTable;
14868
- for (let hi = 0; hi < hops.length - 1; hi++) {
14869
- const hr = model.getRelation(`${penultimate}.${hops[hi] ?? ""}`);
14870
- penultimate = hr ? hr.target : hops[hi] ?? penultimate;
14871
- }
14872
- out.add(`${penultimate}:${hops.join(".")}`);
14873
- const lastHop = hops[hops.length - 1] ?? "";
14874
- const lastRel = model.getRelation(`${penultimate}.${lastHop}`);
14875
- const finalTarget = lastRel ? lastRel.target : lastHop;
14876
- out.add(`${penultimate}:${finalTarget}`);
14877
- }
14878
- let nextSource = sourceTable;
14879
- if (hops.length >= 1) {
14880
- let cur = sourceTable;
14881
- for (const hop of hops) {
14882
- const rel = model.getRelation(`${cur}.${hop}`);
14883
- cur = rel ? rel.target : hop;
14884
- }
14885
- nextSource = cur;
14886
- }
14887
- if (w.where) {
14888
- collectNestedExistsTargets(w.where, true, nextSource, model, out);
14889
- }
15061
+ function snakeToCamel(s) {
15062
+ return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
15063
+ }
15064
+ function synthesizeMissingJoinDecisions(plan, coveredRelations, model, defaultPk = DEFAULT_PK_COLUMN, deriveFk = defaultFkDerivation) {
15065
+ const includes = plan.intent?.include;
15066
+ if (!includes || includes.length === 0) return [];
15067
+ const sourceTable = plan.rootTable;
15068
+ const relationsFromSource = model.getRelationsFrom(sourceTable);
15069
+ const synthesized = [];
15070
+ for (const inc of includes) {
15071
+ const alias = inc.relation;
15072
+ if (inc.join !== "inner" && inc.join !== "left") continue;
15073
+ if (coveredRelations.has(alias)) continue;
15074
+ const rel = relationsFromSource.find(
15075
+ (r) => r.name === alias || snakeToCamel(r.name) === alias
15076
+ );
15077
+ if (!rel) continue;
15078
+ const rawFk = rel.foreignKey ? Array.isArray(rel.foreignKey) ? rel.foreignKey[0] : rel.foreignKey : deriveFk(
15079
+ rel.type === "belongsTo" ? rel.target : sourceTable,
15080
+ defaultPk
15081
+ );
15082
+ let columns = [defaultPk];
15083
+ if (inc.select?.type === "fields" && inc.select.fields) {
15084
+ const extraFields = inc.select.fields.filter((f) => f !== defaultPk);
15085
+ columns = [defaultPk, ...extraFields];
15086
+ }
15087
+ let conditions;
15088
+ if (inc.where) {
15089
+ const converted = convertWhereToDecisions(inc.where, alias);
15090
+ if (converted.length > 0) conditions = converted;
15091
+ }
15092
+ synthesized.push({
15093
+ type: "includeStrategy",
15094
+ choice: "join",
15095
+ relationName: alias,
15096
+ relationPath: alias,
15097
+ targetTable: rel.target,
15098
+ sourceTable,
15099
+ ...rel.type && {
15100
+ relationType: rel.type
15101
+ },
15102
+ foreignKey: Array.isArray(rawFk) ? rawFk[0] : rawFk,
15103
+ parentKey: defaultPk,
15104
+ columns,
15105
+ joinType: inc.join,
15106
+ ...conditions && { conditions }
15107
+ });
15108
+ }
15109
+ return synthesized;
15110
+ }
15111
+ function buildIncludeTree(allDecisions) {
15112
+ const decisionsByPath = /* @__PURE__ */ new Map();
15113
+ for (const d of allDecisions) {
15114
+ if (d.intentPath) decisionsByPath.set(d.intentPath, d);
15115
+ }
15116
+ function parentPath(path) {
15117
+ const lastDot = path.lastIndexOf(".include[");
15118
+ return lastDot > 0 ? path.substring(0, lastDot) : void 0;
15119
+ }
15120
+ const childrenMap = /* @__PURE__ */ new Map();
15121
+ const rootDecisions = [];
15122
+ for (const d of allDecisions) {
15123
+ const pp = d.intentPath ? parentPath(d.intentPath) : void 0;
15124
+ if (pp && decisionsByPath.has(pp)) {
15125
+ const siblings = childrenMap.get(pp) ?? [];
15126
+ siblings.push(d);
15127
+ childrenMap.set(pp, siblings);
14890
15128
  } else {
14891
- const rawRelation = w.relation;
14892
- const hops = Array.isArray(rawRelation) ? rawRelation : typeof rawRelation === "string" ? [rawRelation] : [];
14893
- let nextSource = sourceTable;
14894
- if (hops.length >= 1) {
14895
- let cur = sourceTable;
14896
- for (const hop of hops) {
14897
- const rel = model.getRelation(`${cur}.${hop}`);
14898
- cur = rel ? rel.target : hop;
14899
- }
14900
- nextSource = cur;
14901
- }
14902
- if (w.where) {
14903
- collectNestedExistsTargets(w.where, true, nextSource, model, out);
14904
- }
15129
+ rootDecisions.push(d);
15130
+ }
15131
+ }
15132
+ function attachChildren(decision) {
15133
+ const path = decision.intentPath;
15134
+ const children = path ? childrenMap.get(path) : void 0;
15135
+ if (!children || children.length === 0) return decision;
15136
+ return {
15137
+ ...decision,
15138
+ children: children.map(attachChildren)
15139
+ };
15140
+ }
15141
+ return rootDecisions.map(attachChildren);
15142
+ }
15143
+
15144
+ // src/adapter-compiler-select.ts
15145
+ var ADAPTER_MULTIWORD_BASE_TYPES = [
15146
+ "timestamp with time zone",
15147
+ "timestamp without time zone",
15148
+ "time with time zone",
15149
+ "time without time zone",
15150
+ "double precision",
15151
+ "character varying",
15152
+ "bit varying"
15153
+ ];
15154
+ var ADAPTER_IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
15155
+ function assertSafeTypeName(typeName, colIndex) {
15156
+ const raw = typeName.trim();
15157
+ if (raw.length === 0) {
15158
+ throw new Error(
15159
+ `BatchValues compile error: type name at column index ${colIndex} must not be empty.`
15160
+ );
15161
+ }
15162
+ let rest = raw;
15163
+ if (rest.endsWith("[]")) {
15164
+ rest = rest.slice(0, -2);
15165
+ if (rest.endsWith("[]")) {
15166
+ throw new Error(
15167
+ `BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. At most one array suffix "[]" is allowed as a raw type-name input.`
15168
+ );
15169
+ }
15170
+ }
15171
+ const modifierMatch = rest.match(/\(([^)]*)\)$/);
15172
+ if (modifierMatch) {
15173
+ const inner = modifierMatch[1] ?? "";
15174
+ if (!/^\d+(?:,\d+)?$/.test(inner)) {
15175
+ throw new Error(
15176
+ `BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. Type modifier must be "(N)" or "(N,M)" with digits only; got "(${inner})".`
15177
+ );
14905
15178
  }
14906
- return;
15179
+ rest = rest.slice(0, rest.length - modifierMatch[0].length).trimEnd();
14907
15180
  }
14908
- if (w.conditions && Array.isArray(w.conditions)) {
14909
- for (const c of w.conditions) {
14910
- collectNestedExistsTargets(c, insideExists, sourceTable, model, out);
14911
- }
15181
+ const baseLower = rest.toLowerCase();
15182
+ if (ADAPTER_MULTIWORD_BASE_TYPES.includes(baseLower)) {
15183
+ return;
14912
15184
  }
14913
- if (w.condition) {
14914
- collectNestedExistsTargets(
14915
- w.condition,
14916
- insideExists,
14917
- sourceTable,
14918
- model,
14919
- out
15185
+ const parts = rest.split(".");
15186
+ if (parts.length > 2) {
15187
+ throw new Error(
15188
+ `BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. Schema-qualified types allow at most one dot (schema.type).`
14920
15189
  );
14921
15190
  }
14922
- }
14923
- function collectExistsStubs(decisions, out) {
14924
- decisions.forEach((d, i) => {
14925
- if (!d) return;
14926
- if (d.type === "where" && (d.operator === "exists" || d.operator === "notExists" || d.operator === "every")) {
14927
- if (d.relationName) return;
14928
- out.push({ decision: d, container: decisions, index: i });
14929
- } else if ((d.type === "whereAnd" || d.type === "whereOr" || d.type === "whereNot") && d.conditions) {
14930
- collectExistsStubs(d.conditions, out);
15191
+ for (const part of parts) {
15192
+ if (!ADAPTER_IDENT_RE.test(part)) {
15193
+ throw new Error(
15194
+ `BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. Base type "${part}" is not a valid SQL identifier and is not in the multi-word type allowlist.`
15195
+ );
14931
15196
  }
14932
- });
14933
- }
14934
- function normalizeStubRelation(targetTable) {
14935
- if (Array.isArray(targetTable) && targetTable.length > 0)
14936
- return targetTable[targetTable.length - 1];
14937
- if (typeof targetTable === "string") return targetTable;
14938
- return void 0;
15197
+ }
14939
15198
  }
14940
- function normalizeStubRelationPath(targetTable) {
14941
- if (Array.isArray(targetTable) && targetTable.length > 0)
14942
- return targetTable.join(".");
14943
- if (typeof targetTable === "string") return targetTable;
14944
- return void 0;
15199
+ function bridgeLegacyDecisions(decisions) {
15200
+ return decisions;
14945
15201
  }
14946
- function enrichExistsDecisionsInPlace(decisions, plan, model) {
14947
- const stubs = [];
14948
- collectExistsStubs(decisions, stubs);
14949
- const nestedExistsTargets = /* @__PURE__ */ new Set();
14950
- if (plan.intent?.where && model) {
14951
- collectNestedExistsTargets(
14952
- plan.intent.where,
14953
- false,
14954
- plan.rootTable,
14955
- model,
14956
- nestedExistsTargets
14957
- );
15202
+ function buildBatchValuesRangeFn(bv, startParamIndex, aliasOverride) {
15203
+ const params = [];
15204
+ let paramIdx = startParamIndex - 1;
15205
+ const effectiveAlias = aliasOverride ?? bv.alias;
15206
+ for (let ci = 0; ci < bv.columns.length; ci++) {
15207
+ const rawType = bv.types[ci];
15208
+ if (rawType) assertSafeTypeName(rawType, ci);
14958
15209
  }
14959
- const filterDecisions = plan.decisions.filter(
14960
- (d) => d.type === "filter-strategy" && (d.choice === "exists" || d.choice === "notExists" || d.choice === "join")
15210
+ const unnestArgs = bv.columns.map((col, i) => {
15211
+ const colArray = bv.data[i] ?? [];
15212
+ let pgBaseType;
15213
+ if (bv.types[i]) {
15214
+ const rawType = bv.types[i];
15215
+ pgBaseType = rawType.endsWith("[]") ? rawType.slice(0, -2) : rawType;
15216
+ } else {
15217
+ const sampleValue = colArray.find((v) => v !== null && v !== void 0);
15218
+ const pgArrayType = inferPgArrayType(col, {}, sampleValue);
15219
+ pgBaseType = stripArraySuffix(pgArrayType);
15220
+ }
15221
+ params.push(colArray);
15222
+ paramIdx++;
15223
+ return createTypeCastParamRef(paramIdx, pgBaseType, true);
15224
+ });
15225
+ const unnestCall = funcCall("unnest", unnestArgs);
15226
+ const colnames = [...bv.columns, ...bv.ordinality ? ["ord"] : []].map(
15227
+ (c) => ({ String: { sval: c } })
14961
15228
  );
14962
- const placedDecisions = [];
14963
- const consumedStubIndices = /* @__PURE__ */ new Set();
14964
- if (filterDecisions.length > 0) {
14965
- const existsIntents = plan.intent?.where ? findExistsIntents(plan.intent.where) : [];
14966
- for (const d of filterDecisions) {
14967
- const context = d.context;
14968
- if (!context.target) continue;
14969
- const contextRelationPath = context.relationPath;
14970
- const contextSourceTableForIntent = context.sourceTable;
14971
- const isNestedRelation = contextSourceTableForIntent !== void 0 && contextSourceTableForIntent !== plan.rootTable && !contextRelationPath;
14972
- const matchIdx = isNestedRelation ? -1 : existsIntents.findIndex((i) => {
14973
- if (contextRelationPath) {
14974
- const intentPath = Array.isArray(i.relation) ? i.relation.join(".") : typeof i.relation === "string" ? i.relation : void 0;
14975
- return intentPath === contextRelationPath;
14976
- }
14977
- const rel = Array.isArray(i.relation) ? i.relation.length > 0 ? i.relation[i.relation.length - 1] : void 0 : typeof i.relation === "string" ? i.relation : void 0;
14978
- return rel === context.relation || rel === context.target || rel === context.includeAlias;
15229
+ const rangeFunction = {
15230
+ RangeFunction: {
15231
+ functions: [{ List: { items: [unnestCall] } }],
15232
+ ordinality: bv.ordinality,
15233
+ alias: { aliasname: effectiveAlias, colnames }
15234
+ }
15235
+ };
15236
+ return { rangeFunction, params };
15237
+ }
15238
+ function compileJoinIntents(joins, rootTable, schemaName, deps) {
15239
+ if (joins.length === 0) return [];
15240
+ const model = deps.model;
15241
+ const naming = deps.naming;
15242
+ const deriveFk = deps.deriveFk ?? defaultFkDerivation;
15243
+ const defaultPk = deps.defaultPk;
15244
+ const results = [];
15245
+ for (const intent of joins) {
15246
+ if (intent.relation !== void 0) {
15247
+ if (!model) {
15248
+ throw new Error(
15249
+ `join('${intent.relation}'): relation-mode join requires a model for FK resolution.`
15250
+ );
15251
+ }
15252
+ const relationsFromRoot = model.getRelationsFrom(rootTable);
15253
+ const rel = relationsFromRoot.find((r) => r.name === intent.relation);
15254
+ if (!rel) {
15255
+ throw new Error(
15256
+ `join('${intent.relation}'): relation not found on table '${rootTable}'. Available: ${relationsFromRoot.map((r) => r.name).join(", ")}`
15257
+ );
15258
+ }
15259
+ const isBelongsTo = rel.type === "belongsTo";
15260
+ const rawFk = rel.foreignKey ? Array.isArray(rel.foreignKey) ? rel.foreignKey[0] : rel.foreignKey : deriveFk(isBelongsTo ? rootTable : rel.target, defaultPk);
15261
+ const sourceColumn = isBelongsTo ? rawFk : defaultPk;
15262
+ const targetColumn = isBelongsTo ? defaultPk : rawFk;
15263
+ const alias = intent.alias ?? intent.relation;
15264
+ results.push({
15265
+ type: "join",
15266
+ targetTable: rel.target,
15267
+ alias,
15268
+ sourceColumn,
15269
+ targetColumn,
15270
+ joinType: intent.type
14979
15271
  });
14980
- const matchingIntent = matchIdx >= 0 ? existsIntents[matchIdx] : void 0;
14981
- if (matchIdx >= 0) existsIntents.splice(matchIdx, 1);
14982
- const isMultiHop = matchingIntent && Array.isArray(matchingIntent.relation) && matchingIntent.relation.length > 1 && model;
14983
- let enriched;
14984
- if (isMultiHop && matchingIntent && model) {
14985
- const mode = matchingIntent.mode ?? "some";
14986
- const isEvery = matchingIntent.kind !== "notExists" && mode === "every";
14987
- const outerOperator = matchingIntent.kind === "notExists" || mode === "none" || isEvery ? "notExists" : "exists";
14988
- const lastHopTarget = context.target;
14989
- const rawInnerConditions = matchingIntent.where ? (() => {
14990
- const c = convertWhereToDecisions(
14991
- matchingIntent.where,
14992
- lastHopTarget
14993
- );
14994
- if (c.length > 0) {
14995
- enrichExistsStubsInConditions(c, lastHopTarget, model);
14996
- }
14997
- return c.length > 0 ? c : void 0;
14998
- })() : void 0;
14999
- if (isEvery && !rawInnerConditions) {
15000
- enriched = buildEnrichedExistsDecision(
15001
- d,
15002
- matchingIntent,
15003
- plan.rootTable,
15004
- model
15005
- );
15006
- } else {
15007
- const innerConditions = isEvery && rawInnerConditions ? [
15008
- {
15009
- type: "logical",
15010
- operator: "not",
15011
- conditions: rawInnerConditions
15012
- }
15013
- ] : rawInnerConditions;
15014
- enriched = buildMultiHopExistsChain(
15015
- matchingIntent.relation,
15016
- plan.rootTable,
15017
- outerOperator,
15018
- innerConditions,
15019
- model
15272
+ } else if (intent.batchValues !== void 0) {
15273
+ const bv = intent.batchValues;
15274
+ const alias = intent.alias ?? bv.alias;
15275
+ const { rangeFunction, params: bvParams } = buildBatchValuesRangeFn(
15276
+ bv,
15277
+ 1,
15278
+ alias
15279
+ );
15280
+ const bvOnParamState = createCompilerState();
15281
+ bvOnParamState.paramIndex = bvParams.length;
15282
+ const bvCtx = {
15283
+ rootTable,
15284
+ aliases: /* @__PURE__ */ new Map(),
15285
+ paramState: bvOnParamState,
15286
+ naming,
15287
+ outerTable: alias,
15288
+ ...schemaName !== void 0 && { schemaName },
15289
+ ...deps.bindingNames !== void 0 && {
15290
+ bindingNames: deps.bindingNames
15291
+ },
15292
+ ...model !== void 0 && { model },
15293
+ ...deps.dialectCapabilities !== void 0 && {
15294
+ dialectCapabilities: deps.dialectCapabilities
15295
+ },
15296
+ compileSubquery: () => {
15297
+ throw new Error(
15298
+ "Subquery in BatchValues JOIN ON condition is not supported."
15020
15299
  );
15021
15300
  }
15022
- } else {
15023
- enriched = buildEnrichedExistsDecision(
15024
- d,
15025
- matchingIntent,
15026
- plan.rootTable,
15027
- model
15028
- );
15301
+ };
15302
+ const onNode = compileWhereIntent(intent.on, bvCtx);
15303
+ const allBvParams = [
15304
+ ...bvParams,
15305
+ ...bvOnParamState.parameters
15306
+ ];
15307
+ results.push({
15308
+ type: "join",
15309
+ targetTable: alias,
15310
+ alias,
15311
+ joinType: intent.type,
15312
+ joinRarg: rangeFunction,
15313
+ joinOnNode: onNode,
15314
+ // batchValuesParams are spliced into this.state.parameters BEFORE
15315
+ // other params in compiler.ts, so $1/$2/... refs align correctly.
15316
+ batchValuesParams: allBvParams
15317
+ });
15318
+ } else {
15319
+ const paramState = createCompilerState();
15320
+ const tableAlias = intent.alias ?? intent.table;
15321
+ const tableAliasMap = /* @__PURE__ */ new Map();
15322
+ tableAliasMap.set(rootTable, rootTable);
15323
+ if (tableAlias !== rootTable) {
15324
+ tableAliasMap.set(tableAlias, intent.table);
15029
15325
  }
15030
- const contextSourceTable = context.sourceTable;
15031
- const stubIdx = stubs.findIndex((s, idx) => {
15032
- if (consumedStubIndices.has(idx)) return false;
15033
- if (contextRelationPath) {
15034
- const stubPath = normalizeStubRelationPath(s.decision.targetTable);
15035
- return stubPath === contextRelationPath;
15036
- }
15037
- if (contextSourceTable !== void 0 && contextSourceTable !== plan.rootTable) {
15038
- return false;
15326
+ const ctx = {
15327
+ rootTable,
15328
+ aliases: tableAliasMap,
15329
+ paramState,
15330
+ naming,
15331
+ // outerTable = tableAlias so FieldRef(scope:'outer') resolves to the
15332
+ // joined alias (e.g. 'e2' in self-join ON conditions).
15333
+ outerTable: tableAlias,
15334
+ ...schemaName !== void 0 && { schemaName },
15335
+ ...deps.bindingNames !== void 0 && {
15336
+ bindingNames: deps.bindingNames
15337
+ },
15338
+ ...model !== void 0 && { model },
15339
+ ...deps.dialectCapabilities !== void 0 && {
15340
+ dialectCapabilities: deps.dialectCapabilities
15341
+ },
15342
+ compileSubquery: () => {
15343
+ throw new Error("Subquery in JOIN ON condition is not supported.");
15039
15344
  }
15040
- const stubRelation = normalizeStubRelation(s.decision.targetTable);
15041
- return stubRelation === context.relation || stubRelation === context.target || stubRelation === context.includeAlias;
15345
+ };
15346
+ const onNode = compileWhereIntent(intent.on, ctx);
15347
+ const joinedRangeVar = rangeVar(
15348
+ intent.table,
15349
+ tableAlias,
15350
+ schemaForFromName(schemaName, intent.table, deps.bindingNames, naming),
15351
+ naming
15352
+ );
15353
+ results.push({
15354
+ type: "join",
15355
+ targetTable: intent.table,
15356
+ alias: tableAlias,
15357
+ joinType: intent.type,
15358
+ joinRarg: joinedRangeVar,
15359
+ joinOnNode: onNode
15042
15360
  });
15043
- if (stubIdx >= 0) {
15044
- const stub = stubs[stubIdx];
15045
- if (stub) {
15046
- stub.container[stub.index] = enriched;
15047
- consumedStubIndices.add(stubIdx);
15048
- placedDecisions.push(enriched);
15049
- }
15050
- } else {
15051
- const srcForKey = context.sourceTable ?? "";
15052
- const targetName = context.target;
15053
- const relationPath = contextRelationPath;
15054
- const compoundTarget = targetName ? `${srcForKey}:${targetName}` : "";
15055
- const compoundPath = relationPath ? `${srcForKey}:${relationPath}` : "";
15056
- if (compoundTarget && nestedExistsTargets.has(compoundTarget) || compoundPath && nestedExistsTargets.has(compoundPath)) {
15057
- continue;
15058
- }
15059
- decisions.push(enriched);
15060
- placedDecisions.push(enriched);
15061
- }
15062
15361
  }
15063
15362
  }
15064
- for (let i = 0; i < stubs.length; i++) {
15065
- if (consumedStubIndices.has(i)) continue;
15066
- const stub = stubs[i];
15067
- if (!stub) continue;
15068
- const rawTargetTable = stub.decision.targetTable;
15069
- const relationPath = Array.isArray(rawTargetTable) ? rawTargetTable : [rawTargetTable];
15070
- const displayName = Array.isArray(rawTargetTable) ? rawTargetTable.join(".") : String(rawTargetTable ?? "");
15071
- if (!displayName) continue;
15072
- if (!model) {
15073
- throw new Error(
15074
- `exists('${displayName}'): cannot resolve relation '${displayName}' on table '${plan.rootTable}' \u2014 no model is configured. Use rawExists(subquery(...)) for an EXISTS over an uncorrelated or undeclared subquery.`
15075
- );
15076
- }
15077
- let currentSource = plan.rootTable;
15078
- let allHopsDeclared = true;
15079
- for (const hop of relationPath) {
15080
- if (!hop) {
15081
- allHopsDeclared = false;
15082
- break;
15083
- }
15084
- const rel = model.getRelation(`${currentSource}.${hop}`);
15085
- if (!rel) {
15086
- allHopsDeclared = false;
15087
- break;
15363
+ return results;
15364
+ }
15365
+ function stripJoinColumnsForAggregation(decisions, intent) {
15366
+ const isAggregateOnly = intent.select && "type" in intent.select && intent.select.type === "aggregate" && !("fields" in intent.select && intent.select.fields);
15367
+ const isDistinct = intent.distinct === true;
15368
+ const hasGroupBy = intent.groupBy && intent.groupBy.length > 0;
15369
+ const hasExplicitColumns = intent.select && "type" in intent.select && intent.select.type === "expressions";
15370
+ if (isAggregateOnly || isDistinct || hasGroupBy || hasExplicitColumns) {
15371
+ for (const d of decisions) {
15372
+ if (d.type === "includeStrategy" && d.choice === "join") {
15373
+ d.columns = [];
15088
15374
  }
15089
- currentSource = rel.target;
15090
- }
15091
- if (!allHopsDeclared) {
15092
- throw new Error(
15093
- `exists('${displayName}'): no relation '${displayName}' is declared on table '${plan.rootTable}'. Use rawExists(subquery(...)) for an EXISTS over an undeclared or uncorrelated subquery.`
15094
- );
15095
15375
  }
15096
15376
  }
15097
- return placedDecisions;
15098
15377
  }
15099
- function extractAllIncludeDecisions(plan, defaultPk = DEFAULT_PK_COLUMN, deriveFk = defaultFkDerivation) {
15100
- const includeDecisions = plan.decisions.filter(
15101
- (d) => d.type === "include-strategy"
15102
- );
15103
- if (includeDecisions.length === 0) return [];
15104
- const treeStrategies = /* @__PURE__ */ new Set(["json_agg", "subquery", "lateral"]);
15105
- const treeDecisions = [];
15106
- const flatDecisions = [];
15107
- for (const d of includeDecisions) {
15108
- const choice = d.choice;
15109
- if (treeStrategies.has(choice)) {
15110
- const converted = toIncludeDecision(d, choice, plan, defaultPk, deriveFk);
15111
- if (converted) treeDecisions.push(converted);
15112
- } else if (choice === "join") {
15113
- const converted = toJoinIncludeDecision(d, plan, defaultPk, deriveFk);
15114
- if (converted) flatDecisions.push(converted);
15115
- } else if (choice === "cte") {
15116
- const converted = toIncludeDecision(d, choice, plan, defaultPk, deriveFk);
15117
- if (converted) flatDecisions.push(converted);
15378
+ function buildRelationColumnsMap(decisions, includedRelations) {
15379
+ const map = /* @__PURE__ */ new Map();
15380
+ for (const d of decisions) {
15381
+ if (!(d.type === "selectRelationColumn" && d.relation && d.column))
15382
+ continue;
15383
+ const col = d.column;
15384
+ const alias = d.alias;
15385
+ const fullRelation = d.relation;
15386
+ const rootRelation = fullRelation.split(".")[0] ?? "";
15387
+ if (!includedRelations.has(rootRelation)) continue;
15388
+ const mapKey = fullRelation;
15389
+ if (col === "*") {
15390
+ map.set(mapKey, [{ col: "*" }]);
15391
+ continue;
15118
15392
  }
15119
- }
15120
- const builtTree = buildIncludeTree(treeDecisions);
15121
- return [...builtTree, ...flatDecisions];
15122
- }
15123
- function toIncludeDecision(d, choice, plan, defaultPk = DEFAULT_PK_COLUMN, deriveFk = defaultFkDerivation) {
15124
- const context = d.context;
15125
- const relationName = resolveIncludeAlias(context);
15126
- if (!context.target || !relationName) return void 0;
15127
- const foreignKey = deriveForeignKey(context, deriveFk, defaultPk) ?? defaultPk;
15128
- const relationType = context.relationType;
15129
- const effectiveChoice = choice === "subquery" ? "json_agg" : choice;
15130
- const includeIntent = resolveIncludeByPath(
15131
- plan.intent?.include,
15132
- context.intentPath,
15133
- relationName
15134
- );
15135
- const limit = includeIntent?.limit;
15136
- return {
15137
- type: "includeStrategy",
15138
- choice: effectiveChoice,
15139
- relationName,
15140
- relationPath: deriveRelationPathFromIntentPath(
15141
- Array.isArray(plan.intent?.include) ? plan.intent.include : void 0,
15142
- context.intentPath,
15143
- relationName
15144
- ) ?? relationName,
15145
- targetTable: context.target,
15146
- ...context.sourceTable && { sourceTable: context.sourceTable },
15147
- ...relationType && { relationType },
15148
- foreignKey: Array.isArray(foreignKey) ? foreignKey[0] : foreignKey,
15149
- parentKey: defaultPk,
15150
- ...context.intentPath && { intentPath: context.intentPath },
15151
- ...limit != null && { limit }
15152
- };
15153
- }
15154
- function toJoinIncludeDecision(d, plan, defaultPk = DEFAULT_PK_COLUMN, deriveFk = defaultFkDerivation) {
15155
- const context = d.context;
15156
- const relationName = context.relation ?? context.includeAlias;
15157
- if (!context.target || !relationName) return void 0;
15158
- const intentPath = context?.intentPath;
15159
- const relationPath = deriveRelationPathFromIntentPath(
15160
- Array.isArray(plan.intent?.include) ? plan.intent.include : void 0,
15161
- intentPath,
15162
- relationName
15163
- ) ?? relationName;
15164
- const includeIntent = resolveIncludeByPath(
15165
- plan.intent?.include,
15166
- intentPath,
15167
- relationName
15168
- );
15169
- let columns = [defaultPk];
15170
- if (includeIntent?.select?.type === "fields" && includeIntent.select.fields) {
15171
- const fields = includeIntent.select.fields.filter((f) => f !== defaultPk);
15172
- columns = [defaultPk, ...fields];
15173
- }
15174
- const foreignKey = deriveForeignKey(context, deriveFk, defaultPk) ?? defaultPk;
15175
- const relationType = context.relationType;
15176
- let conditions;
15177
- if (includeIntent?.where) {
15178
- const converted = convertWhereToDecisions(
15179
- includeIntent.where,
15180
- relationName
15181
- );
15182
- if (converted.length > 0) {
15183
- conditions = converted;
15393
+ const existing = map.get(mapKey);
15394
+ if (existing) {
15395
+ if (existing.length === 1 && existing[0]?.col === "*") continue;
15396
+ if (!existing.some((e) => e.col === col)) {
15397
+ existing.push({ col, ...alias !== void 0 && { alias } });
15398
+ }
15399
+ } else {
15400
+ map.set(mapKey, [{ col, ...alias !== void 0 && { alias } }]);
15184
15401
  }
15185
15402
  }
15186
- const joinType = d.joinType;
15187
- return {
15188
- type: "includeStrategy",
15189
- choice: "join",
15190
- relationName,
15191
- relationPath,
15192
- targetTable: context.target,
15193
- ...context.sourceTable && { sourceTable: context.sourceTable },
15194
- ...relationType && { relationType },
15195
- foreignKey: Array.isArray(foreignKey) ? foreignKey[0] : foreignKey,
15196
- parentKey: defaultPk,
15197
- columns,
15198
- ...joinType && { joinType },
15199
- ...conditions && { conditions }
15200
- };
15201
- }
15202
- function snakeToCamel(s) {
15203
- return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
15403
+ return map;
15204
15404
  }
15205
- function synthesizeMissingJoinDecisions(plan, coveredRelations, model, defaultPk = DEFAULT_PK_COLUMN, deriveFk = defaultFkDerivation) {
15206
- const includes = plan.intent?.include;
15207
- if (!includes || includes.length === 0) return [];
15208
- const sourceTable = plan.rootTable;
15209
- const relationsFromSource = model.getRelationsFrom(sourceTable);
15210
- const synthesized = [];
15211
- for (const inc of includes) {
15212
- const alias = inc.relation;
15213
- if (inc.join !== "inner" && inc.join !== "left") continue;
15214
- if (coveredRelations.has(alias)) continue;
15215
- const rel = relationsFromSource.find(
15216
- (r) => r.name === alias || snakeToCamel(r.name) === alias
15217
- );
15218
- if (!rel) continue;
15219
- const rawFk = rel.foreignKey ? Array.isArray(rel.foreignKey) ? rel.foreignKey[0] : rel.foreignKey : deriveFk(
15220
- rel.type === "belongsTo" ? rel.target : sourceTable,
15221
- defaultPk
15222
- );
15223
- let columns = [defaultPk];
15224
- if (inc.select?.type === "fields" && inc.select.fields) {
15225
- const extraFields = inc.select.fields.filter((f) => f !== defaultPk);
15226
- columns = [defaultPk, ...extraFields];
15227
- }
15228
- let conditions;
15229
- if (inc.where) {
15230
- const converted = convertWhereToDecisions(inc.where, alias);
15231
- if (converted.length > 0) conditions = converted;
15405
+ function injectAndValidateRelationColumns(enrichedUnifiedDecisions, relationColumnsMap, model) {
15406
+ if (relationColumnsMap.size === 0) return;
15407
+ for (const d of enrichedUnifiedDecisions) {
15408
+ if (d.type === "includeStrategy" && d.relationName) {
15409
+ const mapKey = d.relationPath ?? d.relationName;
15410
+ const entries = mapKey ? relationColumnsMap.get(mapKey) : void 0;
15411
+ if (entries) {
15412
+ const mut = d;
15413
+ mut.columns = entries.map((e) => e.col);
15414
+ const aliasMap = {};
15415
+ for (const { col, alias } of entries) {
15416
+ if (alias) aliasMap[col] = alias;
15417
+ }
15418
+ if (Object.keys(aliasMap).length > 0) {
15419
+ mut.columnAliases = aliasMap;
15420
+ }
15421
+ }
15422
+ }
15423
+ }
15424
+ if (!model) return;
15425
+ for (const d of enrichedUnifiedDecisions) {
15426
+ if (d.type === "includeStrategy" && d.columns && d.targetTable && !(d.columns.length === 1 && d.columns[0] === "*")) {
15427
+ const targetTable = model.getTable(d.targetTable);
15428
+ if (targetTable) {
15429
+ const validColumnNames = new Set(
15430
+ targetTable.columns.map((c) => c.name)
15431
+ );
15432
+ const invalid = d.columns.filter(
15433
+ (c) => !validColumnNames.has(c)
15434
+ );
15435
+ if (invalid.length > 0) {
15436
+ throw new Error(
15437
+ `Unknown column(s) ${invalid.map((c) => `'${c}'`).join(", ")} in relation '${d.relationName}' (table '${d.targetTable}'). Available: ${[...validColumnNames].join(", ")}`
15438
+ );
15439
+ }
15440
+ }
15232
15441
  }
15233
- synthesized.push({
15234
- type: "includeStrategy",
15235
- choice: "join",
15236
- relationName: alias,
15237
- relationPath: alias,
15238
- targetTable: rel.target,
15239
- sourceTable,
15240
- ...rel.type && {
15241
- relationType: rel.type
15242
- },
15243
- foreignKey: Array.isArray(rawFk) ? rawFk[0] : rawFk,
15244
- parentKey: defaultPk,
15245
- columns,
15246
- joinType: inc.join,
15247
- ...conditions && { conditions }
15248
- });
15249
15442
  }
15250
- return synthesized;
15251
15443
  }
15252
- function buildIncludeTree(allDecisions) {
15253
- const decisionsByPath = /* @__PURE__ */ new Map();
15254
- for (const d of allDecisions) {
15255
- if (d.intentPath) decisionsByPath.set(d.intentPath, d);
15444
+ function applyJoinHydrationPrefixes(decisions) {
15445
+ const usages = [];
15446
+ for (const d of decisions) {
15447
+ if (d.type !== "includeStrategy" || d.choice !== "join" || !d.relationName) {
15448
+ continue;
15449
+ }
15450
+ const relationName = d.relationName;
15451
+ const relationPath = d.relationPath ?? relationName;
15452
+ usages.push({ relationName, relationPath });
15256
15453
  }
15257
- function parentPath(path) {
15258
- const lastDot = path.lastIndexOf(".include[");
15259
- return lastDot > 0 ? path.substring(0, lastDot) : void 0;
15454
+ const pathCountsByRelation = countDistinctRelationPathsByName(usages);
15455
+ for (const d of decisions) {
15456
+ if (d.type !== "includeStrategy" || d.choice !== "join" || !d.relationName) {
15457
+ continue;
15458
+ }
15459
+ const relationName = d.relationName;
15460
+ const relationPath = d.relationPath ?? relationName;
15461
+ const usesFullPath = (pathCountsByRelation.get(relationName) ?? 0) > 1;
15462
+ d.hydrationPrefix = usesFullPath ? relationPath : relationName;
15260
15463
  }
15261
- const childrenMap = /* @__PURE__ */ new Map();
15262
- const rootDecisions = [];
15263
- for (const d of allDecisions) {
15264
- const pp = d.intentPath ? parentPath(d.intentPath) : void 0;
15265
- if (pp && decisionsByPath.has(pp)) {
15266
- const siblings = childrenMap.get(pp) ?? [];
15267
- siblings.push(d);
15268
- childrenMap.set(pp, siblings);
15269
- } else {
15270
- rootDecisions.push(d);
15464
+ }
15465
+ function enrichRangeDecisions(allDecisions, model, rootTable) {
15466
+ if (!model) return;
15467
+ for (let i = 0; i < allDecisions.length; i++) {
15468
+ const d = allDecisions[i];
15469
+ if (d && d.type === "where" && (d.operator === "contains" || d.operator === "containedBy" || d.operator === "overlaps")) {
15470
+ const tableName = d.table || rootTable;
15471
+ const table = model.getTable(tableName);
15472
+ if (table) {
15473
+ const col = table.columns.find((c) => c.name === d.column);
15474
+ if (col?.type.endsWith("range")) {
15475
+ allDecisions[i] = { ...d, dataType: col.type };
15476
+ }
15477
+ }
15271
15478
  }
15272
15479
  }
15273
- function attachChildren(decision) {
15274
- const path = decision.intentPath;
15275
- const children = path ? childrenMap.get(path) : void 0;
15276
- if (!children || children.length === 0) return decision;
15480
+ }
15481
+ function buildSimplifiedPlanReport(plan, allDecisions, schemaName) {
15482
+ const bvFromSource = plan.intent?.batchValuesSource;
15483
+ const batchValuesFromFields = bvFromSource ? (() => {
15484
+ const { rangeFunction, params } = buildBatchValuesRangeFn(
15485
+ bvFromSource,
15486
+ 1
15487
+ );
15277
15488
  return {
15278
- ...decision,
15279
- children: children.map(attachChildren)
15489
+ batchValuesFromNode: rangeFunction,
15490
+ batchValuesFromParams: params
15280
15491
  };
15281
- }
15282
- return rootDecisions.map(attachChildren);
15492
+ })() : {};
15493
+ return {
15494
+ rootTable: plan.rootTable,
15495
+ decisions: allDecisions,
15496
+ ...schemaName ? { schema: schemaName } : {},
15497
+ ...plan.intent?.existsWrap ? { existsWrap: true } : {},
15498
+ ...plan.intent?.lock ? { lock: plan.intent.lock } : {},
15499
+ ...batchValuesFromFields
15500
+ };
15283
15501
  }
15284
-
15285
- // src/adapter-compiler-select.ts
15286
- var ADAPTER_MULTIWORD_BASE_TYPES = [
15287
- "timestamp with time zone",
15288
- "timestamp without time zone",
15289
- "time with time zone",
15290
- "time without time zone",
15291
- "double precision",
15292
- "character varying",
15293
- "bit varying"
15294
- ];
15295
- var ADAPTER_IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
15296
- function assertSafeTypeName(typeName, colIndex) {
15297
- const raw = typeName.trim();
15298
- if (raw.length === 0) {
15299
- throw new Error(
15300
- `BatchValues compile error: type name at column index ${colIndex} must not be empty.`
15502
+ function compileSelect(plan, options, deps) {
15503
+ const schemaName = deps.schemaName;
15504
+ const resolvedModelForCompiler = options?.model ?? deps.model;
15505
+ const compilerOptions = {
15506
+ naming: deps.naming,
15507
+ ...schemaName && { schema: schemaName },
15508
+ defaultPkColumnName: deps.defaultPk,
15509
+ deriveFkColumnName: deps.deriveFk,
15510
+ ...deps.bindingNames !== void 0 && {
15511
+ bindingNames: deps.bindingNames
15512
+ },
15513
+ ...deps.dialectCapabilities !== void 0 && {
15514
+ dialectCapabilities: deps.dialectCapabilities
15515
+ },
15516
+ ...resolvedModelForCompiler != null && {
15517
+ model: resolvedModelForCompiler
15518
+ }
15519
+ };
15520
+ const execIntent = plan.executableIntent ?? plan.intent;
15521
+ const planForCompilation = plan.executableIntent !== void 0 ? { ...plan, intent: plan.executableIntent } : plan;
15522
+ let simplifiedPlan;
15523
+ if (execIntent) {
15524
+ let decisions = intentToDecisions(execIntent, plan.rootTable);
15525
+ const resolvedModel = options?.model ?? deps.model;
15526
+ if (resolvedModel) {
15527
+ decisions = convertDottedFieldsToExists(
15528
+ decisions,
15529
+ plan.rootTable,
15530
+ resolvedModel
15531
+ );
15532
+ }
15533
+ enrichExistsDecisionsInPlace(
15534
+ decisions,
15535
+ planForCompilation,
15536
+ options?.model ?? deps.model
15301
15537
  );
15302
- }
15303
- let rest = raw;
15304
- if (rest.endsWith("[]")) {
15305
- rest = rest.slice(0, -2);
15306
- if (rest.endsWith("[]")) {
15307
- throw new Error(
15308
- `BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. At most one array suffix "[]" is allowed as a raw type-name input.`
15538
+ const unifiedIncludeDecisions = extractAllIncludeDecisions(
15539
+ planForCompilation,
15540
+ deps.defaultPk,
15541
+ deps.deriveFk
15542
+ );
15543
+ const coveredByPlanner = new Set(
15544
+ unifiedIncludeDecisions.filter((d) => d.type === "includeStrategy").map((d) => d.relationName).filter(Boolean)
15545
+ );
15546
+ const synthesizedModel = options?.model ?? deps.model;
15547
+ const synthesizedJoins = synthesizedModel ? synthesizeMissingJoinDecisions(
15548
+ planForCompilation,
15549
+ coveredByPlanner,
15550
+ synthesizedModel,
15551
+ deps.defaultPk,
15552
+ deps.deriveFk
15553
+ ) : [];
15554
+ const allUnifiedIncludeDecisions = synthesizedJoins.length > 0 ? [...unifiedIncludeDecisions, ...synthesizedJoins] : unifiedIncludeDecisions;
15555
+ const enrichedUnifiedDecisions = [
15556
+ ...allUnifiedIncludeDecisions
15557
+ ];
15558
+ stripJoinColumnsForAggregation(enrichedUnifiedDecisions, execIntent);
15559
+ applyJoinHydrationPrefixes(enrichedUnifiedDecisions);
15560
+ const includedRelations = new Set(
15561
+ enrichedUnifiedDecisions.filter((d) => d.type === "includeStrategy").map((d) => d.relationName).filter(Boolean)
15562
+ );
15563
+ if (includedRelations.size > 0) {
15564
+ const relationColumnsMap = buildRelationColumnsMap(
15565
+ decisions,
15566
+ includedRelations
15567
+ );
15568
+ injectAndValidateRelationColumns(
15569
+ enrichedUnifiedDecisions,
15570
+ relationColumnsMap,
15571
+ options?.model ?? deps.model
15309
15572
  );
15310
15573
  }
15311
- }
15312
- const modifierMatch = rest.match(/\(([^)]*)\)$/);
15313
- if (modifierMatch) {
15314
- const inner = modifierMatch[1] ?? "";
15315
- if (!/^\d+(?:,\d+)?$/.test(inner)) {
15316
- throw new Error(
15317
- `BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. Type modifier must be "(N)" or "(N,M)" with digits only; got "(${inner})".`
15318
- );
15574
+ const deduplicatedDecisions = includedRelations.size > 0 ? decisions.filter((d) => {
15575
+ if (d.type === "selectRelationColumn" && d.relation) {
15576
+ const rel = d.relation;
15577
+ const rootRelation = rel.split(".")[0] ?? rel;
15578
+ if (includedRelations.has(rootRelation)) {
15579
+ return false;
15580
+ }
15581
+ }
15582
+ return true;
15583
+ }) : decisions;
15584
+ const joinIntentDecisions = execIntent?.joins && execIntent.joins.length > 0 ? compileJoinIntents(
15585
+ execIntent.joins,
15586
+ plan.rootTable,
15587
+ schemaName,
15588
+ deps
15589
+ ) : [];
15590
+ const allDecisions = [
15591
+ ...deduplicatedDecisions,
15592
+ ...enrichedUnifiedDecisions,
15593
+ ...joinIntentDecisions
15594
+ ];
15595
+ const rangeModel = options?.model ?? deps.model;
15596
+ enrichRangeDecisions(allDecisions, rangeModel, plan.rootTable);
15597
+ simplifiedPlan = buildSimplifiedPlanReport(
15598
+ planForCompilation,
15599
+ allDecisions,
15600
+ schemaName
15601
+ );
15602
+ } else {
15603
+ simplifiedPlan = {
15604
+ rootTable: plan.rootTable,
15605
+ decisions: bridgeLegacyDecisions(plan.decisions),
15606
+ ...schemaName ? { schema: schemaName } : {}
15607
+ };
15608
+ }
15609
+ const result = compilePlan(simplifiedPlan, compilerOptions);
15610
+ return {
15611
+ sql: result.sql,
15612
+ parameters: result.parameters
15613
+ };
15614
+ }
15615
+ function compileWithIncludes(plan, options, deps) {
15616
+ const main = compileSelect(plan, options, deps);
15617
+ const subqueryIncludes = [];
15618
+ for (const d of plan.decisions) {
15619
+ if (d.type !== "include-strategy" || d.choice !== "subquery") continue;
15620
+ const ctx = d.context;
15621
+ if (!ctx.target) continue;
15622
+ const relationName = ctx.includeAlias ?? ctx.relation;
15623
+ if (!relationName) continue;
15624
+ const rawFk = deriveForeignKey(ctx, deps.deriveFk, deps.defaultPk) ?? deps.defaultPk;
15625
+ const fk = Array.isArray(rawFk) ? rawFk[0] : rawFk;
15626
+ const isBelongsTo = ctx.relationType === "belongsTo";
15627
+ const sourceKey = isBelongsTo ? fk : "id";
15628
+ const targetFk = isBelongsTo ? "id" : fk;
15629
+ const includeIntent = plan.intent?.include?.find(
15630
+ (i) => i.relation === relationName || i.relation === ctx.includeAlias
15631
+ );
15632
+ const entry = {
15633
+ relationName,
15634
+ targetTable: ctx.target,
15635
+ foreignKey: targetFk,
15636
+ sourceKey,
15637
+ sourceTable: ctx.sourceTable ?? plan.rootTable
15638
+ };
15639
+ if (typeof ctx.relationType === "string") {
15640
+ entry.relationType = ctx.relationType;
15641
+ }
15642
+ if (includeIntent?.select != null) {
15643
+ entry.select = includeIntent.select;
15319
15644
  }
15320
- rest = rest.slice(0, rest.length - modifierMatch[0].length).trimEnd();
15321
- }
15322
- const baseLower = rest.toLowerCase();
15323
- if (ADAPTER_MULTIWORD_BASE_TYPES.includes(baseLower)) {
15324
- return;
15325
- }
15326
- const parts = rest.split(".");
15327
- if (parts.length > 2) {
15328
- throw new Error(
15329
- `BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. Schema-qualified types allow at most one dot (schema.type).`
15330
- );
15331
- }
15332
- for (const part of parts) {
15333
- if (!ADAPTER_IDENT_RE.test(part)) {
15334
- throw new Error(
15335
- `BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. Base type "${part}" is not a valid SQL identifier and is not in the multi-word type allowlist.`
15336
- );
15645
+ if (includeIntent?.where != null) {
15646
+ entry.where = includeIntent.where;
15337
15647
  }
15648
+ subqueryIncludes.push(entry);
15338
15649
  }
15650
+ return { main, subqueryIncludes };
15339
15651
  }
15340
- function bridgeLegacyDecisions(decisions) {
15341
- return decisions;
15652
+
15653
+ // src/adapter-compiler-mutations.ts
15654
+ init_binding_registry();
15655
+ init_compile_where();
15656
+ init_compiler_utils();
15657
+ init_handlers();
15658
+ function whereIntentAsDecision(where) {
15659
+ return where;
15342
15660
  }
15343
- function buildBatchValuesRangeFn(bv, startParamIndex, aliasOverride) {
15344
- const params = [];
15345
- let paramIdx = startParamIndex - 1;
15346
- const effectiveAlias = aliasOverride ?? bv.alias;
15347
- for (let ci = 0; ci < bv.columns.length; ci++) {
15348
- const rawType = bv.types[ci];
15349
- if (rawType) assertSafeTypeName(rawType, ci);
15350
- }
15351
- const unnestArgs = bv.columns.map((col, i) => {
15352
- const colArray = bv.data[i] ?? [];
15353
- let pgBaseType;
15354
- if (bv.types[i]) {
15355
- const rawType = bv.types[i];
15356
- pgBaseType = rawType.endsWith("[]") ? rawType.slice(0, -2) : rawType;
15357
- } else {
15358
- const sampleValue = colArray.find((v) => v !== null && v !== void 0);
15359
- const pgArrayType = inferPgArrayType(col, {}, sampleValue);
15360
- pgBaseType = stripArraySuffix(pgArrayType);
15361
- }
15362
- params.push(colArray);
15363
- paramIdx++;
15364
- return createTypeCastParamRef(paramIdx, pgBaseType, true);
15661
+ function renumberSqlParams(sql, offset) {
15662
+ if (offset === 0) return sql;
15663
+ return sql.replace(/\$(\d+)/g, (_match, num) => {
15664
+ return `$${Number.parseInt(num, 10) + offset}`;
15365
15665
  });
15366
- const unnestCall = funcCall("unnest", unnestArgs);
15367
- const colnames = [...bv.columns, ...bv.ordinality ? ["ord"] : []].map(
15368
- (c) => ({ String: { sval: c } })
15369
- );
15370
- const rangeFunction = {
15371
- RangeFunction: {
15372
- functions: [{ List: { items: [unnestCall] } }],
15373
- ordinality: bv.ordinality,
15374
- alias: { aliasname: effectiveAlias, colnames }
15375
- }
15376
- };
15377
- return { rangeFunction, params };
15378
15666
  }
15379
- function compileJoinIntents(joins, rootTable, schemaName, deps) {
15380
- if (joins.length === 0) return [];
15667
+ function compileSourceQueryCte(operation, sourceName, sourceQuery, options, deps) {
15381
15668
  const model = deps.model;
15382
- const naming = deps.naming;
15383
- const deriveFk = deps.deriveFk ?? defaultFkDerivation;
15384
- const defaultPk = deps.defaultPk;
15385
- const results = [];
15386
- for (const intent of joins) {
15387
- if (intent.relation !== void 0) {
15388
- if (!model) {
15389
- throw new Error(
15390
- `join('${intent.relation}'): relation-mode join requires a model for FK resolution.`
15391
- );
15392
- }
15393
- const relationsFromRoot = model.getRelationsFrom(rootTable);
15394
- const rel = relationsFromRoot.find((r) => r.name === intent.relation);
15395
- if (!rel) {
15396
- throw new Error(
15397
- `join('${intent.relation}'): relation not found on table '${rootTable}'. Available: ${relationsFromRoot.map((r) => r.name).join(", ")}`
15398
- );
15399
- }
15400
- const isBelongsTo = rel.type === "belongsTo";
15401
- const rawFk = rel.foreignKey ? Array.isArray(rel.foreignKey) ? rel.foreignKey[0] : rel.foreignKey : deriveFk(isBelongsTo ? rootTable : rel.target, defaultPk);
15402
- const sourceColumn = isBelongsTo ? rawFk : defaultPk;
15403
- const targetColumn = isBelongsTo ? defaultPk : rawFk;
15404
- const alias = intent.alias ?? intent.relation;
15405
- results.push({
15406
- type: "join",
15407
- targetTable: rel.target,
15408
- alias,
15409
- sourceColumn,
15410
- targetColumn,
15411
- joinType: intent.type
15412
- });
15413
- } else if (intent.batchValues !== void 0) {
15414
- const bv = intent.batchValues;
15415
- const alias = intent.alias ?? bv.alias;
15416
- const { rangeFunction, params: bvParams } = buildBatchValuesRangeFn(
15417
- bv,
15418
- 1,
15419
- alias
15420
- );
15421
- const bvOnParamState = createCompilerState();
15422
- bvOnParamState.paramIndex = bvParams.length;
15423
- const bvCtx = {
15424
- rootTable,
15425
- aliases: /* @__PURE__ */ new Map(),
15426
- paramState: bvOnParamState,
15427
- naming,
15428
- outerTable: alias,
15429
- ...schemaName !== void 0 && { schemaName },
15430
- ...model !== void 0 && { model },
15431
- compileSubquery: () => {
15432
- throw new Error(
15433
- "Subquery in BatchValues JOIN ON condition is not supported."
15434
- );
15435
- }
15436
- };
15437
- const onNode = compileWhereIntent(intent.on, bvCtx);
15438
- const allBvParams = [
15439
- ...bvParams,
15440
- ...bvOnParamState.parameters
15441
- ];
15442
- results.push({
15443
- type: "join",
15444
- targetTable: alias,
15445
- alias,
15446
- joinType: intent.type,
15447
- joinRarg: rangeFunction,
15448
- joinOnNode: onNode,
15449
- // batchValuesParams are spliced into this.state.parameters BEFORE
15450
- // other params in compiler.ts, so $1/$2/... refs align correctly.
15451
- batchValuesParams: allBvParams
15452
- });
15453
- } else {
15454
- const paramState = createCompilerState();
15455
- const tableAlias = intent.alias ?? intent.table;
15456
- const tableAliasMap = /* @__PURE__ */ new Map();
15457
- tableAliasMap.set(rootTable, rootTable);
15458
- if (tableAlias !== rootTable) {
15459
- tableAliasMap.set(tableAlias, intent.table);
15460
- }
15461
- const ctx = {
15462
- rootTable,
15463
- aliases: tableAliasMap,
15464
- paramState,
15465
- naming,
15466
- // outerTable = tableAlias so FieldRef(scope:'outer') resolves to the
15467
- // joined alias (e.g. 'e2' in self-join ON conditions).
15468
- outerTable: tableAlias,
15469
- ...schemaName !== void 0 && { schemaName },
15470
- ...model !== void 0 && { model },
15471
- compileSubquery: () => {
15472
- throw new Error("Subquery in JOIN ON condition is not supported.");
15473
- }
15474
- };
15475
- const onNode = compileWhereIntent(intent.on, ctx);
15476
- const joinedRangeVar = rangeVar(
15477
- intent.table,
15478
- tableAlias,
15479
- schemaName,
15480
- naming
15481
- );
15482
- results.push({
15483
- type: "join",
15484
- targetTable: intent.table,
15485
- alias: tableAlias,
15486
- joinType: intent.type,
15487
- joinRarg: joinedRangeVar,
15488
- joinOnNode: onNode
15489
- });
15490
- }
15669
+ if (model === void 0) {
15670
+ throw new Error(
15671
+ `${operation} with sourceQuery requires a model to emit the source CTE for '${sourceName}'.`
15672
+ );
15491
15673
  }
15492
- return results;
15674
+ const sourcePlan = planFn(sourceQuery, model, {
15675
+ dialectCapabilities: deps.dialectCapabilities ?? POSTGRESQL_CAPABILITIES
15676
+ });
15677
+ return compileSelect(sourcePlan, options, deps);
15493
15678
  }
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
- }
15504
- }
15679
+ function prependSourceCte(sql, parameters, sourceName, sourceCte, naming) {
15680
+ if (sourceCte === void 0) {
15681
+ return { sql, parameters };
15505
15682
  }
15683
+ const cteParamCount = sourceCte.parameters.length;
15684
+ const sourceCteName = emittedBindName(sourceName, naming);
15685
+ return {
15686
+ sql: `WITH ${quoteIdent2(sourceCteName, "alias")} as (${sourceCte.sql}) ${renumberSqlParams(sql, cteParamCount)}`,
15687
+ parameters: [...sourceCte.parameters, ...parameters]
15688
+ };
15506
15689
  }
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;
15521
- }
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 } }]);
15530
- }
15690
+ function resolveMutationExistsForeignKey(foreignKey, relationName) {
15691
+ if (typeof foreignKey === "string") return foreignKey;
15692
+ if (!Array.isArray(foreignKey)) return void 0;
15693
+ if (foreignKey.length > 1) {
15694
+ throw new Error(
15695
+ `Mutation exists()/notExists() guards do not support composite foreignKey relations yet: relation '${relationName}' has foreignKey ${JSON.stringify(foreignKey)}. Composite FK correlation is tracked by #179.`
15696
+ );
15697
+ }
15698
+ return foreignKey[0];
15699
+ }
15700
+ function resolveExistsRelation(sourceTable, relation, model) {
15701
+ if (!model) return { targetTable: relation };
15702
+ const relationName = `${sourceTable}.${relation}`;
15703
+ const rel = model.getRelation(relationName);
15704
+ if (!rel) return { targetTable: relation };
15705
+ const targetTable = rel.target;
15706
+ if (rel.type === "belongsTo") {
15707
+ const fk2 = resolveMutationExistsForeignKey(rel.foreignKey, relationName);
15708
+ return {
15709
+ targetTable,
15710
+ ...fk2 !== void 0 && { sourceColumn: fk2 },
15711
+ targetColumn: "id"
15712
+ };
15531
15713
  }
15532
- return map;
15714
+ const fk = resolveMutationExistsForeignKey(rel.foreignKey, relationName);
15715
+ return {
15716
+ targetTable,
15717
+ sourceColumn: rel.sourceKey ?? "id",
15718
+ ...fk !== void 0 && { targetColumn: fk }
15719
+ };
15533
15720
  }
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
- }
15721
+ function resolveExistsIntent(where, sourceTable, deps) {
15722
+ const w = where;
15723
+ const kind = w.kind;
15724
+ if (kind === "and" || kind === "or") {
15725
+ const conditions = w.conditions;
15726
+ if (!conditions) return where;
15727
+ const enriched = conditions.map(
15728
+ (c) => resolveExistsIntent(c, sourceTable, deps)
15729
+ );
15730
+ const changed = enriched.some((c, i) => c !== conditions[i]);
15731
+ return changed ? { ...w, conditions: enriched } : where;
15552
15732
  }
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
- }
15570
- }
15733
+ if (kind === "not") {
15734
+ const condition = w.condition;
15735
+ if (!condition) return where;
15736
+ const enriched = resolveExistsIntent(condition, sourceTable, deps);
15737
+ return enriched !== condition ? { ...w, condition: enriched } : where;
15738
+ }
15739
+ if (kind !== "exists" && kind !== "notExists") return where;
15740
+ const relation = w.relation;
15741
+ const resolved = resolveExistsRelation(sourceTable, relation, deps.model);
15742
+ const nestedWhere = w.where;
15743
+ const enrichedNestedWhere = nestedWhere !== void 0 ? resolveExistsIntent(nestedWhere, resolved.targetTable, deps) : void 0;
15744
+ if (resolved.targetTable === relation && !resolved.sourceColumn && !resolved.targetColumn && enrichedNestedWhere === nestedWhere) {
15745
+ return where;
15571
15746
  }
15747
+ return {
15748
+ ...w,
15749
+ targetTable: resolved.targetTable,
15750
+ ...resolved.sourceColumn !== void 0 && {
15751
+ sourceColumn: resolved.sourceColumn
15752
+ },
15753
+ ...resolved.targetColumn !== void 0 && {
15754
+ targetColumn: resolved.targetColumn
15755
+ },
15756
+ ...enrichedNestedWhere !== void 0 && { where: enrichedNestedWhere }
15757
+ };
15572
15758
  }
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;
15759
+ function getColumnTypes(tableName, columns, deps) {
15760
+ if (!deps.model) return void 0;
15761
+ const table = deps.model.getTable(tableName);
15762
+ if (!table) return void 0;
15763
+ let result;
15764
+ for (const col of columns) {
15765
+ const columnIR = table.columns.find((c) => c.name === col);
15766
+ if (columnIR) {
15767
+ result ??= {};
15768
+ result[col] = columnIR.originalDbType ?? columnIR.type;
15578
15769
  }
15579
- const relationName = d.relationName;
15580
- const relationPath = d.relationPath ?? relationName;
15581
- usages.push({ relationName, relationPath });
15582
15770
  }
15583
- const pathCountsByRelation = countDistinctRelationPathsByName(usages);
15584
- for (const d of decisions) {
15585
- if (d.type !== "includeStrategy" || d.choice !== "join" || !d.relationName) {
15586
- continue;
15587
- }
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;
15771
+ return result;
15772
+ }
15773
+ function compileUpsertActionWhere(where, table, state, deps, schemaName) {
15774
+ const whereCtx = {
15775
+ rootTable: table,
15776
+ aliases: /* @__PURE__ */ new Map(),
15777
+ paramState: state,
15778
+ naming: deps.naming,
15779
+ ...schemaName !== void 0 && { schemaName },
15780
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15781
+ ...deps.model !== void 0 && { model: deps.model },
15782
+ ...deps.dialectCapabilities !== void 0 && {
15783
+ dialectCapabilities: deps.dialectCapabilities
15784
+ },
15785
+ compileSubquery: (sqIntent, paramOffset) => buildSubqueryFromIntent(
15786
+ sqIntent,
15787
+ paramOffset,
15788
+ deps.naming,
15789
+ schemaName,
15790
+ "rawExists",
15791
+ deps.bindingNames,
15792
+ deps.dialectCapabilities
15793
+ )
15794
+ };
15795
+ return compileWhereIntent(where, whereCtx);
15796
+ }
15797
+ function compileInsert2(intent, options, deps) {
15798
+ const schemaName = deps.schemaName;
15799
+ const ctx = {
15800
+ naming: deps.naming,
15801
+ rootTable: intent.table,
15802
+ ...schemaName !== void 0 && { schema: schemaName },
15803
+ ...deps.dialectCapabilities !== void 0 && {
15804
+ dialectCapabilities: deps.dialectCapabilities
15805
+ },
15806
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15807
+ maxRecursiveDepth: 100
15808
+ };
15809
+ const state = createCompilerState();
15810
+ const firstRow = intent.values?.[0] ?? {};
15811
+ const columns = Object.keys(firstRow);
15812
+ const rows = intent.values ?? [];
15813
+ const values = rows.map((row) => columns.map((col) => row[col]));
15814
+ const columnTypes = getColumnTypes(intent.table, columns, deps);
15815
+ const config = {
15816
+ table: intent.table,
15817
+ columns,
15818
+ values,
15819
+ ...intent.returning && { returning: [...intent.returning] },
15820
+ ...columnTypes && { columnTypes }
15821
+ };
15822
+ const maxBatchSize = options?.maxBatchSize;
15823
+ if (maxBatchSize !== void 0 && values.length > maxBatchSize) {
15824
+ throw new InvalidOperationError3(
15825
+ "insert",
15826
+ `Batch size ${values.length} exceeds maxBatchSize ${maxBatchSize}`
15827
+ );
15592
15828
  }
15829
+ const batchThreshold = options?.batchThreshold ?? 50;
15830
+ const useUnnest = values.length > 0 && (batchThreshold === 0 || values.length > batchThreshold);
15831
+ const ast = useUnnest ? compileUnnestInsert(config, ctx, state) : compileInsert(config, ctx, state);
15832
+ const sql = deparseQuoted(ast);
15833
+ return {
15834
+ sql,
15835
+ parameters: state.parameters
15836
+ };
15593
15837
  }
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
- }
15838
+ function compileInsertFrom2(intent, options, deps) {
15839
+ const schemaName = deps.schemaName;
15840
+ const sourceCte = intent.sourceQuery !== void 0 && !hasBindingName(deps.bindingNames, intent.source, deps.naming) ? compileSourceQueryCte(
15841
+ "compileInsertFrom",
15842
+ intent.source,
15843
+ intent.sourceQuery,
15844
+ options,
15845
+ deps
15846
+ ) : void 0;
15847
+ const bindingNames = intent.sourceQuery !== void 0 ? withBindingName(deps.bindingNames, intent.source, deps.naming) : deps.bindingNames;
15848
+ const ctx = {
15849
+ naming: deps.naming,
15850
+ rootTable: intent.source,
15851
+ ...schemaName !== void 0 && { schema: schemaName },
15852
+ ...deps.dialectCapabilities !== void 0 && {
15853
+ dialectCapabilities: deps.dialectCapabilities
15854
+ },
15855
+ ...bindingNames !== void 0 && { bindingNames },
15856
+ maxRecursiveDepth: 100
15857
+ };
15858
+ const state = createCompilerState();
15859
+ const resolvedWhere = intent.where ? resolveExistsIntent(intent.where, intent.source, deps) : void 0;
15860
+ const config = {
15861
+ targetTable: intent.table,
15862
+ sourceTable: intent.source,
15863
+ ...intent.columns && { columns: [...intent.columns] },
15864
+ ...resolvedWhere && { where: [whereIntentAsDecision(resolvedWhere)] },
15865
+ ...intent.limit !== void 0 && { limit: intent.limit },
15866
+ ...intent.returning && { returning: [...intent.returning] }
15867
+ };
15868
+ const ast = compileInsertFrom(config, ctx, state);
15869
+ const sql = deparseQuoted(ast);
15870
+ return prependSourceCte(
15871
+ sql,
15872
+ state.parameters,
15873
+ intent.source,
15874
+ sourceCte,
15875
+ deps.naming
15876
+ );
15877
+ }
15878
+ function compileUpdate2(intent, options, deps) {
15879
+ const schemaName = deps.schemaName;
15880
+ const resolvedModel = options?.model ?? deps.model;
15881
+ const ctx = {
15882
+ naming: deps.naming,
15883
+ rootTable: intent.table,
15884
+ ...schemaName !== void 0 && { schema: schemaName },
15885
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15886
+ ...resolvedModel !== void 0 && { model: resolvedModel },
15887
+ ...deps.dialectCapabilities !== void 0 && {
15888
+ dialectCapabilities: deps.dialectCapabilities
15889
+ },
15890
+ maxRecursiveDepth: 100
15891
+ };
15892
+ const state = createCompilerState();
15893
+ const setColumns = Object.keys(intent.set ?? {});
15894
+ const columnTypes = getColumnTypes(intent.table, setColumns, deps);
15895
+ const resolvedWhere = intent.where ? resolveExistsIntent(intent.where, intent.table, deps) : void 0;
15896
+ const config = {
15897
+ table: intent.table,
15898
+ set: Object.entries(intent.set ?? {}).map(([column, value]) => ({
15899
+ column,
15900
+ value
15901
+ })),
15902
+ ...resolvedWhere && { where: [whereIntentAsDecision(resolvedWhere)] },
15903
+ ...intent.returning && { returning: [...intent.returning] },
15904
+ ...columnTypes && { columnTypes }
15905
+ };
15906
+ const ast = compileUpdate(config, ctx, state);
15907
+ const sql = deparseQuoted(ast);
15908
+ return {
15909
+ sql,
15910
+ parameters: state.parameters
15911
+ };
15912
+ }
15913
+ function compileBatchUpdate(intent, _options, deps) {
15914
+ const schemaName = deps.schemaName;
15915
+ const ctx = {
15916
+ naming: deps.naming,
15917
+ rootTable: intent.table,
15918
+ ...schemaName !== void 0 && { schema: schemaName },
15919
+ ...deps.dialectCapabilities !== void 0 && {
15920
+ dialectCapabilities: deps.dialectCapabilities
15921
+ },
15922
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
15923
+ maxRecursiveDepth: 100
15924
+ };
15925
+ const state = createCompilerState();
15926
+ if (intent.updates.length === 0) {
15927
+ throw new InvalidOperationError3(
15928
+ "update",
15929
+ "batchSet requires at least one row"
15930
+ );
15931
+ }
15932
+ const allColumns = Object.keys(intent.updates[0]);
15933
+ const matchColumns = [...intent.matchColumns];
15934
+ for (const mc of matchColumns) {
15935
+ if (!allColumns.includes(mc)) {
15936
+ throw new InvalidOperationError3(
15937
+ "update",
15938
+ `Match column "${mc}" not found in update data. Each row must include the match column(s).`
15939
+ );
15607
15940
  }
15608
15941
  }
15942
+ const values = intent.updates.map((row) => allColumns.map((col) => row[col]));
15943
+ validateBatchCardinality(allColumns, values);
15944
+ const columnArrays = transposeToColumnArrays(allColumns, values);
15945
+ const columnTypes = getColumnTypes(intent.table, allColumns, deps);
15946
+ const scalarSet = intent.scalarSet ? Object.entries(intent.scalarSet).map(([column, value]) => ({
15947
+ column,
15948
+ value
15949
+ })) : void 0;
15950
+ let whereGuard;
15951
+ if (intent.where) {
15952
+ const resolvedWhere = resolveExistsIntent(intent.where, intent.table, deps);
15953
+ const whereCtx = {
15954
+ rootTable: intent.table,
15955
+ aliases: /* @__PURE__ */ new Map(),
15956
+ paramState: state,
15957
+ naming: deps.naming,
15958
+ ...schemaName !== void 0 && { schemaName },
15959
+ ...deps.bindingNames !== void 0 && {
15960
+ bindingNames: deps.bindingNames
15961
+ },
15962
+ ...deps.model !== void 0 && { model: deps.model },
15963
+ ...deps.dialectCapabilities !== void 0 && {
15964
+ dialectCapabilities: deps.dialectCapabilities
15965
+ },
15966
+ compileSubquery: (sqIntent, paramOffset) => buildSubqueryFromIntent(
15967
+ sqIntent,
15968
+ paramOffset,
15969
+ deps.naming,
15970
+ schemaName,
15971
+ "rawExists",
15972
+ deps.bindingNames,
15973
+ deps.dialectCapabilities
15974
+ )
15975
+ };
15976
+ whereGuard = compileWhereIntent(resolvedWhere, whereCtx);
15977
+ }
15978
+ const config = {
15979
+ table: intent.table,
15980
+ matchColumns,
15981
+ allColumns,
15982
+ columnArrays,
15983
+ ...scalarSet && { scalarSet },
15984
+ ...intent.returning && { returning: [...intent.returning] },
15985
+ ...columnTypes && { columnTypes },
15986
+ ...whereGuard !== void 0 && { whereGuard }
15987
+ };
15988
+ const ast = compileUnnestUpdate(config, ctx, state);
15989
+ const sql = deparseQuoted(ast);
15990
+ return {
15991
+ sql,
15992
+ parameters: state.parameters
15993
+ };
15609
15994
  }
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
- })() : {};
15995
+ function compileDelete2(intent, options, deps) {
15996
+ const schemaName = deps.schemaName;
15997
+ const resolvedModel = options?.model ?? deps.model;
15998
+ const ctx = {
15999
+ naming: deps.naming,
16000
+ rootTable: intent.table,
16001
+ ...schemaName !== void 0 && { schema: schemaName },
16002
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
16003
+ ...deps.dialectCapabilities !== void 0 && {
16004
+ dialectCapabilities: deps.dialectCapabilities
16005
+ },
16006
+ maxRecursiveDepth: 100,
16007
+ ...resolvedModel !== void 0 && { model: resolvedModel }
16008
+ };
16009
+ const state = createCompilerState();
16010
+ const resolvedWhere = intent.where ? resolveExistsIntent(intent.where, intent.table, deps) : void 0;
16011
+ const config = {
16012
+ table: intent.table,
16013
+ ...resolvedWhere && { where: [whereIntentAsDecision(resolvedWhere)] },
16014
+ ...intent.returning && { returning: [...intent.returning] }
16015
+ };
16016
+ const ast = compileDelete(config, ctx, state);
16017
+ const sql = deparseQuoted(ast);
15622
16018
  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
16019
+ sql,
16020
+ parameters: state.parameters
15629
16021
  };
15630
16022
  }
15631
- function compileSelect(plan, options, deps) {
16023
+ function compileUpsert2(intent, options, deps) {
15632
16024
  const schemaName = deps.schemaName;
15633
- const resolvedModelForCompiler = options?.model ?? deps.model;
15634
- const compilerOptions = {
16025
+ const ctx = {
15635
16026
  naming: deps.naming,
15636
- ...schemaName && { schema: schemaName },
15637
- defaultPkColumnName: deps.defaultPk,
15638
- deriveFkColumnName: deps.deriveFk,
15639
- ...resolvedModelForCompiler != null && {
15640
- model: resolvedModelForCompiler
15641
- }
16027
+ rootTable: intent.table,
16028
+ ...schemaName !== void 0 && { schema: schemaName },
16029
+ ...deps.bindingNames !== void 0 && { bindingNames: deps.bindingNames },
16030
+ ...deps.model !== void 0 && { model: deps.model },
16031
+ ...deps.dialectCapabilities !== void 0 && {
16032
+ dialectCapabilities: deps.dialectCapabilities
16033
+ },
16034
+ maxRecursiveDepth: 100
15642
16035
  };
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
- );
16036
+ const state = createCompilerState();
16037
+ const firstRow = intent.values?.[0] ?? {};
16038
+ const rawExprs = {};
16039
+ const scalarSet = {};
16040
+ if (intent.action.type === "doUpdate" && intent.action.set) {
16041
+ for (const [key, val] of Object.entries(intent.action.set)) {
16042
+ if (isSqlRaw2(val)) {
16043
+ rawExprs[key] = val.sql;
16044
+ } else {
16045
+ scalarSet[key] = val;
16046
+ }
15655
16047
  }
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
- );
16048
+ }
16049
+ const hasScalarSet = Object.keys(scalarSet).length > 0;
16050
+ const mergedFirstRow = hasScalarSet ? { ...firstRow, ...scalarSet } : firstRow;
16051
+ const columns = Object.keys(mergedFirstRow);
16052
+ const values = (intent.values ?? []).map((row) => {
16053
+ const mergedRow = hasScalarSet ? { ...row, ...scalarSet } : row;
16054
+ return columns.map((col) => mergedRow[col]);
16055
+ });
16056
+ const conflictTarget = {};
16057
+ if ("columns" in intent.onConflict) {
16058
+ conflictTarget.columns = [...intent.onConflict.columns];
16059
+ } else if ("constraint" in intent.onConflict) {
16060
+ conflictTarget.constraint = intent.onConflict.constraint;
16061
+ }
16062
+ const conflictAction = intent.action.type === "doNothing" ? "nothing" : "update";
16063
+ let updateColumns;
16064
+ if (intent.action.type === "doUpdate") {
16065
+ if (intent.action.set) {
16066
+ updateColumns = Object.keys(intent.action.set);
16067
+ } else {
16068
+ const conflictCols = "columns" in intent.onConflict ? intent.onConflict.columns : [];
16069
+ updateColumns = columns.filter((col) => !conflictCols.includes(col));
15696
16070
  }
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
15724
- );
15725
- } else {
15726
- simplifiedPlan = {
15727
- rootTable: plan.rootTable,
15728
- decisions: bridgeLegacyDecisions(plan.decisions),
15729
- ...schemaName ? { schema: schemaName } : {}
15730
- };
15731
16071
  }
15732
- const result = compilePlan(simplifiedPlan, compilerOptions);
15733
- return {
15734
- sql: result.sql,
15735
- parameters: result.parameters
16072
+ const columnTypes = getColumnTypes(intent.table, columns, deps);
16073
+ const hasRawExprs = Object.keys(rawExprs).length > 0;
16074
+ const actionWhere = intent.action.type === "doUpdate" && intent.action.where ? intent.action.where : void 0;
16075
+ const resolvedActionWhere = actionWhere ? resolveExistsIntent(actionWhere, intent.table, deps) : void 0;
16076
+ const config = {
16077
+ table: intent.table,
16078
+ columns,
16079
+ values,
16080
+ conflictTarget,
16081
+ conflictAction,
16082
+ ...updateColumns && { updateColumns },
16083
+ ...intent.returning && { returning: [...intent.returning] },
16084
+ ...columnTypes && { columnTypes },
16085
+ ...hasRawExprs && { updateExpressions: rawExprs },
16086
+ ...resolvedActionWhere && {
16087
+ actionWhereIntent: resolvedActionWhere,
16088
+ compileActionWhere: (where, paramState) => compileUpsertActionWhere(
16089
+ where,
16090
+ intent.table,
16091
+ paramState,
16092
+ deps,
16093
+ schemaName
16094
+ )
16095
+ }
15736
16096
  };
15737
- }
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
16097
+ const maxBatchSize = options?.maxBatchSize;
16098
+ if (maxBatchSize !== void 0 && values.length > maxBatchSize) {
16099
+ throw new InvalidOperationError3(
16100
+ "upsert",
16101
+ `Batch size ${values.length} exceeds maxBatchSize ${maxBatchSize}`
15754
16102
  );
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;
16103
+ }
16104
+ const batchThreshold = options?.batchThreshold ?? 50;
16105
+ const useUnnest = values.length > 0 && (batchThreshold === 0 || values.length > batchThreshold);
16106
+ const ast = useUnnest ? compileUnnestUpsert(config, ctx, state) : compileUpsert(config, ctx, state);
16107
+ const sql = deparseQuoted(ast);
16108
+ return {
16109
+ sql,
16110
+ parameters: state.parameters
16111
+ };
16112
+ }
16113
+ function compileUpsertFrom2(intent, options, deps) {
16114
+ const schemaName = deps.schemaName;
16115
+ const sourceCte = intent.sourceQuery !== void 0 && !hasBindingName(deps.bindingNames, intent.source, deps.naming) ? compileSourceQueryCte(
16116
+ "compileUpsertFrom",
16117
+ intent.source,
16118
+ intent.sourceQuery,
16119
+ options,
16120
+ deps
16121
+ ) : void 0;
16122
+ const bindingNames = intent.sourceQuery !== void 0 ? withBindingName(deps.bindingNames, intent.source, deps.naming) : deps.bindingNames;
16123
+ const ctx = {
16124
+ naming: deps.naming,
16125
+ rootTable: intent.source,
16126
+ ...schemaName !== void 0 && { schema: schemaName },
16127
+ ...deps.dialectCapabilities !== void 0 && {
16128
+ dialectCapabilities: deps.dialectCapabilities
16129
+ },
16130
+ ...bindingNames !== void 0 && { bindingNames },
16131
+ maxRecursiveDepth: 100
16132
+ };
16133
+ const state = createCompilerState();
16134
+ const resolvedWhere = intent.where ? resolveExistsIntent(intent.where, intent.source, deps) : void 0;
16135
+ let columns;
16136
+ if (intent.columns) {
16137
+ columns = [...intent.columns];
16138
+ } else if (options?.model) {
16139
+ const targetTable = options.model.getTable(intent.table);
16140
+ if (targetTable) {
16141
+ columns = targetTable.columns.map((c) => c.name);
15770
16142
  }
15771
- subqueryIncludes.push(entry);
15772
16143
  }
15773
- return { main, subqueryIncludes };
16144
+ const config = {
16145
+ targetTable: intent.table,
16146
+ sourceTable: intent.source,
16147
+ conflictColumns: [...intent.conflictColumns],
16148
+ ...columns && { columns },
16149
+ ...resolvedWhere && { where: [whereIntentAsDecision(resolvedWhere)] },
16150
+ ...intent.limit !== void 0 && { limit: intent.limit },
16151
+ ...intent.returning && { returning: [...intent.returning] }
16152
+ };
16153
+ const ast = compileUpsertFrom(config, ctx, state);
16154
+ const sql = deparseQuoted(ast);
16155
+ return prependSourceCte(
16156
+ sql,
16157
+ state.parameters,
16158
+ intent.source,
16159
+ sourceCte,
16160
+ deps.naming
16161
+ );
15774
16162
  }
15775
16163
 
15776
16164
  // src/adapter-compiler-recursive.ts
@@ -16859,6 +17247,7 @@ function buildRecursiveAnchorWhere(where, tableAlias, deps, state) {
16859
17247
  // src/pgsql-adapter.ts
16860
17248
  init_assert_field();
16861
17249
  init_ast_helpers();
17250
+ init_binding_registry();
16862
17251
 
16863
17252
  // src/ddl/index-operations.ts
16864
17253
  init_validate();
@@ -17025,9 +17414,9 @@ function compileLeafOrBranch(intent, compileFn) {
17025
17414
  const result = compileFn(intent);
17026
17415
  return { sql: result.sql, parameters: result.parameters };
17027
17416
  }
17028
- function createLeafCompileFn(adapter, model, planFn2, options) {
17417
+ function createLeafCompileFn(adapter, model, planFn3, options) {
17029
17418
  return (query) => {
17030
- const planReport = planFn2(query, model, {
17419
+ const planReport = planFn3(query, model, {
17031
17420
  dialectCapabilities: adapter.dialectCapabilities
17032
17421
  });
17033
17422
  return adapter.compile(planReport, { ...options, model });
@@ -17149,14 +17538,257 @@ function mapFetchDirection(direction) {
17149
17538
 
17150
17539
  // src/pgsql-adapter.ts
17151
17540
  init_validate();
17152
- function renumberSqlParams(sql, offset) {
17541
+ var MAX_NQL_RUNTIME_BINDING_VALUES_PARAMETERS = 32e3;
17542
+ function renumberSqlParams2(sql, offset) {
17153
17543
  if (offset === 0) return sql;
17154
17544
  return sql.replace(/\$(\d+)/g, (_match, num) => {
17155
17545
  return `$${Number.parseInt(num, 10) + offset}`;
17156
17546
  });
17157
17547
  }
17158
17548
  function isCompiledNqlQuery(input) {
17159
- return !("intent" in input) && ("query" in input || "cteQuery" in input || "mutation" in input || "setOperation" in input || "bindings" in input || "mutationBindings" in input);
17549
+ return !("intent" in input) && ("query" in input || "cteQuery" in input || "mutation" in input || "setOperation" in input || "bindings" in input || "mutationBindings" in input || "runtimeBindings" in input);
17550
+ }
17551
+ function formatGuardPathSegment(key) {
17552
+ return /^[A-Za-z_$][\w$]*$/.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
17553
+ }
17554
+ function findNqlBindingRefMarker(value, path, seen = /* @__PURE__ */ new WeakSet()) {
17555
+ if (value === null || typeof value !== "object") {
17556
+ return void 0;
17557
+ }
17558
+ if (isNqlBindingRef2(value)) {
17559
+ return {
17560
+ ref: getNqlBindingRefName2(value),
17561
+ path
17562
+ };
17563
+ }
17564
+ if (seen.has(value)) {
17565
+ return void 0;
17566
+ }
17567
+ seen.add(value);
17568
+ if (Array.isArray(value)) {
17569
+ for (let i = 0; i < value.length; i++) {
17570
+ const found = findNqlBindingRefMarker(value[i], `${path}[${i}]`, seen);
17571
+ if (found) return found;
17572
+ }
17573
+ return void 0;
17574
+ }
17575
+ const record = value;
17576
+ for (const key of Object.keys(record)) {
17577
+ const found = findNqlBindingRefMarker(
17578
+ record[key],
17579
+ `${path}${formatGuardPathSegment(key)}`,
17580
+ seen
17581
+ );
17582
+ if (found) return found;
17583
+ }
17584
+ return void 0;
17585
+ }
17586
+ function guardCompiledQuery(query, context) {
17587
+ const found = findNqlBindingRefMarker(query.parameters, "parameters");
17588
+ if (found) {
17589
+ throw new Error(
17590
+ `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.`
17591
+ );
17592
+ }
17593
+ return query;
17594
+ }
17595
+ function guardCompileResultWithIncludes(result, context) {
17596
+ return {
17597
+ ...result,
17598
+ main: guardCompiledQuery(result.main, context)
17599
+ };
17600
+ }
17601
+ function findPhysicalTableNameCollision(model, bindingName, naming) {
17602
+ for (const [modelTableName, table] of model.tables) {
17603
+ if (bindingName === table.name) return table.name;
17604
+ if (bindingName === modelTableName) return table.name;
17605
+ const emittedTableName = naming.toDatabase(table.name);
17606
+ if (bindingName === emittedTableName) return emittedTableName;
17607
+ const emittedModelTableName = naming.toDatabase(modelTableName);
17608
+ if (bindingName === emittedModelTableName) return emittedModelTableName;
17609
+ }
17610
+ return void 0;
17611
+ }
17612
+ function findDuplicateEmittedNqlBindingName(bindingNames, naming) {
17613
+ const seen = /* @__PURE__ */ new Map();
17614
+ for (const bindingName of bindingNames) {
17615
+ const emittedName = emittedBindName(bindingName, naming);
17616
+ const originalName = seen.get(emittedName);
17617
+ if (originalName !== void 0 && originalName !== bindingName) {
17618
+ return { originalName, duplicateName: bindingName, emittedName };
17619
+ }
17620
+ seen.set(emittedName, bindingName);
17621
+ }
17622
+ return void 0;
17623
+ }
17624
+ function orderedNqlBindingNames(bundle) {
17625
+ const names = [];
17626
+ const seen = /* @__PURE__ */ new Set();
17627
+ for (const name of bundle.bindings?.keys() ?? []) {
17628
+ names.push(name);
17629
+ seen.add(name);
17630
+ }
17631
+ for (const name of bundle.runtimeBindings?.keys() ?? []) {
17632
+ if (!seen.has(name)) {
17633
+ names.push(name);
17634
+ seen.add(name);
17635
+ }
17636
+ }
17637
+ return names;
17638
+ }
17639
+ function mapRuntimeBindingColumnType(type) {
17640
+ switch (type) {
17641
+ case "string":
17642
+ return "text";
17643
+ case "text":
17644
+ return "text";
17645
+ case "number":
17646
+ case "integer":
17647
+ return "integer";
17648
+ case "bigint":
17649
+ return "bigint";
17650
+ case "decimal":
17651
+ return "numeric";
17652
+ case "boolean":
17653
+ return "boolean";
17654
+ case "date":
17655
+ return "date";
17656
+ case "time":
17657
+ return "time";
17658
+ case "datetime":
17659
+ case "timestamp":
17660
+ return "timestamptz";
17661
+ case "json":
17662
+ case "jsonb":
17663
+ return "jsonb";
17664
+ case "uuid":
17665
+ return "uuid";
17666
+ case "daterange":
17667
+ return "daterange";
17668
+ case "tsrange":
17669
+ return "tsrange";
17670
+ case "tstzrange":
17671
+ return "tstzrange";
17672
+ case "int4range":
17673
+ return "int4range";
17674
+ case "int8range":
17675
+ return "int8range";
17676
+ case "numrange":
17677
+ return "numrange";
17678
+ default:
17679
+ return void 0;
17680
+ }
17681
+ }
17682
+ function findRuntimeBindingSourceTable(model, sourceTable) {
17683
+ return model.getTable(sourceTable) ?? [...model.tables.values()].find((table) => table.name === sourceTable);
17684
+ }
17685
+ function resolveRuntimeBindingColumnType(bindingName, sourceTable, columnName) {
17686
+ const column = sourceTable.columns.find(
17687
+ (candidate) => candidate.name === columnName
17688
+ );
17689
+ if (column === void 0) {
17690
+ throw new Error(
17691
+ `NQL runtime binding '${bindingName}' cannot resolve projected column '${columnName}' on source mutation table '${sourceTable.name}'.`
17692
+ );
17693
+ }
17694
+ const dbType = column.originalDbType?.trim() || mapRuntimeBindingColumnType(column.type);
17695
+ if (dbType === void 0 || dbType.trim() === "") {
17696
+ throw new Error(
17697
+ `NQL runtime binding '${bindingName}' cannot resolve a PostgreSQL type for projected column '${columnName}' on source mutation table '${sourceTable.name}'.`
17698
+ );
17699
+ }
17700
+ const typeName = dbType.trim();
17701
+ try {
17702
+ validateTypeName4(typeName);
17703
+ } catch (error) {
17704
+ const reason = error instanceof Error ? error.message : String(error);
17705
+ throw new Error(
17706
+ `NQL runtime binding '${bindingName}' cannot use PostgreSQL cast type for projected column '${columnName}': ${reason}`
17707
+ );
17708
+ }
17709
+ return typeName;
17710
+ }
17711
+ function resolveRuntimeBindingColumnTypes(name, binding, model, sourceTableName) {
17712
+ if (model === void 0) {
17713
+ throw new Error(
17714
+ `NQL runtime binding '${name}' cannot materialize non-empty rows because no model is available for source-table column type resolution.`
17715
+ );
17716
+ }
17717
+ const sourceTable = findRuntimeBindingSourceTable(model, sourceTableName);
17718
+ if (sourceTable === void 0) {
17719
+ throw new Error(
17720
+ `NQL runtime binding '${name}' cannot resolve source mutation table '${sourceTableName}' in the model.`
17721
+ );
17722
+ }
17723
+ return binding.columns.map(
17724
+ (column) => resolveRuntimeBindingColumnType(name, sourceTable, column)
17725
+ );
17726
+ }
17727
+ function assertRuntimeBindingValuesParameterCount(name, binding, parameterOffset) {
17728
+ const valueParameterCount = binding.rows.length * binding.columns.length;
17729
+ if (valueParameterCount <= MAX_NQL_RUNTIME_BINDING_VALUES_PARAMETERS && parameterOffset + valueParameterCount <= MAX_NQL_RUNTIME_BINDING_VALUES_PARAMETERS) {
17730
+ return;
17731
+ }
17732
+ const totalParameterCount = parameterOffset + valueParameterCount;
17733
+ throw new Error(
17734
+ `NQL runtime binding '${name}' would materialize ${valueParameterCount} VALUES parameters; limit is ${MAX_NQL_RUNTIME_BINDING_VALUES_PARAMETERS}. Current parameter offset is ${parameterOffset}, which would make ${totalParameterCount} total parameters before compiling the final statement.`
17735
+ );
17736
+ }
17737
+ function compileNqlRuntimeBindingCte(name, binding, naming, parameterOffset, sourceTable, schemaName, model) {
17738
+ if (binding.columns.length === 0) {
17739
+ throw new Error(
17740
+ `NQL runtime binding '${name}' cannot be materialized without projected columns.`
17741
+ );
17742
+ }
17743
+ const cteName = quoteIdent2(emittedBindName(name, naming), "alias");
17744
+ const columnSql = binding.columns.map((column) => quoteIdent2(naming.toDatabase(column), "column")).join(", ");
17745
+ if (sourceTable === void 0) {
17746
+ throw new Error(
17747
+ `NQL runtime binding '${name}' cannot materialize a typed relation because its source mutation table is unavailable.`
17748
+ );
17749
+ }
17750
+ const projectedColumns = binding.columns.map((column) => quoteIdent2(naming.toDatabase(column), "column")).join(", ");
17751
+ const sourceAnchorSql = `SELECT ${projectedColumns} FROM ${qualifyTableIdent(sourceTable, schemaName, naming)} WHERE false`;
17752
+ if (binding.rows.length === 0) {
17753
+ return {
17754
+ cte: `${cteName} (${columnSql}) as (${sourceAnchorSql})`,
17755
+ parameters: []
17756
+ };
17757
+ }
17758
+ assertRuntimeBindingValuesParameterCount(name, binding, parameterOffset);
17759
+ const columnTypes = resolveRuntimeBindingColumnTypes(
17760
+ name,
17761
+ binding,
17762
+ model,
17763
+ sourceTable
17764
+ );
17765
+ const parameters = [];
17766
+ let nextParam = parameterOffset + 1;
17767
+ const valuesSql = binding.rows.map((row) => {
17768
+ const placeholders = binding.columns.map((column, columnIndex) => {
17769
+ parameters.push(row[column]);
17770
+ return `$${nextParam++}::${columnTypes[columnIndex]}`;
17771
+ });
17772
+ return `(${placeholders.join(", ")})`;
17773
+ }).join(", ");
17774
+ return {
17775
+ cte: `${cteName} (${columnSql}) as (${sourceAnchorSql} UNION ALL VALUES ${valuesSql})`,
17776
+ parameters
17777
+ };
17778
+ }
17779
+ function createNqlBindingSelectPlan(query) {
17780
+ return {
17781
+ rootTable: query.from,
17782
+ decisions: [],
17783
+ warnings: [],
17784
+ ctes: [],
17785
+ intent: query,
17786
+ metadata: {
17787
+ planningTimeMs: 0,
17788
+ relationsAnalyzed: 0,
17789
+ isAmbiguous: false
17790
+ }
17791
+ };
17160
17792
  }
17161
17793
  var PgsqlAdapter = class _PgsqlAdapter {
17162
17794
  pool;
@@ -17224,17 +17856,20 @@ var PgsqlAdapter = class _PgsqlAdapter {
17224
17856
  ...overrides
17225
17857
  };
17226
17858
  }
17227
- buildCompileDeps(options) {
17859
+ buildCompileDeps(options, bindingNames) {
17228
17860
  if (options?.schemaName) {
17229
17861
  validateIdentifier(options.schemaName, "schema");
17230
17862
  }
17863
+ const naming = options?.naming ?? this.naming;
17231
17864
  return {
17232
- naming: this.naming,
17865
+ naming,
17233
17866
  // `||` (not `??`): empty string is treated as "no override" and falls back to this.schemaName (which may be a configured schema or undefined)
17234
17867
  schemaName: options?.schemaName || this.schemaName,
17235
17868
  model: options?.model ?? this.model,
17869
+ dialectCapabilities: options?.dialectCapabilities ?? this.dialectCapabilities,
17236
17870
  defaultPk: this.defaultPk,
17237
- deriveFk: this.deriveFk
17871
+ deriveFk: this.deriveFk,
17872
+ ...bindingNames !== void 0 && { bindingNames }
17238
17873
  };
17239
17874
  }
17240
17875
  requireNqlCompileModel(options) {
@@ -17246,7 +17881,24 @@ var PgsqlAdapter = class _PgsqlAdapter {
17246
17881
  }
17247
17882
  return model;
17248
17883
  }
17249
- compileNqlMutation(bundle, options) {
17884
+ assertNqlBindingNamesDisjointFromTables(bindingNames, options) {
17885
+ if (bindingNames === void 0 || bindingNames.size === 0) return;
17886
+ const model = this.requireNqlCompileModel(options);
17887
+ const naming = this.buildCompileDeps(options, bindingNames).naming;
17888
+ for (const bindingName of bindingNames) {
17889
+ const physicalTableName = findPhysicalTableNameCollision(
17890
+ model,
17891
+ bindingName,
17892
+ naming
17893
+ );
17894
+ if (physicalTableName !== void 0) {
17895
+ throw new Error(
17896
+ `NQL binding '${bindingName}' collides with physical table name '${physicalTableName}'. NQL binding names must be disjoint from model table names.`
17897
+ );
17898
+ }
17899
+ }
17900
+ }
17901
+ compileNqlMutation(bundle, options, bindingNames) {
17250
17902
  const mutation = bundle.mutation;
17251
17903
  if (mutation === void 0) {
17252
17904
  throw new Error("NQL bundle did not contain a mutation intent.");
@@ -17256,84 +17908,140 @@ var PgsqlAdapter = class _PgsqlAdapter {
17256
17908
  return compileInsert2(
17257
17909
  mutation,
17258
17910
  options,
17259
- this.buildCompileDeps(options)
17911
+ this.buildCompileDeps(options, bindingNames)
17260
17912
  );
17261
17913
  case "insert_from":
17262
17914
  return compileInsertFrom2(
17263
17915
  mutation,
17264
17916
  options,
17265
- this.buildCompileDeps(options)
17917
+ this.buildCompileDeps(options, bindingNames)
17266
17918
  );
17267
17919
  case "update":
17268
17920
  return compileUpdate2(
17269
17921
  mutation,
17270
17922
  options,
17271
- this.buildCompileDeps(options)
17923
+ this.buildCompileDeps(options, bindingNames)
17272
17924
  );
17273
17925
  case "delete":
17274
17926
  return compileDelete2(
17275
17927
  mutation,
17276
17928
  options,
17277
- this.buildCompileDeps(options)
17929
+ this.buildCompileDeps(options, bindingNames)
17278
17930
  );
17279
17931
  case "upsert":
17280
17932
  return compileUpsert2(
17281
17933
  mutation,
17282
17934
  options,
17283
- this.buildCompileDeps(options)
17935
+ this.buildCompileDeps(options, bindingNames)
17284
17936
  );
17285
17937
  case "upsert_from":
17286
17938
  return compileUpsertFrom2(
17287
17939
  mutation,
17288
17940
  options,
17289
- this.buildCompileDeps(options)
17941
+ this.buildCompileDeps(options, bindingNames)
17290
17942
  );
17291
17943
  }
17292
17944
  throw new Error(
17293
17945
  `Unsupported NQL mutation type: ${mutation.type}`
17294
17946
  );
17295
17947
  }
17296
- compileNqlBundleLeaf(bundle, options) {
17948
+ compileNqlBundleLeaf(bundle, options, bindingNames) {
17297
17949
  if (bundle.query !== void 0) {
17298
- const model = this.requireNqlCompileModel(options);
17299
- const planReport = planFn(bundle.query, model, {
17300
- dialectCapabilities: this.dialectCapabilities
17950
+ const deps = this.buildCompileDeps(options, bindingNames);
17951
+ const queryFromBinding = hasBindingName(
17952
+ bindingNames,
17953
+ bundle.query.from,
17954
+ deps.naming
17955
+ );
17956
+ const planReport = queryFromBinding ? createNqlBindingSelectPlan(bundle.query) : planFn2(bundle.query, this.requireNqlCompileModel(options), {
17957
+ dialectCapabilities: options?.dialectCapabilities ?? this.dialectCapabilities
17301
17958
  });
17302
- return compileSelect(
17303
- planReport,
17304
- options,
17305
- this.buildCompileDeps(options)
17959
+ return guardCompiledQuery(
17960
+ compileSelect(planReport, options, deps),
17961
+ "NQL query"
17306
17962
  );
17307
17963
  }
17308
17964
  if (bundle.cteQuery !== void 0) {
17309
- return compileCteQuery(
17310
- bundle.cteQuery,
17311
- options,
17312
- this.buildCompileDeps(options)
17965
+ return guardCompiledQuery(
17966
+ compileCteQuery(
17967
+ bundle.cteQuery,
17968
+ options,
17969
+ this.buildCompileDeps(options, bindingNames)
17970
+ ),
17971
+ "NQL CTE query"
17313
17972
  );
17314
17973
  }
17315
17974
  if (bundle.setOperation !== void 0) {
17316
17975
  const model = this.requireNqlCompileModel(options);
17317
- return this.compileSetOperation(
17318
- bundle.setOperation,
17319
- model,
17320
- options
17976
+ return guardCompiledQuery(
17977
+ this.compileSetOperationWithBindings(
17978
+ bundle.setOperation,
17979
+ model,
17980
+ options,
17981
+ bindingNames
17982
+ ),
17983
+ "NQL set operation"
17321
17984
  );
17322
17985
  }
17323
17986
  if (bundle.mutation !== void 0) {
17324
- return this.compileNqlMutation(bundle, options);
17987
+ return guardCompiledQuery(
17988
+ this.compileNqlMutation(
17989
+ bundle,
17990
+ options,
17991
+ bindingNames
17992
+ ),
17993
+ "NQL mutation"
17994
+ );
17325
17995
  }
17326
17996
  throw new Error("NQL bundle did not contain a compilable intent.");
17327
17997
  }
17328
17998
  compileNqlBundle(bundle, options) {
17329
17999
  const ctes = [];
17330
18000
  const parameters = [];
17331
- for (const [name, queryIntent] of bundle.bindings ?? []) {
17332
- const cteName = quoteIdent2(name, "alias");
18001
+ const deps = this.buildCompileDeps(options);
18002
+ const { naming } = deps;
18003
+ const bindingNamesInOrder = orderedNqlBindingNames(bundle);
18004
+ const duplicateEmittedBinding = bindingNamesInOrder.length > 0 ? findDuplicateEmittedNqlBindingName(bindingNamesInOrder, naming) : void 0;
18005
+ if (duplicateEmittedBinding !== void 0) {
18006
+ throw new Error(
18007
+ `NQL bindings '${duplicateEmittedBinding.originalName}' and '${duplicateEmittedBinding.duplicateName}' emit to duplicate CTE name '${duplicateEmittedBinding.emittedName}'. NQL binding names must be unique after database naming.`
18008
+ );
18009
+ }
18010
+ const bindingNames = bindingNamesInOrder.length > 0 ? new Set(
18011
+ bindingNamesInOrder.map((name) => emittedBindName(name, naming))
18012
+ ) : void 0;
18013
+ this.assertNqlBindingNamesDisjointFromTables(bindingNames, options);
18014
+ for (const name of bindingNamesInOrder) {
18015
+ const runtimeBinding = bundle.runtimeBindings?.get(name);
18016
+ if (runtimeBinding !== void 0) {
18017
+ const compiledRuntimeBinding = compileNqlRuntimeBindingCte(
18018
+ name,
18019
+ runtimeBinding,
18020
+ naming,
18021
+ parameters.length,
18022
+ bundle.mutationBindings?.get(name)?.table,
18023
+ deps.schemaName,
18024
+ deps.model
18025
+ );
18026
+ ctes.push(compiledRuntimeBinding.cte);
18027
+ parameters.push(...compiledRuntimeBinding.parameters);
18028
+ continue;
18029
+ }
18030
+ const queryIntent = bundle.bindings?.get(name);
18031
+ if (queryIntent === void 0) {
18032
+ throw new Error(
18033
+ `NQL binding '${name}' has no query intent or runtime rows to materialize.`
18034
+ );
18035
+ }
18036
+ const cteName = quoteIdent2(emittedBindName(name, naming), "alias");
17333
18037
  const bindingBundle = bundle.mutationBindings?.has(name) ? { mutation: bundle.mutationBindings.get(name) } : { query: queryIntent };
17334
- const compiled2 = this.compileNqlBundleLeaf(bindingBundle, options);
18038
+ const compiled2 = this.compileNqlBundleLeaf(
18039
+ bindingBundle,
18040
+ options,
18041
+ bindingNames
18042
+ );
17335
18043
  ctes.push(
17336
- `${cteName} as (${renumberSqlParams(compiled2.sql, parameters.length)})`
18044
+ `${cteName} as (${renumberSqlParams2(compiled2.sql, parameters.length)})`
17337
18045
  );
17338
18046
  parameters.push(...compiled2.parameters);
17339
18047
  }
@@ -17346,14 +18054,21 @@ var PgsqlAdapter = class _PgsqlAdapter {
17346
18054
  setOperation: bundle.setOperation
17347
18055
  }
17348
18056
  };
17349
- const compiled = this.compileNqlBundleLeaf(leafBundle, options);
18057
+ const compiled = this.compileNqlBundleLeaf(
18058
+ leafBundle,
18059
+ options,
18060
+ bindingNames
18061
+ );
17350
18062
  if (ctes.length === 0) {
17351
- return compiled;
18063
+ return guardCompiledQuery(compiled, "NQL bundle");
17352
18064
  }
17353
- return {
17354
- sql: `WITH ${ctes.join(", ")} ${renumberSqlParams(compiled.sql, parameters.length)}`,
17355
- parameters: [...parameters, ...compiled.parameters]
17356
- };
18065
+ return guardCompiledQuery(
18066
+ {
18067
+ sql: `WITH ${ctes.join(", ")} ${renumberSqlParams2(compiled.sql, parameters.length)}`,
18068
+ parameters: [...parameters, ...compiled.parameters]
18069
+ },
18070
+ "NQL bundle"
18071
+ );
17357
18072
  }
17358
18073
  /**
17359
18074
  * Returns the pool/client executor, or throws if in compile-only mode.
@@ -17373,7 +18088,7 @@ var PgsqlAdapter = class _PgsqlAdapter {
17373
18088
  }
17374
18089
  /** PostgreSQL dialect capabilities for planner strategy selection */
17375
18090
  get dialectCapabilities() {
17376
- return POSTGRESQL_CAPABILITIES;
18091
+ return POSTGRESQL_CAPABILITIES2;
17377
18092
  }
17378
18093
  /**
17379
18094
  * DB column casing convention used by this adapter.
@@ -17392,21 +18107,28 @@ var PgsqlAdapter = class _PgsqlAdapter {
17392
18107
  // =========================================================================
17393
18108
  /**
17394
18109
  * Compile a plan to executable SQL.
18110
+ *
18111
+ * When passed a direct `CompiledNqlQuery`, the adapter trusts that the bundle
18112
+ * has already been semantically validated by the NQL compiler. This method
18113
+ * still performs adapter-owned SQL safety checks for emitted binding names
18114
+ * before CTE emission.
17395
18115
  */
17396
18116
  compile(plan, options) {
17397
18117
  if (isCompiledNqlQuery(plan)) {
17398
18118
  return this.compileNqlBundle(plan, options);
17399
18119
  }
17400
- return compileSelect(plan, options, this.buildCompileDeps(options));
18120
+ return guardCompiledQuery(
18121
+ compileSelect(plan, options, this.buildCompileDeps(options)),
18122
+ "select plan"
18123
+ );
17401
18124
  }
17402
18125
  /**
17403
18126
  * Compile a plan with includes, returning subquery include metadata (DX-033).
17404
18127
  */
17405
18128
  compileWithIncludes(plan, options) {
17406
- return compileWithIncludes(
17407
- plan,
17408
- options,
17409
- this.buildCompileDeps(options)
18129
+ return guardCompileResultWithIncludes(
18130
+ compileWithIncludes(plan, options, this.buildCompileDeps(options)),
18131
+ "select plan with includes"
17410
18132
  );
17411
18133
  }
17412
18134
  /**
@@ -17419,11 +18141,14 @@ var PgsqlAdapter = class _PgsqlAdapter {
17419
18141
  * @returns Compiled query for fetching related records
17420
18142
  */
17421
18143
  compileSubqueryInclude(info, parentIds, options) {
17422
- return compileSubqueryInclude(
17423
- info,
17424
- parentIds,
17425
- options,
17426
- this.buildCompileDeps(options)
18144
+ return guardCompiledQuery(
18145
+ compileSubqueryInclude(
18146
+ info,
18147
+ parentIds,
18148
+ options,
18149
+ this.buildCompileDeps(options)
18150
+ ),
18151
+ "subquery include"
17427
18152
  );
17428
18153
  }
17429
18154
  /**
@@ -17438,10 +18163,12 @@ var PgsqlAdapter = class _PgsqlAdapter {
17438
18163
  compileSelectExpression(expr) {
17439
18164
  const naming = this.naming;
17440
18165
  const schemaName = this.schemaName;
18166
+ const dialectCapabilities = this.dialectCapabilities;
17441
18167
  const state = createCompilerState();
17442
18168
  const ctx = {
17443
18169
  naming,
17444
18170
  ...schemaName !== void 0 && { schema: schemaName },
18171
+ dialectCapabilities,
17445
18172
  rootTable: "",
17446
18173
  maxRecursiveDepth: 100,
17447
18174
  // Wire compileSubquery so that SubqueryExpressionIntent nested inside
@@ -17451,7 +18178,8 @@ var PgsqlAdapter = class _PgsqlAdapter {
17451
18178
  compileSubquery(query, paramOffset) {
17452
18179
  const innerCompiler = new PlanCompiler({
17453
18180
  naming,
17454
- ...schemaName !== void 0 && { schema: schemaName }
18181
+ ...schemaName !== void 0 && { schema: schemaName },
18182
+ dialectCapabilities
17455
18183
  });
17456
18184
  const innerPlan = {
17457
18185
  rootTable: query.from,
@@ -17467,7 +18195,10 @@ var PgsqlAdapter = class _PgsqlAdapter {
17467
18195
  targetList: [{ ResTarget: { val: node } }]
17468
18196
  });
17469
18197
  const sql = deparseQuoted(ast);
17470
- return { sql, parameters: state.parameters };
18198
+ return guardCompiledQuery(
18199
+ { sql, parameters: state.parameters },
18200
+ "select expression"
18201
+ );
17471
18202
  }
17472
18203
  /**
17473
18204
  * Compile an insert intent to executable SQL.
@@ -17477,24 +18208,29 @@ var PgsqlAdapter = class _PgsqlAdapter {
17477
18208
  * - rows > batchThreshold OR batchThreshold === 0: SELECT unnest($1::type[]),...
17478
18209
  */
17479
18210
  compileInsert(intent, options) {
17480
- return compileInsert2(intent, options, this.buildCompileDeps(options));
18211
+ return guardCompiledQuery(
18212
+ compileInsert2(intent, options, this.buildCompileDeps(options)),
18213
+ "insert"
18214
+ );
17481
18215
  }
17482
18216
  /**
17483
18217
  * Compile an insert-from intent to executable SQL (NQL-ALIGN).
17484
18218
  * INSERT INTO target (cols) SELECT cols FROM source WHERE ... LIMIT ... RETURNING ...
17485
18219
  */
17486
18220
  compileInsertFrom(intent, options) {
17487
- return compileInsertFrom2(
17488
- intent,
17489
- options,
17490
- this.buildCompileDeps(options)
18221
+ return guardCompiledQuery(
18222
+ compileInsertFrom2(intent, options, this.buildCompileDeps(options)),
18223
+ "insert from"
17491
18224
  );
17492
18225
  }
17493
18226
  /**
17494
18227
  * Compile an update intent to executable SQL.
17495
18228
  */
17496
18229
  compileUpdate(intent, options) {
17497
- return compileUpdate2(intent, options, this.buildCompileDeps(options));
18230
+ return guardCompiledQuery(
18231
+ compileUpdate2(intent, options, this.buildCompileDeps(options)),
18232
+ "update"
18233
+ );
17498
18234
  }
17499
18235
  /**
17500
18236
  * Compile a batch update intent to executable SQL using unnest FROM strategy (BATCH-001).
@@ -17506,33 +18242,37 @@ var PgsqlAdapter = class _PgsqlAdapter {
17506
18242
  * [RETURNING ...]
17507
18243
  */
17508
18244
  compileBatchUpdate(intent, options) {
17509
- return compileBatchUpdate(
17510
- intent,
17511
- options,
17512
- this.buildCompileDeps(options)
18245
+ return guardCompiledQuery(
18246
+ compileBatchUpdate(intent, options, this.buildCompileDeps(options)),
18247
+ "batch update"
17513
18248
  );
17514
18249
  }
17515
18250
  /**
17516
18251
  * Compile a delete intent to executable SQL.
17517
18252
  */
17518
18253
  compileDelete(intent, options) {
17519
- return compileDelete2(intent, options, this.buildCompileDeps(options));
18254
+ return guardCompiledQuery(
18255
+ compileDelete2(intent, options, this.buildCompileDeps(options)),
18256
+ "delete"
18257
+ );
17520
18258
  }
17521
18259
  /**
17522
18260
  * Compile an upsert intent to executable SQL (DX-026).
17523
18261
  */
17524
18262
  compileUpsert(intent, options) {
17525
- return compileUpsert2(intent, options, this.buildCompileDeps(options));
18263
+ return guardCompiledQuery(
18264
+ compileUpsert2(intent, options, this.buildCompileDeps(options)),
18265
+ "upsert"
18266
+ );
17526
18267
  }
17527
18268
  /**
17528
18269
  * Compile an upsert-from intent to executable SQL (NQL-BIND).
17529
18270
  * INSERT INTO target SELECT ... FROM source ON CONFLICT (cols) DO UPDATE SET ...
17530
18271
  */
17531
18272
  compileUpsertFrom(intent, options) {
17532
- return compileUpsertFrom2(
17533
- intent,
17534
- options,
17535
- this.buildCompileDeps(options)
18273
+ return guardCompiledQuery(
18274
+ compileUpsertFrom2(intent, options, this.buildCompileDeps(options)),
18275
+ "upsert from"
17536
18276
  );
17537
18277
  }
17538
18278
  /**
@@ -17540,11 +18280,14 @@ var PgsqlAdapter = class _PgsqlAdapter {
17540
18280
  * Supports adjacency-list and edge-table traversal modes.
17541
18281
  */
17542
18282
  compileRecursive(report, model, options) {
17543
- return compileRecursive(
17544
- report,
17545
- model,
17546
- options,
17547
- this.buildCompileDeps(options)
18283
+ return guardCompiledQuery(
18284
+ compileRecursive(
18285
+ report,
18286
+ model,
18287
+ options,
18288
+ this.buildCompileDeps(options)
18289
+ ),
18290
+ "recursive query"
17548
18291
  );
17549
18292
  }
17550
18293
  /**
@@ -17555,18 +18298,45 @@ var PgsqlAdapter = class _PgsqlAdapter {
17555
18298
  * to start after CTE params and prepend WITH clause.
17556
18299
  */
17557
18300
  compileCteQuery(intent, options) {
17558
- return compileCteQuery(intent, options, this.buildCompileDeps(options));
18301
+ return guardCompiledQuery(
18302
+ compileCteQuery(intent, options, this.buildCompileDeps(options)),
18303
+ "CTE query"
18304
+ );
17559
18305
  }
17560
18306
  /**
17561
18307
  * Compile a set operation (UNION / INTERSECT / EXCEPT) to SQL.
17562
18308
  */
17563
18309
  compileSetOperation(intent, model, options) {
17564
- const compileFn = createLeafCompileFn(this, model, planFn, options);
17565
- const result = compileSetOperation(intent, compileFn);
17566
- return {
17567
- sql: result.sql,
17568
- parameters: result.parameters
18310
+ return guardCompiledQuery(
18311
+ this.compileSetOperationWithBindings(intent, model, options),
18312
+ "set operation"
18313
+ );
18314
+ }
18315
+ compileSetOperationWithBindings(intent, model, options, bindingNames) {
18316
+ const compileFn = (query) => {
18317
+ const leafOptions = {
18318
+ ...options,
18319
+ model
18320
+ };
18321
+ const deps = this.buildCompileDeps(leafOptions, bindingNames);
18322
+ const queryFromBinding = hasBindingName(
18323
+ bindingNames,
18324
+ query.from,
18325
+ deps.naming
18326
+ );
18327
+ const planReport = queryFromBinding ? createNqlBindingSelectPlan(query) : planFn2(query, model, {
18328
+ dialectCapabilities: options?.dialectCapabilities ?? this.dialectCapabilities
18329
+ });
18330
+ return compileSelect(planReport, leafOptions, deps);
17569
18331
  };
18332
+ const result = compileSetOperation(intent, compileFn);
18333
+ return guardCompiledQuery(
18334
+ {
18335
+ sql: result.sql,
18336
+ parameters: result.parameters
18337
+ },
18338
+ "set operation"
18339
+ );
17570
18340
  }
17571
18341
  /**
17572
18342
  * Create a dump for observability.