@malloydata/malloy-query-builder 0.0.240-dev250311202829 → 0.0.240-dev250311214430

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/src/query-ast.ts CHANGED
@@ -8,11 +8,12 @@ import * as Malloy from '@malloydata/malloy-interfaces';
8
8
  import {Tag, TagSetValue} from '@malloydata/malloy-tag';
9
9
  import * as Filter from '@malloydata/malloy-filter';
10
10
 
11
+ export type FilterType = 'string' | 'boolean' | 'number' | 'temporal';
11
12
  export type ParsedFilter =
12
- | {kind: 'string'; clauses: Filter.StringClause[]}
13
- | {kind: 'number'; clauses: Filter.NumberClause[]}
14
- | {kind: 'boolean'; clauses: Filter.BooleanClause[]}
15
- | {kind: 'date'; clauses: Filter.DateClause[]};
13
+ | {kind: 'string'; parsed: Filter.StringClause | null}
14
+ | {kind: 'number'; parsed: Filter.NumberClause | null}
15
+ | {kind: 'boolean'; parsed: Filter.BooleanClause | null}
16
+ | {kind: 'temporal'; parsed: Filter.TemporalClause | null};
16
17
 
17
18
  export type PathSegment = number | string;
18
19
  export type Path = PathSegment[];
@@ -76,9 +77,9 @@ abstract class ASTNode<T> {
76
77
  if (node instanceof ASTReference) return node;
77
78
  throw new Error('Not an ASTReference');
78
79
  },
