@malloydata/malloy 0.0.120-dev240131220408 → 0.0.120

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.
Files changed (46) hide show
  1. package/dist/dialect/dialect_map.js +12 -2
  2. package/dist/dialect/functions/all_functions.js +3 -0
  3. package/dist/dialect/functions/string_agg.d.ts +3 -0
  4. package/dist/dialect/functions/string_agg.js +47 -0
  5. package/dist/dialect/functions/util.d.ts +5 -0
  6. package/dist/dialect/functions/util.js +3 -0
  7. package/dist/dialect/postgres/functions/postgres_functions.js +3 -0
  8. package/dist/dialect/postgres/functions/string_agg.d.ts +3 -0
  9. package/dist/dialect/postgres/functions/string_agg.js +47 -0
  10. package/dist/dialect/standardsql/functions/standardsql_functions.js +3 -0
  11. package/dist/dialect/standardsql/functions/string_agg.d.ts +3 -0
  12. package/dist/dialect/standardsql/functions/string_agg.js +49 -0
  13. package/dist/index.d.ts +2 -2
  14. package/dist/lang/ast/expressions/expr-func.d.ts +11 -0
  15. package/dist/lang/ast/expressions/expr-func.js +69 -10
  16. package/dist/lang/ast/expressions/{expr-filter.d.ts → expr-props.d.ts} +5 -4
  17. package/dist/lang/ast/expressions/expr-props.js +122 -0
  18. package/dist/lang/ast/expressions/function-ordering.d.ts +18 -0
  19. package/dist/lang/ast/expressions/function-ordering.js +75 -0
  20. package/dist/lang/ast/expressions/partition_by.d.ts +7 -0
  21. package/dist/lang/ast/expressions/partition_by.js +35 -0
  22. package/dist/lang/ast/index.d.ts +4 -1
  23. package/dist/lang/ast/index.js +4 -1
  24. package/dist/lang/ast/query-items/field-references.d.ts +4 -0
  25. package/dist/lang/ast/query-items/field-references.js +12 -1
  26. package/dist/lang/ast/types/expression-def.d.ts +4 -0
  27. package/dist/lang/ast/types/expression-def.js +12 -0
  28. package/dist/lang/ast/types/field-prop-statement.d.ts +7 -0
  29. package/dist/lang/ast/types/field-prop-statement.js +37 -0
  30. package/dist/lang/lib/Malloy/MalloyLexer.d.ts +136 -135
  31. package/dist/lang/lib/Malloy/MalloyLexer.js +1186 -1174
  32. package/dist/lang/lib/Malloy/MalloyParser.d.ts +278 -213
  33. package/dist/lang/lib/Malloy/MalloyParser.js +2200 -1683
  34. package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +76 -36
  35. package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +48 -22
  36. package/dist/lang/malloy-to-ast.d.ts +6 -2
  37. package/dist/lang/malloy-to-ast.js +33 -9
  38. package/dist/lang/test/expressions.spec.js +132 -9
  39. package/dist/model/malloy_query.d.ts +2 -2
  40. package/dist/model/malloy_query.js +59 -25
  41. package/dist/model/malloy_types.d.ts +20 -2
  42. package/dist/model/malloy_types.js +5 -1
  43. package/dist/run_sql_options.d.ts +1 -0
  44. package/dist/runtime_types.d.ts +4 -1
  45. package/package.json +1 -1
  46. package/dist/lang/ast/expressions/expr-filter.js +0 -66
@@ -249,12 +249,12 @@ class QueryField extends QueryNode {
249
249
  return [param.name, { param, argIndexes }];
250
250
  }));
251
251
  }
