@malloydata/malloy 0.0.34-dev230523182411 → 0.0.34

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 (49) hide show
  1. package/dist/dialect/dialect.d.ts +20 -5
  2. package/dist/dialect/dialect.js +40 -7
  3. package/dist/dialect/duckdb.d.ts +5 -5
  4. package/dist/dialect/duckdb.js +54 -120
  5. package/dist/dialect/postgres.d.ts +5 -5
  6. package/dist/dialect/postgres.js +50 -38
  7. package/dist/dialect/standardsql.d.ts +5 -5
  8. package/dist/dialect/standardsql.js +78 -42
  9. package/dist/lang/ast/elements/refined-source.js +8 -0
  10. package/dist/lang/ast/executors/reduce-executor.js +4 -0
  11. package/dist/lang/ast/expressions/expr-cast.js +1 -1
  12. package/dist/lang/ast/expressions/expr-granular-time.d.ts +2 -2
  13. package/dist/lang/ast/expressions/expr-granular-time.js +10 -19
  14. package/dist/lang/ast/expressions/expr-time-extract.js +31 -4
  15. package/dist/lang/ast/expressions/time-literal.d.ts +12 -6
  16. package/dist/lang/ast/expressions/time-literal.js +74 -67
  17. package/dist/lang/ast/field-space/dynamic-space.d.ts +2 -0
  18. package/dist/lang/ast/field-space/dynamic-space.js +6 -0
  19. package/dist/lang/ast/field-space/query-spaces.js +3 -0
  20. package/dist/lang/ast/index.d.ts +1 -0
  21. package/dist/lang/ast/index.js +1 -0
  22. package/dist/lang/ast/source-properties/timezone-statement.d.ts +6 -0
  23. package/dist/lang/ast/source-properties/timezone-statement.js +35 -0
  24. package/dist/lang/ast/sources/named-source.js +1 -1
  25. package/dist/lang/ast/sources/sql-source.js +4 -0
  26. package/dist/lang/ast/time-utils.d.ts +2 -1
  27. package/dist/lang/ast/time-utils.js +5 -1
  28. package/dist/lang/ast/types/expression-def.d.ts +2 -0
  29. package/dist/lang/ast/types/expression-def.js +48 -18
  30. package/dist/lang/ast/types/query-property.d.ts +2 -1
  31. package/dist/lang/ast/types/query-property.js +3 -1
  32. package/dist/lang/ast/types/source-property.d.ts +2 -1
  33. package/dist/lang/ast/types/source-property.js +3 -1
  34. package/dist/lang/lib/Malloy/MalloyLexer.d.ts +3 -3
  35. package/dist/lang/lib/Malloy/MalloyLexer.js +958 -972
  36. package/dist/lang/lib/Malloy/MalloyParser.d.ts +52 -33
  37. package/dist/lang/lib/Malloy/MalloyParser.js +1243 -1128
  38. package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +24 -0
  39. package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +15 -0
  40. package/dist/lang/malloy-to-ast.d.ts +2 -0
  41. package/dist/lang/malloy-to-ast.js +6 -0
  42. package/dist/lang/test/parse-expects.d.ts +1 -0
  43. package/dist/lang/test/parse-expects.js +11 -1
  44. package/dist/lang/test/parse.spec.js +29 -37
  45. package/dist/malloy.js +31 -23
  46. package/dist/model/malloy_query.d.ts +2 -0
  47. package/dist/model/malloy_query.js +42 -41
  48. package/dist/model/malloy_types.d.ts +3 -2
  49. package/package.json +1 -1
@@ -29,19 +29,33 @@ const dialect_1 = require("./dialect");
29
29
  const castMap = {
30
30
  'number': 'float64',
31
31
  };
32
- // These are the units that "TIMESTAMP_ADD" accepts
33
- const timestampAddUnits = [
34
- 'microsecond',
35
- 'millisecond',
36
- 'second',
37
- 'minute',
38
- 'hour',
39
- 'day',
40
- ];
32
+ // These are the units that "TIMESTAMP_ADD" "TIMESTAMP_DIFF" accept
33
+ function timestampMeasureable(units) {
34
+ return [
35
+ 'microsecond',
36
+ 'millisecond',
37
+ 'second',
38
+ 'minute',
39
+ 'hour',
40
+ 'day',
41
+ ].includes(units);
42
+ }
43
+ function dateMeasureable(units) {
44
+ return ['day', 'week', 'month', 'quarter', 'year'].includes(units);
45
+ }
41
46
  const extractMap = {
42
47
  'day_of_week': 'dayofweek',
43
48
  'day_of_year': 'dayofyear',
44
49
  };
