@dbsp/nql 1.0.4 → 1.1.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
@@ -440,6 +440,8 @@ interface NqlCompilerOptions {
440
440
  readonly params?: Readonly<Record<string, unknown>>;
441
441
  /** Maximum number of items allowed in an ANY(:param) array. Defaults to MAX_ANY_ITEMS (10000). */
442
442
  readonly maxAnyItems?: number;
443
+ /** Permit a where-less update/delete to compile to an unfiltered, all-rows mutation. Default false — a where-less mutation throws unless this is set. */
444
+ readonly allowUnfilteredMutations?: boolean;
443
445
  }
444
446
 
445
447
  /**
package/dist/index.js CHANGED
@@ -387,6 +387,21 @@ function applyIncludeLimit(includes, path, limit) {
387
387
  }
388
388
 
389
389
  // src/compiler/compile-query.ts
390
+ function compileNestedQuery(query, ctx, fns, bindings) {
391
+ const savedContext = {
392
+ currentFromTable: ctx.currentFromTable,
393
+ currentRelationTarget: ctx.currentRelationTarget
394
+ };
395
+ try {
396
+ if (bindings) {
397
+ return compileQuery(query, ctx, fns, bindings);
398
+ }
399
+ return fns.compileQuery(query, ctx);
400
+ } finally {
401
+ ctx.currentFromTable = savedContext.currentFromTable;
402
+ ctx.currentRelationTarget = savedContext.currentRelationTarget;
403
+ }
404
+ }
390
405
  function compileQuery(query, ctx, fns, bindings) {
391
406
  const setClauseIndex = query.clauses.findIndex(
392
407
  (c) => c.type === "setOperation"
@@ -569,6 +584,12 @@ function getExplicitColumnCount(intent) {
569
584
  }
570
585
  function compileSetOperation(query, setClauseIndex, ctx, fns, bindings) {
571
586
  const setClause = query.clauses[setClauseIndex];
587
+ if (setClauseIndex < query.clauses.length - 1) {
588
+ const trailingTypes = query.clauses.slice(setClauseIndex + 1).map((c) => c.type).join(", ");
589
+ throw new Error(
590
+ `Clauses after a set operation are not supported (found: ${trailingTypes}). Wrap the set operation in a subquery or move the clause before the set operation.`
591
+ );
592
+ }
572
593
  const leftQuery = {
573
594
  table: query.table,
574
595
  clauses: query.clauses.slice(0, setClauseIndex)
@@ -576,7 +597,7 @@ function compileSetOperation(query, setClauseIndex, ctx, fns, bindings) {
576
597
  const left = compileQuery(leftQuery, ctx, fns, bindings);
577
598
  let right;
578
599
  if (setClause.right) {
579
- right = compileQuery(setClause.right, ctx, fns, bindings);
600
+ right = compileNestedQuery(setClause.right, ctx, fns, bindings);
580
601
  } else if (setClause.boundName) {
581
602
  const bound = bindings?.get(setClause.boundName);
582
603
  if (!bound) {
@@ -646,7 +667,7 @@ function compileWithQuery(astNode, ctx, fns) {
646
667
  try {
647
668
  const ctes = [];
648
669
  for (const cte of astNode.ctes) {
649
- const bodyResult = compileQuery(cte.query, ctx, fns);
670
+ const bodyResult = compileNestedQuery(cte.query, ctx, fns);
650
671
  if ("kind" in bodyResult && bodyResult.kind === "setOperation") {
651
672
  throw new NqlSemanticException(
652
673
  NqlErrorCodes.SEM_UNKNOWN_TABLE,
@@ -1005,7 +1026,11 @@ function compileMembership(expr, ctx, fns, aliasContext, outerAliases) {
1005
1026
  return expandDateRangeList(field, dateRangeValues, inExpr.negated);
1006
1027
  }
1007
1028
  } else if ("type" in inExpr.values && inExpr.values.type === "subquery") {
1008
- const subquery = fns.compileQuery(inExpr.values.query, ctx);
1029
+ const subquery = compileNestedQuery(
1030
+ inExpr.values.query,
1031
+ ctx,
1032
+ fns
1033
+ );
1009
1034
  const result2 = {
1010
1035
  kind: "in",
1011
1036
  field,
@@ -1331,7 +1356,11 @@ function compileInsert(insert, ctx) {
1331
1356
  };
1332
1357
  }
1333
1358
  function compileInsertFrom(insertFrom, ctx, fns, bindings) {
1359
+ ctx.validator?.validateTable(insertFrom.table);
1334
1360
  const sourceQuery = bindings?.get(insertFrom.source);
1361
+ if (!sourceQuery) {
1362
+ ctx.validator?.validateTable(insertFrom.source);
1363
+ }
1335
1364
  ctx.currentFromTable = insertFrom.source;
1336
1365
  return {
1337
1366
  type: "insert_from",
@@ -1365,6 +1394,11 @@ function compileUpdate(update, ctx, fns, bindings) {
1365
1394
  )
1366
1395
  };
1367
1396
  }
1397
+ if (!ctx.allowUnfilteredMutations) {
1398
+ throw new Error(
1399
+ "update without a where clause would affect all rows; pass { allowUnfilteredMutations: true } to the compiler to allow an unfiltered update"
1400
+ );
1401
+ }
1368
1402
  return {
1369
1403
  type: "update",
1370
1404
  table: update.table,
@@ -1385,6 +1419,11 @@ function compileDelete(del, ctx, fns, bindings) {
1385
1419
  )
1386
1420
  };
1387
1421
  }
1422
+ if (!ctx.allowUnfilteredMutations) {
1423
+ throw new Error(
1424
+ "delete without a where clause would affect all rows; pass { allowUnfilteredMutations: true } to the compiler to allow an unfiltered delete"
1425
+ );
1426
+ }
1388
1427
  return {
1389
1428
  type: "delete",
1390
1429
  table: del.table,
@@ -1401,6 +1440,11 @@ function compileUpsert(upsert, ctx) {
1401
1440
  for (const col of upsert.conflictColumns) {
1402
1441
  ctx.validator?.validateColumn(upsert.table, col);
1403
1442
  }
1443
+ if (upsert.where) {
1444
+ throw new Error(
1445
+ "conditional upsert is not yet supported: a WHERE clause on `upsert` (ON CONFLICT DO UPDATE ... WHERE) is parsed but cannot be honored by the SQL generator yet. Remove the WHERE, or use a plain conditional update. Tracked for a future release."
1446
+ );
1447
+ }
1404
1448
  return {
1405
1449
  type: "upsert",
1406
1450
  table: upsert.table,
@@ -1410,7 +1454,14 @@ function compileUpsert(upsert, ctx) {
1410
1454
  };
1411
1455
  }
1412
1456
  function compileUpsertFrom(upsertFrom, ctx, fns, bindings) {
1457
+ ctx.validator?.validateTable(upsertFrom.table);
1413
1458
  const sourceQuery = bindings?.get(upsertFrom.source);
1459
+ if (!sourceQuery) {
1460
+ ctx.validator?.validateTable(upsertFrom.source);
1461
+ }
1462
+ for (const col of upsertFrom.conflictColumns) {
1463
+ ctx.validator?.validateColumn(upsertFrom.table, col);
1464
+ }
1414
1465
  ctx.currentFromTable = upsertFrom.source;
1415
1466
  return {
1416
1467
  type: "upsert_from",
@@ -1666,7 +1717,7 @@ function compileSelectExpression(item, ctx, fns) {
1666
1717
  if (expr.type === "subquery") {
1667
1718
  return {
1668
1719
  kind: "subquery",
1669
- query: fns.compileQuery(expr.query, ctx),
1720
+ query: compileNestedQuery(expr.query, ctx, fns),
1670
1721
  ...exprItem.alias !== void 0 && { as: exprItem.alias }
1671
1722
  };
1672
1723
  }
@@ -1945,7 +1996,8 @@ var NqlCompiler = class {
1945
1996
  recursiveKeywords,
1946
1997
  validator,
1947
1998
  params: options?.params ?? {},
1948
- maxAnyItems: maxAnyItemsRaw ?? MAX_ANY_ITEMS
1999
+ maxAnyItems: maxAnyItemsRaw ?? MAX_ANY_ITEMS,
2000
+ allowUnfilteredMutations: options?.allowUnfilteredMutations ?? false
1949
2001
  };
1950
2002
  this.fns = {
1951
2003
  compileQuery: (query, ctx) => compileQuery(query, ctx, this.fns),
@@ -1963,15 +2015,27 @@ var NqlCompiler = class {
1963
2015
  if (program.statements.length === 1) {
1964
2016
  return this.compileSingleStatement(program.statements[0]);
1965
2017
  }
2018
+ for (let i = 0; i < program.statements.length - 1; i++) {
2019
+ const stmt = program.statements[i];
2020
+ const bindName = extractBindName(stmt);
2021
+ if (!bindName) {
2022
+ throw new Error(
2023
+ `Multiple statements require explicit binding: statement ${i + 1} of ${program.statements.length} has no '| bind <name>' clause. Add a \`| bind <name>\` clause to each statement except the last, or pass a single statement.`
2024
+ );
2025
+ }
2026
+ }
1966
2027
  const bindings = /* @__PURE__ */ new Map();
