@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.
- package/dist/dialect/dialect.d.ts +20 -5
- package/dist/dialect/dialect.js +40 -7
- package/dist/dialect/duckdb.d.ts +5 -5
- package/dist/dialect/duckdb.js +54 -120
- package/dist/dialect/postgres.d.ts +5 -5
- package/dist/dialect/postgres.js +50 -38
- package/dist/dialect/standardsql.d.ts +5 -5
- package/dist/dialect/standardsql.js +78 -42
- package/dist/lang/ast/elements/refined-source.js +8 -0
- package/dist/lang/ast/executors/reduce-executor.js +4 -0
- package/dist/lang/ast/expressions/expr-cast.js +1 -1
- package/dist/lang/ast/expressions/expr-granular-time.d.ts +2 -2
- package/dist/lang/ast/expressions/expr-granular-time.js +10 -19
- package/dist/lang/ast/expressions/expr-time-extract.js +31 -4
- package/dist/lang/ast/expressions/time-literal.d.ts +12 -6
- package/dist/lang/ast/expressions/time-literal.js +74 -67
- package/dist/lang/ast/field-space/dynamic-space.d.ts +2 -0
- package/dist/lang/ast/field-space/dynamic-space.js +6 -0
- package/dist/lang/ast/field-space/query-spaces.js +3 -0
- package/dist/lang/ast/index.d.ts +1 -0
- package/dist/lang/ast/index.js +1 -0
- package/dist/lang/ast/source-properties/timezone-statement.d.ts +6 -0
- package/dist/lang/ast/source-properties/timezone-statement.js +35 -0
- package/dist/lang/ast/sources/named-source.js +1 -1
- package/dist/lang/ast/sources/sql-source.js +4 -0
- package/dist/lang/ast/time-utils.d.ts +2 -1
- package/dist/lang/ast/time-utils.js +5 -1
- package/dist/lang/ast/types/expression-def.d.ts +2 -0
- package/dist/lang/ast/types/expression-def.js +48 -18
- package/dist/lang/ast/types/query-property.d.ts +2 -1
- package/dist/lang/ast/types/query-property.js +3 -1
- package/dist/lang/ast/types/source-property.d.ts +2 -1
- package/dist/lang/ast/types/source-property.js +3 -1
- package/dist/lang/lib/Malloy/MalloyLexer.d.ts +3 -3
- package/dist/lang/lib/Malloy/MalloyLexer.js +958 -972
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +52 -33
- package/dist/lang/lib/Malloy/MalloyParser.js +1243 -1128
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +24 -0
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +15 -0
- package/dist/lang/malloy-to-ast.d.ts +2 -0
- package/dist/lang/malloy-to-ast.js +6 -0
- package/dist/lang/test/parse-expects.d.ts +1 -0
- package/dist/lang/test/parse-expects.js +11 -1
- package/dist/lang/test/parse.spec.js +29 -37
- package/dist/malloy.js +31 -23
- package/dist/model/malloy_query.d.ts +2 -0
- package/dist/model/malloy_query.js +42 -41
- package/dist/model/malloy_types.d.ts +3 -2
- 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"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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 ((
|
|
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
|
-
|
|
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' &&
|
|
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
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
if (
|
|
344
|
-
|
|
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 (
|
|
347
|
-
|
|
382
|
+
if (from.valueType !== to.valueType) {
|
|
383
|
+
throw new Error("Can't measure difference between different types");
|
|
348
384
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
112
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
|
78
|
-
right: { valueType
|
|
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
|
|
25
|
-
protected
|
|
22
|
+
protected makeLiteral(val: string, typ: TimeFieldType): TimeLiteralFragment;
|
|
23
|
+
protected makeValue(val: string, dataType: TimeFieldType): TimeResult;
|
|
26
24
|
getExpression(_fs: FieldSpace): ExprValue;
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
71
|
+
makeLiteral(val, typ) {
|
|
72
|
+
const timeFrag = {
|
|
82
73
|
type: 'dialect',
|
|
83
74
|
function: 'timeLiteral',
|
|
84
|
-
literal:
|
|
85
|
-
literalType:
|
|
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
|
-
|
|
91
|
-
const timeFrag = this.
|
|
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.
|
|
93
|
+
return this.makeValue(this.literalPart, this.timeType);
|
|
102
94
|
}
|
|
103
|
-
|
|
95
|
+
getNext() {
|
|
104
96
|
if (this.nextLit) {
|
|
105
|
-
|
|
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
|
|
122
|
+
// mtoy TODO subsecond units not ignored
|
|
146
123
|
}
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
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
|
-
|
|
153
|
-
if (
|
|
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 =
|
|
158
|
-
|
|
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
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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(
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
}
|