@dbsp/core 1.2.0 → 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
@@ -5524,7 +5524,7 @@ async function runMutationWithHooksInner(opts, store) {
5524
5524
  opts.onHookError
5525
5525
  );
5526
5526
  if (prepared.returnAfterMutationResult) {
5527
- return transformed;
5527
+ return prepared.mapAfterMutationResult?.(result, transformed) ?? transformed;
5528
5528
  }
5529
5529
  }
5530
5530
  return result;
@@ -6163,20 +6163,77 @@ import {
6163
6163
  NqlLexer,
6164
6164
  compile as nqlCompile
6165
6165
  } from "@dbsp/nql";
6166
- import { NQL_INTERNAL_COMPILER_OPTIONS } from "@dbsp/types/internal";
6166
+ import {
6167
+ getTrustedNqlRelationFilterFields,
6168
+ NQL_INTERNAL_COMPILER_OPTIONS
6169
+ } from "@dbsp/types/internal";
6167
6170
  var NQL_RAW_FRAGMENT = /* @__PURE__ */ Symbol("NqlRawFragment");
6168
6171
  function hasNqlBindings(bundle) {
6169
6172
  return (bundle.bindings?.size ?? 0) > 0;
6170
6173
  }
6171
- function assertNoMutationBindingBodies(bundle) {
6172
- const mutationBindings = bundle.mutationBindings;
6173
- if (!mutationBindings || mutationBindings.size === 0) {
6174
- return;
6174
+ function isBindingFinalQuery(bundle) {
6175
+ return bundle.query !== void 0 && (bundle.bindings?.has(bundle.query.from) ?? false);
6176
+ }
6177
+ function formatBindingRelationPath(path) {
6178
+ return typeof path === "string" ? path : path.join(".");
6179
+ }
6180
+ function findBindingFinalRelationFilter(where) {
6181
+ switch (where.kind) {
6182
+ case "and":
6183
+ case "or":
6184
+ for (const condition of where.conditions) {
6185
+ const found = findBindingFinalRelationFilter(condition);
6186
+ if (found !== void 0) return found;
6187
+ }
6188
+ return void 0;
6189
+ case "not":
6190
+ return findBindingFinalRelationFilter(where.condition);
6191
+ case "relationFilter":
6192
+ if (getTrustedNqlRelationFilterFields(where) !== void 0) {
6193
+ return void 0;
6194
+ }
6195
+ return formatBindingRelationPath(where.relation);
6196
+ case "exists":
6197
+ case "notExists":
6198
+ return where.relation;
6199
+ case "rawExists":
6200
+ case "rawNotExists":
6201
+ return "raw EXISTS";
6202
+ default:
6203
+ return void 0;
6175
6204
  }
6176
- const bindNames = [...mutationBindings.keys()].map((name) => `'${name}'`).join(", ");
6177
- throw new Error(
6178
- `NQL tag programs cannot use mutation bodies in | bind clauses (${bindNames}); writable binding CTEs are disabled for tag execution (#173). Use a read-only bind before a single final mutation, or execute the mutation explicitly.`
6179
- );
6205
+ }
6206
+ function assertBindingFinalQueryCanUseSyntheticPlan(intent) {
6207
+ const relationColumns = intent.select?.type === "expressions" ? intent.select.columns.flatMap((column) => {
6208
+ if (column.kind !== "relationColumn") return [];
6209
+ const trusted = getTrustedNqlRelationFilterFields(column);
6210
+ if (trusted?.selectedColumn !== void 0 && trusted.cardinality === "one") {
6211
+ return [];
6212
+ }
6213
+ return [column.relation];
6214
+ }) : [];
6215
+ const relationFilter = intent.where ? findBindingFinalRelationFilter(intent.where) : void 0;
6216
+ const havingRelationFilter = intent.having ? findBindingFinalRelationFilter(intent.having) : void 0;
6217
+ if ((intent.include?.length ?? 0) > 0 || relationColumns.length > 0 || (intent.joins?.length ?? 0) > 0 || relationFilter !== void 0 || havingRelationFilter !== void 0) {
6218
+ throw new Error(
6219
+ `NQL binding-final query '${intent.from}' cannot select relation columns, use includes, joins, or relation filters. Relation planning requires a physical model table, not a CTE binding.`
6220
+ );
6221
+ }
6222
+ }
6223
+ function createBindingFinalPlan(intent) {
6224
+ assertBindingFinalQueryCanUseSyntheticPlan(intent);
6225
+ return {
6226
+ rootTable: intent.from,
6227
+ decisions: [],
6228
+ warnings: [],
6229
+ ctes: [],
6230
+ intent,
6231
+ metadata: {
6232
+ planningTimeMs: 0,
6233
+ relationsAnalyzed: 0,
6234
+ isAmbiguous: false
6235
+ }
6236
+ };
6180
6237
  }
6181
6238
  function nqlRaw(fragment) {
6182
6239
  if (typeof fragment !== "string") {
@@ -6289,7 +6346,250 @@ function assembleNqlTemplate(strings, values) {
6289
6346
  sourceError: findInternalParamSourceError(query, generatedRanges)
6290
6347
  };
6291
6348
  }
6292
- function createNqlTag(schemaDefinition, model, adapter, schemaName, hookStore, onHookError, inTransaction) {
6349
+ function hasMutationBindingBodies(bundle) {
6350
+ return (bundle.mutationBindings?.size ?? 0) > 0;
6351
+ }
6352
+ function requireMutationBindingColumns(bundle, bindName) {
6353
+ const columns = bundle.bindingOutputSchemas?.get(bindName)?.columns;
6354
+ if (columns === void 0 || columns.length === 0) {
6355
+ throw new Error(
6356
+ `NQL mutation binding '${bindName}' cannot be materialized because its projected column schema is unavailable.`
6357
+ );
6358
+ }
6359
+ return columns;
6360
+ }
6361
+ function toSnakeCaseIdentifier(identifier) {
6362
+ if (!identifier) return identifier;
6363
+ const leadingUnderscores = identifier.match(/^_+/)?.[0] ?? "";
6364
+ const rest = identifier.slice(leadingUnderscores.length);
6365
+ if (!rest) return identifier;
6366
+ const snakeCase = rest.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase();
6367
+ return leadingUnderscores + snakeCase;
6368
+ }
6369
+ function toDatabaseColumnName(column, dbCasing) {
6370
+ return dbCasing === "snake_case" ? toSnakeCaseIdentifier(column) : column;
6371
+ }
6372
+ function toRuntimeBindingRow(bindName, row, columns, dbCasing) {
6373
+ if (typeof row !== "object" || row === null || Array.isArray(row)) {
6374
+ throw new Error(
6375
+ `NQL mutation binding '${bindName}' returned a non-object row; runtime VALUES materialization requires object rows keyed by projected column name.`
6376
+ );
6377
+ }
6378
+ const source = row;
6379
+ const materialized = {};
6380
+ for (const column of columns) {
6381
+ const dbColumn = toDatabaseColumnName(column, dbCasing);
6382
+ const sourceColumn = Object.hasOwn(source, column) ? column : dbColumn !== column && Object.hasOwn(source, dbColumn) ? dbColumn : void 0;
6383
+ if (sourceColumn === void 0) {
6384
+ throw new Error(
6385
+ `NQL mutation binding '${bindName}' returned a row without projected column '${column}'.`
6386
+ );
6387
+ }
6388
+ materialized[column] = source[sourceColumn];
6389
+ }
6390
+ return materialized;
6391
+ }
6392
+ function createRuntimeBinding(bundle, bindName, rows, dbCasing) {
6393
+ const columns = requireMutationBindingColumns(bundle, bindName);
6394
+ return {
6395
+ columns,
6396
+ rows: rows.map(
6397
+ (row) => toRuntimeBindingRow(bindName, row, columns, dbCasing)
6398
+ )
6399
+ };
6400
+ }
6401
+ function bindingEntryIsFinal(compiledIntent, bindName, boundQuery, sourceBundle) {
6402
+ if (compiledIntent.kind === "query") {
6403
+ return boundQuery === compiledIntent.intent;
6404
+ }
6405
+ return sourceBundle.mutationBindings?.get(bindName) === compiledIntent.intent;
6406
+ }
6407
+ function orderedSequenceStepToProgramStep(step) {
6408
+ const dependencyStep = step;
6409
+ if (step.kind === "query") {
6410
+ return {
6411
+ kind: "query",
6412
+ intent: step.query,
6413
+ ...step.bindName !== void 0 && { bindName: step.bindName },
6414
+ final: step.final,
6415
+ ...dependencyStep.bindingDependencies !== void 0 && {
6416
+ bindingDependencies: dependencyStep.bindingDependencies
6417
+ }
6418
+ };
6419
+ }
6420
+ return {
6421
+ kind: "mutation",
6422
+ intent: step.mutation,
6423
+ ...step.bindName !== void 0 && { bindName: step.bindName },
6424
+ final: step.final,
6425
+ ...dependencyStep.bindingDependencies !== void 0 && {
6426
+ bindingDependencies: dependencyStep.bindingDependencies
6427
+ }
6428
+ };
6429
+ }
6430
+ function snapshotMutationRows(rows) {
6431
+ return rows.map((row) => {
6432
+ if (Array.isArray(row)) {
6433
+ return [...row];
6434
+ }
6435
+ if (row !== null && typeof row === "object") {
6436
+ return { ...row };
6437
+ }
6438
+ return row;
6439
+ });
6440
+ }
6441
+ function assertNqlProgramSteps(compiledIntent, steps) {
6442
+ if (steps.length === 0) {
6443
+ throw new Error("NQL program sequence did not contain any statements.");
6444
+ }
6445
+ const finalIndexes = steps.map((step, index) => step.final ? index : -1).filter((index) => index !== -1);
6446
+ if (finalIndexes.length !== 1 || finalIndexes[0] !== steps.length - 1) {
6447
+ throw new Error(
6448
+ "NQL program sequence must contain exactly one final statement at the end."
6449
+ );
6450
+ }
6451
+ const finalStep = steps.at(-1);
6452
+ if (finalStep === void 0) {
6453
+ throw new Error("NQL program sequence did not contain a final statement.");
6454
+ }
6455
+ if (finalStep.kind !== compiledIntent.kind || finalStep.intent !== compiledIntent.intent) {
6456
+ throw new Error(
6457
+ "NQL program sequence final statement does not match the compiled NQL result."
6458
+ );
6459
+ }
6460
+ const seenBindNames = /* @__PURE__ */ new Set();
6461
+ for (const step of steps) {
6462
+ if (step.bindName === void 0) continue;
6463
+ if (seenBindNames.has(step.bindName)) {
6464
+ throw new Error(
6465
+ `NQL program sequence contains duplicate binding '${step.bindName}'.`
6466
+ );
6467
+ }
6468
+ seenBindNames.add(step.bindName);
6469
+ if (!(compiledIntent.bundle.bindings?.has(step.bindName) ?? false)) {
6470
+ throw new Error(
6471
+ `NQL program sequence references unknown binding '${step.bindName}'.`
6472
+ );
6473
+ }
6474
+ }
6475
+ }
6476
+ function createNqlProgramSteps(compiledIntent) {
6477
+ const sourceBundle = compiledIntent.bundle;
6478
+ if (sourceBundle.nqlProgramSequence !== void 0) {
6479
+ const steps2 = sourceBundle.nqlProgramSequence.map(
6480
+ orderedSequenceStepToProgramStep
6481
+ );
6482
+ assertNqlProgramSteps(compiledIntent, steps2);
6483
+ return steps2;
6484
+ }
6485
+ const bindingEntries = [...sourceBundle.bindings ?? /* @__PURE__ */ new Map()];
6486
+ const steps = bindingEntries.map(
6487
+ ([bindName, boundQuery]) => {
6488
+ const boundMutation = sourceBundle.mutationBindings?.get(bindName);
6489
+ if (boundMutation !== void 0) {
6490
+ return {
6491
+ kind: "mutation",
6492
+ intent: boundMutation,
6493
+ bindName,
6494
+ final: false
6495
+ };
6496
+ }
6497
+ return {
6498
+ kind: "query",
6499
+ intent: boundQuery,
6500
+ bindName,
6501
+ final: false
6502
+ };
6503
+ }
6504
+ );
6505
+ const lastBindingEntry = bindingEntries.at(-1);
6506
+ const finalIsBound = lastBindingEntry !== void 0 && bindingEntryIsFinal(
6507
+ compiledIntent,
6508
+ lastBindingEntry[0],
6509
+ lastBindingEntry[1],
6510
+ sourceBundle
6511
+ );
6512
+ if (finalIsBound) {
6513
+ const lastStep = steps.at(-1);
6514
+ if (lastStep === void 0) return steps;
6515
+ steps[steps.length - 1] = { ...lastStep, final: true };
6516
+ return steps;
6517
+ }
6518
+ if (compiledIntent.kind === "query") {
6519
+ steps.push({
6520
+ kind: "query",
6521
+ intent: compiledIntent.intent,
6522
+ final: true
6523
+ });
6524
+ } else {
6525
+ steps.push({
6526
+ kind: "mutation",
6527
+ intent: compiledIntent.intent,
6528
+ final: true
6529
+ });
6530
+ }
6531
+ assertNqlProgramSteps(compiledIntent, steps);
6532
+ return steps;
6533
+ }
6534
+ function renumberDumpSqlParams(sql2, offset) {
6535
+ if (offset === 0) return sql2;
6536
+ return sql2.replace(/\$(\d+)/g, (_match, num) => {
6537
+ return `$${Number.parseInt(num, 10) + offset}`;
6538
+ });
6539
+ }
6540
+ function joinDumpSequenceSql(sequence) {
6541
+ let parameterOffset = 0;
6542
+ return sequence.map((step) => {
6543
+ const sql2 = renumberDumpSqlParams(step.sql, parameterOffset);
6544
+ parameterOffset += step.params.length;
6545
+ return sql2;
6546
+ }).join(";\n");
6547
+ }
6548
+ function flattenDumpSequenceParams(sequence) {
6549
+ return sequence.flatMap((step) => [...step.params]);
6550
+ }
6551
+ function filterMapByBindingDependencies(source, bindingDependencies) {
6552
+ if (source === void 0) return /* @__PURE__ */ new Map();
6553
+ if (bindingDependencies === void 0) return new Map(source);
6554
+ const filtered = /* @__PURE__ */ new Map();
6555
+ for (const bindName of bindingDependencies) {
6556
+ const value = source.get(bindName);
6557
+ if (value !== void 0) {
6558
+ filtered.set(bindName, value);
6559
+ }
6560
+ }
6561
+ return filtered;
6562
+ }
6563
+ function filterOptionalMapByBindingDependencies(source, bindingDependencies) {
6564
+ const filtered = filterMapByBindingDependencies(source, bindingDependencies);
6565
+ return filtered.size > 0 ? filtered : void 0;
6566
+ }
6567
+ function filterMapByRuntimeBindingDependencies(source, runtimeSourceBindings, bindingDependencies) {
6568
+ if (source === void 0) return /* @__PURE__ */ new Map();
6569
+ if (bindingDependencies === void 0 || runtimeSourceBindings === void 0 || runtimeSourceBindings.size === 0) {
6570
+ return new Map(source);
6571
+ }
6572
+ const dependencies = new Set(bindingDependencies);
6573
+ const filtered = /* @__PURE__ */ new Map();
6574
+ for (const [bindName, value] of source) {
6575
+ if (!runtimeSourceBindings.has(bindName) || dependencies.has(bindName)) {
6576
+ filtered.set(bindName, value);
6577
+ }
6578
+ }
6579
+ return filtered;
6580
+ }
6581
+ function filterOptionalMapByNames(source, names) {
6582
+ if (source === void 0) return void 0;
6583
+ const filtered = /* @__PURE__ */ new Map();
6584
+ for (const name of names) {
6585
+ const value = source.get(name);
6586
+ if (value !== void 0) {
6587
+ filtered.set(name, value);
6588
+ }
6589
+ }
6590
+ return filtered.size > 0 ? filtered : void 0;
6591
+ }
6592
+ function createNqlTag(_schemaDefinition, model, adapter, schemaName, hookStore, onHookError, inTransaction) {
6293
6593
  return function nql(strings, ...values) {
6294
6594
  const assembled = assembleNqlTemplate(strings, values);
6295
6595
  return new NqlBuilderImpl(
@@ -6297,7 +6597,6 @@ function createNqlTag(schemaDefinition, model, adapter, schemaName, hookStore, o
6297
6597
  assembled.params,
6298
6598
  assembled.hasBoundParams,
6299
6599
  assembled.sourceError,
6300
- schemaDefinition,
6301
6600
  model,
6302
6601
  adapter,
6303
6602
  schemaName,
@@ -6313,19 +6612,17 @@ var NqlBuilderImpl = class {
6313
6612
  params;
6314
6613
  hasBoundParams;
6315
6614
  sourceError;
6316
- schemaDefinition;
6317
6615
  model;
6318
6616
  _schemaName;
6319
6617
  adapter;
6320
6618
  hookStore;
6321
6619
  onHookError;
6322
6620
  inTransaction;
6323
- constructor(query, params, hasBoundParams, sourceError, schemaDefinition, model, adapter, schemaName, hookStore, onHookError, inTransaction) {
6621
+ constructor(query, params, hasBoundParams, sourceError, model, adapter, schemaName, hookStore, onHookError, inTransaction) {
6324
6622
  this.query = query;
6325
6623
  this.params = params;
6326
6624
  this.hasBoundParams = hasBoundParams;
6327
6625
  this.sourceError = sourceError;
6328
- this.schemaDefinition = schemaDefinition;
6329
6626
  this.model = model;
6330
6627
  this.adapter = adapter;
6331
6628
  this._schemaName = schemaName;
@@ -6347,7 +6644,7 @@ var NqlBuilderImpl = class {
6347
6644
  };
6348
6645
  const result = nqlCompile(
6349
6646
  this.query,
6350
- this.schemaDefinition,
6647
+ this.model,
6351
6648
  void 0,
6352
6649
  compilerOptions
6353
6650
  );
@@ -6360,7 +6657,6 @@ var NqlBuilderImpl = class {
6360
6657
  if (!bundle) {
6361
6658
  throw new Error("NQL compilation failed: no query AST produced");
6362
6659
  }
6363
- assertNoMutationBindingBodies(bundle);
6364
6660
  if (bundle.query) {
6365
6661
  this._compiled = {
6366
6662
  kind: "query",
@@ -6392,18 +6688,28 @@ var NqlBuilderImpl = class {
6392
6688
  return plan(compiled.intent, this.model);
6393
6689
  }
6394
6690
  plan() {
6395
- return this.planInternal();
6691
+ const compiled = this.compile();
6692
+ if (compiled.kind === "mutation") {
6693
+ throw new Error(
6694
+ "NQL mutations do not have execution plans; use dump() for SQL and parameters."
6695
+ );
6696
+ }
6697
+ return isBindingFinalQuery(compiled.bundle) ? createBindingFinalPlan(compiled.intent) : this.planInternal();
6396
6698
  }
6397
6699
  dump(meta) {
6398
6700
  const compiledIntent = this.compile();
6701
+ if (hasMutationBindingBodies(compiledIntent.bundle)) {
6702
+ return this.dumpNqlProgramSequence(compiledIntent, meta);
6703
+ }
6399
6704
  if (compiledIntent.kind === "mutation") {
6400
6705
  return this.dumpMutation(
6401
- compiledIntent.bundle,
6706
+ this.createFinalNqlStatementBundle(compiledIntent),
6402
6707
  compiledIntent.intent,
6403
6708
  meta
6404
6709
  );
6405
6710
  }
6406
- const planReport = this.planInternal();
6711
+ const bindingFinalQuery = isBindingFinalQuery(compiledIntent.bundle);
6712
+ const planReport = bindingFinalQuery ? createBindingFinalPlan(compiledIntent.intent) : this.planInternal();
6407
6713
  if (!this.adapter) {
6408
6714
  return {
6409
6715
  plan: planReport,
@@ -6412,10 +6718,8 @@ var NqlBuilderImpl = class {
6412
6718
  ...meta !== void 0 && { meta }
6413
6719
  };
6414
6720
  }
6415
- const compiled = hasNqlBindings(compiledIntent.bundle) ? this.adapter.compile(
6416
- compiledIntent.bundle,
6417
- this.nqlBundleCompileOptions()
6418
- ) : this.adapter.compile(planReport);
6721
+ const finalBundle = this.createFinalNqlStatementBundle(compiledIntent);
6722
+ const compiled = bindingFinalQuery || hasNqlBindings(finalBundle) ? this.adapter.compile(finalBundle, this.nqlBundleCompileOptions()) : this.adapter.compile(planReport);
6419
6723
  try {
6420
6724
  return this.adapter.createDump(planReport, compiled, meta);
6421
6725
  } catch (err) {
@@ -6466,6 +6770,268 @@ var NqlBuilderImpl = class {
6466
6770
  ...this._schemaName !== void 0 && { schemaName: this._schemaName }
6467
6771
  };
6468
6772
  }
6773
+ createNqlStatementBundle(statement, bindings, runtimeBindings, sourceBundle, bindingDependencies) {
6774
+ const statementBindings = filterMapByRuntimeBindingDependencies(
6775
+ bindings,
6776
+ sourceBundle.mutationBindings,
6777
+ bindingDependencies
6778
+ );
6779
+ const statementRuntimeBindings = filterMapByBindingDependencies(
6780
+ runtimeBindings,
6781
+ bindingDependencies
6782
+ );
6783
+ const emittedBindingNames = /* @__PURE__ */ new Set([
6784
+ ...statementBindings.keys(),
6785
+ ...statementRuntimeBindings.keys()
6786
+ ]);
6787
+ const statementBindingOutputSchemas = filterOptionalMapByNames(
6788
+ sourceBundle.bindingOutputSchemas,
6789
+ emittedBindingNames
6790
+ );
6791
+ const statementMutationBindings = filterOptionalMapByBindingDependencies(
6792
+ sourceBundle.mutationBindings,
6793
+ bindingDependencies
6794
+ );
6795
+ return {
6796
+ ...statement,
6797
+ ...statementBindings.size > 0 && { bindings: statementBindings },
6798
+ ...statementBindingOutputSchemas !== void 0 && {
6799
+ bindingOutputSchemas: statementBindingOutputSchemas
6800
+ },
6801
+ ...statementMutationBindings !== void 0 && {
6802
+ mutationBindings: statementMutationBindings
6803
+ },
6804
+ ...statementRuntimeBindings.size > 0 && {
6805
+ runtimeBindings: statementRuntimeBindings
6806
+ }
6807
+ };
6808
+ }
6809
+ createFinalNqlStatementBundle(compiledIntent) {
6810
+ const finalStep = createNqlProgramSteps(compiledIntent).at(-1);
6811
+ if (compiledIntent.kind === "query") {
6812
+ return this.createNqlStatementBundle(
6813
+ { query: compiledIntent.intent },
6814
+ compiledIntent.bundle.bindings ?? /* @__PURE__ */ new Map(),
6815
+ compiledIntent.bundle.runtimeBindings ?? /* @__PURE__ */ new Map(),
6816
+ compiledIntent.bundle,
6817
+ finalStep?.bindingDependencies
6818
+ );
6819
+ }
6820
+ return this.createNqlStatementBundle(
6821
+ { mutation: compiledIntent.intent },
6822
+ compiledIntent.bundle.bindings ?? /* @__PURE__ */ new Map(),
6823
+ compiledIntent.bundle.runtimeBindings ?? /* @__PURE__ */ new Map(),
6824
+ compiledIntent.bundle,
6825
+ finalStep?.bindingDependencies
6826
+ );
6827
+ }
6828
+ runMutationStatement(adapter, bundle, intent, inTransaction) {
6829
+ const hasReturning = (intent.returning?.length ?? 0) > 0;
6830
+ return runMutationWithHooks({
6831
+ table: intent.table,
6832
+ intent,
6833
+ hookStore: this.hookStore,
6834
+ onHookError: this.onHookError,
6835
+ schemaName: this._schemaName,
6836
+ inTransaction,
6837
+ prepare: () => {
6838
+ const compiled = adapter.compile(
6839
+ bundle,
6840
+ this.mutationCompileOptions()
6841
+ );
6842
+ return {
6843
+ sql: compiled.sql,
6844
+ parameters: compiled.parameters,
6845
+ execute: async () => {
6846
+ const hookRows = await adapter.execute(compiled);
6847
+ const rawRows = snapshotMutationRows(hookRows);
6848
+ return {
6849
+ rawRows,
6850
+ hookRows,
6851
+ transformedRows: hookRows
6852
+ };
6853
+ },
6854
+ getAfterMutationResult: (result) => hasReturning ? result.hookRows : [],
6855
+ mapAfterMutationResult: (result, transformedRows) => ({
6856
+ rawRows: result.rawRows,
6857
+ hookRows: result.hookRows,
6858
+ transformedRows: [...transformedRows]
6859
+ }),
6860
+ returnAfterMutationResult: hasReturning
6861
+ };
6862
+ }
6863
+ });
6864
+ }
6865
+ async executeNqlProgramSequence(compiledIntent, adapter) {
6866
+ if (!supportsTransactions(adapter)) {
6867
+ throw new ExecutionError({
6868
+ operation: "nql",
6869
+ reason: "NQL programs with mutation bindings require adapter transaction support",
6870
+ fix: "Use an adapter that implements transaction(fn), such as the PostgreSQL adapter."
6871
+ });
6872
+ }
6873
+ return adapter.transaction(async (txAdapter) => {
6874
+ const sourceBundle = compiledIntent.bundle;
6875
+ const priorBindings = /* @__PURE__ */ new Map();
6876
+ const runtimeBindings = /* @__PURE__ */ new Map();
6877
+ const steps = createNqlProgramSteps(compiledIntent);
6878
+ let finalRows = [];
6879
+ for (const step of steps) {
6880
+ if (step.kind === "mutation") {
6881
+ if ((step.intent.returning?.length ?? 0) === 0 && step.bindName) {
6882
+ throw new Error(
6883
+ `NQL mutation binding '${step.bindName}' cannot execute without a RETURNING projection.`
6884
+ );
6885
+ }
6886
+ const statementBundle = this.createNqlStatementBundle(
6887
+ { mutation: step.intent },
6888
+ priorBindings,
6889
+ runtimeBindings,
6890
+ sourceBundle,
6891
+ step.bindingDependencies
6892
+ );
6893
+ const rows = await this.runMutationStatement(
6894
+ txAdapter,
6895
+ statementBundle,
6896
+ step.intent,
6897
+ true
6898
+ );
6899
+ if (step.bindName) {
6900
+ runtimeBindings.set(
6901
+ step.bindName,
6902
+ createRuntimeBinding(
6903
+ sourceBundle,
6904
+ step.bindName,
6905
+ rows.rawRows,
6906
+ txAdapter.dbCasing
6907
+ )
6908
+ );
6909
+ }
6910
+ if (step.final) {
6911
+ finalRows = rows.transformedRows;
6912
+ }
6913
+ } else if (step.final) {
6914
+ const statementBundle = this.createNqlStatementBundle(
6915
+ { query: step.intent },
6916
+ priorBindings,
6917
+ runtimeBindings,
6918
+ sourceBundle,
6919
+ step.bindingDependencies
6920
+ );
6921
+ const compiled = txAdapter.compile(
6922
+ statementBundle,
6923
+ this.nqlBundleCompileOptions()
6924
+ );
6925
+ finalRows = await txAdapter.execute(compiled);
6926
+ }
6927
+ if (step.bindName) {
6928
+ const boundQuery = sourceBundle.bindings?.get(step.bindName);
6929
+ if (boundQuery !== void 0) {
6930
+ priorBindings.set(step.bindName, boundQuery);
6931
+ }
6932
+ }
6933
+ }
6934
+ return finalRows;
6935
+ });
6936
+ }
6937
+ dumpNqlProgramSequence(compiledIntent, meta) {
6938
+ const adapter = this.requireAdapter("dump");
6939
+ const sourceBundle = compiledIntent.bundle;
6940
+ const priorBindings = /* @__PURE__ */ new Map();
6941
+ const runtimeBindings = /* @__PURE__ */ new Map();
6942
+ const sequence = [];
6943
+ const steps = createNqlProgramSteps(compiledIntent);
6944
+ for (const step of steps) {
6945
+ if (step.kind === "mutation") {
6946
+ const statementBundle = this.createNqlStatementBundle(
6947
+ { mutation: step.intent },
6948
+ priorBindings,
6949
+ runtimeBindings,
6950
+ sourceBundle,
6951
+ step.bindingDependencies
6952
+ );
6953
+ const compiled = adapter.compile(
6954
+ statementBundle,
6955
+ this.mutationCompileOptions()
6956
+ );
6957
+ sequence.push({
6958
+ kind: "mutation",
6959
+ ...step.bindName !== void 0 && { bindName: step.bindName },
6960
+ sql: compiled.sql,
6961
+ params: compiled.parameters
6962
+ });
6963
+ if (step.bindName !== void 0) {
6964
+ runtimeBindings.set(step.bindName, {
6965
+ columns: requireMutationBindingColumns(sourceBundle, step.bindName),
6966
+ rows: []
6967
+ });
6968
+ }
6969
+ } else {
6970
+ const statementBundle = this.createNqlStatementBundle(
6971
+ { query: step.intent },
6972
+ priorBindings,
6973
+ runtimeBindings,
6974
+ sourceBundle,
6975
+ step.bindingDependencies
6976
+ );
6977
+ const compiled = adapter.compile(
6978
+ statementBundle,
6979
+ this.nqlBundleCompileOptions()
6980
+ );
6981
+ sequence.push({
6982
+ kind: "query",
6983
+ ...step.bindName !== void 0 && { bindName: step.bindName },
6984
+ sql: compiled.sql,
6985
+ params: compiled.parameters
6986
+ });
6987
+ }
6988
+ if (step.bindName !== void 0) {
6989
+ const boundQuery = sourceBundle.bindings?.get(step.bindName);
6990
+ if (boundQuery !== void 0) {
6991
+ priorBindings.set(step.bindName, boundQuery);
6992
+ }
6993
+ }
6994
+ }
6995
+ const finalStep = steps.at(-1);
6996
+ if (finalStep?.kind === "query") {
6997
+ const planReport = isBindingFinalQuery(compiledIntent.bundle) ? createBindingFinalPlan(finalStep.intent) : this.planInternal();
6998
+ const dumpMeta2 = {
6999
+ compiledAt: /* @__PURE__ */ new Date(),
7000
+ ...this._schemaName !== void 0 && { schema: this._schemaName },
7001
+ ...meta?.queryName !== void 0 && { queryName: meta.queryName },
7002
+ ...meta?.correlationId !== void 0 && {
7003
+ correlationId: meta.correlationId
7004
+ }
7005
+ };
7006
+ return {
7007
+ plan: planReport,
7008
+ sql: joinDumpSequenceSql(sequence),
7009
+ params: flattenDumpSequenceParams(sequence),
7010
+ meta: dumpMeta2,
7011
+ sequence
7012
+ };
7013
+ }
7014
+ if (finalStep?.kind !== "mutation") {
7015
+ throw new Error(
7016
+ "NQL program sequence did not contain a final statement."
7017
+ );
7018
+ }
7019
+ const dumpMeta = {
7020
+ compiledAt: /* @__PURE__ */ new Date(),
7021
+ ...this._schemaName !== void 0 && { schema: this._schemaName },
7022
+ ...meta?.queryName !== void 0 && { queryName: meta.queryName },
7023
+ ...meta?.correlationId !== void 0 && {
7024
+ correlationId: meta.correlationId
7025
+ }
7026
+ };
7027
+ return {
7028
+ sql: joinDumpSequenceSql(sequence),
7029
+ parameters: flattenDumpSequenceParams(sequence),
7030
+ intent: finalStep.intent,
7031
+ meta: dumpMeta,
7032
+ sequence
7033
+ };
7034
+ }
6469
7035
  dumpMutation(bundle, intent, meta) {
6470
7036
  const adapter = this.requireAdapter("dump");
6471
7037
  const compiled = adapter.compile(bundle, this.mutationCompileOptions(meta));
@@ -6492,35 +7058,27 @@ var NqlBuilderImpl = class {
6492
7058
  );
6493
7059
  }
6494
7060
  const compiledIntent = this.compile();
7061
+ if (hasMutationBindingBodies(compiledIntent.bundle)) {
7062
+ return this.executeNqlProgramSequence(compiledIntent, adapter);
7063
+ }
6495
7064
  if (compiledIntent.kind === "mutation") {
6496
- const hasReturning = (compiledIntent.intent.returning?.length ?? 0) > 0;
6497
- return runMutationWithHooks({
6498
- table: compiledIntent.intent.table,
6499
- intent: compiledIntent.intent,
6500
- hookStore: this.hookStore,
6501
- onHookError: this.onHookError,
6502
- schemaName: this._schemaName,
6503
- inTransaction: this.inTransaction,
6504
- prepare: () => {
6505
- const compiled2 = adapter.compile(
6506
- compiledIntent.bundle,
6507
- this.mutationCompileOptions()
6508
- );
6509
- return {
6510
- sql: compiled2.sql,
6511
- parameters: compiled2.parameters,
6512
- execute: () => adapter.execute(compiled2),
6513
- getAfterMutationResult: (result) => hasReturning ? result : [],
6514
- returnAfterMutationResult: hasReturning
6515
- };
6516
- }
6517
- });
7065
+ return (await this.runMutationStatement(
7066
+ adapter,
7067
+ this.createFinalNqlStatementBundle(compiledIntent),
7068
+ compiledIntent.intent,
7069
+ this.inTransaction
7070
+ )).transformedRows;
7071
+ }
7072
+ if (isBindingFinalQuery(compiledIntent.bundle)) {
7073
+ const compiled2 = adapter.compile(
7074
+ this.createFinalNqlStatementBundle(compiledIntent),
7075
+ this.nqlBundleCompileOptions()
7076
+ );
7077
+ return adapter.execute(compiled2);
6518
7078
  }
6519
7079
  const planReport = this.planInternal();
6520
- const compiled = hasNqlBindings(compiledIntent.bundle) ? adapter.compile(
6521
- compiledIntent.bundle,
6522
- this.nqlBundleCompileOptions()
6523
- ) : adapter.compile(planReport);
7080
+ const finalBundle = this.createFinalNqlStatementBundle(compiledIntent);
7081
+ const compiled = hasNqlBindings(finalBundle) ? adapter.compile(finalBundle, this.nqlBundleCompileOptions()) : adapter.compile(planReport);
6524
7082
  return adapter.execute(compiled);
6525
7083
  }
6526
7084
  async run() {
@@ -10422,6 +10980,8 @@ var POSTGRESQL_CAPABILITIES = {
10422
10980
  supportsJsonType: true,
10423
10981
  supportsJsonOperators: true,
10424
10982
  // PG: ->, ->>, @>, <@, ?, #>, #>>
10983
+ supportsRowLevelLocks: true,
10984
+ supportsLockWaitPolicies: true,
10425
10985
  supportsSchemas: true,
10426
10986
  // Include Strategy Capabilities (CORE-006)
10427
10987
  supportsLateralJoin: true,
@@ -10466,6 +11026,9 @@ var MYSQL_CAPABILITIES = {
10466
11026
  supportsJsonType: true,
10467
11027
  supportsJsonOperators: false,
10468
11028
  // MySQL uses JSON_EXTRACT() functions, not operators
11029
+ // Coarse PostgreSQL-shaped lock flags; revisit MySQL with per-strength caps.
11030
+ supportsRowLevelLocks: false,
11031
+ supportsLockWaitPolicies: false,
10469
11032
  supportsSchemas: true,
10470
11033
  // MySQL uses database as schema
10471
11034
  // Include Strategy Capabilities (CORE-006)
@@ -10496,6 +11059,8 @@ var SQLITE_CAPABILITIES = {
10496
11059
  // SQLite 3.38+ (JSON1 extension)
10497
11060
  supportsJsonOperators: false,
10498
11061
  // SQLite uses json_extract() functions
11062
+ supportsRowLevelLocks: false,
11063
+ supportsLockWaitPolicies: false,
10499
11064
  supportsSchemas: false,
10500
11065
  // SQLite uses ATTACH for multiple databases
10501
11066
  // Include Strategy Capabilities (CORE-006)
@@ -10524,6 +11089,8 @@ var DUCKDB_CAPABILITIES = {
10524
11089
  supportsJsonType: true,
10525
11090
  supportsJsonOperators: false,
10526
11091
  // DuckDB uses json_extract() style
11092
+ supportsRowLevelLocks: false,
11093
+ supportsLockWaitPolicies: false,
10527
11094
  supportsSchemas: true,
10528
11095
  // Include Strategy Capabilities (CORE-006)
10529
11096
  supportsLateralJoin: true,
@@ -10553,6 +11120,9 @@ var MSSQL_CAPABILITIES = {
10553
11120
  // SQL Server 2016+
10554
11121
  supportsJsonOperators: false,
10555
11122
  // MSSQL uses JSON_VALUE/JSON_QUERY functions
11123
+ // Coarse PostgreSQL-shaped lock flags; revisit MSSQL hints with per-strength caps.
11124
+ supportsRowLevelLocks: false,
11125
+ supportsLockWaitPolicies: false,
10556
11126
  supportsSchemas: true,
10557
11127
  // Include Strategy Capabilities (CORE-006)
10558
11128
  supportsLateralJoin: true,
@@ -10610,6 +11180,8 @@ function createDialectCapabilities(overrides, options) {
10610
11180
  supportsRangeTypes: false,
10611
11181
  supportsJsonType: false,
10612
11182
  supportsJsonOperators: false,
11183
+ supportsRowLevelLocks: false,
11184
+ supportsLockWaitPolicies: false,
10613
11185
  supportsSchemas: false,
10614
11186
  supportsLateralJoin: false,
10615
11187
  supportsJsonAgg: false,