@malloydata/malloy 0.0.263-dev250414165131 → 0.0.263-dev250414204152
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/api/core.d.ts +6 -3
- package/dist/api/core.js +8 -6
- package/dist/lang/ast/expressions/expr-filter-expr.d.ts +0 -3
- package/dist/lang/ast/expressions/expr-filter-expr.js +7 -59
- package/dist/lang/ast/expressions/expr-func.js +3 -0
- package/dist/lang/ast/expressions/pick-when.js +6 -0
- package/dist/lang/ast/field-space/dynamic-space.js +1 -1
- package/dist/lang/ast/field-space/join-space-field.js +1 -1
- package/dist/lang/ast/index.d.ts +1 -0
- package/dist/lang/ast/index.js +1 -0
- package/dist/lang/ast/parameters/has-parameter.d.ts +6 -3
- package/dist/lang/ast/parameters/has-parameter.js +28 -9
- package/dist/lang/ast/query-items/field-declaration.d.ts +1 -2
- package/dist/lang/ast/query-items/field-declaration.js +0 -4
- package/dist/lang/ast/source-elements/named-source.js +6 -0
- package/dist/lang/ast/source-properties/join.d.ts +3 -3
- package/dist/lang/ast/source-properties/join.js +2 -2
- package/dist/lang/ast/types/expression-def.d.ts +2 -1
- package/dist/lang/ast/types/expression-def.js +79 -17
- package/dist/lang/ast/types/space-param.d.ts +1 -3
- package/dist/lang/ast/types/space-param.js +8 -8
- package/dist/lang/lib/Malloy/MalloyLexer.d.ts +114 -113
- package/dist/lang/lib/Malloy/MalloyLexer.js +1272 -1266
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +253 -239
- package/dist/lang/lib/Malloy/MalloyParser.js +2244 -2142
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +11 -0
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +7 -0
- package/dist/lang/malloy-to-ast.d.ts +1 -1
- package/dist/lang/malloy-to-ast.js +38 -12
- package/dist/lang/test/expr-to-str.js +3 -20
- package/dist/model/filter_compilers.d.ts +27 -0
- package/dist/model/filter_compilers.js +335 -3
- package/dist/model/malloy_query.d.ts +6 -2
- package/dist/model/malloy_query.js +64 -22
- package/dist/model/malloy_types.d.ts +19 -6
- package/dist/model/malloy_types.js +6 -2
- package/dist/to_stable.js +19 -5
- package/package.json +4 -4
- package/dist/model/filter_temporal_compiler.d.ts +0 -29
- package/dist/model/filter_temporal_compiler.js +0 -343
|
@@ -890,20 +890,55 @@ class QueryField extends QueryNode {
|
|
|
890
890
|
case 'compositeField':
|
|
891
891
|
return '{COMPOSITE_FIELD}';
|
|
892
892
|
case 'filterMatch':
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
expr.dataType === 'timestamp' ||
|
|
897
|
-
expr.dataType === 'boolean') {
|
|
898
|
-
if (expr.filter === null || (0, malloy_filter_1.isFilterExpression)(expr.filter)) {
|
|
899
|
-
return filter_compilers_1.FilterCompilers.compile(expr.dataType, expr.filter, expr.e.sql || '', this.parent.dialect);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
throw new Error(`Internal Error: Filter Compiler Undefined Type '${expr.dataType}'`);
|
|
893
|
+
return this.generateAppliedFilter(context, expr);
|
|
894
|
+
case 'filterLiteral':
|
|
895
|
+
return 'INTERNAL ERROR FILTER EXPRESSION VALUE SHOULD NOT BE USED';
|
|
903
896
|
default:
|
|
904
897
|
throw new Error(`Internal Error: Unknown expression node '${expr.node}' ${JSON.stringify(expr, undefined, 2)}`);
|
|
905
898
|
}
|
|
906
899
|
}
|
|
900
|
+
generateAppliedFilter(context, filterMatchExpr) {
|
|
901
|
+
var _a;
|
|
902
|
+
let filterExpr = filterMatchExpr.kids.filterExpr;
|
|
903
|
+
while (filterExpr.node === '()') {
|
|
904
|
+
filterExpr = filterExpr.e;
|
|
905
|
+
}
|
|
906
|
+
if (filterExpr.node === 'parameter') {
|
|
907
|
+
const name = filterExpr.path[0];
|
|
908
|
+
(_a = context.eventStream) === null || _a === void 0 ? void 0 : _a.emit('source-argument-compiled', { name });
|
|
909
|
+
const argument = context.arguments()[name];
|
|
910
|
+
if (argument.value) {
|
|
911
|
+
filterExpr = argument.value;
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
throw new Error(`Parameter ${name} was expected to be a filter expression`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (filterExpr.node !== 'filterLiteral') {
|
|
918
|
+
throw new Error('Can only use filter expression literals or parameters as filter expressions');
|
|
919
|
+
}
|
|
920
|
+
const filterSrc = filterExpr.filterSrc;
|
|
921
|
+
let fParse;
|
|
922
|
+
switch (filterMatchExpr.dataType) {
|
|
923
|
+
case 'string':
|
|
924
|
+
fParse = malloy_filter_1.StringFilterExpression.parse(filterSrc);
|
|
925
|
+
break;
|
|
926
|
+
case 'number':
|
|
927
|
+
fParse = malloy_filter_1.NumberFilterExpression.parse(filterSrc);
|
|
928
|
+
break;
|
|
929
|
+
case 'boolean':
|
|
930
|
+
fParse = malloy_filter_1.BooleanFilterExpression.parse(filterSrc);
|
|
931
|
+
break;
|
|
932
|
+
case 'date':
|
|
933
|
+
case 'timestamp':
|
|
934
|
+
fParse = malloy_filter_1.TemporalFilterExpression.parse(filterSrc);
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
if (fParse.log.length > 0) {
|
|
938
|
+
throw new Error(`Filter expression parse error: ${fParse.log[0]}`);
|
|
939
|
+
}
|
|
940
|
+
return filter_compilers_1.FilterCompilers.compile(filterMatchExpr.dataType, fParse.parsed, filterMatchExpr.kids.expr.sql || '', context.dialect);
|
|
941
|
+
}
|
|
907
942
|
isNestedInParent(parentDef) {
|
|
908
943
|
switch (parentDef.type) {
|
|
909
944
|
case 'record':
|
|
@@ -3725,28 +3760,34 @@ class QueryModel {
|
|
|
3725
3760
|
};
|
|
3726
3761
|
}
|
|
3727
3762
|
addDefaultRowLimit(query, defaultRowLimit) {
|
|
3763
|
+
const nope = { query, addedDefaultRowLimit: undefined };
|
|
3728
3764
|
if (defaultRowLimit === undefined)
|
|
3729
|
-
return
|
|
3765
|
+
return nope;
|
|
3730
3766
|
const lastSegment = query.pipeline[query.pipeline.length - 1];
|
|
3731
3767
|
if (lastSegment.type === 'raw')
|
|
3732
|
-
return
|
|
3768
|
+
return nope;
|
|
3733
3769
|
if (lastSegment.limit !== undefined)
|
|
3734
|
-
return
|
|
3770
|
+
return nope;
|
|
3735
3771
|
return {
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3772
|
+
query: {
|
|
3773
|
+
...query,
|
|
3774
|
+
pipeline: [
|
|
3775
|
+
...query.pipeline.slice(0, -1),
|
|
3776
|
+
{
|
|
3777
|
+
...lastSegment,
|
|
3778
|
+
limit: defaultRowLimit,
|
|
3779
|
+
},
|
|
3780
|
+
],
|
|
3781
|
+
},
|
|
3782
|
+
addedDefaultRowLimit: defaultRowLimit,
|
|
3744
3783
|
};
|
|
3745
3784
|
}
|
|
3746
3785
|
compileQuery(query, prepareResultOptions, finalize = true) {
|
|
3747
3786
|
var _a, _b;
|
|
3748
3787
|
let newModel;
|
|
3749
|
-
|
|
3788
|
+
const addDefaultRowLimit = this.addDefaultRowLimit(query, prepareResultOptions === null || prepareResultOptions === void 0 ? void 0 : prepareResultOptions.defaultRowLimit);
|
|
3789
|
+
query = addDefaultRowLimit.query;
|
|
3790
|
+
const addedDefaultRowLimit = addDefaultRowLimit.addedDefaultRowLimit;
|
|
3750
3791
|
const m = newModel || this;
|
|
3751
3792
|
const ret = m.loadQuery(query, undefined, prepareResultOptions, finalize, false);
|
|
3752
3793
|
const structRef = (_a = query.compositeResolvedSourceDef) !== null && _a !== void 0 ? _a : query.structRef;
|
|
@@ -3774,6 +3815,7 @@ class QueryModel {
|
|
|
3774
3815
|
connectionName: ret.connectionName,
|
|
3775
3816
|
annotation: query.annotation,
|
|
3776
3817
|
queryTimezone: ret.structs[0].queryTimezone,
|
|
3818
|
+
defaultRowLimitAdded: addedDefaultRowLimit,
|
|
3777
3819
|
};
|
|
3778
3820
|
}
|
|
3779
3821
|
async searchIndex(connection, explore, searchValue, limit = 1000, searchField = undefined) {
|
|
@@ -25,7 +25,7 @@ export type AnyExpr = ExprE | ExprOptionalE | ExprWithKids | ExprLeaf;
|
|
|
25
25
|
export declare function exprHasKids(e: AnyExpr): e is ExprWithKids;
|
|
26
26
|
export declare function exprHasE(e: AnyExpr): e is ExprE;
|
|
27
27
|
export declare function exprIsLeaf(e: AnyExpr): boolean;
|
|
28
|
-
export type Expr = BinaryExpr | UnaryExpr | FunctionCallNode | OutputFieldNode | FilterCondition | FilteredExpr | AggregateExpr | EmptyExpr | UngroupNode | FunctionParameterNode | SpreadExpr | AggregateOrderByNode | AggregateLimitNode | FieldnameNode | SourceReferenceNode | ParameterNode | NowNode | MeasureTimeExpr | TimeExtractExpr | TimeDeltaExpr | TimeTruncExpr | TimeLiteralNode | TypecastExpr | RegexMatchExpr | RegexLiteralNode | FilterMatchExpr | StringLiteralNode | NumberLiteralNode | BooleanLiteralNode | RecordLiteralNode | ArrayLiteralNode | FunctionOrderBy | GenericSQLExpr | NullNode | CaseExpr | InCompareExpr | CompositeFieldExpr | ErrorNode;
|
|
28
|
+
export type Expr = BinaryExpr | UnaryExpr | FunctionCallNode | OutputFieldNode | FilterCondition | FilteredExpr | AggregateExpr | EmptyExpr | UngroupNode | FunctionParameterNode | SpreadExpr | AggregateOrderByNode | AggregateLimitNode | FieldnameNode | SourceReferenceNode | ParameterNode | NowNode | MeasureTimeExpr | TimeExtractExpr | TimeDeltaExpr | TimeTruncExpr | TimeLiteralNode | TypecastExpr | RegexMatchExpr | RegexLiteralNode | FilterMatchExpr | FilterLiteralExpr | StringLiteralNode | NumberLiteralNode | BooleanLiteralNode | RecordLiteralNode | ArrayLiteralNode | FunctionOrderBy | GenericSQLExpr | NullNode | CaseExpr | InCompareExpr | CompositeFieldExpr | ErrorNode;
|
|
29
29
|
export type BinaryOperator = '+' | '-' | '*' | '%' | '/' | 'and' | 'or' | '=' | '!=' | '>' | '<' | '>=' | '<=' | 'coalesce' | 'like' | '!like';
|
|
30
30
|
export interface BinaryExpr extends ExprWithKids {
|
|
31
31
|
node: BinaryOperator;
|
|
@@ -175,13 +175,18 @@ export interface RegexMatchExpr extends ExprWithKids {
|
|
|
175
175
|
}
|
|
176
176
|
export type FilterExprType = 'string' | 'number' | 'boolean' | 'date' | 'timestamp';
|
|
177
177
|
export declare function isFilterExprType(s: string): s is FilterExprType;
|
|
178
|
-
export interface FilterMatchExpr extends
|
|
178
|
+
export interface FilterMatchExpr extends ExprWithKids {
|
|
179
179
|
node: 'filterMatch';
|
|
180
180
|
dataType: FilterExprType;
|
|
181
181
|
notMatch?: true;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
kids: {
|
|
183
|
+
filterExpr: Expr;
|
|
184
|
+
expr: Expr;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export interface FilterLiteralExpr extends ExprLeaf {
|
|
188
|
+
node: 'filterLiteral';
|
|
189
|
+
filterSrc: string;
|
|
185
190
|
}
|
|
186
191
|
export interface TimeLiteralNode extends ExprLeaf {
|
|
187
192
|
node: 'timeLiteral';
|
|
@@ -262,7 +267,14 @@ interface ParameterInfo {
|
|
|
262
267
|
name: string;
|
|
263
268
|
value: ConstantExpr | null;
|
|
264
269
|
}
|
|
265
|
-
export
|
|
270
|
+
export interface FilterExpressionDef {
|
|
271
|
+
type: 'filter expression';
|
|
272
|
+
filterType?: FilterExprType;
|
|
273
|
+
}
|
|
274
|
+
export type ParameterType = CastType | 'filter expression';
|
|
275
|
+
export declare function isParameterType(t: string): t is ParameterType;
|
|
276
|
+
export type ParameterTypeDef = AtomicTypeDef | FilterExpressionDef;
|
|
277
|
+
export type Parameter = ParameterTypeDef & ParameterInfo;
|
|
266
278
|
export type Argument = Parameter;
|
|
267
279
|
export declare function paramHasValue(p: Parameter): boolean;
|
|
268
280
|
export interface DocumentRange {
|
|
@@ -827,6 +839,7 @@ export interface CompiledQuery extends DrillSource {
|
|
|
827
839
|
annotation?: Annotation;
|
|
828
840
|
dependenciesToMaterialize?: Record<string, QueryToMaterialize>;
|
|
829
841
|
materialization?: QueryToMaterialize;
|
|
842
|
+
defaultRowLimitAdded?: number;
|
|
830
843
|
}
|
|
831
844
|
/** Result type for running a Malloy query. */
|
|
832
845
|
export interface QueryResult extends CompiledQuery {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
24
|
*/
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.
|
|
27
|
-
exports.TD = exports.isValueDate = exports.isValueTimestamp = exports.isValueBoolean = exports.isValueNumber = exports.isValueString = exports.isAtomic = exports.isTurtle = exports.getIdentifier = exports.isBasicAtomic = exports.mergeEvalSpaces = exports.isLiteral = exports.isBaseTable = exports.isSourceDef = void 0;
|
|
26
|
+
exports.isSegmentSQL = exports.isIndexSegment = exports.isRawSegment = exports.isSamplingEnable = exports.isSamplingPercent = exports.isSamplingRows = exports.isQuerySegment = exports.isProjectSegment = exports.isPartialSegment = exports.isReduceSegment = exports.structHasErrors = exports.segmentHasErrors = exports.refIsStructDef = exports.ValueType = exports.isExtractUnit = exports.isTimestampUnit = exports.isDateUnit = exports.isJoinedSource = exports.isJoined = exports.isJoinable = exports.isMatrixOperation = exports.isBasicArray = exports.isRepeatedRecord = exports.isRepeatedRecordFunctionParam = exports.mkArrayDef = exports.mkFieldDef = exports.fieldIsIntrinsic = exports.isCastType = exports.canOrderBy = exports.isAtomicFieldType = exports.isTemporalType = exports.hasExpression = exports.maxOfExpressionTypes = exports.maxExpressionType = exports.isExpressionTypeLEQ = exports.expressionIsAnalytic = exports.expressionIsCalculation = exports.expressionInvolvesAggregate = exports.expressionIsUngroupedAggregate = exports.expressionIsAggregate = exports.expressionIsScalar = exports.paramHasValue = exports.isParameterType = exports.isFilterExprType = exports.isRawCast = exports.mkTemporal = exports.isAsymmetricExpr = exports.exprIsLeaf = exports.exprHasE = exports.exprHasKids = void 0;
|
|
27
|
+
exports.TD = exports.isValueDate = exports.isValueTimestamp = exports.isValueBoolean = exports.isValueNumber = exports.isValueString = exports.isAtomic = exports.isTurtle = exports.getIdentifier = exports.isBasicAtomic = exports.mergeEvalSpaces = exports.isLiteral = exports.isBaseTable = exports.isSourceDef = exports.sourceBase = void 0;
|
|
28
28
|
function exprHasKids(e) {
|
|
29
29
|
return 'kids' in e;
|
|
30
30
|
}
|
|
@@ -75,6 +75,10 @@ function isFilterExprType(s) {
|
|
|
75
75
|
return ['string', 'number', 'boolean', 'date', 'timestamp'].includes(s);
|
|
76
76
|
}
|
|
77
77
|
exports.isFilterExprType = isFilterExprType;
|
|
78
|
+
function isParameterType(t) {
|
|
79
|
+
return isCastType(t) || t === 'filter expression';
|
|
80
|
+
}
|
|
81
|
+
exports.isParameterType = isParameterType;
|
|
78
82
|
function paramHasValue(p) {
|
|
79
83
|
return p.value !== null;
|
|
80
84
|
}
|
package/dist/to_stable.js
CHANGED
|
@@ -21,11 +21,20 @@ function modelDefToModelInfo(modelDef) {
|
|
|
21
21
|
continue;
|
|
22
22
|
if ((0, model_1.isSourceDef)(entry)) {
|
|
23
23
|
const parameters = entry.parameters && Object.entries(entry.parameters).length > 0
|
|
24
|
-
? Object.entries(entry.parameters).map(([name, parameter]) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
? Object.entries(entry.parameters).map(([name, parameter]) => {
|
|
25
|
+
if ((0, model_1.isAtomic)(parameter)) {
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
type: typeDefToType(parameter),
|
|
29
|
+
default_value: convertParameterDefaultValue(parameter.value),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
name,
|
|
34
|
+
type: { kind: 'filter_expression_type' },
|
|
35
|
+
default_value: convertParameterDefaultValue(parameter.value),
|
|
36
|
+
};
|
|
37
|
+
})
|
|
29
38
|
: undefined;
|
|
30
39
|
const sourceInfo = {
|
|
31
40
|
kind: 'source',
|
|
@@ -89,6 +98,11 @@ function convertParameterDefaultValue(value) {
|
|
|
89
98
|
return { kind: 'number_literal', number_value: parseFloat(value.literal) };
|
|
90
99
|
case 'stringLiteral':
|
|
91
100
|
return { kind: 'string_literal', string_value: value.literal };
|
|
101
|
+
case 'filterLiteral':
|
|
102
|
+
return {
|
|
103
|
+
kind: 'filter_expression_literal',
|
|
104
|
+
filter_expression_value: value.filterSrc,
|
|
105
|
+
};
|
|
92
106
|
case 'timeLiteral':
|
|
93
107
|
return {
|
|
94
108
|
kind: 'timestamp_literal',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/malloy",
|
|
3
|
-
"version": "0.0.263-
|
|
3
|
+
"version": "0.0.263-dev250414204152",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@malloydata/malloy-filter": "^0.0.263-
|
|
45
|
-
"@malloydata/malloy-interfaces": "^0.0.263-
|
|
46
|
-
"@malloydata/malloy-tag": "^0.0.263-
|
|
44
|
+
"@malloydata/malloy-filter": "^0.0.263-dev250414204152",
|
|
45
|
+
"@malloydata/malloy-interfaces": "^0.0.263-dev250414204152",
|
|
46
|
+
"@malloydata/malloy-tag": "^0.0.263-dev250414204152",
|
|
47
47
|
"antlr4ts": "^0.5.0-alpha.4",
|
|
48
48
|
"assert": "^2.0.0",
|
|
49
49
|
"jaro-winkler": "^0.2.8",
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { TemporalFilter } from '@malloydata/malloy-filter';
|
|
2
|
-
import type { Dialect } from '../dialect';
|
|
3
|
-
/**
|
|
4
|
-
* I felt like there was enough "helpful functions needed to make everything
|
|
5
|
-
* work, all of which need to know the dialect", to justify making a class
|
|
6
|
-
* for this. Maybe this should just be a set of functions which take
|
|
7
|
-
* a dialect as an argument?
|
|
8
|
-
*/
|
|
9
|
-
export declare class TemporalFilterCompiler {
|
|
10
|
-
readonly expr: string;
|
|
11
|
-
readonly timetype: 'timestamp' | 'date';
|
|
12
|
-
readonly d: Dialect;
|
|
13
|
-
constructor(expr: string, dialect: Dialect, timetype?: 'timestamp' | 'date');
|
|
14
|
-
time(timeSQL: string): string;
|
|
15
|
-
compile(tc: TemporalFilter): string;
|
|
16
|
-
private expandLiteral;
|
|
17
|
-
private literalNode;
|
|
18
|
-
private nowExpr;
|
|
19
|
-
private n;
|
|
20
|
-
private delta;
|
|
21
|
-
private dayofWeek;
|
|
22
|
-
private nowDot;
|
|
23
|
-
private thisUnit;
|
|
24
|
-
private lastUnit;
|
|
25
|
-
private nextUnit;
|
|
26
|
-
mod7(n: string): string;
|
|
27
|
-
private moment;
|
|
28
|
-
private isIn;
|
|
29
|
-
}
|
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.TemporalFilterCompiler = void 0;
|
|
10
|
-
const malloy_types_1 = require("./malloy_types");
|
|
11
|
-
const luxon_1 = require("luxon");
|
|
12
|
-
const fYear = 'yyyy';
|
|
13
|
-
const fMonth = `${fYear}-LL`;
|
|
14
|
-
const fDay = `${fMonth}-dd`;
|
|
15
|
-
const fHour = `${fDay} HH`;
|
|
16
|
-
const fMinute = `${fHour}:mm`;
|
|
17
|
-
const fTimestamp = `${fMinute}:ss`;
|
|
18
|
-
/**
|
|
19
|
-
* I felt like there was enough "helpful functions needed to make everything
|
|
20
|
-
* work, all of which need to know the dialect", to justify making a class
|
|
21
|
-
* for this. Maybe this should just be a set of functions which take
|
|
22
|
-
* a dialect as an argument?
|
|
23
|
-
*/
|
|
24
|
-
class TemporalFilterCompiler {
|
|
25
|
-
constructor(expr, dialect, timetype = 'timestamp') {
|
|
26
|
-
this.expr = expr;
|
|
27
|
-
this.timetype = timetype;
|
|
28
|
-
this.d = dialect;
|
|
29
|
-
}
|
|
30
|
-
time(timeSQL) {
|
|
31
|
-
if (this.timetype === 'timestamp') {
|
|
32
|
-
return timeSQL;
|
|
33
|
-
}
|
|
34
|
-
return this.d.sqlCast({}, {
|
|
35
|
-
node: 'cast',
|
|
36
|
-
e: {
|
|
37
|
-
node: 'genericSQLExpr',
|
|
38
|
-
src: ['', timeSQL],
|
|
39
|
-
kids: { args: [] },
|
|
40
|
-
sql: timeSQL,
|
|
41
|
-
},
|
|
42
|
-
srcType: { type: 'timestamp' },
|
|
43
|
-
dstType: { type: 'date' },
|
|
44
|
-
safe: false,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
compile(tc) {
|
|
48
|
-
const x = this.expr;
|
|
49
|
-
switch (tc.operator) {
|
|
50
|
-
case 'after':
|
|
51
|
-
return `${x} ${tc.not ? '<' : '>='} ${this.time(this.moment(tc.after).end)}`;
|
|
52
|
-
case 'before':
|
|
53
|
-
return `${x} ${tc.not ? '>=' : '<'} ${this.time(this.moment(tc.before).begin.sql)}`;
|
|
54
|
-
case 'in': {
|
|
55
|
-
const m = this.moment(tc.in);
|
|
56
|
-
if (m.begin.sql === m.end) {
|
|
57
|
-
return tc.not
|
|
58
|
-
? `${x} != ${this.time(m.end)} OR ${x} IS NULL`
|
|
59
|
-
: `${x} = ${this.time(m.end)}`;
|
|
60
|
-
}
|
|
61
|
-
return this.isIn(tc.not, m.begin.sql, m.end);
|
|
62
|
-
}
|
|
63
|
-
case 'for': {
|
|
64
|
-
const start = this.moment(tc.begin);
|
|
65
|
-
const end = this.delta(start.begin, '+', tc.n, tc.units);
|
|
66
|
-
return this.isIn(tc.not, start.begin.sql, end.sql);
|
|
67
|
-
}
|
|
68
|
-
case 'in_last': {
|
|
69
|
-
// last N units means "N - 1 UNITS AGO FOR N UNITS"
|
|
70
|
-
const back = Number(tc.n) - 1;
|
|
71
|
-
const thisUnit = this.nowDot(tc.units);
|
|
72
|
-
const start = back > 0
|
|
73
|
-
? this.delta(thisUnit, '-', back.toString(), tc.units)
|
|
74
|
-
: thisUnit;
|
|
75
|
-
const end = this.delta(thisUnit, '+', '1', tc.units);
|
|
76
|
-
return this.isIn(tc.not, start.sql, end.sql);
|
|
77
|
-
}
|
|
78
|
-
case 'to': {
|
|
79
|
-
const firstMoment = this.moment(tc.fromMoment);
|
|
80
|
-
const lastMoment = this.moment(tc.toMoment);
|
|
81
|
-
return this.isIn(tc.not, firstMoment.begin.sql, lastMoment.begin.sql);
|
|
82
|
-
}
|
|
83
|
-
case 'last': {
|
|
84
|
-
const thisUnit = this.nowDot(tc.units);
|
|
85
|
-
const start = this.delta(thisUnit, '-', tc.n, tc.units);
|
|
86
|
-
return this.isIn(tc.not, start.sql, thisUnit.sql);
|
|
87
|
-
}
|
|
88
|
-
case 'next': {
|
|
89
|
-
const thisUnit = this.nowDot(tc.units);
|
|
90
|
-
const start = this.delta(thisUnit, '+', '1', tc.units);
|
|
91
|
-
const end = this.delta(thisUnit, '+', (Number(tc.n) + 1).toString(), tc.units);
|
|
92
|
-
return this.isIn(tc.not, start.sql, end.sql);
|
|
93
|
-
}
|
|
94
|
-
case 'null':
|
|
95
|
-
return tc.not ? `${x} IS NOT NULL` : `${x} IS NULL`;
|
|
96
|
-
case '()': {
|
|
97
|
-
const wrapped = '(' + this.compile(tc.expr) + ')';
|
|
98
|
-
return tc.not ? `NOT ${wrapped}` : wrapped;
|
|
99
|
-
}
|
|
100
|
-
case 'and':
|
|
101
|
-
case 'or':
|
|
102
|
-
return tc.members
|
|
103
|
-
.map(m => this.compile(m))
|
|
104
|
-
.join(` ${tc.operator.toUpperCase()} `);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
expandLiteral(tl) {
|
|
108
|
-
let literal = tl.literal;
|
|
109
|
-
switch (tl.units) {
|
|
110
|
-
case 'year': {
|
|
111
|
-
const y = luxon_1.DateTime.fromFormat(literal, fYear);
|
|
112
|
-
const begin = this.literalNode(y.toFormat(fTimestamp));
|
|
113
|
-
const next = y.plus({ year: 1 });
|
|
114
|
-
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
115
|
-
}
|
|
116
|
-
case 'month': {
|
|
117
|
-
const yyyymm = luxon_1.DateTime.fromFormat(literal, fMonth);
|
|
118
|
-
const begin = this.literalNode(yyyymm.toFormat(fTimestamp));
|
|
119
|
-
const next = yyyymm.plus({ month: 1 });
|
|
120
|
-
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
121
|
-
}
|
|
122
|
-
case 'day': {
|
|
123
|
-
const yyyymmdd = luxon_1.DateTime.fromFormat(literal, fDay);
|
|
124
|
-
const begin = this.literalNode(yyyymmdd.toFormat(fTimestamp));
|
|
125
|
-
const next = yyyymmdd.plus({ day: 1 });
|
|
126
|
-
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
127
|
-
}
|
|
128
|
-
case 'hour': {
|
|
129
|
-
const ymdh = luxon_1.DateTime.fromFormat(literal, fHour);
|
|
130
|
-
const begin = this.literalNode(ymdh.toFormat(fTimestamp));
|
|
131
|
-
const next = ymdh.plus({ hour: 1 });
|
|
132
|
-
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
133
|
-
}
|
|
134
|
-
case 'minute': {
|
|
135
|
-
const ymdhm = luxon_1.DateTime.fromFormat(literal, fMinute);
|
|
136
|
-
const begin = this.literalNode(ymdhm.toFormat(fTimestamp));
|
|
137
|
-
const next = ymdhm.plus({ minute: 1 });
|
|
138
|
-
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
139
|
-
}
|
|
140
|
-
case 'week': {
|
|
141
|
-
const a = luxon_1.DateTime.fromFormat(literal.slice(0, 10), fDay);
|
|
142
|
-
// Luxon uses monday weeks, so look for the Monday week which contains
|
|
143
|
-
// the day after, which for all days except Sunday is the same as
|
|
144
|
-
// the sunday week, and on Sunday it is this monday week instead of
|
|
145
|
-
// last monday week.
|
|
146
|
-
const mondayWeek = a.plus({ day: 1 }).startOf('week');
|
|
147
|
-
// Now back that up by one day and we have the Sunday week
|
|
148
|
-
const ymd_wk = mondayWeek.minus({ day: 1 });
|
|
149
|
-
const begin = this.literalNode(ymd_wk.toFormat(fTimestamp));
|
|
150
|
-
const next = ymd_wk.plus({ days: 7 });
|
|
151
|
-
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
152
|
-
}
|
|
153
|
-
case 'quarter': {
|
|
154
|
-
const yyyy = literal.slice(0, 4);
|
|
155
|
-
const q = literal.slice(6);
|
|
156
|
-
if (q === '1') {
|
|
157
|
-
literal = `${yyyy}-01-01 00:00:00`;
|
|
158
|
-
}
|
|
159
|
-
else if (q === '2') {
|
|
160
|
-
literal = `${yyyy}-03-01 00:00:00`;
|
|
161
|
-
}
|
|
162
|
-
else if (q === '3') {
|
|
163
|
-
literal = `${yyyy}-06-01 00:00:00`;
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
literal = `${yyyy}-09-01 00:00:00`;
|
|
167
|
-
}
|
|
168
|
-
const begin = this.literalNode(literal);
|
|
169
|
-
const ymd_q = luxon_1.DateTime.fromFormat(literal, fTimestamp);
|
|
170
|
-
const next = ymd_q.plus({ months: 3 });
|
|
171
|
-
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
172
|
-
}
|
|
173
|
-
case undefined:
|
|
174
|
-
case 'second': {
|
|
175
|
-
const begin = this.literalNode(literal);
|
|
176
|
-
return { begin, end: begin.sql };
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
literalNode(literal) {
|
|
181
|
-
const literalNode = {
|
|
182
|
-
node: 'timeLiteral',
|
|
183
|
-
typeDef: { type: 'timestamp' },
|
|
184
|
-
literal,
|
|
185
|
-
};
|
|
186
|
-
return { ...literalNode, sql: this.d.sqlLiteralTime({}, literalNode) };
|
|
187
|
-
}
|
|
188
|
-
nowExpr() {
|
|
189
|
-
return {
|
|
190
|
-
node: 'now',
|
|
191
|
-
typeDef: { type: 'timestamp' },
|
|
192
|
-
sql: this.d.sqlNowExpr(),
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
n(literal) {
|
|
196
|
-
return { node: 'numberLiteral', literal, sql: literal };
|
|
197
|
-
}
|
|
198
|
-
delta(from, op, n, units) {
|
|
199
|
-
const ret = {
|
|
200
|
-
node: 'delta',
|
|
201
|
-
op,
|
|
202
|
-
units,
|
|
203
|
-
kids: {
|
|
204
|
-
base: (0, malloy_types_1.mkTemporal)(from, 'timestamp'),
|
|
205
|
-
delta: this.n(n),
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
return { ...ret, sql: this.d.sqlAlterTimeExpr(ret) };
|
|
209
|
-
}
|
|
210
|
-
dayofWeek(e) {
|
|
211
|
-
const t = {
|
|
212
|
-
node: 'extract',
|
|
213
|
-
e: (0, malloy_types_1.mkTemporal)(e, 'timestamp'),
|
|
214
|
-
units: 'day_of_week',
|
|
215
|
-
};
|
|
216
|
-
return { ...t, sql: this.d.sqlTimeExtractExpr({}, t) };
|
|
217
|
-
}
|
|
218
|
-
nowDot(units) {
|
|
219
|
-
const nowTruncExpr = {
|
|
220
|
-
node: 'trunc',
|
|
221
|
-
e: this.nowExpr(),
|
|
222
|
-
units,
|
|
223
|
-
};
|
|
224
|
-
return { ...nowTruncExpr, sql: this.d.sqlTruncExpr({}, nowTruncExpr) };
|
|
225
|
-
}
|
|
226
|
-
thisUnit(units) {
|
|
227
|
-
const thisUnit = this.nowDot(units);
|
|
228
|
-
const nextUnit = this.delta(thisUnit, '+', '1', units);
|
|
229
|
-
return { begin: thisUnit, end: nextUnit.sql };
|
|
230
|
-
}
|
|
231
|
-
lastUnit(units) {
|
|
232
|
-
const thisUnit = this.nowDot(units);
|
|
233
|
-
const lastUnit = this.delta(thisUnit, '-', '1', units);
|
|
234
|
-
return { begin: lastUnit, end: thisUnit.sql };
|
|
235
|
-
}
|
|
236
|
-
nextUnit(units) {
|
|
237
|
-
const thisUnit = this.nowDot(units);
|
|
238
|
-
const nextUnit = this.delta(thisUnit, '+', '1', units);
|
|
239
|
-
const next2Unit = this.delta(thisUnit, '+', '2', units);
|
|
240
|
-
return { begin: nextUnit, end: next2Unit.sql };
|
|
241
|
-
}
|
|
242
|
-
mod7(n) {
|
|
243
|
-
return this.d.hasModOperator ? `(${n})%7` : `MOD(${n},7)`;
|
|
244
|
-
}
|
|
245
|
-
moment(m) {
|
|
246
|
-
switch (m.moment) {
|
|
247
|
-
case 'now': {
|
|
248
|
-
const now = this.nowExpr();
|
|
249
|
-
return { begin: now, end: now.sql };
|
|
250
|
-
}
|
|
251
|
-
case 'literal':
|
|
252
|
-
return this.expandLiteral(m);
|
|
253
|
-
case 'ago':
|
|
254
|
-
case 'from_now': {
|
|
255
|
-
const nowTruncExpr = this.nowDot(m.units);
|
|
256
|
-
const nowTrunc = (0, malloy_types_1.mkTemporal)(nowTruncExpr, 'timestamp');
|
|
257
|
-
const beginExpr = this.delta(nowTrunc, m.moment === 'ago' ? '-' : '+', m.n, m.units);
|
|
258
|
-
// Now the end is one unit after that .. either n-1 units ago or n+1 units from now
|
|
259
|
-
if (m.moment === 'ago' && m.n === '1') {
|
|
260
|
-
return { begin: beginExpr, end: nowTruncExpr.sql };
|
|
261
|
-
}
|
|
262
|
-
const oneDifferent = Number(m.n) + (m.moment === 'ago' ? -1 : 1);
|
|
263
|
-
const endExpr = {
|
|
264
|
-
...beginExpr,
|
|
265
|
-
kids: { base: nowTrunc, delta: this.n(oneDifferent.toString()) },
|
|
266
|
-
};
|
|
267
|
-
return { begin: beginExpr, end: this.d.sqlAlterTimeExpr(endExpr) };
|
|
268
|
-
}
|
|
269
|
-
case 'today':
|
|
270
|
-
return this.thisUnit('day');
|
|
271
|
-
case 'yesterday':
|
|
272
|
-
return this.lastUnit('day');
|
|
273
|
-
case 'tomorrow':
|
|
274
|
-
return this.nextUnit('day');
|
|
275
|
-
case 'this':
|
|
276
|
-
return this.thisUnit(m.units);
|
|
277
|
-
case 'last':
|
|
278
|
-
return this.lastUnit(m.units);
|
|
279
|
-
case 'next':
|
|
280
|
-
return this.nextUnit(m.units);
|
|
281
|
-
case 'monday':
|
|
282
|
-
case 'tuesday':
|
|
283
|
-
case 'wednesday':
|
|
284
|
-
case 'thursday':
|
|
285
|
-
case 'friday':
|
|
286
|
-
case 'saturday':
|
|
287
|
-
case 'sunday': {
|
|
288
|
-
const destDay = [
|
|
289
|
-
'sunday',
|
|
290
|
-
'monday',
|
|
291
|
-
'tuesday',
|
|
292
|
-
'wednesday',
|
|
293
|
-
'thursday',
|
|
294
|
-
'friday',
|
|
295
|
-
'saturday',
|
|
296
|
-
].indexOf(m.moment);
|
|
297
|
-
const dow = this.dayofWeek(this.nowExpr()).sql;
|
|
298
|
-
if (m.which === 'next') {
|
|
299
|
-
const nForwards = `${this.mod7(`${destDay}-(${dow}-1)+6`)}+1`;
|
|
300
|
-
const begin = this.delta(this.thisUnit('day').begin, '+', nForwards, 'day');
|
|
301
|
-
const end = this.delta(this.thisUnit('day').begin, '+', `${nForwards}+1`, 'day');
|
|
302
|
-
// console.log(
|
|
303
|
-
// `SELECT ${
|
|
304
|
-
// this.nowExpr().sql
|
|
305
|
-
// } as now,\n ${destDay} as destDay,\n ${dow} as dow,\n ${nForwards} as nForwards,\n ${
|
|
306
|
-
// begin.sql
|
|
307
|
-
// } as begin,\n ${end.sql} as end`
|
|
308
|
-
// );
|
|
309
|
-
return { begin, end: end.sql };
|
|
310
|
-
}
|
|
311
|
-
// dacks back = mod((daw0 - dst) + 6, 7) + 1;
|
|
312
|
-
// dacks back = mod(((daw - 1) - dst) + 6, 7) + 1;
|
|
313
|
-
// dacks back = mod(((daw) - dst) + 7, 7) + 1;
|
|
314
|
-
const nBack = `${this.mod7(`(${dow}-1)-${destDay}+6`)}+1`;
|
|
315
|
-
const begin = this.delta(this.thisUnit('day').begin, '-', nBack, 'day');
|
|
316
|
-
const end = this.delta(this.thisUnit('day').begin, '-', `(${nBack})-1`, 'day');
|
|
317
|
-
// console.log(
|
|
318
|
-
// `SELECT ${
|
|
319
|
-
// this.nowExpr().sql
|
|
320
|
-
// } as now,\n ${destDay} as destDay,\n ${dow} as dow,\n ${nBack} as nBack,\n ${
|
|
321
|
-
// begin.sql
|
|
322
|
-
// } as begin,\n ${end.sql} as end`
|
|
323
|
-
// );
|
|
324
|
-
return { begin, end: end.sql };
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
isIn(notIn, begin, end) {
|
|
329
|
-
let begOp = '>=';
|
|
330
|
-
let endOp = '<';
|
|
331
|
-
let joinOp = 'AND';
|
|
332
|
-
if (notIn) {
|
|
333
|
-
joinOp = 'OR';
|
|
334
|
-
begOp = '<';
|
|
335
|
-
endOp = '>=';
|
|
336
|
-
}
|
|
337
|
-
begin = this.time(begin);
|
|
338
|
-
end = this.time(end);
|
|
339
|
-
return `${this.expr} ${begOp} ${begin} ${joinOp} ${this.expr} ${endOp} ${end}`;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
exports.TemporalFilterCompiler = TemporalFilterCompiler;
|
|
343
|
-
//# sourceMappingURL=filter_temporal_compiler.js.map
|