@dbsp/nql 1.8.0 → 1.9.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.d.ts CHANGED
@@ -532,8 +532,8 @@ declare class NqlCompiler {
532
532
  * binding. #213 B2: detection consumes `ctx.lastMutationReturningItems`
533
533
  * (alias-aware, positional) — NEVER the collapsed `columns` names — so an
534
534
  * alias colliding with a real column (`returning email as name`) is
535
- * flagged 'aliased-returning' rather than silently mistyped as the
536
- * colliding column's type.
535
+ * typed by its physical source column rather than silently mistyped as the
536
+ * colliding output column's type.
537
537
  */
538
538
  private buildMutationReturningColumnTypes;
539
539
  private compileSingleStatement;
package/dist/index.js CHANGED
@@ -1176,6 +1176,7 @@ function assignMutationValue(target, column, value, ctx) {
1176
1176
  function compileMutationPipeline(pipeline, ctx, fns, bindings) {
1177
1177
  ctx.currentFromTable = pipeline.mutation.table;
1178
1178
  const mutation = compileMutation(pipeline.mutation, ctx, fns, bindings);
1179
+ ctx.currentFromTable = pipeline.mutation.table;
1179
1180
  let returning;
1180
1181
  let returningItems;
1181
1182
  for (const clause of pipeline.clauses) {
@@ -1186,8 +1187,18 @@ function compileMutationPipeline(pipeline, ctx, fns, bindings) {
1186
1187
  }
1187
1188
  ctx.lastMutationReturningItems = returningItems;
1188
1189
  if (returning) {
1190
+ const aliasAwareReturningItems = returningItems !== void 0 && returningItems !== "star" && returningItems.some((item) => item.aliased) ? returningItems.map((item) => ({
1191
+ source: item.source,
1192
+ output: item.output
1193
+ })) : void 0;
1189
1194
  return {
1190
- mutation: { ...mutation, returning }
1195
+ mutation: {
1196
+ ...mutation,
1197
+ returning,
1198
+ ...aliasAwareReturningItems !== void 0 && {
1199
+ returningItems: aliasAwareReturningItems
1200
+ }
1201
+ }
1191
1202
  };
1192
1203
  }
1193
1204
  return { mutation };
@@ -1374,19 +1385,42 @@ function compileUpsertFrom(upsertFrom, ctx, fns, bindings) {
1374
1385
  }
1375
1386
  function extractReturningItems(clause, ctx) {
1376
1387
  const items = [];
1388
+ const outputs = /* @__PURE__ */ new Set();
1377
1389
  for (const item of clause.items) {
1378
1390
  if (item.type === "star") {
1391
+ if (clause.items.length > 1) {
1392
+ throw new NqlSemanticException(
1393
+ NqlErrorCodes.SEM_INVALID_SYNTAX,
1394
+ "Mutation RETURNING cannot mix `select *` with explicit projection items."
1395
+ );
1396
+ }
1379
1397
  return "star";
1380
1398
  }
1381
1399
  if (item.type === "expression") {
1382
1400
  const field = expressionToField(item.expression);
1383
1401
  if (field) {
1402
+ const aliased = item.alias !== void 0;
1403
+ if (aliased && field.includes(".")) {
1404
+ throw new NqlSemanticException(
1405
+ NqlErrorCodes.SEM_INVALID_SYNTAX,
1406
+ `Mutation RETURNING alias cannot use dotted source '${field}'.`
1407
+ );
1408
+ }
1384
1409
  if (ctx.currentFromTable && !field.includes(".")) {
1385
1410
  ctx.validator?.validateColumn(ctx.currentFromTable, field);
1386
1411
  }
1412
+ const output = item.alias ?? field;
1413
+ if (outputs.has(output)) {
1414
+ throw new NqlSemanticException(
1415
+ NqlErrorCodes.SEM_INVALID_SYNTAX,
1416
+ `Mutation RETURNING has duplicate output name '${output}'.`
1417
+ );
1418
+ }
1419
+ outputs.add(output);
1387
1420
  items.push({
1388
- output: item.alias ?? field,
1389
- aliased: item.alias !== void 0
1421
+ source: field,
1422
+ output,
1423
+ aliased
1390
1424
  });
1391
1425
  }
1392
1426
  }
@@ -4243,6 +4277,9 @@ function programSequenceStepFromResult(result, bindName, final, bindingDependenc
4243
4277
  function stringArraysEqual(left, right) {
4244
4278
  return left.length === right.length && left.every((value, index) => value === right[index]);
4245
4279
  }
4280
+ function mutationReturningItemsMatchReturning(returning, returningItems) {
4281
+ return returningItems.length === returning.length && returningItems.every((item, index) => item.output === returning[index]);
4282
+ }
4246
4283
  function isNqlAstRecord(value) {
4247
4284
  return typeof value === "object" && value !== null;
4248
4285
  }
@@ -4733,7 +4770,50 @@ var NqlCompiler = class {
4733
4770
  mutation
4734
4771
  );
4735
4772
  const returning = mutation.returning;
4736
- if (returning === void 0 || returning.length === 0 || returning.includes("*") || outputSchema === void 0 || stringArraysEqual(returning, outputSchema.columns)) {
4773
+ const returningItems = mutation.returningItems;
4774
+ if (returning === void 0 || returning.length === 0 || returning.includes("*") || outputSchema === void 0) {
4775
+ return { mutation, outputSchema };
4776
+ }
4777
+ const seenOutputs = /* @__PURE__ */ new Set();
4778
+ for (const column of outputSchema.columns) {
4779
+ if (seenOutputs.has(column)) {
4780
+ throw new NqlSemanticException(
4781
+ NqlErrorCodes.SEM_INVALID_SYNTAX,
4782
+ `Mutation RETURNING resolves to duplicate output name '${column}' after column canonicalization for binding '${bindName}'.`
4783
+ );
4784
+ }
4785
+ seenOutputs.add(column);
4786
+ }
4787
+ if (returningItems !== void 0) {
4788
+ const internalItems = this.ctx.lastMutationReturningItems !== void 0 && this.ctx.lastMutationReturningItems !== "star" ? this.ctx.lastMutationReturningItems : void 0;
4789
+ const canonicalItems = returningItems.map((item, index) => {
4790
+ const source = this.ctx.validator?.resolveColumnName(mutation.table, item.source) ?? item.source;
4791
+ const internalItem = internalItems?.[index];
4792
+ const output = internalItem?.aliased ? item.output : source;
4793
+ return source === item.source && output === item.output ? item : { source, output };
4794
+ });
4795
+ if (!mutationReturningItemsMatchReturning(
4796
+ outputSchema.columns,
4797
+ canonicalItems
4798
+ )) {
4799
+ throw new NqlSemanticException(
4800
+ NqlErrorCodes.SEM_INVALID_SYNTAX,
4801
+ `Mutation RETURNING item outputs drifted during canonicalization for binding '${bindName}'.`
4802
+ );
4803
+ }
4804
+ if (stringArraysEqual(returning, outputSchema.columns) && canonicalItems.every((item, index) => item === returningItems[index])) {
4805
+ return { mutation, outputSchema };
4806
+ }
4807
+ return {
4808
+ mutation: {
4809
+ ...mutation,
4810
+ returning: outputSchema.columns,
4811
+ returningItems: canonicalItems
4812
+ },
4813
+ outputSchema
4814
+ };
4815
+ }
4816
+ if (stringArraysEqual(returning, outputSchema.columns)) {
4737
4817
  return { mutation, outputSchema };
4738
4818
  }
4739
4819
  return {
@@ -4764,6 +4844,18 @@ var NqlCompiler = class {
4764
4844
  const columns = returning.map(
4765
4845
  (column) => this.ctx.validator?.resolveColumnName(mutation.table, column) ?? column
4766
4846
  );
4847
+ const returningItems = mutation.returningItems;
4848
+ if (returningItems !== void 0) {
4849
+ const internalItems = this.ctx.lastMutationReturningItems !== void 0 && this.ctx.lastMutationReturningItems !== "star" ? this.ctx.lastMutationReturningItems : void 0;
4850
+ const columns2 = returningItems.map((item, index) => {
4851
+ const source = this.ctx.validator?.resolveColumnName(mutation.table, item.source) ?? item.source;
4852
+ return internalItems?.[index]?.aliased ? item.output : source;
4853
+ });
4854
+ return {
4855
+ columns: columns2,
4856
+ ...this.buildMutationReturningColumnTypes(mutation.table, columns2)
4857
+ };
4858
+ }
4767
4859
  return {
4768
4860
  columns,
4769
4861
  ...this.buildMutationReturningColumnTypes(mutation.table, columns)
@@ -4774,8 +4866,8 @@ var NqlCompiler = class {
4774
4866
  * binding. #213 B2: detection consumes `ctx.lastMutationReturningItems`
4775
4867
  * (alias-aware, positional) — NEVER the collapsed `columns` names — so an
4776
4868
  * alias colliding with a real column (`returning email as name`) is
4777
- * flagged 'aliased-returning' rather than silently mistyped as the
4778
- * colliding column's type.
4869
+ * typed by its physical source column rather than silently mistyped as the
4870
+ * colliding output column's type.
4779
4871
  */
4780
4872
  buildMutationReturningColumnTypes(table, columns) {
4781
4873
  const items = this.ctx.lastMutationReturningItems;
@@ -4783,7 +4875,11 @@ var NqlCompiler = class {
4783
4875
  (column, index) => {
4784
4876
  const item = items !== void 0 && items !== "star" ? items[index] : void 0;
4785
4877
  if (item?.aliased) {
4786
- return { column, untypeable: "aliased-returning" };
4878
+ const typeInfo2 = this.ctx.validator?.getTableColumnType(
4879
+ table,
4880
+ item.source
4881
+ );
4882
+ return typeInfo2 !== void 0 ? { column, typed: typeInfo2 } : { column, untypeable: "unresolvable-source" };
4787
4883
  }
4788
4884
  const typeInfo = this.ctx.validator?.getTableColumnType(table, column);
4789
4885
  return typeInfo !== void 0 ? { column, typed: typeInfo } : { column, untypeable: "unresolvable-source" };