50
+ /**
51
+ * Return a non UTC timezone, if one was specificed.
52
+ */
53
+ function qtz(qi) {
54
+ const tz = qi.queryTimezone;
55
+ if (tz && tz !== 'UTC') {
56
+ return tz;
57
+ }
58
+ }
45
59
  class StandardSQLDialect extends dialect_1.Dialect {
46
60
  constructor() {
47
61
  super(...arguments);
@@ -272,23 +286,27 @@ ${(0, utils_1.indent)(sql)}
272
286
  sqlNow() {
273
287
  return (0, malloy_types_1.mkExpr) `CURRENT_TIMESTAMP()`;
274
288
  }
275
- sqlTrunc(sqlTime, units) {
289
+ sqlTrunc(qi, sqlTime, units) {
290
+ const tz = qtz(qi);
291
+ const tzAdd = tz ? `, "${tz}"` : '';
276
292
  if (sqlTime.valueType === 'date') {
277
- if ((0, malloy_types_1.isDateUnit)(units)) {
293
+ if (dateMeasureable(units)) {
278
294
  return (0, malloy_types_1.mkExpr) `DATE_TRUNC(${sqlTime.value},${units})`;
279
295
  }
280
- return (0, malloy_types_1.mkExpr) `TIMESTAMP(${sqlTime.value})`;
296
+ return (0, malloy_types_1.mkExpr) `TIMESTAMP(${sqlTime.value}${tzAdd})`;
281
297
  }
282
- return (0, malloy_types_1.mkExpr) `TIMESTAMP_TRUNC(${sqlTime.value},${units})`;
298
+ return (0, malloy_types_1.mkExpr) `TIMESTAMP_TRUNC(${sqlTime.value},${units}${tzAdd})`;
283
299
  }
284
- sqlExtract(expr, units) {
300
+ sqlExtract(qi, expr, units) {
285
301
  const extractTo = extractMap[units] || units;
286
- return (0, malloy_types_1.mkExpr) `EXTRACT(${extractTo} FROM ${expr.value})`;
302
+ const tz = expr.valueType === 'timestamp' && qtz(qi);
303
+ const tzAdd = tz ? ` AT TIME ZONE '${tz}'` : '';
304
+ return (0, malloy_types_1.mkExpr) `EXTRACT(${extractTo} FROM ${expr.value}${tzAdd})`;
287
305
  }
288
306
  sqlAlterTime(op, expr, n, timeframe) {
289
307
  let theTime = expr.value;
290
308
  let computeType = expr.valueType;
291
- if (timeframe !== 'day' && timestampAddUnits.includes(timeframe)) {
309
+ if (timeframe !== 'day' && timestampMeasureable(timeframe)) {
292
310
  // The units must be done in timestamp, no matter the input type
293
311
  computeType = 'timestamp';
294
312
  if (expr.valueType !== 'timestamp') {
@@ -301,21 +319,25 @@ ${(0, utils_1.indent)(sql)}
301
319
  }
302
320
  const funcName = computeType.toUpperCase() + (op === '+' ? '_ADD' : '_SUB');
303
321
  const newTime = (0, malloy_types_1.mkExpr) `${funcName}(${theTime}, INTERVAL ${n} ${timeframe})`;
304
- return computeType === 'datetime' ? (0, malloy_types_1.mkExpr) `TIMESTAMP(${newTime})` : newTime;
322
+ if (computeType === expr.valueType) {
323
+ return newTime;
324
+ }
325
+ return (0, malloy_types_1.mkExpr) `${expr.valueType.toUpperCase()}(${newTime})`;
305
326
  }
306
327
  ignoreInProject(fieldName) {
307
328
  return fieldName === '_PARTITIONTIME';
308
329
  }
309
- sqlCast(cast) {
330
+ sqlCast(qi, cast) {
331
+ const op = `${cast.srcType}::${cast.dstType}`;
332
+ const tz = qtz(qi);
333
+ if (op === 'timestamp::date' && tz) {
334
+ return (0, malloy_types_1.mkExpr) `DATE(${cast.expr},'${tz}')`;
335
+ }
336
+ if (op === 'date::timestamp' && tz) {
337
+ return (0, malloy_types_1.mkExpr) `TIMESTAMP(${cast.expr}, '${tz}')`;
338
+ }
310
339
  if (cast.srcType !== cast.dstType) {
311
340
  const dstType = castMap[cast.dstType] || cast.dstType;
312
- // This just makes the code look a little prettier ...
313
- if (!cast.safe && cast.srcType && (0, malloy_types_1.isTimeFieldType)(cast.srcType)) {
314
- if (dstType === 'date') {
315
- return (0, malloy_types_1.mkExpr) `DATE(${cast.expr})`;
316
- }
317
- return (0, malloy_types_1.mkExpr) `TIMESTAMP(${cast.expr})`;
318
- }
319
341
  const castFunc = cast.safe ? 'SAFE_CAST' : 'CAST';
320
342
  return (0, malloy_types_1.mkExpr) `${castFunc}(${cast.expr} AS ${dstType})`;
321
343
  }
@@ -324,39 +346,53 @@ ${(0, utils_1.indent)(sql)}
324
346
  sqlRegexpMatch(expr, regexp) {
325
347
  return (0, malloy_types_1.mkExpr) `REGEXP_CONTAINS(${expr}, r${regexp})`;
326
348
  }
327
- sqlLiteralTime(timeString, type, timezone) {
349
+ sqlLiteralTime(qi, timeString, type, timezone) {
328
350
  if (type === 'date') {
329
351
  return `DATE('${timeString}')`;
330
352
  }
331
353
  else if (type === 'timestamp') {
332
- return `TIMESTAMP('${timeString}', '${timezone}')`;
354
+ let timestampArgs = `'${timeString}'`;
355
+ const tz = timezone || qtz(qi);
356
+ if (tz && tz !== 'UTC') {
357
+ timestampArgs += `,'${tz}'`;
358
+ }
359
+ return `TIMESTAMP(${timestampArgs})`;
333
360
  }
334
361
  else {
335
- throw new Error(`Unknown Liternal time format ${type}`);
362
+ throw new Error(`Unsupported Literal time format ${type}`);
336
363
  }
337
364
  }
338
365
  sqlMeasureTime(from, to, units) {
366
+ const measureMap = {
367
+ microsecond: { use: 'microsecond', ratio: 1 },
368
+ millisecond: { use: 'microsecond', ratio: 1000 },
369
+ second: { use: 'millisecond', ratio: 1000 },
370
+ minute: { use: 'second', ratio: 60 },
371
+ hour: { use: 'minute', ratio: 60 },
372
+ day: { use: 'hour', ratio: 24 },
373
+ week: { use: 'day', ratio: 7 },
374
+ };
339
375
  let lVal = from.value;
340
376
  let rVal = to.value;
341
- let diffUsing = 'TIMESTAMP_DIFF';
342
- if (units === 'second' || units === 'minute' || units === 'hour') {
343
- if (from.valueType !== 'timestamp') {
344
- lVal = (0, malloy_types_1.mkExpr) `TIMESTAMP(${lVal})`;
377
+ if (measureMap[units]) {
378
+ const { use: measureIn, ratio } = measureMap[units];
379
+ if (!timestampMeasureable(measureIn)) {
380
+ throw new Error(`Measure in '${measureIn} not implemented`);
345
381
  }
346
- if (to.valueType !== 'timestamp') {
347
- rVal = (0, malloy_types_1.mkExpr) `TIMESTAMP(${rVal})`;
382
+ if (from.valueType !== to.valueType) {
383
+ throw new Error("Can't measure difference between different types");
348
384
  }
349
- }
350
- else {
351
- diffUsing = 'DATE_DIFF';
352
- if (from.valueType !== 'date') {
353
- lVal = (0, malloy_types_1.mkExpr) `DATE(${lVal})`;
385
+ if (from.valueType === 'date') {
386
+ lVal = (0, malloy_types_1.mkExpr) `TIMESTAMP(${lVal})`;
387
+ rVal = (0, malloy_types_1.mkExpr) `TIMESTAMP(${rVal})`;
354
388
  }
355
- if (to.valueType !== 'date') {
356
- rVal = (0, malloy_types_1.mkExpr) `DATE(${rVal})`;
389
+ let measured = (0, malloy_types_1.mkExpr) `TIMESTAMP_DIFF(${rVal},${lVal},${measureIn})`;
390
+ if (ratio !== 1) {
391
+ measured = (0, malloy_types_1.mkExpr) `FLOOR(${measured}/${ratio.toString()}.0)`;
357
392
  }
393
+ return measured;
358
394
  }
359
- return (0, malloy_types_1.mkExpr) `${diffUsing}(${rVal}, ${lVal}, ${units})`;
395
+ throw new Error(`Measure '${units} not implemented`);
360
396
  }
361
397
  sqlSampleTable(tableSQL, sample) {
362
398
  if (sample !== undefined) {
@@ -37,6 +37,7 @@ const primary_key_1 = require("../source-properties/primary-key");
37
37
  const renames_1 = require("../source-properties/renames");
38
38
  const turtles_1 = require("../source-properties/turtles");
39
39
  const source_1 = require("./source");
40
+ const timezone_statement_1 = require("../source-properties/timezone-statement");
40
41
  /**
41
42
  * A Source made from a source reference and a set of refinements
42
43
  */
@@ -55,6 +56,7 @@ class RefinedSource extends source_1.Source {
55
56
  let fieldListEdit;
56
57
  const fields = [];
57
58
  const filters = [];
59
+ let newTimezone;
58
60
  for (const el of this.refinement.list) {
59
61
  const errTo = el;
60
62
  if (el instanceof primary_key_1.PrimaryKey) {
@@ -80,6 +82,9 @@ class RefinedSource extends source_1.Source {
80
82
  else if (el instanceof filters_1.Filter) {
81
83
  filters.push(el);
82
84
  }
85
+ else if (el instanceof timezone_statement_1.TimezoneStatement) {
86
+ newTimezone = el.tz;
87
+ }
83
88
  else {
84
89
  errTo.log(`Unexpected explore property: '${errTo.elementType}'`);
85
90
  }
@@ -89,6 +94,9 @@ class RefinedSource extends source_1.Source {
89
94
  from.primaryKey = primaryKey.field.name;
90
95
  }
91
96
  const fs = refined_space_1.RefinedSpace.filteredFrom(from, fieldListEdit);
97
+ if (newTimezone) {
98
+ fs.setTimezone(newTimezone);
99
+ }
92
100
  fs.addField(...fields);
93
101
  if (pList) {
94
102
  fs.addParameters(pList);
@@ -36,6 +36,7 @@ const top_1 = require("../query-properties/top");
36
36
  const nests_1 = require("../query-properties/nests");
37
37
  const nest_1 = require("../query-properties/nest");
38
38
  const query_spaces_1 = require("../field-space/query-spaces");
39
+ const timezone_statement_1 = require("../source-properties/timezone-statement");
39
40
  class ReduceExecutor {
40
41
  constructor(baseFS) {
41
42
  this.filters = [];
@@ -94,6 +95,9 @@ class ReduceExecutor {
94
95
  this.inputFS.extendSource(qel);
95
96
  }
96
97
  }
98
+ else if (qp instanceof timezone_statement_1.TimezoneStatement) {
99
+ this.resultFS.setTimezone(qp.tz);
100
+ }
97
101
  }
98
102
  refineFrom(from, to) {
99
103
  if (from && from.type !== 'index') {
@@ -43,7 +43,7 @@ class ExprCast extends expression_def_1.ExpressionDef {
43
43
  return {
44
44
  dataType: this.castType,
45
45
  expressionType: expr.expressionType,
46
- value: (0, utils_1.compressExpr)((0, time_utils_1.castTo)(this.castType, expr.value, this.safe)),
46
+ value: (0, utils_1.compressExpr)((0, time_utils_1.castTo)(this.castType, expr.value, expr.dataType, this.safe)),
47
47
  };
48
48
  }
49
49
  }
@@ -2,6 +2,7 @@ import { TimestampUnit } from '../../../model/malloy_types';
2
2
  import { ExprValue } from '../types/expr-value';
3
3
  import { ExpressionDef } from '../types/expression-def';
4
4
  import { FieldSpace } from '../types/field-space';
5
+ import { Range } from './range';
5
6
  /**
6
7
  * GranularTime is a moment in time which ALSO has a "granularity"
7
8
  * commonly this are created by applying ".datePart" to an expression
@@ -19,6 +20,5 @@ export declare class ExprGranularTime extends ExpressionDef {
19
20
  granular(): boolean;
20
21
  getExpression(fs: FieldSpace): ExprValue;
21
22
  apply(fs: FieldSpace, op: string, left: ExpressionDef): ExprValue;
22
- protected timestampRange(fs: FieldSpace, op: string, expr: ExpressionDef): ExprValue;
23
- protected dateRange(fs: FieldSpace, op: string, expr: ExpressionDef): ExprValue;
23
+ protected getRange(fs: FieldSpace): Range;
24
24
  }
@@ -74,13 +74,7 @@ class ExprGranularTime extends expression_def_1.ExpressionDef {
74
74
  return (0, ast_utils_1.errorFor)('granularity typecheck');
75
75
  }
76
76
  apply(fs, op, left) {
77
- const rangeType = this.getExpression(fs).dataType;
78
- const _valueType = left.getExpression(fs).dataType;
79
- const granularityType = (0, malloy_types_1.isDateUnit)(this.units) ? 'date' : 'timestamp';
80
- if (rangeType === 'date' && granularityType === 'date') {
81
- return this.dateRange(fs, op, left);
82
- }
83
- return this.timestampRange(fs, op, left);
77
+ return this.getRange(fs).apply(fs, op, left);
84
78
  /*
85
79
  write tests for each of these cases ....
86
80
 
@@ -97,20 +91,17 @@ class ExprGranularTime extends expression_def_1.ExpressionDef {
97
91
 
98
92
  */
99
93
  }
100
- timestampRange(fs, op, expr) {
101
- const begin = this.getExpression(fs);
102
- const beginTime = expr_time_1.ExprTime.fromValue('timestamp', begin);
103
- const endTime = new expr_time_1.ExprTime('timestamp', (0, time_utils_1.timeOffset)('timestamp', begin.value, '+', (0, malloy_types_1.mkExpr) `1`, this.units), begin.expressionType);
104
- const range = new range_1.Range(beginTime, endTime);
105
- return range.apply(fs, op, expr);
106
- }
107
- dateRange(fs, op, expr) {
94
+ getRange(fs) {
108
95
  const begin = this.getExpression(fs);
109
- const beginTime = new expr_time_1.ExprTime('date', begin.value, begin.expressionType);
96
+ if (begin.dataType === 'timestamp') {
97
+ const beginTS = expr_time_1.ExprTime.fromValue('timestamp', begin);
98
+ const endTS = new expr_time_1.ExprTime('timestamp', (0, time_utils_1.timeOffset)('timestamp', begin.value, '+', (0, malloy_types_1.mkExpr) `1`, this.units), begin.expressionType);
99
+ return new range_1.Range(beginTS, endTS);
100
+ }
101
+ const beginDate = new expr_time_1.ExprTime('date', begin.value, begin.expressionType);
110
102
  const endAt = (0, time_utils_1.timeOffset)('date', begin.value, '+', ['1'], this.units);
111
- const end = new expr_time_1.ExprTime('date', endAt, begin.expressionType);
112
- const range = new range_1.Range(beginTime, end);
113
- return range.apply(fs, op, expr);
103
+ const endDate = new expr_time_1.ExprTime('date', endAt, begin.expressionType);
104
+ return new range_1.Range(beginDate, endDate);
114
105
  }
115
106
  }
116
107
  exports.ExprGranularTime = ExprGranularTime;
@@ -52,8 +52,8 @@ class ExprTimeExtract extends expression_def_1.ExpressionDef {
52
52
  }
53
53
  const from = this.args[0];
54
54
  if (from instanceof range_1.Range) {
55
- const first = from.first.getExpression(fs);
56
- const last = from.last.getExpression(fs);
55
+ let first = from.first.getExpression(fs);
56
+ let last = from.last.getExpression(fs);
57
57
  if (!(0, malloy_types_1.isTimeFieldType)(first.dataType)) {
58
58
  from.first.log(`Can't extract ${extractTo} from '${first.dataType}'`);
59
59
  return (0, ast_utils_1.errorFor)(`${extractTo} bad type ${first.dataType}`);
@@ -62,6 +62,33 @@ class ExprTimeExtract extends expression_def_1.ExpressionDef {
62
62
  from.last.log(`Cannot extract ${extractTo} from '${last.dataType}'`);
63
63
  return (0, ast_utils_1.errorFor)(`${extractTo} bad type ${last.dataType}`);
64
64
  }
65
+ let valueType = first.dataType;
66
+ if (first.dataType !== last.dataType) {
67
+ let cannotMeasure = true;
68
+ valueType = 'timestamp';
69
+ if (first.dataType === 'date') {
70
+ const newFirst = (0, expression_def_1.getMorphicValue)(first, 'timestamp');
71
+ if (newFirst) {
72
+ first = newFirst;
73
+ cannotMeasure = false;
74
+ }
75
+ }
76
+ else {
77
+ const newLast = (0, expression_def_1.getMorphicValue)(last, 'timestamp');
78
+ if (newLast) {
79
+ last = newLast;
80
+ cannotMeasure = false;
81
+ }
82
+ }
83
+ if (cannotMeasure) {
84
+ from.first.log(`Cannot measure from ${first.dataType} to ${last.dataType}`);
85
+ return (0, ast_utils_1.errorFor)(`${extractTo} range mismatch`);
86
+ }
87
+ }
88
+ if (['week', 'month', 'quarter', 'year'].includes(extractTo)) {
89
+ this.log(`Cannot measure interval using '${extractTo}'`);
90
+ return (0, ast_utils_1.errorFor)(`${extractTo} civil extraction`);
91
+ }
65
92
  if (!(0, malloy_types_1.isTimestampUnit)(extractTo)) {
66
93
  this.log(`Cannot extract ${extractTo} from a range`);
67
94
  return (0, ast_utils_1.errorFor)(`${extractTo} bad extraction`);
@@ -74,8 +101,8 @@ class ExprTimeExtract extends expression_def_1.ExpressionDef {
74
101
  type: 'dialect',
75
102
  function: 'timeDiff',
76
103
  units: extractTo,
77
- left: { valueType: first.dataType, value: first.value },
78
- right: { valueType: last.dataType, value: last.value },
104
+ left: { valueType, value: first.value },
105
+ right: { valueType, value: last.value },
79
106
  },
80
107
  ],
81
108
  };
@@ -8,7 +8,6 @@ export declare class TimeFormatError extends Error {
8
8
  interface TimeText {
9
9
  text: string;
10
10
  tzSpec?: string;
11
- isLocale?: boolean;
12
11
  }
13
12
  /**
14
13
  * Literals specified with an @ in Malloy all become one of these
@@ -19,12 +18,11 @@ declare abstract class TimeLiteral extends ExpressionDef {
19
18
  literalPart: string;
20
19
  nextLit?: string;
21
20
  timeZone?: string;
22
- isLocale?: boolean;
23
21
  constructor(tm: TimeText, units: TimestampUnit | undefined, timeType: TimeFieldType);
24
- protected getLiteral(): TimeLiteralFragment;
25
- protected getValue(): TimeResult;
22
+ protected makeLiteral(val: string, typ: TimeFieldType): TimeLiteralFragment;
23
+ protected makeValue(val: string, dataType: TimeFieldType): TimeResult;
26
24
  getExpression(_fs: FieldSpace): ExprValue;
27
- apply(fs: FieldSpace, op: string, left: ExpressionDef): ExprValue;
25
+ getNext(): ExprValue | undefined;
28
26
  granular(): boolean;
29
27
  }
30
28
  export declare class LiteralTimestamp extends TimeLiteral {
@@ -36,18 +34,26 @@ export declare class LiteralTimestamp extends TimeLiteral {
36
34
  * Granular literals imply a range. The end of that range is a constant
37
35
  * in the generated expression, computed at compile time, to make filters faster.
38
36
  */
39
- declare abstract class GranularLiteral extends TimeLiteral {
37
+ declare class GranularLiteral extends TimeLiteral {
40
38
  readonly nextLit: string;
39
+ elementType: string;
41
40
  constructor(tm: TimeText, units: TimestampUnit | undefined, timeType: TimeFieldType, nextLit: string);
41
+ apply(fs: FieldSpace, op: string, left: ExpressionDef): ExprValue;
42
42
  }
43
43
  export declare class LiteralHour extends GranularLiteral {
44
44
  elementType: string;
45
45
  constructor(tm: TimeText, next: string);
46
46
  static parse(literalTs: string): LiteralHour | undefined;
47
47
  }
48
+ /**
49
+ * DateBasedLiteral and all of the children are special because a literal
50
+ * of this type (e.g. @2003) can be used in expressions with Date or
51
+ * Timestamp data, and the correct literal will be used based on context.
52
+ */
48
53
  declare abstract class DateBasedLiteral extends GranularLiteral {
49
54
  constructor(tm: TimeText, units: TimestampUnit, nextLit: string);
50
55
  getExpression(_fs: FieldSpace): ExprValue;
56
+ getNext(): ExprValue | undefined;
51
57
  }
52
58
  export declare class LiteralDay extends DateBasedLiteral {
53
59
  elementType: string;
@@ -44,14 +44,8 @@ function preParse(literal, checkTz) {
44
44
  return {
45
45
  tzSpec,
46
46
  text: text.slice(0, -hasLocale[0].length),
47
- isLocale: true,
48
47
  };
49
48
  }
50
- const hasOffsetTz = text.match(/[+-]\d+(:\d+)?$/);
51
- if (hasOffsetTz) {
52
- const tzSpec = hasOffsetTz[0];
53
- return { tzSpec, text: text.slice(0, -tzSpec.length) };
54
- }
55
49
  }
56
50
  return { text };
57
51
  }
@@ -72,24 +66,22 @@ class TimeLiteral extends expression_def_1.ExpressionDef {
72
66
  this.literalPart = tm.text;
73
67
  if (tm.tzSpec) {
74
68
  this.timeZone = tm.tzSpec;
75
- if (tm.isLocale) {
76
- this.isLocale = true;
77
- }
78
69
  }
79
70
  }
80
- getLiteral() {
81
- return {
71
+ makeLiteral(val, typ) {
72
+ const timeFrag = {
82
73
  type: 'dialect',
83
74
  function: 'timeLiteral',
84
- literal: this.literalPart,
85
- literalType: this.timeType,
86
- timezone: this.timeZone || 'UTC',
87
- tzIsLocale: this.isLocale,
75
+ literal: val,
76
+ literalType: typ,
88
77
  };
78
+ if (this.timeZone) {
79
+ timeFrag.timezone = this.timeZone;
80
+ }
81
+ return timeFrag;
89
82
  }
90
- getValue() {
91
- const timeFrag = this.getLiteral();
92
- const dataType = timeFrag.literalType;
83
+ makeValue(val, dataType) {
84
+ const timeFrag = this.makeLiteral(val, dataType);
93
85
  const expressionType = 'scalar';
94
86
  const value = [timeFrag];
95
87
  if (this.units) {
@@ -98,25 +90,12 @@ class TimeLiteral extends expression_def_1.ExpressionDef {
98
90
  return { dataType, expressionType, value };
99
91
  }
100
92
  getExpression(_fs) {
101
- return this.getValue();
93
+ return this.makeValue(this.literalPart, this.timeType);
102
94
  }
103
- apply(fs, op, left) {
95
+ getNext() {
104
96
  if (this.nextLit) {
105
- // We have a chance to write our own range comparison will all constants.
106
- const lhs = left.getExpression(fs);
107
- // MTOY todo ... only apply range on ? maybe
108
- if ((0, malloy_types_1.isTimeFieldType)(lhs.dataType)) {
109
- let rangeType = 'timestamp';
110
- if (lhs.dataType === 'date' && !this.timeType) {
111
- rangeType = 'date';
112
- }
113
- const rangeStart = this.getLiteral();
114
- const rangeEnd = { ...rangeStart, literal: this.nextLit };
115
- const range = new range_1.Range(new expr_time_1.ExprTime(rangeType, [rangeStart]), new expr_time_1.ExprTime(rangeType, [rangeEnd]));
116
- return range.apply(fs, op, left);
117
- }
97
+ return this.makeValue(this.nextLit, this.timeType);
118
98
  }
119
- return super.apply(fs, op, left);
120
99
  }
121
100
  granular() {
122
101
  return this.nextLit !== undefined;
@@ -129,9 +108,7 @@ class LiteralTimestamp extends TimeLiteral {
129
108
  }
130
109
  static parse(literalTs) {
131
110
  // let subSecs: string | undefined;
132
- let validParse = false;
133
111
  let units = undefined;
134
- let next = undefined;
135
112
  const tm = preParse(literalTs, true);
136
113
  literalTs = tm.text;
137
114
  if (literalTs[10] === 'T') {
@@ -142,29 +119,22 @@ class LiteralTimestamp extends TimeLiteral {
142
119
  if (hasSubsecs) {
143
120
  literalTs = hasSubsecs[1];
144
121
  // subSecs = hasSubsecs[2];
145
- // TODO mtoy subsecond units not ignored
122
+ // mtoy TODO subsecond units not ignored
146
123
  }
147
- const tss = luxon_1.DateTime.fromFormat(literalTs, fTimestamp);
148
- if (tss.isValid) {
149
- validParse = true;
124
+ let ts = luxon_1.DateTime.fromFormat(literalTs, fTimestamp);
125
+ if (ts.isValid) {
126
+ return new LiteralTimestamp(tm, units);
150
127
  }
151
128
  else {
152
- const tsm = luxon_1.DateTime.fromFormat(literalTs, fMinute);
153
- if (tsm.isValid) {
154
- validParse = true;
129
+ ts = luxon_1.DateTime.fromFormat(literalTs, fMinute);
130
+ if (ts.isValid) {
155
131
  tm.text = tm.text + ':00';
156
132
  units = 'minute';
157
- next = tss.plus({ minute: 1 }).toFormat(fTimestamp);
158
- // MTOY todo minutes should be granular
133
+ const next = ts.plus({ minute: 1 }).toFormat(fTimestamp);
134
+ const astMinute = new GranularLiteral(tm, units, 'timestamp', next);
135
+ return astMinute;
159
136
  }
160
137
  }
161
- if (validParse) {
162
- const ts = new LiteralTimestamp(tm, units);
163
- if (next) {
164
- ts.nextLit = next;
165
- }
166
- return ts;
167
- }
168
138
  return undefined;
169
139
  }
170
140
  }
@@ -177,6 +147,32 @@ class GranularLiteral extends TimeLiteral {
177
147
  constructor(tm, units, timeType, nextLit) {
178
148
  super(tm, units, timeType);
179
149
  this.nextLit = nextLit;
150
+ this.elementType = 'granularTimeLiteral';
151
+ }
152
+ apply(fs, op, left) {
153
+ // We have a chance to write our own range comparison will all constants.
154
+ let rangeStart = this.getExpression(fs);
155
+ let rangeEnd = this.getNext();
156
+ if (rangeEnd) {
157
+ const testValue = left.getExpression(fs);
158
+ if (testValue.dataType === 'timestamp') {
159
+ const newStart = (0, expression_def_1.getMorphicValue)(rangeStart, 'timestamp');
160
+ const newEnd = (0, expression_def_1.getMorphicValue)(rangeEnd, 'timestamp');
161
+ if (newStart && newEnd) {
162
+ rangeStart = newStart;
163
+ rangeEnd = newEnd;
164
+ }
165
+ else {
166
+ return super.apply(fs, op, left);
167
+ }
168
+ }
169
+ if ((0, malloy_types_1.isTimeFieldType)(testValue.dataType)) {
170
+ const rangeType = testValue.dataType;
171
+ const range = new range_1.Range(new expr_time_1.ExprTime(rangeType, rangeStart.value), new expr_time_1.ExprTime(rangeType, rangeEnd.value));
172
+ return range.apply(fs, op, left);
173
+ }
174
+ }
175
+ return super.apply(fs, op, left);
180
176
  }
181
177
  }
182
178
  // MTOY todo should probably put the time zone information into the luxon object
@@ -201,17 +197,28 @@ class LiteralHour extends GranularLiteral {
201
197
  }
202
198
  }
203
199
  exports.LiteralHour = LiteralHour;
200
+ /**
201
+ * DateBasedLiteral and all of the children are special because a literal
202
+ * of this type (e.g. @2003) can be used in expressions with Date or
203
+ * Timestamp data, and the correct literal will be used based on context.
204
+ */
204
205
  class DateBasedLiteral extends GranularLiteral {
205
206
  constructor(tm, units, nextLit) {
206
207
  super(tm, units, 'date', nextLit);
207
208
  }
208
209
  getExpression(_fs) {
209
- const morphicValue = this.getValue();
210
- const tsLiteral = this.getLiteral();
211
- tsLiteral.literalType = 'timestamp';
212
- tsLiteral.literal = tsLiteral.literal + ' 00:00:00';
213
- morphicValue.morphic = { timestamp: [tsLiteral] };
214
- return morphicValue;
210
+ const dateValue = this.makeValue(this.literalPart, 'date');
211
+ const timestamp = [
212
+ this.makeLiteral(`${this.literalPart} 00:00:00`, 'timestamp'),
213
+ ];
214
+ return { ...dateValue, morphic: { timestamp } };
215
+ }
216
+ getNext() {
217
+ const dateValue = this.makeValue(this.nextLit, 'date');
218
+ const timestamp = [
219
+ this.makeLiteral(`${this.nextLit} 00:00:00`, 'timestamp'),
220
+ ];
221
+ return { ...dateValue, morphic: { timestamp } };
215
222
  }
216
223
  }
217
224
  class LiteralDay extends DateBasedLiteral {
@@ -224,7 +231,7 @@ class LiteralDay extends DateBasedLiteral {
224
231
  let nextDay = tm.text;
225
232
  const dayParse = luxon_1.DateTime.fromFormat(tm.text, fDay);
226
233
  if (dayParse.isValid) {
227
- nextDay = dayParse.plus({ day: 1 }).toFormat(fTimestamp);
234
+ nextDay = dayParse.plus({ day: 1 }).toFormat(fDay);
228
235
  return new LiteralDay(tm, nextDay);
229
236
  }
230
237
  }
@@ -241,15 +248,15 @@ class LiteralWeek extends DateBasedLiteral {
241
248
  const datePart = tm.text.slice(0, 10);
242
249
  const yyyymmdd = luxon_1.DateTime.fromFormat(datePart, fDay);
243
250
  if (yyyymmdd.isValid) {
244
- // wonky because luxon uses monday weeks and bigquery uses sunday weeks
245
- let sunday = yyyymmdd;
246
- if (yyyymmdd.weekday !== 7) {
247
- sunday = yyyymmdd.startOf('week').minus({ day: 1 });
251
+ const luxonDay = yyyymmdd.weekday;
252
+ if (luxonDay === 7) {
253
+ // Weeks must start on a sunday. If you don't know when the
254
+ // week starts use @YYYY-MM-DD.week to truncate
255
+ const next = yyyymmdd.plus({ days: 7 });
256
+ tm.text = yyyymmdd.toFormat(fDay);
257
+ nextWeek = next.toFormat(fDay);
258
+ return new LiteralWeek(tm, nextWeek);
248
259
  }
249
- const next = sunday.plus({ days: 7 });
250
- tm.text = sunday.toFormat(fDay);
251
- nextWeek = next.toFormat(fDay);
252
- return new LiteralWeek(tm, nextWeek);
253
260
  }
254
261
  }
255
262
  }
@@ -11,6 +11,7 @@ export declare abstract class DynamicSpace extends StaticSpace {
11
11
  outputFS?: FieldSpace;
12
12
  completions: (() => void)[];
13
13
  private complete;
14
+ protected newTimezone?: string;
14
15
  constructor(extending: SourceSpec);
15
16
  whenComplete(finalizeStep: () => void): void;
16
17
  isComplete(): void;
@@ -18,5 +19,6 @@ export declare abstract class DynamicSpace extends StaticSpace {
18
19
  addParameters(params: HasParameter[]): DynamicSpace;
19
20
  newEntry(name: string, astEl: MalloyElement, entry: SpaceEntry): void;
20
21
  addFieldDef(fd: model.FieldDef): void;
22
+ setTimezone(tz: string): void;
21
23
  structDef(): model.StructDef;
22
24
  }