79
- SourceReference(): ASTSourceReference {
80
- if (node instanceof ASTSourceReference) return node;
81
- throw new Error('Not an ASTSourceReference');
80
+ ReferenceQueryArrowSource(): ASTReferenceQueryArrowSource {
81
+ if (node instanceof ASTReferenceQueryArrowSource) return node;
82
+ throw new Error('Not an ASTReferenceQueryArrowSource');
82
83
  },
83
84
  ParameterValueList(): ASTParameterValueList {
84
85
  if (node instanceof ASTParameterValueList) return node;
@@ -189,8 +190,8 @@ abstract class ASTNode<T> {
189
190
  Reference(path: Path): ASTReference {
190
191
  return node.findAny(path).as.Reference();
191
192
  },
192
- SourceReference(path: Path): ASTSourceReference {
193
- return node.findAny(path).as.SourceReference();
193
+ ReferenceQueryArrowSource(path: Path): ASTReferenceQueryArrowSource {
194
+ return node.findAny(path).as.ReferenceQueryArrowSource();
194
195
  },
195
196
  ParameterValueList(path: Path): ASTParameterValueList {
196
197
  return node.findAny(path).as.ParameterValueList();
@@ -601,8 +602,11 @@ export class ASTQuery
601
602
  }
602
603
  if (options.query) {
603
604
  const definition = options.query.definition;
604
- if (definition.kind === 'arrow') {
605
- const name = definition.source_reference.name;
605
+ if (
606
+ definition.kind === 'arrow' &&
607
+ definition.source.kind === 'source_reference'
608
+ ) {
609
+ const name = definition.source.name;
606
610
  chosenSource = options.model.entries.find(e => e.name === name);
607
611
  if (chosenSource === undefined) {
608
612
  throw new Error(
@@ -622,7 +626,8 @@ export class ASTQuery
622
626
  const query = options.query ?? {
623
627
  definition: {
624
628
  kind: 'arrow',
625
- source_reference: {
629
+ source: {
630
+ kind: 'source_reference',
626
631
  name: source.name,
627
632
  },
628
633
  view: {
@@ -699,7 +704,7 @@ export class ASTQuery
699
704
  }
700
705
  this.definition = new ASTArrowQueryDefinition({
701
706
  kind: 'arrow',
702
- source_reference: this.definition.sourceReference.build(),
707
+ source: this.definition.source.build(),
703
708
  view: {
704
709
  kind: 'segment',
705
710
  operations: [],
@@ -712,6 +717,14 @@ export class ASTQuery
712
717
  return this.definition.isRunnable();
713
718
  }
714
719
 
720
+ isEmpty() {
721
+ return (
722
+ this.definition instanceof ASTArrowQueryDefinition &&
723
+ this.definition.view instanceof ASTSegmentViewDefinition &&
724
+ this.definition.view.operations.length === 0
725
+ );
726
+ }
727
+
715
728
  /**
716
729
  * Gets an {@link ASTSegmentViewDefinition} for the "default" place to add query
717
730
  * operations, or creates one if it doesn't exist.
@@ -755,7 +768,10 @@ export class ASTQuery
755
768
  */
756
769
  public setSource(name: string) {
757
770
  if (this.definition instanceof ASTArrowQueryDefinition) {
758
- if (this.definition.sourceReference.name === name) {
771
+ if (
772
+ this.definition.source instanceof ASTReferenceQueryArrowSource &&
773
+ this.definition.source.name === name
774
+ ) {
759
775
  return;
760
776
  }
761
777
  }
@@ -765,7 +781,10 @@ export class ASTQuery
765
781
  }
766
782
  this.definition = new ASTArrowQueryDefinition({
767
783
  kind: 'arrow',
768
- source_reference: {name},
784
+ source: {
785
+ kind: 'source_reference',
786
+ name,
787
+ },
769
788
  view:
770
789
  this.definition instanceof ASTArrowQueryDefinition
771
790
  ? this.definition.view.build()
@@ -862,7 +881,7 @@ export class ASTQuery
862
881
  }
863
882
  this.definition = new ASTArrowQueryDefinition({
864
883
  kind: 'arrow',
865
- source_reference: this.definition.sourceReference.build(),
884
+ source: this.definition.source.build(),
866
885
  view: {
867
886
  kind: 'view_reference',
868
887
  name,
@@ -871,18 +890,23 @@ export class ASTQuery
871
890
  return this.definition.view.as.ReferenceViewDefinition();
872
891
  }
873
892
 
874
- getInheritedAnnotations(): Malloy.Annotation[] {
875
- if (this.definition instanceof ASTReferenceQueryDefinition) {
876
- const query = this.getQueryInfo(this.definition.name);
893
+ private _getInheritedAnnotations(
894
+ definition: ASTQueryDefinition
895
+ ): Malloy.Annotation[] {
896
+ if (definition instanceof ASTReferenceQueryDefinition) {
897
+ const query = this.getQueryInfo(definition.name);
877
898
  return query.annotations ?? [];
878
- } else if (this.definition instanceof ASTRefinementQueryDefinition) {
879
- const query = this.getQueryInfo(this.definition.queryReference.name);
880
- return query.annotations ?? [];
881
- } else if (this.definition instanceof ASTArrowQueryDefinition) {
882
- return this.definition.view.getInheritedAnnotations();
899
+ } else if (definition instanceof ASTRefinementQueryDefinition) {
900
+ return this._getInheritedAnnotations(definition.base);
901
+ } else if (definition instanceof ASTArrowQueryDefinition) {
902
+ return definition.view.getInheritedAnnotations();
883
903
  }
884
904
  return [];
885
905
  }
906
+
907
+ getInheritedAnnotations(): Malloy.Annotation[] {
908
+ return this._getInheritedAnnotations(this.definition);
909
+ }
886
910
  }
887
911
 
888
912
  export type RawLiteralValue =
@@ -1050,44 +1074,6 @@ export class ASTFieldReference extends ASTReference {
1050
1074
  }
1051
1075
  }
1052
1076
 
1053
- export class ASTSourceReference extends ASTReference {
1054
- /**
1055
- * @internal
1056
- */
1057
- get query(): ASTQuery {
1058
- return this.parent.parent.as.Query();
1059
- }
1060
-
1061
- /**
1062
- * Gets the `Malloy.SourceInfo` for the referenced source
1063
- *
1064
- * @returns The source information for the referenced source
1065
- */
1066
- public getSourceInfo(): Malloy.SourceInfo {
1067
- const info = this.query.model.entries.find(e => e.name === this.name);
1068
- if (info === undefined) {
1069
- throw new Error('No source info found');
1070
- }
1071
- return info;
1072
- }
1073
-
1074
- public getSourceParameters(): Malloy.ParameterInfo[] {
1075
- return this.getSourceInfo().parameters ?? [];
1076
- }
1077
-
1078
- areRequiredParametersSet() {
1079
- const sourceParameters = this.getSourceParameters();
1080
- for (const parameterInfo of sourceParameters) {
1081
- if (parameterInfo.default_value !== undefined) continue;
1082
- const parameter = this.tryGetParameter(parameterInfo.name);
1083
- if (parameter === undefined) {
1084
- return false;
1085
- }
1086
- }
1087
- return true;
1088
- }
1089
- }
1090
-
1091
1077
  export class ASTParameterValueList extends ASTListNode<
1092
1078
  Malloy.ParameterValue,
1093
1079
  ASTParameterValue
@@ -1339,12 +1325,26 @@ export interface IASTQueryDefinition extends IASTQueryOrViewDefinition {
1339
1325
  isRunnable(): boolean;
1340
1326
  }
1341
1327
 
1328
+ export type ASTQueryArrowSource =
1329
+ | ASTReferenceQueryArrowSource
1330
+ | ASTRefinementQueryDefinition;
1331
+ export const ASTQueryArrowSource = {
1332
+ from(definition: Malloy.QueryArrowSource) {
1333
+ switch (definition.kind) {
1334
+ case 'refinement':
1335
+ return new ASTRefinementQueryDefinition(definition);
1336
+ case 'source_reference':
1337
+ return new ASTReferenceQueryArrowSource(definition);
1338
+ }
1339
+ },
1340
+ };
1341
+
1342
1342
  export type ASTQueryDefinition =
1343
1343
  | ASTReferenceQueryDefinition
1344
1344
  | ASTArrowQueryDefinition
1345
1345
  | ASTRefinementQueryDefinition;
1346
1346
  export const ASTQueryDefinition = {
1347
- from: (definition: Malloy.QueryDefinition) => {
1347
+ from(definition: Malloy.QueryDefinition) {
1348
1348
  switch (definition.kind) {
1349
1349
  case 'arrow':
1350
1350
  return new ASTArrowQueryDefinition(definition);
@@ -1361,7 +1361,7 @@ export class ASTArrowQueryDefinition
1361
1361
  Malloy.QueryDefinitionWithArrow,
1362
1362
  {
1363
1363
  kind: 'arrow';
1364
- source_reference: ASTSourceReference;
1364
+ source: ASTQueryArrowSource;
1365
1365
  view: ASTViewDefinition;
1366
1366
  }
1367
1367
  >
@@ -1370,7 +1370,7 @@ export class ASTArrowQueryDefinition
1370
1370
  constructor(public node: Malloy.QueryDefinitionWithArrow) {
1371
1371
  super(node, {
1372
1372
  kind: 'arrow',
1373
- source_reference: new ASTSourceReference(node.source_reference),
1373
+ source: ASTQueryArrowSource.from(node.source),
1374
1374
  view: ASTViewDefinition.from(node.view),
1375
1375
  });
1376
1376
  }
@@ -1385,8 +1385,13 @@ export class ASTArrowQueryDefinition
1385
1385
  view.parent = this;
1386
1386
  }
1387
1387
 
1388
- get sourceReference() {
1389
- return this.children.source_reference;
1388
+ get source() {
1389
+ return this.children.source;
1390
+ }
1391
+
1392
+ set source(source: ASTQueryArrowSource) {
1393
+ this.edit();
1394
+ this.children.source = source;
1390
1395
  }
1391
1396
 
1392
1397
  getOrAddDefaultSegment(): ASTSegmentViewDefinition {
@@ -1394,7 +1399,7 @@ export class ASTArrowQueryDefinition
1394
1399
  }
1395
1400
 
1396
1401
  getSourceInfo() {
1397
- return this.sourceReference.getSourceInfo();
1402
+ return this.source.getSourceInfo();
1398
1403
  }
1399
1404
 
1400
1405
  getOutputSchema() {
@@ -1402,9 +1407,7 @@ export class ASTArrowQueryDefinition
1402
1407
  }
1403
1408
 
1404
1409
  isRunnable(): boolean {
1405
- return (
1406
- this.view.isRunnable() && this.sourceReference.areRequiredParametersSet()
1407
- );
1410
+ return this.view.isRunnable() && this.source.isRunnable();
1408
1411
  }
1409
1412
 
1410
1413
  /**
@@ -1443,7 +1446,7 @@ export class ASTRefinementQueryDefinition
1443
1446
  Malloy.QueryDefinitionWithRefinement,
1444
1447
  {
1445
1448
  kind: 'refinement';
1446
- query_reference: ASTReference;
1449
+ base: ASTQueryDefinition;
1447
1450
  refinement: ASTViewDefinition;
1448
1451
  }
1449
1452
  >
@@ -1452,13 +1455,13 @@ export class ASTRefinementQueryDefinition
1452
1455
  constructor(public node: Malloy.QueryDefinitionWithRefinement) {
1453
1456
  super(node, {
1454
1457
  kind: 'refinement',
1455
- query_reference: new ASTReference(node.query_reference),
1458
+ base: ASTQueryDefinition.from(node.base),
1456
1459
  refinement: ASTViewDefinition.from(node.refinement),
1457
1460
  });
1458
1461
  }
1459
1462
 
1460
- get queryReference() {
1461
- return this.children.query_reference;
1463
+ get base() {
1464
+ return this.children.base;
1462
1465
  }
1463
1466
 
1464
1467
  get refinement() {
@@ -1487,12 +1490,7 @@ export class ASTRefinementQueryDefinition
1487
1490
  }
1488
1491
 
1489
1492
  getOutputSchema() {
1490
- const model = this.query.model;
1491
- const query = model.entries.find(e => e.name === this.queryReference.name);
1492
- if (query === undefined) {
1493
- throw new Error(`Query not found with name ${this.queryReference.name}`);
1494
- }
1495
- const base = query.schema;
1493
+ const base = this.base.getOutputSchema();
1496
1494
  const refinement = this.refinement.getRefinementSchema();
1497
1495
  return ASTQuery.schemaMerge(base, refinement);
1498
1496
  }
@@ -1515,6 +1513,10 @@ export class ASTRefinementQueryDefinition
1515
1513
  reorderFields(names: string[]): void {
1516
1514
  this.query.getOrAddAnnotations().setTagProperty(['field_order'], names);
1517
1515
  }
1516
+
1517
+ getSourceInfo() {
1518
+ return this.base.getSourceInfo();
1519
+ }
1518
1520
  }
1519
1521
 
1520
1522
  export class ASTReferenceQueryDefinition
@@ -1566,11 +1568,7 @@ export class ASTReferenceQueryDefinition
1566
1568
  getOrAddDefaultSegment(): ASTSegmentViewDefinition {
1567
1569
  const newQuery = new ASTRefinementQueryDefinition({
1568
1570
  kind: 'refinement',
1569
- query_reference: {
1570
- name: this.name,
1571
- path: this.path,
1572
- parameters: this.parameters?.build(),
1573
- },
1571
+ base: this.build(),
1574
1572
  refinement: {
1575
1573
  kind: 'segment',
1576
1574
  operations: [],
@@ -1609,6 +1607,149 @@ export class ASTReferenceQueryDefinition
1609
1607
  public tryGetParameter(name: string): ASTParameterValue | undefined {
1610
1608
  return ASTReference.tryGetParameter(this, name);
1611
1609
  }
1610
+
1611
+ public getOutputSchema() {
1612
+ return this.getSourceInfo().schema;
1613
+ }
1614
+
1615
+ getSourceInfo() {
1616
+ const model = this.query.model;
1617
+ const query = model.entries.find(e => e.name === this.name);
1618
+ if (query === undefined) {
1619
+ throw new Error(`Query not found with name ${this.name}`);
1620
+ }
1621
+ return query;
1622
+ }
1623
+ }
1624
+
1625
+ export class ASTReferenceQueryArrowSource
1626
+ extends ASTObjectNode<
1627
+ Malloy.QueryArrowSourceWithSourceReference,
1628
+ {
1629
+ kind: 'source_reference';
1630
+ name: string;
1631
+ path?: string[];
1632
+ parameters?: ASTParameterValueList;
1633
+ }
1634
+ >
1635
+ implements IASTReference
1636
+ {
1637
+ constructor(public node: Malloy.QueryArrowSourceWithSourceReference) {
1638
+ super(node, {
1639
+ kind: 'source_reference',
1640
+ name: node.name,
1641
+ path: node.path,
1642
+ parameters: node.parameters && new ASTParameterValueList(node.parameters),
1643
+ });
1644
+ }
1645
+
1646
+ isRunnable(): boolean {
1647
+ return this.areRequiredParametersSet();
1648
+ }
1649
+
1650
+ get name() {
1651
+ return this.children.name;
1652
+ }
1653
+
1654
+ get arrow() {
1655
+ return this.parent.as.ArrowQueryDefinition();
1656
+ }
1657
+
1658
+ get parameters() {
1659
+ return this.children.parameters;
1660
+ }
1661
+
1662
+ set parameters(parameters: ASTParameterValueList | undefined) {
1663
+ this.edit();
1664
+ this.children.parameters = parameters;
1665
+ }
1666
+
1667
+ get path() {
1668
+ return this.children.path;
1669
+ }
1670
+
1671
+ getSourceInfo() {
1672
+ const entry = this.arrow.query.model.entries.find(
1673
+ entry => entry.name === this.name
1674
+ );
1675
+ if (entry === undefined) {
1676
+ throw new Error(`No query or source named ${this.name}`);
1677
+ }
1678
+ return entry;
1679
+ }
1680
+
1681
+ getOrAddDefaultSegment(): ASTSegmentViewDefinition {
1682
+ const entry = this.getSourceInfo();
1683
+ if (entry.kind !== 'query') {
1684
+ throw new Error(`Cannot refine source ${this.name}`);
1685
+ }
1686
+ const newQuery = new ASTRefinementQueryDefinition({
1687
+ kind: 'refinement',
1688
+ base: {
1689
+ ...this.build(),
1690
+ kind: 'query_reference',
1691
+ },
1692
+ refinement: {
1693
+ kind: 'segment',
1694
+ operations: [],
1695
+ },
1696
+ });
1697
+ this.arrow.source = newQuery;
1698
+ return newQuery.refinement.as.SegmentViewDefinition();
1699
+ }
1700
+
1701
+ /**
1702
+ * @internal
1703
+ */
1704
+ propagateUp(_f: PropagationFunction): void {
1705
+ return;
1706
+ }
1707
+
1708
+ /**
1709
+ * @internal
1710
+ */
1711
+ propagateDown(_f: PropagationFunction): void {
1712
+ return;
1713
+ }
1714
+
1715
+ public getOrAddParameters() {
1716
+ return ASTReference.getOrAddParameters(this);
1717
+ }
1718
+
1719
+ public setParameter(name: string, value: RawLiteralValue) {
1720
+ return ASTReference.setParameter(this, name, value);
1721
+ }
1722
+
1723
+ public tryGetParameter(name: string): ASTParameterValue | undefined {
1724
+ return ASTReference.tryGetParameter(this, name);
1725
+ }
1726
+
1727
+ /**
1728
+ * @internal
1729
+ */
1730
+ get query(): ASTQuery {
1731
+ return this.parent.parent.as.Query();
1732
+ }
1733
+
1734
+ public getSourceParameters(): Malloy.ParameterInfo[] {
1735
+ const sourceInfo = this.getSourceInfo();
1736
+ if (sourceInfo.kind === 'query') {
1737
+ return [];
1738
+ }
1739
+ return sourceInfo.parameters ?? [];
1740
+ }
1741
+
1742
+ areRequiredParametersSet() {
1743
+ const sourceParameters = this.getSourceParameters();
1744
+ for (const parameterInfo of sourceParameters) {
1745
+ if (parameterInfo.default_value !== undefined) continue;
1746
+ const parameter = this.tryGetParameter(parameterInfo.name);
1747
+ if (parameter === undefined) {
1748
+ return false;
1749
+ }
1750
+ }
1751
+ return true;
1752
+ }
1612
1753
  }
1613
1754
 
1614
1755
  export interface IASTViewDefinition extends IASTQueryOrViewDefinition {
@@ -3966,7 +4107,7 @@ export class ASTFilterWithFilterString extends ASTObjectNode<
3966
4107
  return field;
3967
4108
  }
3968
4109
 
3969
- getFilterType(): 'string' | 'boolean' | 'number' | 'date' | 'other' {
4110
+ getFilterType(): FilterType | 'other' {
3970
4111
  const fieldInfo = this.getFieldInfo();
3971
4112
  return getFilterType(fieldInfo);
3972
4113
  }
@@ -4328,20 +4469,17 @@ function tagFromAnnotations(
4328
4469
  function serializeFilter(filter: ParsedFilter) {
4329
4470
  switch (filter.kind) {
4330
4471
  case 'string':
4331
- return new Filter.StringSerializer(filter.clauses).serialize();
4472
+ return Filter.StringFilterExpression.unparse(filter.parsed);
4332
4473
  case 'number':
4333
- return new Filter.NumberSerializer(filter.clauses).serialize();
4474
+ return Filter.NumberFilterExpression.unparse(filter.parsed);
4334
4475
  case 'boolean':
4335
- return new Filter.BooleanSerializer(filter.clauses).serialize();
4336
- case 'date':
4337
- return new Filter.DateSerializer(filter.clauses).serialize();
4476
+ return Filter.BooleanFilterExpression.unparse(filter.parsed);
4477
+ case 'temporal':
4478
+ return Filter.TemporalFilterExpression.unparse(filter.parsed);
4338
4479
  }
4339
4480
  }
4340
4481
 
4341
- function parseFilter(
4342
- filterString: string,
4343
- kind: 'string' | 'number' | 'boolean' | 'date' | 'other'
4344
- ) {
4482
+ function parseFilter(filterString: string, kind: FilterType | 'other') {
4345
4483
  function handleError(logs: Filter.FilterLog[]) {
4346
4484
  const errors = logs.filter(l => l.severity === 'error');
4347
4485
  if (errors.length === 0) return;
@@ -4349,24 +4487,24 @@ function parseFilter(
4349
4487
  }
4350
4488
  switch (kind) {
4351
4489
  case 'string': {
4352
- const result = new Filter.StringParser(filterString).parse();
4353
- handleError(result.logs);
4354
- return {kind, clauses: result.clauses};
4490
+ const result = Filter.StringFilterExpression.parse(filterString);
4491
+ handleError(result.log);
4492
+ return {kind, parsed: result.parsed};
4355
4493
  }
4356
4494
  case 'number': {
4357
- const result = new Filter.NumberParser(filterString).parse();
4358
- handleError(result.logs);
4359
- return {kind, clauses: result.clauses};
4495
+ const result = Filter.NumberFilterExpression.parse(filterString);
4496
+ handleError(result.log);
4497
+ return {kind, parsed: result.parsed};
4360
4498
  }
4361
4499
  case 'boolean': {
4362
- const result = new Filter.BooleanParser(filterString).parse();
4363
- handleError(result.logs);
4364
- return {kind, clauses: result.clauses};
4500
+ const result = Filter.BooleanFilterExpression.parse(filterString);
4501
+ handleError(result.log);
4502
+ return {kind, parsed: result.parsed};
4365
4503
  }
4366
- case 'date': {
4367
- const result = new Filter.DateParser(filterString).parse();
4368
- handleError(result.logs);
4369
- return {kind, clauses: result.clauses};
4504
+ case 'temporal': {
4505
+ const result = Filter.TemporalFilterExpression.parse(filterString);
4506
+ handleError(result.log);
4507
+ return {kind, parsed: result.parsed};
4370
4508
  }
4371
4509
  case 'other':
4372
4510
  throw new Error('Not implemented');
@@ -4427,7 +4565,7 @@ function pathsMatch(a: string[] | undefined, b: string[] | undefined): boolean {
4427
4565
 
4428
4566
  function getFilterType(
4429
4567
  fieldInfo: Malloy.FieldInfoWithDimension | Malloy.FieldInfoWithMeasure
4430
- ): 'string' | 'boolean' | 'number' | 'date' | 'other' {
4568
+ ): FilterType | 'other' {
4431
4569
  switch (fieldInfo.type.kind) {
4432
4570
  case 'string_type':
4433
4571
  return 'string';
@@ -4437,7 +4575,7 @@ function getFilterType(
4437
4575
  return 'number';
4438
4576
  case 'date_type':
4439
4577
  case 'timestamp_type':
4440
- return 'date';
4578
+ return 'temporal';
4441
4579
  default:
4442
4580
  return 'other';
4443
4581
  }