@dbsp/nql 1.9.5 → 1.10.1

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
@@ -1,4 +1,4 @@
1
- import { ColumnType, CompiledNqlQuery } from '@dbsp/types';
1
+ import { ColumnType, ColumnJsReadType, CompiledNqlQuery } from '@dbsp/types';
2
2
  export { CompiledNqlQuery, DeleteIntent, ExpressionIntent, IncludeIntent, InsertIntent, MutationIntent, OrderByIntent, QueryIntent, SelectAllIntent, SelectFieldsIntent, SelectIntent, SelectWithExpressionsIntent, UpdateIntent, UpsertIntent, WhereAndIntent, WhereComparisonIntent, WhereInIntent, WhereIntent, WhereLikeIntent, WhereNotIntent, WhereNullIntent, WhereOrIntent, WhereRangeIntent } from '@dbsp/types';
3
3
  import * as chevrotain from 'chevrotain';
4
4
  import { Lexer, CstParser, CstNode, IToken } from 'chevrotain';
@@ -463,6 +463,7 @@ interface ColumnValidatorPseudoColumn {
463
463
  interface ColumnValidatorTableColumn {
464
464
  readonly name: string;
465
465
  readonly type?: ColumnType;
466
+ readonly js?: ColumnJsReadType;
466
467
  readonly originalDbType?: string;
467
468
  readonly originalDbTypeSchema?: string;
468
469
  readonly originalDbTypeSchemaScope?: 'target' | 'absolute';
@@ -529,6 +530,7 @@ declare class NqlCompiler {
529
530
  private registerQueryBindingOutputSchema;
530
531
  private canonicalizeMutationBinding;
531
532
  private getMutationBindingOutputSchema;
533
+ private buildMutationReturningDeclaredOutputs;
532
534
  /**
533
535
  * Build `columnTypes`/`columnTypesUnavailable` for a mutation-RETURNING
534
536
  * binding. #213 B2: detection consumes `ctx.lastMutationReturningItems`
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { NQL_INTERNAL_COMPILER_OPTIONS, explainUnsupportedNqlBindingIncludeHop, markNqlTrustedRelationFilter, createNqlBindingRef, isNqlBindingRef, getNqlBindingRefName } from '@dbsp/types/internal';
1
+ import { NQL_INTERNAL_COMPILER_OPTIONS, explainUnsupportedNqlBindingIncludeHop, markNqlTrustedRelationFilter, getTrustedNqlRelationFilterFields, createNqlBindingRef, isNqlBindingRef, getNqlBindingRefName } from '@dbsp/types/internal';
2
2
  import { NQL_SELECT_AGGREGATE_FUNCTIONS, NQL_SELECT_VALUE_FUNCTIONS, NQL_SELECT_JSON_FUNCTIONS, NQL_SELECT_SCALAR_FUNCTIONS, toColumnList, isNqlSelectWindowFunctionAllowed, isRankingWindowFunction, isParamIntent } from '@dbsp/types';
3
3
  import { createToken, Lexer, CstParser } from 'chevrotain';
4
4
 
@@ -181,6 +181,12 @@ var ColumnValidator = class _ColumnValidator {
181
181
  }
182
182
  };
183
183
  }
184
+ getTableColumnJsReadType(table, column) {
185
+ const resolvedName = this.resolvePhysicalColumnName(table, column);
186
+ if (resolvedName === void 0) return void 0;
187
+ const columnInfo = this.schema.getTable(table)?.columns.find((candidate) => candidate.name === resolvedName);
188
+ return columnInfo?.type === "bigint" ? columnInfo.js : void 0;
189
+ }
184
190
  getPseudoColumns(name) {
185
191
  return this.schema.getTable(name)?.pseudoColumns ?? [];
186
192
  }
@@ -1610,6 +1616,13 @@ function toSnakeCase(name) {
1610
1616
  function columnsMatch(left, right) {
1611
1617
  return left === right || toSnakeCase(left) === toSnakeCase(right);
1612
1618
  }
1619
+ var scalarOneShape = {
1620
+ kind: "scalar",
1621
+ cardinality: "one"
1622
+ };
1623
+ function unknownShape(reason) {
1624
+ return { kind: "unknown", reason };
1625
+ }
1613
1626
  function expressionIntentContainsAggregate(value) {
1614
1627
  if (!value || typeof value !== "object") return false;
1615
1628
  const record = value;
@@ -2578,13 +2591,84 @@ function getQueryOutputSchema(intent, ctx, bindingName, bindingDependencies = []
2578
2591
  const columns = [];
2579
2592
  const seen = /* @__PURE__ */ new Set();
2580
2593
  const directProjectionLineage = [];
2594
+ const declaredOutputCandidates = /* @__PURE__ */ new Map();
2581
2595
  const typeCandidates = [];
2582
2596
  const sourceIsPhysicalTable = ctx.validator?.hasPhysicalTable(intent.from) ?? false;
2583
2597
  const sourceBindingSchema = sourceIsPhysicalTable ? void 0 : sourceBindingSchemas?.get(intent.from);
2584
2598
  const addColumn = (column) => addUnique(columns, seen, column);
2585
2599
  const resolveSourceColumn = (column) => ctx.validator?.resolveColumnName(intent.from, column) ?? column;
2600
+ const addDeclaredOutputCandidate = (outputKey, descriptor) => {
2601
+ const candidates = declaredOutputCandidates.get(outputKey) ?? [];
2602
+ candidates.push(descriptor);
2603
+ declaredOutputCandidates.set(outputKey, candidates);
2604
+ };
2605
+ const unresolvedOutput = (outputKey, reason) => ({
2606
+ outputKey,
2607
+ source: { kind: "unresolved", reason },
2608
+ shape: unknownShape(reason)
2609
+ });
2610
+ const modelColumnOutput = (outputKey, table, column, shape = scalarOneShape) => {
2611
+ const js = ctx.validator?.getTableColumnJsReadType(table, column);
2612
+ return {
2613
+ outputKey,
2614
+ source: {
2615
+ kind: "modelColumn",
2616
+ table,
2617
+ column,
2618
+ ...js !== void 0 ? { js } : {}
2619
+ },
2620
+ shape
2621
+ };
2622
+ };
2623
+ const withOutputKey = (descriptor, outputKey) => ({
2624
+ outputKey,
2625
+ source: descriptor.source,
2626
+ shape: descriptor.shape
2627
+ });
2628
+ const resolveDirectProjectionOutput = (outputKey, resolvedSourceColumn) => {
2629
+ if (sourceIsPhysicalTable) {
2630
+ return modelColumnOutput(outputKey, intent.from, resolvedSourceColumn);
2631
+ }
2632
+ const sourceDescriptors = sourceBindingSchema?.declaredOutputs?.filter(
2633
+ (descriptor) => columnsMatch(descriptor.outputKey, resolvedSourceColumn)
2634
+ );
2635
+ if (sourceDescriptors?.length === 1) {
2636
+ const [descriptor] = sourceDescriptors;
2637
+ if (descriptor !== void 0) return withOutputKey(descriptor, outputKey);
2638
+ }
2639
+ return unresolvedOutput(
2640
+ outputKey,
2641
+ `binding output '${outputKey}' did not resolve to a declared source column`
2642
+ );
2643
+ };
2644
+ const resolvePhysicalRelationColumnOutput = (outputKey, expr) => {
2645
+ if (!sourceIsPhysicalTable || !ctx.validator) return void 0;
2646
+ const relationPath = expr.relation.split(".");
2647
+ if (relationPath.length === 0 || relationPath.some((part) => part === "")) {
2648
+ return void 0;
2649
+ }
2650
+ let targetTable = intent.from;
2651
+ for (const relationName of relationPath) {
2652
+ const relation = ctx.validator.getRelation(targetTable, relationName);
2653
+ if (relation === void 0) return void 0;
2654
+ if (relation.type !== "belongsTo" && relation.type !== "hasOne" || relation.recursive !== void 0) {
2655
+ return void 0;
2656
+ }
2657
+ targetTable = relation.target;
2658
+ }
2659
+ const resolvedColumn = ctx.validator.resolvePhysicalColumnName(
2660
+ targetTable,
2661
+ expr.column
2662
+ );
2663
+ if (resolvedColumn === void 0) return void 0;
2664
+ return modelColumnOutput(outputKey, targetTable, resolvedColumn);
2665
+ };
2586
2666
  const addDirectProjection = (outputColumn, sourceColumn) => {
2587
2667
  const resolvedSourceColumn = resolveSourceColumn(sourceColumn);
2668
+ addDeclaredOutputCandidate(
2669
+ outputColumn,
2670
+ resolveDirectProjectionOutput(outputColumn, resolvedSourceColumn)
2671
+ );
2588
2672
  let typeInfo;
2589
2673
  let untypeableReason = "unresolvable-source";
2590
2674
  if (sourceIsPhysicalTable) {
@@ -2609,10 +2693,51 @@ function getQueryOutputSchema(intent, ctx, bindingName, bindingDependencies = []
2609
2693
  });
2610
2694
  };
2611
2695
  const addUntypedColumn = (outputColumn, reason) => {
2696
+ addDeclaredOutputCandidate(
2697
+ outputColumn,
2698
+ unresolvedOutput(
2699
+ outputColumn,
2700
+ `binding output '${outputColumn}' has no proven scalar model column source (${reason})`
2701
+ )
2702
+ );
2612
2703
  typeCandidates.push({ column: outputColumn, untypeable: reason });
2613
2704
  addColumn(outputColumn);
2614
2705
  };
2706
+ const addRelationColumn = (outputColumn, expr) => {
2707
+ const trusted = getTrustedNqlRelationFilterFields(expr);
2708
+ const targetTable = trusted?.hops.at(-1)?.target ?? trusted?.targetTable;
2709
+ const isScalarRelationColumn = trusted?.selectedColumn !== void 0 && trusted.cardinality === "one" && trusted.recursive === void 0 && (trusted.relationType === "belongsTo" || trusted.relationType === "hasOne");
2710
+ const descriptor = targetTable !== void 0 && isScalarRelationColumn ? modelColumnOutput(outputColumn, targetTable, trusted.selectedColumn) : trusted !== void 0 ? targetTable !== void 0 && trusted.selectedColumn !== void 0 ? modelColumnOutput(
2711
+ outputColumn,
2712
+ targetTable,
2713
+ trusted.selectedColumn,
2714
+ {
2715
+ kind: "array",
2716
+ cardinality: "many"
2717
+ }
2718
+ ) : void 0 : expr.kind === "relationColumn" ? resolvePhysicalRelationColumnOutput(outputColumn, expr) : void 0;
2719
+ addDeclaredOutputCandidate(
2720
+ outputColumn,
2721
+ descriptor ?? unresolvedOutput(
2722
+ outputColumn,
2723
+ `relation output '${outputColumn}' has no proven scalar model column source`
2724
+ )
2725
+ );
2726
+ typeCandidates.push({
2727
+ column: outputColumn,
2728
+ untypeable: "relation-column"
2729
+ });
2730
+ addColumn(outputColumn);
2731
+ };
2615
2732
  const addCountAggregateColumn = (outputColumn) => {
2733
+ addDeclaredOutputCandidate(outputColumn, {
2734
+ outputKey: outputColumn,
2735
+ source: {
2736
+ kind: "expression",
2737
+ reason: `aggregate output '${outputColumn}' has no scalar model column source`
2738
+ },
2739
+ shape: { kind: "aggregate-scalar", aggregate: "count" }
2740
+ });
2616
2741
  typeCandidates.push({
2617
2742
  column: outputColumn,
2618
2743
  typed: { kind: "aggregate", fn: "count" }
@@ -2627,8 +2752,22 @@ function getQueryOutputSchema(intent, ctx, bindingName, bindingDependencies = []
2627
2752
  const finalizeOutputSchema = () => {
2628
2753
  const outputSchema = { columns, directProjectionLineage };
2629
2754
  const typesResult = buildBindingColumnTypes(columns, typeCandidates);
2755
+ const declaredOutputs = columns.map((column) => {
2756
+ const candidates = declaredOutputCandidates.get(column) ?? [];
2757
+ if (candidates.length !== 1) {
2758
+ return unresolvedOutput(
2759
+ column,
2760
+ `binding output '${column}' has ambiguous declared output sources`
2761
+ );
2762
+ }
2763
+ return candidates[0] ?? unresolvedOutput(
2764
+ column,
2765
+ `binding output '${column}' has no declared output source`
2766
+ );
2767
+ });
2630
2768
  return {
2631
2769
  columns: outputSchema.columns,
2770
+ declaredOutputs,
2632
2771
  relationFilters: getBindingRelationFilterMetadata(
2633
2772
  intent,
2634
2773
  ctx,
@@ -2696,10 +2835,11 @@ function getQueryOutputSchema(intent, ctx, bindingName, bindingDependencies = []
2696
2835
  addDirectProjection(outputColumn, expr.column);
2697
2836
  } else if (expr.kind === "aggregate" && expr.function === "count") {
2698
2837
  addCountAggregateColumn(outputColumn);
2838
+ } else if (expr.kind === "relationColumn") {
2839
+ addRelationColumn(outputColumn, expr);
2699
2840
  } else {
2700
2841
  let reason = "computed-expression";
2701
- if (expr.kind === "relationColumn") reason = "relation-column";
2702
- else if (expr.kind === "aggregate") reason = "unsupported-aggregate";
2842
+ if (expr.kind === "aggregate") reason = "unsupported-aggregate";
2703
2843
  addUntypedColumn(outputColumn, reason);
2704
2844
  }
2705
2845
  }
@@ -4838,6 +4978,10 @@ var NqlCompiler = class {
4838
4978
  if (columns2 !== void 0) {
4839
4979
  return {
4840
4980
  columns: columns2,
4981
+ declaredOutputs: this.buildMutationReturningDeclaredOutputs(
4982
+ mutation.table,
4983
+ columns2
4984
+ ),
4841
4985
  ...this.buildMutationReturningColumnTypes(mutation.table, columns2)
4842
4986
  };
4843
4987
  }
@@ -4859,14 +5003,43 @@ var NqlCompiler = class {
4859
5003
  });
4860
5004
  return {
4861
5005
  columns: columns2,
5006
+ declaredOutputs: this.buildMutationReturningDeclaredOutputs(
5007
+ mutation.table,
5008
+ columns2
5009
+ ),
4862
5010
  ...this.buildMutationReturningColumnTypes(mutation.table, columns2)
4863
5011
  };
4864
5012
  }
4865
5013
  return {
4866
5014
  columns,
5015
+ declaredOutputs: this.buildMutationReturningDeclaredOutputs(
5016
+ mutation.table,
5017
+ columns
5018
+ ),
4867
5019
  ...this.buildMutationReturningColumnTypes(mutation.table, columns)
4868
5020
  };
4869
5021
  }
5022
+ buildMutationReturningDeclaredOutputs(table, columns) {
5023
+ const items = this.ctx.lastMutationReturningItems;
5024
+ return columns.map((column, index) => {
5025
+ const item = items !== void 0 && items !== "star" ? items[index] : void 0;
5026
+ const sourceColumn = item !== void 0 ? this.ctx.validator?.resolveColumnName(table, item.source) ?? item.source : column;
5027
+ const js = this.ctx.validator?.getTableColumnJsReadType(
5028
+ table,
5029
+ sourceColumn
5030
+ );
5031
+ return {
5032
+ outputKey: column,
5033
+ source: {
5034
+ kind: "modelColumn",
5035
+ table,
5036
+ column: sourceColumn,
5037
+ ...js !== void 0 && { js }
5038
+ },
5039
+ shape: { kind: "scalar", cardinality: "one" }
5040
+ };
5041
+ });
5042
+ }
4870
5043
  /**
4871
5044
  * Build `columnTypes`/`columnTypesUnavailable` for a mutation-RETURNING
4872
5045
  * binding. #213 B2: detection consumes `ctx.lastMutationReturningItems`