252
- expandFunctionCall(dialect, overload, args) {
252
+ expandFunctionCall(dialect, overload, args, orderBy, limit) {
253
253
  const paramMap = this.getParameterMap(overload, args.length);
254
254
  if (overload.dialect[dialect] === undefined) {
255
255
  throw new Error(`Function is not defined for dialect ${dialect}`);
256
256
  }
257
- return (0, utils_1.exprMap)(overload.dialect[dialect], fragment => {
257
+ return (0, utils_1.exprMap)(overload.dialect[dialect].e, fragment => {
258
258
  if (typeof fragment === 'string') {
259
259
  return [fragment];
260
260
  }
@@ -264,7 +264,6 @@ class QueryField extends QueryNode {
264
264
  typeof param === 'string' ||
265
265
  param.type !== 'function_parameter') {
266
266
  throw new Error('Invalid function definition. Argument to spread must be a function parameter.');
267
- return [];
268
267
  }
269
268
  const entry = paramMap.get(param.name);
270
269
  if (entry === undefined) {
@@ -287,17 +286,34 @@ class QueryField extends QueryNode {
287
286
  return args[entry.argIndexes[0]];
288
287
  }
289
288
  }
289
+ else if (fragment.type === 'aggregate_order_by') {
290
+ return orderBy ? [` ${orderBy}`] : [];
291
+ }
292
+ else if (fragment.type === 'aggregate_limit') {
293
+ return limit ? [` ${limit}`] : [];
294
+ }
290
295
  return [fragment];
291
296
  });
292
297
  }
293
298
  generateFunctionCallExpression(resultSet, context, frag, state) {
294
- var _a;
299
+ var _a, _b;
295
300
  const overload = frag.overload;
296
301
  const args = frag.args;
297
302
  const isSymmetric = (_a = frag.overload.isSymmetric) !== null && _a !== void 0 ? _a : false;
298
303
  const distinctKey = (0, malloy_types_1.expressionIsAggregate)(overload.returnType.expressionType) &&
299
304
  !isSymmetric &&
300
305
  this.generateDistinctKeyIfNecessary(resultSet, context, frag.structPath);
306
+ const aggregateOrderBy = frag.orderBy
307
+ ? 'ORDER BY ' +
308
+ frag.orderBy
309
+ .map(ob => {
310
+ const osql = this.generateDimFragment(resultSet, context, ob.e, state);
311
+ const dirsql = ob.dir === 'asc' ? ' ASC' : ob.dir === 'desc' ? ' DESC' : '';
312
+ return `${osql}${dirsql}`;
313
+ })
314
+ .join(', ')
315
+ : undefined;
316
+ const aggregateLimit = frag.limit ? `LIMIT ${frag.limit}` : undefined;
301
317
  if (distinctKey) {
302
318
  if (!context.dialect.supportsAggDistinct) {
303
319
  throw new Error(`Asymmetric aggregates are not supported for custom functions in ${context.dialect.name}.`);
@@ -306,26 +322,39 @@ class QueryField extends QueryNode {
306
322
  return this.generateDimFragment(resultSet, context, arg, state);
307
323
  });
308
324
  return context.dialect.sqlAggDistinct(distinctKey, argsExpressions, valNames => {
309
- const funcCall = this.expandFunctionCall(context.dialect.name, overload, valNames.map(v => [v]));
325
+ const funcCall = this.expandFunctionCall(context.dialect.name, overload, valNames.map(v => [v]), aggregateOrderBy, aggregateLimit);
310
326
  return this.generateExpressionFromExpr(resultSet, context, funcCall, state);
311
327
  });
312
328
  }
313
329
  else {
314
330
  const mappedArgs = (0, malloy_types_1.expressionIsAggregate)(overload.returnType.expressionType)
315
- ? args.map(arg => {
331
+ ? args.map((arg, index) => {
316
332
  // TODO We assume that all arguments to this aggregate-returning function need to
317
333
  // have filters applied to them. This is not necessarily true in the general case,
318
334
  // e.g. in a function `avg_plus(a, b) = avg(a) + b` -- here, `b` should not be
319
335
  // be filtered. But since there aren't any aggregate functions like this in the
320
336
  // standard library we have planned, we ignore this for now.
321
- return [this.generateDimFragment(resultSet, context, arg, state)];
337
+ // Update: Now we apply this only to arguments whose parameter is not constant-requiring.
338
+ // So in `string_agg(val, sep)`, `sep` does not get filters applied to it because
339
+ // it must be constant
340
+ const param = overload.params[index];
341
+ // TODO technically this should probably look at _which_ allowed param type was matched
342
+ // for this argument and see if that type is at most constant... but we lose type information
343
+ // by this point in the compilation, so that info would have to be passed into the func call
344
+ // fragment.
345
+ return param.allowedTypes.every(t => (0, malloy_types_1.isLiteral)(t.evalSpace))
346
+ ? arg
347
+ : [this.generateDimFragment(resultSet, context, arg, state)];
322
348
  })
323
349
  : args;
324
- const funcCall = this.expandFunctionCall(context.dialect.name, overload, mappedArgs);
350
+ const funcCall = this.expandFunctionCall(context.dialect.name, overload, mappedArgs, aggregateOrderBy, aggregateLimit);
325
351
  if ((0, malloy_types_1.expressionIsAnalytic)(overload.returnType.expressionType)) {
352
+ const extraPartitions = ((_b = frag.partitionBy) !== null && _b !== void 0 ? _b : []).map(outputName => {
353
+ return `(${resultSet.getField(outputName).getAnalyticalSQL(false)})`;
354
+ });
326
355
  // TODO probably need to pass in the function and arguments separately
327
356
  // in order to generate parameter SQL correctly in BQ re: partition
328
- return this.generateAnalyticFragment(resultSet, context, funcCall, overload, state, args);
357
+ return this.generateAnalyticFragment(resultSet, context, funcCall, overload, state, args, extraPartitions, aggregateOrderBy);
329
358
  }
330
359
  return this.generateExpressionFromExpr(resultSet, context, funcCall, state);
331
360
  }
@@ -510,7 +539,7 @@ class QueryField extends QueryNode {
510
539
  return context.getFieldByName(expr.path).getIdentifier();
511
540
  }
512
541
  }
513
- getAnalyticPartitions(resultStruct) {
542
+ getAnalyticPartitions(resultStruct, extraPartitionFields) {
514
543
  const ret = [];
515
544
  let p = resultStruct.parent;
516
545
  while (p !== undefined) {
@@ -519,23 +548,23 @@ class QueryField extends QueryNode {
519
548
  ret.push(...partitionSQLs);
520
549
  p = p.parent;
521
550
  }
522
- return ret.join(', ');
551
+ if (extraPartitionFields) {
552
+ ret.push(...extraPartitionFields);
553
+ }
554
+ return ret;
523
555
  }
524
- generateAnalyticFragment(resultStruct, context, expr, overload, state, args) {
525
- let partitionBy = '';
556
+ generateAnalyticFragment(resultStruct, context, expr, overload, state, args, partitionByFields, funcOrdering) {
526
557
  const isComplex = resultStruct.root().isComplexQuery;
527
- const fieldsString = this.getAnalyticPartitions(resultStruct);
528
- if (isComplex || fieldsString.length > 0) {
529
- partitionBy = 'PARTITION BY ';
530
- if (isComplex) {
531
- partitionBy += 'group_set';
532
- }
533
- if (fieldsString.length > 0) {
534
- partitionBy += `, ${fieldsString}`;
535
- }
536
- }
537
- let orderBy = '';
538
- if (overload.needsWindowOrderBy) {
558
+ const partitionFields = this.getAnalyticPartitions(resultStruct, partitionByFields);
559
+ const allPartitions = [
560
+ ...(isComplex ? ['group_set'] : []),
561
+ ...partitionFields,
562
+ ];
563
+ const partitionBy = allPartitions.length > 0
564
+ ? `PARTITION BY ${allPartitions.join(', ')}`
565
+ : '';
566
+ let orderBy = funcOrdering !== null && funcOrdering !== void 0 ? funcOrdering : '';
567
+ if (!funcOrdering && overload.needsWindowOrderBy) {
539
568
  // calculate the ordering.
540
569
  const obSQL = [];
541
570
  let orderingField;
@@ -1548,6 +1577,11 @@ class QueryQuery extends QueryField {
1548
1577
  if ((0, malloy_types_1.expressionIsAnalytic)(expr.overload.returnType.expressionType)) {
1549
1578
  resultStruct.root().queryUsesPartitioning = true;
1550
1579
  }
1580
+ if (expr.orderBy) {
1581
+ for (const ob of expr.orderBy) {
1582
+ this.addDependantExpr(resultStruct, context, ob.e, joinStack);
1583
+ }
1584
+ }
1551
1585
  }
1552
1586
  }
1553
1587
  }
@@ -124,11 +124,20 @@ export interface FunctionParameterFragment {
124
124
  name: string;
125
125
  }
126
126
  export declare function isFunctionParameterFragment(f: Fragment): f is FunctionParameterFragment;
127
+ export interface AggregateOrderByFragment {
128
+ type: 'aggregate_order_by';
129
+ }
130
+ export interface AggregateLimitFragment {
131
+ type: 'aggregate_limit';
132
+ }
127
133
  export interface FunctionCallFragment {
128
134
  type: 'function_call';
129
135
  overload: FunctionOverloadDef;
130
136
  expressionType: ExpressionType;
131
137
  args: Expr[];
138
+ orderBy?: FunctionOrderBy[];
139
+ limit?: number;
140
+ partitionBy?: string[];
132
141
  structPath?: string[];
133
142
  }
134
143
  export declare function isFunctionCallFragment(f: Fragment): f is FunctionCallFragment;
@@ -240,7 +249,7 @@ export interface NumberLiteralFragment extends DialectFragmentBase {
240
249
  literal: string;
241
250
  }
242
251
  export type DialectFragment = DivFragment | TimeLiteralFragment | NowFragment | TimeDeltaFragment | TimeDiffFragment | TimeTruncFragment | TypecastFragment | TimeExtractFragment | StringLiteralFragment | RegexpLiteralFragment | NumberLiteralFragment | RegexpMatchFragment;
243
- export type Fragment = string | ApplyFragment | ApplyValueFragment | FieldFragment | SourceReferenceFragment | SqlStringFragment | ParameterFragment | FilterFragment | OutputFieldFragment | AggregateFragment | UngroupFragment | DialectFragment | FunctionParameterFragment | FunctionCallFragment | SQLExpressionFragment | SpreadFragment;
252
+ export type Fragment = string | ApplyFragment | ApplyValueFragment | FieldFragment | SourceReferenceFragment | SqlStringFragment | ParameterFragment | FilterFragment | OutputFieldFragment | AggregateFragment | UngroupFragment | DialectFragment | FunctionParameterFragment | AggregateOrderByFragment | AggregateLimitFragment | FunctionCallFragment | SQLExpressionFragment | SpreadFragment;
244
253
  export type Expr = Fragment[];
245
254
  export interface TypedValue {
246
255
  value: Expr;
@@ -377,6 +386,10 @@ export interface OrderBy {
377
386
  field: string | number;
378
387
  dir?: 'asc' | 'desc';
379
388
  }
389
+ export interface FunctionOrderBy {
390
+ e: Expr;
391
+ dir?: 'asc' | 'desc';
392
+ }
380
393
  export interface ByName {
381
394
  by: 'name';
382
395
  name: string;
@@ -561,6 +574,7 @@ export interface FunctionParamTypeDesc {
561
574
  evalSpace: EvalSpace;
562
575
  }
563
576
  export type EvalSpace = 'constant' | 'input' | 'output' | 'literal';
577
+ export declare function isLiteral(evalSpace: EvalSpace): boolean;
564
578
  export declare function mergeEvalSpaces(...evalSpaces: EvalSpace[]): EvalSpace;
565
579
  export interface TypeDesc {
566
580
  dataType: FieldValueType;
@@ -583,7 +597,11 @@ export interface FunctionOverloadDef {
583
597
  isSymmetric?: boolean;
584
598
  params: FunctionParameterDef[];
585
599
  dialect: {
586
- [dialect: string]: Expr;
600
+ [dialect: string]: {
601
+ e: Expr;
602
+ supportsOrderBy?: boolean;
603
+ supportsLimit?: boolean;
604
+ };
587
605
  };
588
606
  }
589
607
  export interface FunctionDef extends NamedObject {
@@ -23,7 +23,7 @@
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.isIndexSegment = exports.isRawSegment = exports.isSamplingEnable = exports.isSamplingPercent = exports.isSamplingRows = exports.isQuerySegment = exports.isProjectSegment = exports.isPartialSegment = exports.isReduceSegment = exports.refIsStructDef = exports.isByExpression = exports.isByName = exports.ValueType = exports.isExtractUnit = exports.isTimestampUnit = exports.isDateUnit = exports.FieldIsIntrinsic = exports.isCastType = exports.isAtomicFieldType = exports.isTimeFieldType = exports.hasExpression = exports.maxOfExpressionTypes = exports.maxExpressionType = exports.isExpressionTypeLEQ = exports.expressionIsAnalytic = exports.expressionIsCalculation = exports.expressionInvolvesAggregate = exports.expressionIsUngroupedAggregate = exports.expressionIsAggregate = exports.expressionIsScalar = exports.mkExpr = exports.isApplyFragment = exports.isApplyValue = exports.isParameterFragment = exports.isSourceReferenceFragment = exports.isSqlStringFragment = exports.isFieldFragment = exports.isSpreadFragment = exports.isSQLExpressionFragment = exports.isFunctionCallFragment = exports.isFunctionParameterFragment = exports.isUngroupFragment = exports.isAsymmetricFragment = exports.isAggregateFragment = exports.isDialectFragment = exports.isFilterFragment = exports.isOutputFieldFragment = exports.paramHasValue = exports.isConditionParameter = exports.isValueParameter = void 0;
26
- exports.isValueDate = exports.isValueTimestamp = exports.isValueBoolean = exports.isValueNumber = exports.isValueString = exports.isMeasureLike = exports.getPhysicalFields = exports.getDimensions = exports.isPhysical = exports.isDimensional = exports.isAtomicField = exports.isTurtleDef = exports.getIdentifier = exports.isFieldStructDef = exports.isFieldTimeBased = exports.isFieldTypeDef = exports.isSQLBlockStruct = exports.mergeEvalSpaces = exports.isSQLFragment = exports.isJoinOn = exports.isMatrixOperation = void 0;
26
+ exports.isValueDate = exports.isValueTimestamp = exports.isValueBoolean = exports.isValueNumber = exports.isValueString = exports.isMeasureLike = exports.getPhysicalFields = exports.getDimensions = exports.isPhysical = exports.isDimensional = exports.isAtomicField = exports.isTurtleDef = exports.getIdentifier = exports.isFieldStructDef = exports.isFieldTimeBased = exports.isFieldTypeDef = exports.isSQLBlockStruct = exports.mergeEvalSpaces = exports.isLiteral = exports.isSQLFragment = exports.isJoinOn = exports.isMatrixOperation = void 0;
27
27
  function isValueParameter(p) {
28
28
  return p.value !== undefined;
29
29
  }
@@ -329,6 +329,10 @@ function isSQLFragment(f) {
329
329
  return f.sql !== undefined;
330
330
  }
331
331
  exports.isSQLFragment = isSQLFragment;
332
+ function isLiteral(evalSpace) {
333
+ return evalSpace === 'literal';
334
+ }
335
+ exports.isLiteral = isLiteral;
332
336
  function mergeEvalSpaces(...evalSpaces) {
333
337
  if (evalSpaces.every(e => e === 'constant' || e === 'literal')) {
334
338
  return 'constant';
@@ -5,3 +5,4 @@ export interface RunSQLOptions {
5
5
  modelAnnotation?: Annotation;
6
6
  queryAnnotation?: Annotation;
7
7
  }
8
+ export type QueryOptionsReader = RunSQLOptions | (() => RunSQLOptions);
@@ -79,7 +79,10 @@ export interface ConnectionParameter {
79
79
  defaultValue?: ConnectionParameterValue;
80
80
  }
81
81
  export type ConnectionConfigSchema = ConnectionParameter[];
82
- export type ConnectionConfig = Record<string, ConnectionParameterValue>;
82
+ export interface ConnectionConfig {
83
+ name: string;
84
+ [key: string]: ConnectionParameterValue | undefined;
85
+ }
83
86
  export interface ConnectionFactory {
84
87
  connectionName: string;
85
88
  configSchema: ConnectionConfigSchema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.120-dev240131220408",
3
+ "version": "0.0.120",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,66 +0,0 @@
1
- "use strict";
2
- /*
3
- * Copyright 2023 Google LLC
4
- *
5
- * Permission is hereby granted, free of charge, to any person obtaining
6
- * a copy of this software and associated documentation files
7
- * (the "Software"), to deal in the Software without restriction,
8
- * including without limitation the rights to use, copy, modify, merge,
9
- * publish, distribute, sublicense, and/or sell copies of the Software,
10
- * and to permit persons to whom the Software is furnished to do so,
11
- * subject to the following conditions:
12
- *
13
- * The above copyright notice and this permission notice shall be
14
- * included in all copies or substantial portions of the Software.
15
- *
16
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
- */
24
- Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.ExprFilter = void 0;
26
- const malloy_types_1 = require("../../../model/malloy_types");
27
- const ast_utils_1 = require("../ast-utils");
28
- const fragtype_utils_1 = require("../fragtype-utils");
29
- const expression_def_1 = require("../types/expression-def");
30
- class ExprFilter extends expression_def_1.ExpressionDef {
31
- constructor(expr, filter) {
32
- super({ expr: expr, filter: filter });
33
- this.expr = expr;
34
- this.filter = filter;
35
- this.elementType = 'filtered expression';
36
- this.legalChildTypes = fragtype_utils_1.FT.anyAtomicT;
37
- }
38
- getExpression(fs) {
39
- const testList = this.filter.getFilterList(fs);
40
- const resultExpr = this.expr.getExpression(fs);
41
- if (!(0, malloy_types_1.expressionIsAggregate)(resultExpr.expressionType)) {
42
- this.expr.log('Filtered expression requires an aggregate computation');
43
- return resultExpr;
44
- }
45
- if (testList.find(cond => (0, malloy_types_1.expressionIsCalculation)(cond.expressionType))) {
46
- this.filter.log('Cannot filter an expresion with an aggregate or analytical computation');
47
- return resultExpr;
48
- }
49
- if (this.typeCheck(this.expr, { ...resultExpr, expressionType: 'scalar' })) {
50
- return {
51
- ...resultExpr,
52
- value: [
53
- {
54
- type: 'filterExpression',
55
- e: resultExpr.value,
56
- filterList: testList,
57
- },
58
- ],
59
- };
60
- }
61
- this.expr.log(`Cannot filter '${resultExpr.dataType}' data`);
62
- return (0, ast_utils_1.errorFor)('cannot filter type');
63
- }
64
- }
65
- exports.ExprFilter = ExprFilter;
66
- //# sourceMappingURL=expr-filter.js.map