@malloydata/malloy 0.0.16-dev230104154319 → 0.0.16

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.
@@ -28,6 +28,7 @@ export declare abstract class Dialect {
28
28
  abstract supportUnnestArrayAgg: boolean;
29
29
  abstract supportsCTEinCoorelatedSubQueries: boolean;
30
30
  abstract dontUnionIndex: boolean;
31
+ abstract supportsQualify: boolean;
31
32
  abstract quoteTablePath(tablePath: string): string;
32
33
  abstract sqlGroupSetTable(groupSetCount: number): string;
33
34
  abstract sqlAnyValue(groupSet: number, fieldName: string): string;
@@ -14,6 +14,7 @@ export declare class DuckDBDialect extends Dialect {
14
14
  supportUnnestArrayAgg: boolean;
15
15
  supportsCTEinCoorelatedSubQueries: boolean;
16
16
  dontUnionIndex: boolean;
17
+ supportsQualify: boolean;
17
18
  functionInfo: Record<string, FunctionInfo>;
18
19
  get udfPrefix(): string;
19
20
  quoteTablePath(tableName: string): string;
@@ -124,6 +124,7 @@ class DuckDBDialect extends dialect_1.Dialect {
124
124
  this.supportUnnestArrayAgg = true;
125
125
  this.supportsCTEinCoorelatedSubQueries = true;
126
126
  this.dontUnionIndex = false;
127
+ this.supportsQualify = true;
127
128
  this.functionInfo = {
128
129
  concat: { returnType: "string" },
129
130
  };
@@ -15,6 +15,7 @@ export declare class PostgresDialect extends Dialect {
15
15
  supportUnnestArrayAgg: boolean;
16
16
  supportsCTEinCoorelatedSubQueries: boolean;
17
17
  dontUnionIndex: boolean;
18
+ supportsQualify: boolean;
18
19
  functionInfo: Record<string, FunctionInfo>;
19
20
  quoteTablePath(tablePath: string): string;
20
21
  sqlGroupSetTable(groupSetCount: number): string;
@@ -53,6 +53,7 @@ class PostgresDialect extends dialect_1.Dialect {
53
53
  this.supportUnnestArrayAgg = true;
54
54
  this.supportsCTEinCoorelatedSubQueries = true;
55
55
  this.dontUnionIndex = false;
56
+ this.supportsQualify = false;
56
57
  this.functionInfo = {
57
58
  concat: { returnType: "string" },
58
59
  };
@@ -15,6 +15,7 @@ export declare class StandardSQLDialect extends Dialect {
15
15
  supportUnnestArrayAgg: boolean;
16
16
  supportsCTEinCoorelatedSubQueries: boolean;
17
17
  dontUnionIndex: boolean;
18
+ supportsQualify: boolean;
18
19
  functionInfo: Record<string, FunctionInfo>;
19
20
  quoteTablePath(tablePath: string): string;
20
21
  sqlGroupSetTable(groupSetCount: number): string;
@@ -47,8 +47,12 @@ class StandardSQLDialect extends dialect_1.Dialect {
47
47
  this.supportUnnestArrayAgg = false;
48
48
  this.supportsCTEinCoorelatedSubQueries = false;
49
49
  this.dontUnionIndex = true; // bigquery can't use a sample table more than once in a query.
50
+ this.supportsQualify = true;
51
+ // I think we want an optional list of parameters types that we force a cast to.
50
52
  this.functionInfo = {
51
- timestamp_seconds: { returnType: "timestamp" },
53
+ timestamp_seconds: {
54
+ returnType: "timestamp",
55
+ },
52
56
  concat: { returnType: "string" },
53
57
  };
54
58
  this.keywords = `
@@ -57,7 +57,7 @@ function applyBinary(fs, left, op, right) {
57
57
  };
58
58
  return {
59
59
  dataType: "number",
60
- aggregate: num.aggregate || denom.aggregate,
60
+ expressionType: (0, malloy_types_1.maxExpressionType)(num.expressionType, denom.expressionType),
61
61
  value: [div],
62
62
  };
63
63
  }
@@ -178,29 +178,29 @@ function equality(fs, left, op, right) {
178
178
  }
179
179
  return {
180
180
  dataType: "boolean",
181
- aggregate: lhs.aggregate || rhs.aggregate,
181
+ expressionType: (0, malloy_types_1.maxExpressionType)(lhs.expressionType, rhs.expressionType),
182
182
  value,
183
183
  };
184
184
  }
185
185
  function compare(fs, left, op, right) {
186
186
  const lhs = left.getExpression(fs);
187
187
  const rhs = right.getExpression(fs);
188
- const anyAggregate = lhs.aggregate || rhs.aggregate;
188
+ const expressionType = (0, malloy_types_1.maxExpressionType)(lhs.expressionType, rhs.expressionType);
189
189
  const value = timeCompare(lhs, op, rhs) || (0, index_1.compose)(lhs.value, op, rhs.value);
190
190
  return {
191
191
  dataType: "boolean",
192
- aggregate: anyAggregate,
192
+ expressionType,
193
193
  value: value,
194
194
  };
195
195
  }
196
196
  function numeric(fs, left, op, right) {
197
197
  const lhs = left.getExpression(fs);
198
198
  const rhs = right.getExpression(fs);
199
- const anyAggregate = lhs.aggregate || rhs.aggregate;
199
+ const expressionType = (0, malloy_types_1.maxExpressionType)(lhs.expressionType, rhs.expressionType);
200
200
  if (allAre("number", lhs, rhs)) {
201
201
  return {
202
202
  dataType: "number",
203
- aggregate: anyAggregate,
203
+ expressionType,
204
204
  value: (0, index_1.compose)(lhs.value, op, rhs.value),
205
205
  };
206
206
  }
@@ -1,4 +1,4 @@
1
- import { By, AtomicFieldType, FieldTypeDef, Fragment, TimeFieldType } from "../../model/malloy_types";
1
+ import { By, AtomicFieldType, FieldTypeDef, Fragment, TimeFieldType, ExpressionType } from "../../model/malloy_types";
2
2
  import { FieldSpace } from "../field-space";
3
3
  import { Filter, MalloyElement, ExprValue, FieldValueType, FragType } from "./index";
4
4
  import { FieldName, FieldReference } from "./ast-main";
@@ -92,7 +92,7 @@ export declare class ExprRegEx extends ExpressionDef {
92
92
  export declare class ExprTime extends ExpressionDef {
93
93
  elementType: string;
94
94
  readonly translationValue: ExprValue;
95
- constructor(timeType: TimeFieldType, value: Fragment[] | string, aggregate?: boolean);
95
+ constructor(timeType: TimeFieldType, value: Fragment[] | string, expressionType?: ExpressionType);
96
96
  getExpression(_fs: FieldSpace): ExprValue;
97
97
  static fromValue(timeType: TimeFieldType, expr: ExprValue): ExprTime;
98
98
  }
@@ -123,7 +123,11 @@ export declare abstract class BinaryBoolean<opType extends string> extends Expre
123
123
  }
124
124
  export declare class ExprLogicalOp extends BinaryBoolean<"and" | "or"> {
125
125
  elementType: string;
126
- legalChildTypes: FragType[];
126
+ legalChildTypes: (FragType | {
127
+ aggregate: boolean;
128
+ dataType: FieldValueType;
129
+ expressionType: ExpressionType;
130
+ })[];
127
131
  }
128
132
  export declare class ExprIdReference extends ExpressionDef {
129
133
  readonly fieldReference: FieldReference;
@@ -87,7 +87,7 @@ class DollarReference extends ExpressionDef {
87
87
  return {
88
88
  dataType: this.refType,
89
89
  value: [{ type: "applyVal" }],
90
- aggregate: false,
90
+ expressionType: "scalar",
91
91
  };
92
92
  }
93
93
  }
@@ -188,8 +188,8 @@ class FieldDeclaration extends index_1.MalloyElement {
188
188
  if (compressValue.length > 0) {
189
189
  template.e = compressValue;
190
190
  }
191
- if (exprValue.aggregate) {
192
- template.aggregate = true;
191
+ if (exprValue.expressionType) {
192
+ template.expressionType = exprValue.expressionType;
193
193
  }
194
194
  if (this.exprSrc) {
195
195
  template.code = this.exprSrc;
@@ -265,20 +265,20 @@ class ExprRegEx extends ExpressionDef {
265
265
  getExpression() {
266
266
  return {
267
267
  dataType: "regular expression",
268
- aggregate: false,
268
+ expressionType: "scalar",
269
269
  value: [`r'${this.regex}'`],
270
270
  };
271
271
  }
272
272
  }
273
273
  exports.ExprRegEx = ExprRegEx;
274
274
  class ExprTime extends ExpressionDef {
275
- constructor(timeType, value, aggregate = false) {
275
+ constructor(timeType, value, expressionType = "scalar") {
276
276
  super();
277
277
  this.elementType = "timestampOrDate";
278
278
  this.elementType = timeType;
279
279
  this.translationValue = {
280
280
  dataType: timeType,
281
- aggregate: aggregate,
281
+ expressionType,
282
282
  value: typeof value === "string" ? [value] : value,
283
283
  };
284
284
  }
@@ -300,7 +300,7 @@ class ExprTime extends ExpressionDef {
300
300
  }
301
301
  value = (0, index_1.compressExpr)([toTs]);
302
302
  }
303
- return new ExprTime(timeType, value, expr.aggregate);
303
+ return new ExprTime(timeType, value, expr.expressionType);
304
304
  }
305
305
  }
306
306
  exports.ExprTime = ExprTime;
@@ -355,7 +355,7 @@ class BinaryBoolean extends ExpressionDef {
355
355
  if (this.typeCheck(this.left, left) && this.typeCheck(this.right, right)) {
356
356
  return {
357
357
  dataType: "boolean",
358
- aggregate: left.aggregate || right.aggregate,
358
+ expressionType: (0, malloy_types_1.maxExpressionType)(left.expressionType, right.expressionType),
359
359
  value: (0, index_1.compose)(left.value, this.op, right.value),
360
360
  };
361
361
  }
@@ -387,9 +387,9 @@ class ExprIdReference extends ExpressionDef {
387
387
  // TODO if type is a query or a struct this should fail nicely
388
388
  const typeMixin = def.found.type();
389
389
  const dataType = typeMixin.type;
390
- const aggregate = !!typeMixin.aggregate;
390
+ const expressionType = typeMixin.expressionType || "scalar";
391
391
  const value = [{ type: def.found.refType, path: this.refString }];
392
- return { dataType, aggregate, value };
392
+ return { dataType, expressionType, value };
393
393
  }
394
394
  this.log(def.error);
395
395
  return (0, index_1.errorFor)(def.error);
@@ -402,7 +402,7 @@ class ExprIdReference extends ExpressionDef {
402
402
  const lval = expr.getExpression(fs);
403
403
  return {
404
404
  dataType: "boolean",
405
- aggregate: lval.aggregate,
405
+ expressionType: lval.expressionType,
406
406
  value: [
407
407
  {
408
408
  type: "apply",
@@ -426,7 +426,7 @@ class ExprNULL extends ExpressionDef {
426
426
  return {
427
427
  dataType: "null",
428
428
  value: ["NULL"],
429
- aggregate: false,
429
+ expressionType: "scalar",
430
430
  };
431
431
  }
432
432
  }
@@ -504,7 +504,7 @@ class ExprAlternationTree extends BinaryBoolean {
504
504
  const choice2 = this.right.apply(fs, applyOp, expr);
505
505
  return {
506
506
  dataType: "boolean",
507
- aggregate: choice1.aggregate || choice2.aggregate,
507
+ expressionType: (0, malloy_types_1.maxExpressionType)(choice1.expressionType, choice2.expressionType),
508
508
  value: (0, index_1.compose)(choice1.value, this.op === "&" ? "and" : "or", choice2.value),
509
509
  };
510
510
  }
@@ -542,7 +542,7 @@ class ExprAggregateFunction extends ExpressionDef {
542
542
  if ((0, malloy_types_1.isAtomicFieldType)(footType.type)) {
543
543
  exprVal = {
544
544
  dataType: footType.type,
545
- aggregate: !!footType.aggregate,
545
+ expressionType: footType.expressionType || "scalar",
546
546
  value: [{ type: "field", path: this.source.refString }],
547
547
  };
548
548
  structPath = this.source.sourceString;
@@ -563,7 +563,10 @@ class ExprAggregateFunction extends ExpressionDef {
563
563
  this.log("Missing expression for aggregate function");
564
564
  return (0, index_1.errorFor)("agggregate without expression");
565
565
  }
566
- if (this.typeCheck(this.expr || this, { ...exprVal, aggregate: false })) {
566
+ if (this.typeCheck(this.expr || this, {
567
+ ...exprVal,
568
+ expressionType: "scalar",
569
+ })) {
567
570
  const f = {
568
571
  type: "aggregate",
569
572
  function: this.func,
@@ -574,7 +577,7 @@ class ExprAggregateFunction extends ExpressionDef {
574
577
  }
575
578
  return {
576
579
  dataType: this.returns(exprVal),
577
- aggregate: true,
580
+ expressionType: "aggregate",
578
581
  value: [f],
579
582
  };
580
583
  }
@@ -653,7 +656,7 @@ class ExprCount extends ExprAggregateFunction {
653
656
  }
654
657
  return {
655
658
  dataType: "number",
656
- aggregate: true,
659
+ expressionType: "aggregate",
657
660
  value: [ret],
658
661
  };
659
662
  }
@@ -687,12 +690,12 @@ class ExprUngroup extends ExpressionDef {
687
690
  }
688
691
  getExpression(fs) {
689
692
  const exprVal = this.expr.getExpression(fs);
690
- if (!exprVal.aggregate) {
693
+ if (!(0, malloy_types_1.expressionIsAggregate)(exprVal.expressionType)) {
691
694
  this.expr.log(`${this.control}() expression must be an aggregate`);
692
695
  return (0, index_1.errorFor)("ungrouped scalar");
693
696
  }
694
697
  const ungroup = { type: this.control, e: exprVal.value };
695
- if (this.typeCheck(this.expr, { ...exprVal, aggregate: false })) {
698
+ if (this.typeCheck(this.expr, { ...exprVal, expressionType: "scalar" })) {
696
699
  if (this.fields.length > 0) {
697
700
  let qs = fs;
698
701
  if (fs instanceof field_space_1.DefSpace) {
@@ -715,7 +718,7 @@ class ExprUngroup extends ExpressionDef {
715
718
  }
716
719
  return {
717
720
  dataType: this.returns(exprVal),
718
- aggregate: true,
721
+ expressionType: "analytic",
719
722
  value: [ungroup],
720
723
  };
721
724
  }
@@ -746,12 +749,12 @@ class ExprCase extends ExpressionDef {
746
749
  }
747
750
  getExpression(fs) {
748
751
  let retType;
749
- let aggregate = false;
752
+ let expressionType = "scalar";
750
753
  const caseExpr = ["CASE "];
751
754
  for (const clause of this.when) {
752
755
  const whenExpr = clause.whenThis.getExpression(fs);
753
756
  const thenExpr = clause.thenThis.getExpression(fs);
754
- aggregate || (aggregate = whenExpr.aggregate || thenExpr.aggregate);
757
+ expressionType = (0, malloy_types_1.maxExpressionType)(expressionType, (0, malloy_types_1.maxExpressionType)(whenExpr.expressionType, thenExpr.expressionType));
755
758
  if (thenExpr.dataType !== "null") {
756
759
  if (retType && !index_1.FT.typeEq(retType, thenExpr)) {
757
760
  this.log(`Mismatched THEN clause types, ${index_1.FT.inspect(retType, thenExpr)}`);
@@ -765,7 +768,7 @@ class ExprCase extends ExpressionDef {
765
768
  }
766
769
  if (this.elseClause) {
767
770
  const elseExpr = this.elseClause.getExpression(fs);
768
- aggregate || (aggregate = elseExpr.aggregate);
771
+ expressionType = (0, malloy_types_1.maxExpressionType)(expressionType, elseExpr.expressionType);
769
772
  caseExpr.push(" ELSE ", ...elseExpr.value);
770
773
  if (elseExpr.dataType !== "null") {
771
774
  if (retType && !index_1.FT.typeEq(retType, elseExpr)) {
@@ -784,7 +787,7 @@ class ExprCase extends ExpressionDef {
784
787
  caseExpr.push(" END");
785
788
  return {
786
789
  dataType: retType.dataType,
787
- aggregate: aggregate,
790
+ expressionType,
788
791
  value: caseExpr,
789
792
  };
790
793
  }
@@ -802,17 +805,17 @@ class ExprFilter extends ExpressionDef {
802
805
  const testList = this.filter.getFilterList(fs);
803
806
  const resultExpr = this.expr.getExpression(fs);
804
807
  for (const cond of testList) {
805
- if (cond.aggregate) {
806
- this.filter.log("Cannot filter a field with an aggregate computation");
808
+ if ((0, malloy_types_1.expressionIsCalculation)(cond.expressionType)) {
809
+ this.filter.log("Cannot filter a field with an aggregate or analytical computation");
807
810
  return (0, index_1.errorFor)("no filter on aggregate");
808
811
  }
809
812
  }
810
- if (!resultExpr.aggregate) {
813
+ if (resultExpr.expressionType === "scalar") {
811
814
  // TODO could log a warning, but I have a problem with the
812
815
  // idea of warnings, so for now ...
813
816
  return resultExpr;
814
817
  }
815
- if (this.typeCheck(this.expr, { ...resultExpr, aggregate: false })) {
818
+ if (this.typeCheck(this.expr, { ...resultExpr, expressionType: "scalar" })) {
816
819
  return {
817
820
  ...resultExpr,
818
821
  value: [
@@ -845,7 +848,7 @@ class ExprCast extends ExpressionDef {
845
848
  const expr = this.expr.getExpression(fs);
846
849
  return {
847
850
  dataType: this.castType,
848
- aggregate: expr.aggregate,
851
+ expressionType: expr.expressionType,
849
852
  value: (0, index_1.compressExpr)((0, time_utils_1.castTo)(this.castType, expr.value, this.safe)),
850
853
  };
851
854
  }
@@ -863,7 +866,7 @@ class TopBy extends index_1.MalloyElement {
863
866
  getBy(fs) {
864
867
  if (this.by instanceof ExpressionDef) {
865
868
  const byExpr = this.by.getExpression(fs);
866
- if (!byExpr.aggregate) {
869
+ if (!(0, malloy_types_1.expressionIsAggregate)(byExpr.expressionType)) {
867
870
  this.log("top by expression must be an aggregate");
868
871
  }
869
872
  return { by: "expression", e: (0, index_1.compressExpr)(byExpr.value) };
@@ -890,7 +893,7 @@ class Range extends ExpressionDef {
890
893
  const toValue = this.last.apply(fs, op3, expr);
891
894
  return {
892
895
  dataType: "boolean",
893
- aggregate: fromValue.aggregate || toValue.aggregate,
896
+ expressionType: (0, malloy_types_1.maxExpressionType)(fromValue.expressionType, toValue.expressionType),
894
897
  value: (0, index_1.compose)(fromValue.value, op2, toValue.value),
895
898
  };
896
899
  }
@@ -961,13 +964,13 @@ class Pick extends ExpressionDef {
961
964
  apply(fs, op, expr) {
962
965
  const caseValue = ["CASE"];
963
966
  let returnType;
964
- let anyAggregate = false;
967
+ let anyExpressionType = "scalar";
965
968
  for (const choice of this.choices) {
966
969
  const whenExpr = choice.when.apply(fs, "=", expr);
967
970
  const thenExpr = choice.pick
968
971
  ? choice.pick.getExpression(fs)
969
972
  : expr.getExpression(fs);
970
- anyAggregate || (anyAggregate = whenExpr.aggregate || thenExpr.aggregate);
973
+ anyExpressionType = (0, malloy_types_1.maxExpressionType)(anyExpressionType, (0, malloy_types_1.maxExpressionType)(whenExpr.expressionType, thenExpr.expressionType));
971
974
  if (returnType) {
972
975
  if (!index_1.FT.typeEq(returnType, thenExpr, true)) {
973
976
  const whenType = index_1.FT.inspect(thenExpr);
@@ -990,7 +993,7 @@ class Pick extends ExpressionDef {
990
993
  }
991
994
  return {
992
995
  dataType: returnType.dataType,
993
- aggregate: anyAggregate || elseVal.aggregate,
996
+ expressionType: (0, malloy_types_1.maxExpressionType)(anyExpressionType, elseVal.expressionType),
994
997
  value: (0, index_1.compressExpr)([...caseValue, " ELSE ", ...elseVal.value, " END"]),
995
998
  };
996
999
  }
@@ -1017,7 +1020,7 @@ class Pick extends ExpressionDef {
1017
1020
  }
1018
1021
  const returnType = choiceValues[0].pick;
1019
1022
  const caseValue = ["CASE"];
1020
- let anyAggregate = returnType.aggregate;
1023
+ let anyExpressionType = returnType.expressionType;
1021
1024
  for (const aChoice of choiceValues) {
1022
1025
  if (!index_1.FT.typeEq(aChoice.when, index_1.FT.boolT)) {
1023
1026
  this.log(`when expression must be boolean, not '${index_1.FT.inspect(aChoice.when)}`);
@@ -1028,11 +1031,11 @@ class Pick extends ExpressionDef {
1028
1031
  this.log(`pick type '${whenType}', expected '${returnType.dataType}'`);
1029
1032
  return (0, index_1.errorFor)("pick value type");
1030
1033
  }
1031
- anyAggregate || (anyAggregate = aChoice.pick.aggregate || aChoice.when.aggregate);
1034
+ anyExpressionType = (0, malloy_types_1.maxExpressionType)(anyExpressionType, (0, malloy_types_1.maxExpressionType)(aChoice.pick.expressionType, aChoice.when.expressionType));
1032
1035
  caseValue.push(" WHEN ", ...aChoice.when.value, " THEN ", ...aChoice.pick.value);
1033
1036
  }
1034
1037
  const defVal = this.elsePick.getExpression(fs);
1035
- anyAggregate || (anyAggregate = defVal.aggregate);
1038
+ anyExpressionType = (0, malloy_types_1.maxExpressionType)(anyExpressionType, defVal.expressionType);
1036
1039
  if (!index_1.FT.typeEq(returnType, defVal, true)) {
1037
1040
  this.elsePick.log(`else type '${index_1.FT.inspect(defVal)}', expected '${returnType.dataType}'`);
1038
1041
  return (0, index_1.errorFor)("pick value type mismatch");
@@ -1040,7 +1043,7 @@ class Pick extends ExpressionDef {
1040
1043
  caseValue.push(" ELSE ", ...defVal.value, " END");
1041
1044
  return {
1042
1045
  dataType: returnType.dataType,
1043
- aggregate: !!anyAggregate,
1046
+ expressionType: anyExpressionType,
1044
1047
  value: (0, index_1.compressExpr)(caseValue),
1045
1048
  };
1046
1049
  }
@@ -546,7 +546,7 @@ class RefinedExplore extends Mallobj {
546
546
  for (const filter of filters) {
547
547
  for (const el of filter.list) {
548
548
  const fc = el.filterExpression(fs);
549
- if (fc.aggregate) {
549
+ if (model.expressionIsCalculation(fc.expressionType)) {
550
550
  el.log("Can't use aggregate computations in top level filters");
551
551
  }
552
552
  else {
@@ -925,15 +925,14 @@ class FilterElement extends MalloyElement {
925
925
  return {
926
926
  code: this.exprSrc,
927
927
  expression: ["_FILTER_MUST_RETURN_BOOLEAN_"],
928
+ expressionType: "scalar",
928
929
  };
929
930
  }
930
931
  const exprCond = {
931
932
  code: this.exprSrc,
932
933
  expression: (0, index_1.compressExpr)(exprVal.value),
934
+ expressionType: exprVal.expressionType,
933
935
  };
934
- if (exprVal.aggregate) {
935
- exprCond.aggregate = true;
936
- }
937
936
  return exprCond;
938
937
  }
939
938
  }
@@ -954,14 +953,14 @@ class Filter extends ListOf {
954
953
  // here allows better reflection of errors back to user.
955
954
  if (this.havingClause !== undefined) {
956
955
  if (this.havingClause) {
957
- if (!fExpr.aggregate) {
958
- oneElement.log("Aggregate expression expected in HAVING filter");
956
+ if (model.expressionIsCalculation(fExpr.expressionType)) {
957
+ oneElement.log("Aggregate or Analytical expression expected in HAVING filter");
959
958
  continue;
960
959
  }
961
960
  }
962
961
  else {
963
- if (fExpr.aggregate) {
964
- oneElement.log("Aggregate expression not allowed in WHERE");
962
+ if (fExpr.expressionType !== "scalar") {
963
+ oneElement.log("Aggregate or Analytical expressions not allowed in WHERE");
965
964
  continue;
966
965
  }
967
966
  }
@@ -1921,7 +1920,7 @@ class Top extends MalloyElement {
1921
1920
  }
1922
1921
  else {
1923
1922
  const byExpr = this.by.getExpression(fs);
1924
- if (!byExpr.aggregate) {
1923
+ if (model.expressionIsAggregate(byExpr.expressionType)) {
1925
1924
  this.log("top by expression must be an aggregate");
1926
1925
  }
1927
1926
  return { by: "expression", e: (0, index_1.compressExpr)(byExpr.value) };
@@ -98,15 +98,15 @@ class ExprGranularTime extends index_1.ExpressionDef {
98
98
  timestampRange(fs, op, expr) {
99
99
  const begin = this.getExpression(fs);
100
100
  const beginTime = index_1.ExprTime.fromValue("timestamp", begin);
101
- const endTime = new index_1.ExprTime("timestamp", (0, index_1.timeOffset)("timestamp", begin.value, "+", (0, malloy_types_1.mkExpr) `1`, this.units), begin.aggregate);
101
+ const endTime = new index_1.ExprTime("timestamp", (0, index_1.timeOffset)("timestamp", begin.value, "+", (0, malloy_types_1.mkExpr) `1`, this.units), begin.expressionType);
102
102
  const range = new index_1.Range(beginTime, endTime);
103
103
  return range.apply(fs, op, expr);
104
104
  }
105
105
  dateRange(fs, op, expr) {
106
106
  const begin = this.getExpression(fs);
107
- const beginTime = new index_1.ExprTime("date", begin.value, begin.aggregate);
107
+ const beginTime = new index_1.ExprTime("date", begin.value, begin.expressionType);
108
108
  const endAt = (0, index_1.timeOffset)("date", begin.value, "+", ["1"], this.units);
109
- const end = new index_1.ExprTime("date", endAt, begin.aggregate);
109
+ const end = new index_1.ExprTime("date", endAt, begin.expressionType);
110
110
  const range = new index_1.Range(beginTime, end);
111
111
  return range.apply(fs, op, expr);
112
112
  }
@@ -206,7 +206,7 @@ class GranularLiteral extends index_1.ExpressionDef {
206
206
  const dataType = this.timeType || "date";
207
207
  const value = {
208
208
  dataType,
209
- aggregate: false,
209
+ expressionType: "scalar",
210
210
  value: (0, index_1.timeLiteral)(this.moment, dataType, "UTC"),
211
211
  };
212
212
  // Literals with date resolution can be used as timestamps or dates,
@@ -236,7 +236,7 @@ class ExprNow extends index_1.ExpressionDef {
236
236
  getExpression(_fs) {
237
237
  return {
238
238
  dataType: "timestamp",
239
- aggregate: false,
239
+ expressionType: "scalar",
240
240
  value: [
241
241
  {
242
242
  type: "dialect",
@@ -275,13 +275,13 @@ class ExprDuration extends index_1.ExpressionDef {
275
275
  const result = (0, index_1.timeOffset)("timestamp", lhs.value, op, num.value, this.timeframe);
276
276
  return (0, index_1.timeResult)({
277
277
  dataType: "timestamp",
278
- aggregate: lhs.aggregate || num.aggregate,
278
+ expressionType: (0, malloy_types_1.maxExpressionType)(lhs.expressionType, num.expressionType),
279
279
  value: result,
280
280
  }, resultGranularity);
281
281
  }
282
282
  return (0, index_1.timeResult)({
283
283
  dataType: "date",
284
- aggregate: lhs.aggregate || num.aggregate,
284
+ expressionType: (0, malloy_types_1.maxExpressionType)(lhs.expressionType, num.expressionType),
285
285
  value: (0, index_1.timeOffset)("date", lhs.value, op, num.value, this.timeframe),
286
286
  }, resultGranularity);
287
287
  }
@@ -290,7 +290,7 @@ class ExprDuration extends index_1.ExpressionDef {
290
290
  getExpression(_fs) {
291
291
  return {
292
292
  dataType: "duration",
293
- aggregate: false,
293
+ expressionType: "scalar",
294
294
  value: ["__ERROR_DURATION_IS_NOT_A_VALUE__"],
295
295
  };
296
296
  }
@@ -408,10 +408,10 @@ class ForRange extends index_1.ExpressionDef {
408
408
  // ... not a literal, need a cast
409
409
  from = (0, index_1.castDateToTimestamp)(from);
410
410
  }
411
- rangeStart = new index_1.ExprTime("timestamp", from, startV.aggregate);
411
+ rangeStart = new index_1.ExprTime("timestamp", from, startV.expressionType);
412
412
  }
413
413
  const to = (0, index_1.timeOffset)("timestamp", from, "+", nV.value, units);
414
- const rangeEnd = new index_1.ExprTime("timestamp", to, startV.aggregate);
414
+ const rangeEnd = new index_1.ExprTime("timestamp", to, startV.expressionType);
415
415
  return new index_1.Range(rangeStart, rangeEnd).apply(fs, op, applyTo);
416
416
  }
417
417
  requestExpression(_fs) {
@@ -464,7 +464,7 @@ class ExprTimeExtract extends index_1.ExpressionDef {
464
464
  }
465
465
  return {
466
466
  dataType: "number",
467
- aggregate: first.aggregate || last.aggregate,
467
+ expressionType: (0, malloy_types_1.maxExpressionType)(first.expressionType, last.expressionType),
468
468
  value: [
469
469
  {
470
470
  type: "dialect",
@@ -481,7 +481,7 @@ class ExprTimeExtract extends index_1.ExpressionDef {
481
481
  if ((0, malloy_types_1.isTimeFieldType)(argV.dataType)) {
482
482
  return {
483
483
  dataType: "number",
484
- aggregate: argV.aggregate,
484
+ expressionType: argV.expressionType,
485
485
  value: [
486
486
  {
487
487
  type: "dialect",
@@ -519,14 +519,12 @@ class ExprFunc extends index_1.ExpressionDef {
519
519
  }
520
520
  getExpression(fs) {
521
521
  var _a, _b, _c;
522
- let anyAggregate = false;
522
+ let expressionType = "scalar";
523
523
  let collectType;
524
524
  const funcCall = [`${this.name}(`];
525
525
  for (const fexpr of this.args) {
526
526
  const expr = fexpr.getExpression(fs);
527
- if (expr.aggregate) {
528
- anyAggregate = true;
529
- }
527
+ expressionType = (0, malloy_types_1.maxExpressionType)(expressionType, expr.expressionType);
530
528
  if (collectType) {
531
529
  funcCall.push(",");
532
530
  }
@@ -540,7 +538,7 @@ class ExprFunc extends index_1.ExpressionDef {
540
538
  const dataType = (_c = (_b = (_a = dialect === null || dialect === void 0 ? void 0 : dialect.getFunctionInfo(this.name)) === null || _a === void 0 ? void 0 : _a.returnType) !== null && _b !== void 0 ? _b : collectType) !== null && _c !== void 0 ? _c : "number";
541
539
  return {
542
540
  dataType: dataType,
543
- aggregate: anyAggregate,
541
+ expressionType,
544
542
  value: (0, index_1.compressExpr)(funcCall),
545
543
  };
546
544
  }
@@ -1,11 +1,11 @@
1
- import { AtomicFieldType, Fragment, TimestampUnit, Expr, TimeFieldType } from "../../model/malloy_types";
1
+ import { AtomicFieldType, Fragment, TimestampUnit, Expr, TimeFieldType, ExpressionType } from "../../model/malloy_types";
2
2
  export declare type ExpressionValueType = AtomicFieldType | "null" | "unknown" | "duration" | "regular expression";
3
3
  export declare function isExpressionValueType(fv: FieldValueType): fv is ExpressionValueType;
4
4
  export declare type StageFieldType = "turtle";
5
5
  export declare type FieldValueType = ExpressionValueType | StageFieldType | "struct";
6
6
  export interface FragType {
7
7
  dataType: FieldValueType;
8
- aggregate: boolean;
8
+ expressionType: ExpressionType;
9
9
  }
10
10
  /**
11
11
  * Collects functions which operate on fragtype compatible objects
@@ -43,7 +43,7 @@ class FT {
43
43
  static eq(good, checkThis) {
44
44
  return (checkThis !== undefined &&
45
45
  good.dataType === checkThis.dataType &&
46
- good.aggregate === checkThis.aggregate);
46
+ good.expressionType === checkThis.expressionType);
47
47
  }
48
48
  /**
49
49
  * Checks if the base types, ignoring aggregate, are equal
@@ -66,8 +66,8 @@ class FT {
66
66
  const strings = types.map((type) => {
67
67
  if (type) {
68
68
  let inspected = type.dataType;
69
- if (type.aggregate) {
70
- inspected = `aggregate ${inspected}`;
69
+ if (type.expressionType != "scalar") {
70
+ inspected = `expressionType ${type.expressionType}`;
71
71
  }
72
72
  return inspected;
73
73
  }
@@ -91,7 +91,7 @@ FT.anyAtomicT = [
91
91
  FT.boolT,
92
92
  ];
93
93
  function mkFragType(dType) {
94
- return { dataType: dType, aggregate: false };
94
+ return { dataType: dType, expressionType: "scalar" };
95
95
  }
96
96
  function isGranularResult(v) {
97
97
  if (v.dataType !== "date" && v.dataType !== "timestamp") {
@@ -111,7 +111,7 @@ exports.isGranularResult = isGranularResult;
111
111
  function errorFor(reason) {
112
112
  return {
113
113
  dataType: "unknown",
114
- aggregate: false,
114
+ expressionType: "scalar",
115
115
  value: [`_ERROR_${reason.replace(/ /g, "_")}`],
116
116
  };
117
117
  }
@@ -555,7 +555,7 @@ class ProjectFieldSpace extends ResultSpace {
555
555
  this.log("Cannot nest queries in project");
556
556
  return false;
557
557
  }
558
- if (qd.aggregate) {
558
+ if (model.expressionIsAggregate(qd.expressionType)) {
559
559
  this.log("Cannot add aggregate measures to project");
560
560
  return false;
561
561
  }
@@ -3,7 +3,7 @@ import { FieldSpace, DynamicSpace, QuerySpace } from "./field-space";
3
3
  import { FieldValueType, FieldDeclaration, TurtleDecl, HasParameter, Join, FieldReference } from "./ast";
4
4
  interface FieldType {
5
5
  type: FieldValueType;
6
- aggregate?: boolean;
6
+ expressionType?: model.ExpressionType;
7
7
  }
8
8
  export declare abstract class SpaceEntry {
9
9
  abstract type(): FieldType;
@@ -82,8 +82,8 @@ class SpaceField extends SpaceEntry {
82
82
  }
83
83
  fieldTypeFromFieldDef(def) {
84
84
  const ref = { type: def.type };
85
- if (model.isFieldTypeDef(def) && def.aggregate) {
86
- ref.aggregate = true;
85
+ if (model.isFieldTypeDef(def) && def.expressionType) {
86
+ ref.expressionType = def.expressionType;
87
87
  }
88
88
  return ref;
89
89
  }
@@ -256,7 +256,7 @@ class FANSPaceField extends SpaceField {
256
256
  type: fieldType,
257
257
  name: this.as,
258
258
  e: [{ type: "parameter", path: this.ref.refString }],
259
- aggregate: false,
259
+ expressionType: "scalar",
260
260
  };
261
261
  }
262
262
  let fieldExpr = [{ type: "field", path: this.ref.refString }];
@@ -274,7 +274,7 @@ class FANSPaceField extends SpaceField {
274
274
  type: fieldType,
275
275
  name: this.as,
276
276
  e: fieldExpr,
277
- aggregate: fieldTypeInfo.aggregate,
277
+ expressionType: fieldTypeInfo.expressionType,
278
278
  };
279
279
  }
280
280
  return undefined;
@@ -832,7 +832,7 @@ describe("qops", () => {
832
832
  const f = q.extendSource[0];
833
833
  expect(f.type).toBe("number");
834
834
  if (f.type == "number") {
835
- expect(f.aggregate).toBeTruthy();
835
+ expect((0, model_1.expressionIsCalculation)(f.expressionType)).toBeTruthy();
836
836
  }
837
837
  }
838
838
  else {
@@ -856,7 +856,7 @@ describe("qops", () => {
856
856
  const f = q.extendSource[0];
857
857
  expect(f.type).toBe("number");
858
858
  if (f.type == "number") {
859
- expect(f.aggregate).toBeTruthy();
859
+ expect((0, model_1.expressionIsCalculation)(f.expressionType)).toBeTruthy();
860
860
  }
861
861
  }
862
862
  else {
@@ -192,7 +192,7 @@ class TestTranslator extends parse_malloy_1.MalloyTranslator {
192
192
  type: "number",
193
193
  name: "acount",
194
194
  numberType: "integer",
195
- aggregate: true,
195
+ expressionType: "aggregate",
196
196
  e: ["COUNT()"],
197
197
  code: "count()",
198
198
  },
package/dist/malloy.d.ts CHANGED
@@ -507,7 +507,7 @@ export declare class AtomicField extends Entity {
507
507
  isQueryField(): this is QueryField;
508
508
  isExploreField(): this is ExploreField;
509
509
  isAtomicField(): this is AtomicField;
510
- isAggregate(): boolean;
510
+ isCalculation(): boolean;
511
511
  get sourceField(): Field;
512
512
  get sourceClasses(): string[];
513
513
  sourceWasMeasure(): boolean;
package/dist/malloy.js CHANGED
@@ -1005,8 +1005,9 @@ class AtomicField extends Entity {
1005
1005
  isAtomicField() {
1006
1006
  return true;
1007
1007
  }
1008
- isAggregate() {
1009
- return !!this.fieldTypeDef.aggregate;
1008
+ isCalculation() {
1009
+ //return !!this.fieldTypeDef.aggregate;
1010
+ return (0, model_1.expressionIsCalculation)(this.fieldTypeDef.expressionType);
1010
1011
  }
1011
1012
  get sourceField() {
1012
1013
  // TODO
@@ -1,5 +1,5 @@
1
1
  import { Dialect, DialectFieldList } from "../dialect";
2
- import { FieldDef, FieldRef, FilterExpression, ModelDef, Query, QueryFieldDef, StructDef, StructRef, OrderBy, ResultMetadataDef, Expr, FieldFragment, AggregateFragment, CompiledQuery, FilterFragment, PipeSegment, TurtleDef, QuerySegment, Filtered, ParameterFragment, Parameter, JoinRelationship, DialectFragment, UngroupFragment, NamedQuery } from "./malloy_types";
2
+ import { FieldDef, FieldRef, FilterExpression, ModelDef, Query, QueryFieldDef, StructDef, StructRef, OrderBy, ResultMetadataDef, Expr, FieldFragment, AggregateFragment, CompiledQuery, FilterFragment, PipeSegment, TurtleDef, QuerySegment, Filtered, ParameterFragment, Parameter, JoinRelationship, DialectFragment, UngroupFragment, NamedQuery, AnalyticFragment } from "./malloy_types";
3
3
  import { AndChain } from "./utils";
4
4
  import { ResultStructMetadataDef, SearchIndexResult } from ".";
5
5
  import { Connection } from "..";
@@ -64,13 +64,14 @@ declare class QueryField extends QueryNode {
64
64
  generateAvgFragment(resultSet: FieldInstanceResult, context: QueryStruct, expr: AggregateFragment, state: GenerateState): string;
65
65
  generateCountFragment(resultSet: FieldInstanceResult, context: QueryStruct, expr: AggregateFragment, state: GenerateState): string;
66
66
  generateDialect(resultSet: FieldInstanceResult, context: QueryStruct, expr: DialectFragment, state: GenerateState): string;
67
+ generateAnalyticFragment(resultStruct: FieldInstanceResult, context: QueryStruct, expr: AnalyticFragment, state: GenerateState): string;
67
68
  generateExpressionFromExpr(resultSet: FieldInstanceResult, context: QueryStruct, e: Expr, state?: GenerateState): string;
68
69
  getExpr(): Expr;
69
70
  generateExpression(resultSet: FieldInstanceResult): string;
70
71
  }
71
72
  declare class QueryAtomicField extends QueryField {
72
73
  includeInWildcard(): boolean;
73
- isAggregate(): boolean;
74
+ isCalculated(): boolean;
74
75
  getFilterList(): FilterExpression[];
75
76
  hasExpression(): boolean;
76
77
  }
@@ -157,7 +158,7 @@ declare class FieldInstanceResultRoot extends FieldInstanceResult {
157
158
  joins: Map<string, JoinInstance>;
158
159
  havings: AndChain;
159
160
  isComplexQuery: boolean;
160
- queryUsesUngrouped: boolean;
161
+ queryUsesPartitioning: boolean;
161
162
  computeOnlyGroups: number[];
162
163
  elimatedComputeGroups: boolean;
163
164
  constructor(turtleDef: TurtleDef);
@@ -340,6 +340,62 @@ class QueryField extends QueryNode {
340
340
  generateDialect(resultSet, context, expr, state) {
341
341
  return this.generateExpressionFromExpr(resultSet, context, context.dialect.dialectExpr(expr), state);
342
342
  }
343
+ generateAnalyticFragment(resultStruct, context, expr, state) {
344
+ const fields = resultStruct.getUngroupPartitions(undefined);
345
+ let partitionBy = "";
346
+ const fieldsString = fields.map((f) => f.getPartitionSQL()).join(", ");
347
+ if (fieldsString.length > 0) {
348
+ partitionBy = `PARTITION BY ${fieldsString}`;
349
+ }
350
+ let orderBy = "";
351
+ // calculate the ordering.
352
+ const obSQL = [];
353
+ let orderingField;
354
+ const orderByDef = resultStruct.firstSegment.orderBy ||
355
+ resultStruct.calculateDefaultOrderBy();
356
+ for (const ordering of orderByDef) {
357
+ if (typeof ordering.field === "string") {
358
+ orderingField = {
359
+ name: ordering.field,
360
+ fif: resultStruct.getField(ordering.field),
361
+ };
362
+ }
363
+ else {
364
+ orderingField = resultStruct.getFieldByNumber(ordering.field);
365
+ }
366
+ if (resultStruct.firstSegment.type === "reduce") {
367
+ obSQL.push(" " +
368
+ orderingField.fif.getSQL() +
369
+ // this.parent.dialect.sqlMaybeQuoteIdentifier(
370
+ // `${orderingField.name}__${resultStruct.groupSet}`
371
+ // ) +
372
+ ` ${ordering.dir || "ASC"}`);
373
+ }
374
+ else if (resultStruct.firstSegment.type === "project") {
375
+ obSQL.push(` ${orderingField.fif.f.generateExpression(resultStruct)} ${ordering.dir || "ASC"}`);
376
+ }
377
+ }
378
+ const func = malloy_types_1.malloyFunctions[expr.function];
379
+ const paramSQL = [];
380
+ if (expr.parameters !== undefined) {
381
+ for (const e of expr.parameters) {
382
+ if (typeof e === "string") {
383
+ paramSQL.push(e); // need to map to dimensional expression.
384
+ }
385
+ else if (typeof e === "number") {
386
+ paramSQL.push(e.toString());
387
+ }
388
+ else {
389
+ paramSQL.push(this.generateExpressionFromExpr(resultStruct, context, e, state));
390
+ }
391
+ }
392
+ }
393
+ if (obSQL.length > 0) {
394
+ orderBy = " " + this.parent.dialect.sqlOrderBy(obSQL);
395
+ }
396
+ const sqlName = func.sqlName || expr.function;
397
+ return `${sqlName}(${paramSQL.join(", ")}) OVER(${partitionBy} ${orderBy} )`;
398
+ }
343
399
  generateExpressionFromExpr(resultSet, context, e, state = new GenerateState()) {
344
400
  let s = "";
345
401
  for (const expr of e) {
@@ -358,6 +414,9 @@ class QueryField extends QueryNode {
358
414
  else if ((0, malloy_types_1.isUngroupFragment)(expr)) {
359
415
  s += this.generateUngroupedFragment(resultSet, context, expr, state);
360
416
  }
417
+ else if ((0, malloy_types_1.isAnalyticFragment)(expr)) {
418
+ s += this.generateAnalyticFragment(resultSet, context, expr, state);
419
+ }
361
420
  else if ((0, malloy_types_1.isAggregateFragment)(expr)) {
362
421
  let agg;
363
422
  if (expr.function === "sum") {
@@ -423,19 +482,18 @@ class QueryField extends QueryNode {
423
482
  return this.generateExpressionFromExpr(resultSet, this.parent, this.getExpr());
424
483
  }
425
484
  }
426
- function isAggregateField(f) {
427
- return f instanceof QueryAtomicField && f.isAggregate();
485
+ function isCalculatedField(f) {
486
+ return f instanceof QueryAtomicField && f.isCalculated();
428
487
  }
429
488
  function isScalarField(f) {
430
- return f instanceof QueryAtomicField && !f.isAggregate();
489
+ return f instanceof QueryAtomicField && !f.isCalculated();
431
490
  }
432
491
  class QueryAtomicField extends QueryField {
433
492
  includeInWildcard() {
434
493
  return true;
435
494
  }
436
- isAggregate() {
437
- return (this.fieldDef.aggregate !== undefined &&
438
- this.fieldDef.aggregate === true);
495
+ isCalculated() {
496
+ return (0, malloy_types_1.expressionIsCalculation)(this.fieldDef.expressionType);
439
497
  }
440
498
  getFilterList() {
441
499
  return [];
@@ -754,7 +812,7 @@ class FieldInstanceResult {
754
812
  if (["date", "timestamp"].indexOf(fi.f.fieldDef.type) > -1) {
755
813
  return [{ dir: "desc", field: fi.fieldUsage.resultIndex }];
756
814
  }
757
- else if (isAggregateField(fi.f)) {
815
+ else if (isCalculatedField(fi.f)) {
758
816
  return [{ dir: "desc", field: fi.fieldUsage.resultIndex }];
759
817
  }
760
818
  }
@@ -873,7 +931,7 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
873
931
  this.joins = new Map();
874
932
  this.havings = new utils_1.AndChain();
875
933
  this.isComplexQuery = false;
876
- this.queryUsesUngrouped = false;
934
+ this.queryUsesPartitioning = false;
877
935
  this.computeOnlyGroups = [];
878
936
  this.elimatedComputeGroups = false;
879
937
  }
@@ -1114,7 +1172,7 @@ class QueryQuery extends QueryField {
1114
1172
  // turtles
1115
1173
  // Timestamps and Dates (are just fine to leave as is).
1116
1174
  // measures
1117
- let e;
1175
+ // let e: Expr;
1118
1176
  if (field instanceof QueryQuery) {
1119
1177
  const newFieldDef = (0, lodash_1.cloneDeep)(field.fieldDef);
1120
1178
  newFieldDef.as = f.name;
@@ -1123,18 +1181,18 @@ class QueryQuery extends QueryField {
1123
1181
  }
1124
1182
  else if (!(field instanceof QueryFieldTimestamp ||
1125
1183
  field instanceof QueryFieldDate)) {
1126
- // its a measure
1127
- e = [{ type: "field", path: field.getFullOutputName() }];
1128
- if ("filterList" in f && f.filterList) {
1129
- e = [{ type: "filterExpression", filterList: f.filterList, e: e }];
1130
- }
1131
- const newFieldDef = {
1132
- type: field.fieldDef.type,
1133
- name: f.as,
1134
- e,
1135
- aggregate: isAggregateField(field),
1136
- };
1137
- field = this.parent.makeQueryField(newFieldDef);
1184
+ throw new Error(`No longer generate code this way. \n ${JSON.stringify(f, undefined, 2)}`);
1185
+ // // its a measure
1186
+ // e = [{ type: "field", path: field.getFullOutputName() }];
1187
+ // if ("filterList" in f && f.filterList) {
1188
+ // e = [{ type: "filterExpression", filterList: f.filterList, e: e }];
1189
+ // }
1190
+ // const newFieldDef = {
1191
+ // type: field.fieldDef.type,
1192
+ // name: f.as,
1193
+ // e,
1194
+ // };
1195
+ // field = this.parent.makeQueryField(newFieldDef as FieldDef);
1138
1196
  }
1139
1197
  // or inline field FieldTypeDef
1140
1198
  }
@@ -1228,7 +1286,7 @@ class QueryQuery extends QueryField {
1228
1286
  if ((0, malloy_types_1.isUngroupFragment)(expr)) {
1229
1287
  resultStruct.resultUsesUngrouped = true;
1230
1288
  resultStruct.root().isComplexQuery = true;
1231
- resultStruct.root().queryUsesUngrouped = true;
1289
+ resultStruct.root().queryUsesPartitioning = true;
1232
1290
  if (expr.fields && expr.fields.length > 0) {
1233
1291
  const key = expr.fields.sort().join("|") + expr.type;
1234
1292
  if (resultStruct.ungroupedSets.get(key) === undefined) {
@@ -1304,6 +1362,9 @@ class QueryQuery extends QueryField {
1304
1362
  }
1305
1363
  this.addDependantExpr(resultStruct, context, expr.e, joinStack);
1306
1364
  }
1365
+ else if ((0, malloy_types_1.isAnalyticFragment)(expr)) {
1366
+ resultStruct.root().queryUsesPartitioning = true;
1367
+ }
1307
1368
  }
1308
1369
  }
1309
1370
  addDependancies(resultStruct, field) {
@@ -1329,7 +1390,7 @@ class QueryQuery extends QueryField {
1329
1390
  type: "result",
1330
1391
  });
1331
1392
  this.addDependancies(resultStruct, field);
1332
- if (isAggregateField(field)) {
1393
+ if (isCalculatedField(field)) {
1333
1394
  if (this.firstSegment.type === "project") {
1334
1395
  throw new Error(`Aggregate Fields cannot be used in PROJECT - '${field.fieldDef.name}'`);
1335
1396
  }
@@ -1387,8 +1448,8 @@ class QueryQuery extends QueryField {
1387
1448
  // in the correct catgory.
1388
1449
  for (const cond of list || []) {
1389
1450
  const context = this.parent;
1390
- if ((which === "having" && cond.aggregate) ||
1391
- (which === "where" && !cond.aggregate)) {
1451
+ if ((which === "having" && (0, malloy_types_1.expressionIsCalculation)(cond.expressionType)) ||
1452
+ (which === "where" && cond.expressionType === "scalar")) {
1392
1453
  const sqlClause = this.generateExpressionFromExpr(resultStruct, context, cond.expression, undefined);
1393
1454
  resultFilters.add(sqlClause);
1394
1455
  }
@@ -1412,7 +1473,7 @@ class QueryQuery extends QueryField {
1412
1473
  const sourceField = fi.f.parent.getFullOutputName() + (fieldDef.name || fieldDef.as);
1413
1474
  const sourceExpression = fieldDef.code;
1414
1475
  const sourceClasses = [sourceField];
1415
- if (isAggregateField(fi.f)) {
1476
+ if (isCalculatedField(fi.f)) {
1416
1477
  filterList = fi.f.getFilterList();
1417
1478
  return {
1418
1479
  sourceField,
@@ -1762,7 +1823,7 @@ class QueryQuery extends QueryField {
1762
1823
  const exp = fi.getSQL();
1763
1824
  if (isScalarField(fi.f)) {
1764
1825
  if (this.parent.dialect.name === "standardsql" &&
1765
- this.rootResult.queryUsesUngrouped) {
1826
+ this.rootResult.queryUsesPartitioning) {
1766
1827
  // BigQuery can't partition aggregate function except when the field has no
1767
1828
  // expression. Additionally it can't partition by floats. We stuff expressions
1768
1829
  // and numbers as strings into a lateral join when the query has ungrouped expressions
@@ -1788,7 +1849,7 @@ class QueryQuery extends QueryField {
1788
1849
  }
1789
1850
  output.dimensionIndexes.push(output.fieldIndex++);
1790
1851
  }
1791
- else if (isAggregateField(fi.f)) {
1852
+ else if (isCalculatedField(fi.f)) {
1792
1853
  output.sql.push(`${exp} as ${outputName}`);
1793
1854
  output.fieldIndex++;
1794
1855
  }
@@ -1919,7 +1980,7 @@ class QueryQuery extends QueryField {
1919
1980
  output.sql.push(`${exp} as ${sqlFieldName}`);
1920
1981
  output.dimensionIndexes.push(output.fieldIndex++);
1921
1982
  }
1922
- else if (isAggregateField(fi.f)) {
1983
+ else if (isCalculatedField(fi.f)) {
1923
1984
  const exp = this.parent.dialect.sqlAnyValue(resultSet.groupSet, sqlFieldName);
1924
1985
  output.sql.push(`${exp} as ${sqlFieldName}`);
1925
1986
  output.fieldIndex++;
@@ -1991,7 +2052,7 @@ class QueryQuery extends QueryField {
1991
2052
  fieldsSQL.push(this.parent.dialect.sqlMaybeQuoteIdentifier(`${name}__${this.rootResult.groupSet}`) + ` as ${sqlName}`);
1992
2053
  dimensionIndexes.push(fieldIndex++);
1993
2054
  }
1994
- else if (isAggregateField(fi.f)) {
2055
+ else if (isCalculatedField(fi.f)) {
1995
2056
  fieldsSQL.push(this.parent.dialect.sqlAnyValueLastTurtle(name, this.rootResult.groupSet, sqlName));
1996
2057
  fieldIndex++;
1997
2058
  }
@@ -111,6 +111,20 @@ export interface UngroupFragment {
111
111
  fields?: string[];
112
112
  }
113
113
  export declare function isUngroupFragment(f: Fragment): f is UngroupFragment;
114
+ export declare type MalloyFunctionParam = AtomicFieldType | "any" | "nconst" | "regexp";
115
+ export interface MalloyFunctionInfo {
116
+ returnType: AtomicFieldType;
117
+ parameters: "any" | "none" | MalloyFunctionParam[];
118
+ expressionType?: "aggregate" | "analytic";
119
+ sqlName?: string;
120
+ }
121
+ export declare const malloyFunctions: Record<string, MalloyFunctionInfo>;
122
+ export interface AnalyticFragment {
123
+ type: "analytic";
124
+ function: string;
125
+ parameters?: (string | number | Expr)[];
126
+ }
127
+ export declare function isAnalyticFragment(f: Fragment): f is AnalyticFragment;
114
128
  export interface FieldFragment {
115
129
  type: "field";
116
130
  path: string;
@@ -189,7 +203,7 @@ export interface StringLiteralFragment extends DialectFragmentBase {
189
203
  literal: string;
190
204
  }
191
205
  export declare type DialectFragment = DivFragment | TimeLiteralFragment | NowFragment | TimeDeltaFragment | TimeDiffFragment | TimeTruncFragment | TypecastFragment | TimeExtractFragment | StringLiteralFragment | RegexpMatchFragment;
192
- export declare type Fragment = string | ApplyFragment | ApplyValueFragment | FieldFragment | ParameterFragment | FilterFragment | AggregateFragment | UngroupFragment | DialectFragment;
206
+ export declare type Fragment = string | ApplyFragment | ApplyValueFragment | FieldFragment | ParameterFragment | FilterFragment | AggregateFragment | UngroupFragment | DialectFragment | AnalyticFragment;
193
207
  export declare type Expr = Fragment[];
194
208
  export interface TypedValue {
195
209
  value: Expr;
@@ -210,11 +224,15 @@ declare type TagElement = Expr | string;
210
224
  * ```
211
225
  */
212
226
  export declare function mkExpr(src: TemplateStringsArray, ...exprs: TagElement[]): Expr;
227
+ export declare type ExpressionType = "scalar" | "aggregate" | "analytic";
213
228
  export interface Expression {
214
229
  e?: Expr;
215
- aggregate?: boolean;
230
+ expressionType?: ExpressionType;
216
231
  code?: string;
217
232
  }
233
+ export declare function expressionIsAggregate(e: ExpressionType | undefined): boolean;
234
+ export declare function expressionIsCalculation(e: ExpressionType | undefined): boolean;
235
+ export declare function maxExpressionType(e1: ExpressionType, e2: ExpressionType): ExpressionType;
218
236
  interface JustExpression {
219
237
  e: Expr;
220
238
  }
@@ -446,7 +464,7 @@ export declare type PrimaryKeyRef = string;
446
464
  export interface FilterExpression {
447
465
  expression: Expr;
448
466
  code: string;
449
- aggregate?: boolean;
467
+ expressionType: ExpressionType;
450
468
  }
451
469
  /** Get the output name for a NamedObject */
452
470
  export declare function getIdentifier(n: AliasedName): string;
@@ -12,7 +12,8 @@
12
12
  * GNU General Public License for more details.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.isValueDate = exports.isValueTimestamp = exports.isValueBoolean = exports.isValueNumber = exports.isValueString = exports.isMeasureLike = exports.getPhysicalFields = exports.getDimensions = exports.isPhysical = exports.isDimensional = exports.isTurtleDef = exports.getIdentifier = exports.isFieldStructDef = exports.isFieldTimeBased = exports.isFieldTypeDef = exports.isSQLBlock = exports.isSQLFragment = exports.isJoinOn = exports.isIndexSegment = exports.isSamplingEnable = exports.isSamplingPercent = exports.isSamplingRows = exports.isQuerySegment = exports.isProjectSegment = exports.isReduceSegment = exports.refIsStructDef = exports.isByExpression = exports.isByName = exports.ValueType = exports.isExtractUnit = exports.isTimestampUnit = exports.isDateUnit = exports.FieldIsIntrinsic = exports.isAtomicFieldType = exports.isTimeFieldType = exports.hasExpression = exports.mkExpr = exports.isApplyFragment = exports.isApplyValue = exports.isParameterFragment = exports.isFieldFragment = exports.isUngroupFragment = exports.isAsymmetricFragment = exports.isAggregateFragment = exports.isDialectFragment = exports.isFilterFragment = exports.isFilteredAliasedName = exports.paramHasValue = exports.isConditionParameter = exports.isValueParameter = void 0;
15
+ exports.isMeasureLike = exports.getPhysicalFields = exports.getDimensions = exports.isPhysical = exports.isDimensional = exports.isTurtleDef = exports.getIdentifier = exports.isFieldStructDef = exports.isFieldTimeBased = exports.isFieldTypeDef = exports.isSQLBlock = exports.isSQLFragment = exports.isJoinOn = exports.isIndexSegment = exports.isSamplingEnable = exports.isSamplingPercent = exports.isSamplingRows = exports.isQuerySegment = exports.isProjectSegment = exports.isReduceSegment = exports.refIsStructDef = exports.isByExpression = exports.isByName = exports.ValueType = exports.isExtractUnit = exports.isTimestampUnit = exports.isDateUnit = exports.FieldIsIntrinsic = exports.isAtomicFieldType = exports.isTimeFieldType = exports.hasExpression = exports.maxExpressionType = exports.expressionIsCalculation = exports.expressionIsAggregate = exports.mkExpr = exports.isApplyFragment = exports.isApplyValue = exports.isParameterFragment = exports.isFieldFragment = exports.isAnalyticFragment = exports.malloyFunctions = exports.isUngroupFragment = exports.isAsymmetricFragment = exports.isAggregateFragment = exports.isDialectFragment = exports.isFilterFragment = exports.isFilteredAliasedName = exports.paramHasValue = exports.isConditionParameter = exports.isValueParameter = void 0;
16
+ exports.isValueDate = exports.isValueTimestamp = exports.isValueBoolean = exports.isValueNumber = exports.isValueString = void 0;
16
17
  function isValueParameter(p) {
17
18
  return p.value !== undefined;
18
19
  }
@@ -55,6 +56,61 @@ function isUngroupFragment(f) {
55
56
  return ftype == "all" || ftype == "exclude";
56
57
  }
57
58
  exports.isUngroupFragment = isUngroupFragment;
59
+ exports.malloyFunctions = {
60
+ row_number: {
61
+ returnType: "number",
62
+ parameters: "none",
63
+ expressionType: "analytic",
64
+ },
65
+ rank: {
66
+ returnType: "number",
67
+ parameters: "none",
68
+ expressionType: "analytic",
69
+ },
70
+ dense_rank: {
71
+ returnType: "number",
72
+ parameters: "none",
73
+ expressionType: "analytic",
74
+ },
75
+ first_value_in_column: {
76
+ returnType: "number",
77
+ parameters: "any",
78
+ expressionType: "analytic",
79
+ sqlName: "first_value",
80
+ },
81
+ last_value_in_column: {
82
+ returnType: "number",
83
+ parameters: "any",
84
+ expressionType: "analytic",
85
+ sqlName: "last_value",
86
+ },
87
+ min_in_column: {
88
+ returnType: "number",
89
+ parameters: "any",
90
+ expressionType: "analytic",
91
+ sqlName: "min",
92
+ },
93
+ max_in_column: {
94
+ returnType: "number",
95
+ parameters: "any",
96
+ expressionType: "analytic",
97
+ sqlName: "max",
98
+ },
99
+ ntile: {
100
+ returnType: "number",
101
+ parameters: ["nconst"],
102
+ expressionType: "analytic",
103
+ },
104
+ lag: {
105
+ returnType: "number",
106
+ parameters: ["number", "nconst"],
107
+ expressionType: "analytic",
108
+ },
109
+ };
110
+ function isAnalyticFragment(f) {
111
+ return (f === null || f === void 0 ? void 0 : f.type) === "analytic";
112
+ }
113
+ exports.isAnalyticFragment = isAnalyticFragment;
58
114
  function isFieldFragment(f) {
59
115
  return (f === null || f === void 0 ? void 0 : f.type) === "field";
60
116
  }
@@ -102,6 +158,25 @@ function mkExpr(src, ...exprs) {
102
158
  return ret;
103
159
  }
104
160
  exports.mkExpr = mkExpr;
161
+ function expressionIsAggregate(e) {
162
+ return e === "aggregate";
163
+ }
164
+ exports.expressionIsAggregate = expressionIsAggregate;
165
+ function expressionIsCalculation(e) {
166
+ return e === "aggregate" || e === "analytic";
167
+ }
168
+ exports.expressionIsCalculation = expressionIsCalculation;
169
+ function maxExpressionType(e1, e2) {
170
+ let ret = "scalar";
171
+ if (e1 === "aggregate" || e2 === "aggregate") {
172
+ ret = "aggregate";
173
+ }
174
+ if (e1 === "analytic" || e2 === "analytic") {
175
+ ret = "analytic";
176
+ }
177
+ return ret;
178
+ }
179
+ exports.maxExpressionType = maxExpressionType;
105
180
  /** Grants access to the expression property of a FielfDef */
106
181
  function hasExpression(f) {
107
182
  return f.e !== undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.16-dev230104154319",
3
+ "version": "0.0.16",
4
4
  "license": "GPL-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",