1967
2028
  const mutationBindings = /* @__PURE__ */ new Map();
2029
+ const materializedBindStatements = /* @__PURE__ */ new Set();
1968
2030
  let lastResult = {};
1969
- for (const stmt of program.statements) {
2031
+ for (let i = 0; i < program.statements.length; i++) {
2032
+ const stmt = program.statements[i];
1970
2033
  lastResult = this.compileSingleStatement(stmt, bindings);
1971
2034
  const bindName = extractBindName(stmt);
1972
2035
  if (bindName) {
1973
2036
  if (lastResult.query) {
1974
2037
  bindings.set(bindName, lastResult.query);
2038
+ materializedBindStatements.add(i);
1975
2039
  } else if (lastResult.mutation?.returning?.length) {
1976
2040
  mutationBindings.set(bindName, lastResult.mutation);
1977
2041
  bindings.set(bindName, {
@@ -1982,9 +2046,18 @@ var NqlCompiler = class {
1982
2046
  fields: [...lastResult.mutation.returning]
1983
2047
  }
1984
2048
  });
2049
+ materializedBindStatements.add(i);
1985
2050
  }
1986
2051
  }
1987
2052
  }
2053
+ for (let i = 0; i < program.statements.length - 1; i++) {
2054
+ const bindName = extractBindName(program.statements[i]);
2055
+ if (bindName && !materializedBindStatements.has(i)) {
2056
+ throw new Error(
2057
+ `statement ${i + 1} of ${program.statements.length} binds '${bindName}' but produces no referenceable result \u2014 a mutation used as a binding must include a \`returning\` clause.`
2058
+ );
2059
+ }
2060
+ }
1988
2061
  const hasMutationBindings = mutationBindings.size > 0;
1989
2062
  if (bindings.size > 0) {
1990
2063
  return {