@malloydata/malloy 0.0.22-dev230117232218 → 0.0.22-dev230119001546
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/lang/ast/apply-expr.d.ts +1 -14
- package/dist/lang/ast/apply-expr.js +1 -208
- package/dist/lang/ast/ast-expr.d.ts +9 -96
- package/dist/lang/ast/ast-expr.js +105 -384
- package/dist/lang/ast/ast-main.d.ts +141 -1
- package/dist/lang/ast/ast-main.js +681 -9
- package/dist/lang/ast/ast-time-expr.d.ts +2 -41
- package/dist/lang/ast/ast-time-expr.js +36 -204
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.TurtleHeadedPipe = exports.ExistingQuery = exports.Index = exports.Turtles = exports.Limit = exports.Ordering = exports.OrderBy = exports.WildcardFieldReference = exports.RenameField = exports.PrimaryKey = exports.Document = exports.QOPDesc = exports.Aggregate = exports.GroupBy = exports.FieldListEdit = exports.ProjectStatement = exports.isFieldCollectionMember = exports.FieldReferences = exports.FieldReference = exports.FieldName = exports.Renames = exports.Dimensions = exports.Measures = exports.DeclareFields = exports.Filter = exports.FilterElement = exports.ExploreDesc = exports.isExploreProperty = exports.isQueryProperty = exports.Joins = exports.ExpressionJoin = exports.KeyJoin = exports.Join = exports.QuerySource = exports.SQLSource = exports.NamedSource = exports.ModelEntryReference = exports.IsValueBlock = exports.TableSource = exports.RefinedExplore = exports.DefineSourceList = exports.DefineExplore = exports.isDocStatement = exports.RunList = exports.Mallobj = exports.isExploreField = exports.Unimplemented = exports.ListOf = exports.MalloyElement = exports.ErrorFactory = void 0;
|
|
27
|
-
exports.SampleProperty = exports.SQLStatement = exports.SQLString = exports.ConstantParameter = exports.HasParameter = exports.ImportStatement = exports.JSONStructDef = exports.JSONElement = exports.Top = exports.AnonymousQuery = exports.DefineQuery = exports.DefineQueryList = exports.isQueryElement = exports.Nests = exports.isNestedQuery = exports.NestDefinition = exports.NestRefinement = exports.NestReference = exports.TurtleDecl = exports.FullQuery = void 0;
|
|
27
|
+
exports.Range = exports.ExprTime = exports.ExprDuration = exports.TypeMistmatch = exports.ExprGranularTime = exports.ExprCompare = exports.BinaryBoolean = exports.applyBinary = exports.FieldDeclaration = exports.ConstantSubExpression = exports.ExpressionDef = exports.SampleProperty = exports.SQLStatement = exports.SQLString = exports.ConstantParameter = exports.HasParameter = exports.ImportStatement = exports.JSONStructDef = exports.JSONElement = exports.Top = exports.AnonymousQuery = exports.DefineQuery = exports.DefineQueryList = exports.isQueryElement = exports.Nests = exports.isNestedQuery = exports.NestDefinition = exports.NestRefinement = exports.NestReference = exports.TurtleDecl = exports.FullQuery = void 0;
|
|
28
28
|
/*
|
|
29
29
|
* Copyright 2023 Google LLC
|
|
30
30
|
*
|
|
@@ -49,13 +49,15 @@ exports.SampleProperty = exports.SQLStatement = exports.SQLString = exports.Cons
|
|
|
49
49
|
*/
|
|
50
50
|
const lodash_1 = require("lodash");
|
|
51
51
|
const model = __importStar(require("../../model/malloy_types"));
|
|
52
|
+
const malloy_types_1 = require("../../model/malloy_types");
|
|
52
53
|
const malloy_query_1 = require("../../model/malloy_query");
|
|
53
54
|
const field_space_1 = require("../field-space");
|
|
54
|
-
const
|
|
55
|
+
const ast_types_1 = require("./ast-types");
|
|
55
56
|
const space_field_1 = require("../space-field");
|
|
56
57
|
const sql_block_1 = require("../../model/sql_block");
|
|
57
58
|
const util_1 = require("util");
|
|
58
59
|
const time_utils_1 = require("./time-utils");
|
|
60
|
+
const apply_expr_1 = require("./apply-expr");
|
|
59
61
|
/*
|
|
60
62
|
** For times when there is a code generation error but your function needs
|
|
61
63
|
** to return some kind of object to type properly, the ErrorFactory is
|
|
@@ -357,7 +359,7 @@ function getStructFieldDef(s, fn) {
|
|
|
357
359
|
return s.fields.find((fld) => (fld.as || fld.name) === fn);
|
|
358
360
|
}
|
|
359
361
|
function isFieldDecl(f) {
|
|
360
|
-
return (f instanceof
|
|
362
|
+
return (f instanceof FieldDeclaration ||
|
|
361
363
|
f instanceof Join ||
|
|
362
364
|
f instanceof TurtleDecl ||
|
|
363
365
|
f instanceof Turtles);
|
|
@@ -861,7 +863,7 @@ class ExpressionJoin extends Join {
|
|
|
861
863
|
}
|
|
862
864
|
const joinRel = inStruct.structRelationship;
|
|
863
865
|
if (model.isJoinOn(joinRel)) {
|
|
864
|
-
joinRel.onExpression = (0,
|
|
866
|
+
joinRel.onExpression = (0, ast_types_1.compressExpr)(exprX.value);
|
|
865
867
|
}
|
|
866
868
|
}
|
|
867
869
|
structDef() {
|
|
@@ -940,7 +942,7 @@ class FilterElement extends MalloyElement {
|
|
|
940
942
|
}
|
|
941
943
|
const exprCond = {
|
|
942
944
|
code: this.exprSrc,
|
|
943
|
-
expression: (0,
|
|
945
|
+
expression: (0, ast_types_1.compressExpr)(exprVal.value),
|
|
944
946
|
expressionType: exprVal.expressionType,
|
|
945
947
|
};
|
|
946
948
|
return exprCond;
|
|
@@ -1066,7 +1068,7 @@ exports.FieldReferences = FieldReferences;
|
|
|
1066
1068
|
function isFieldCollectionMember(el) {
|
|
1067
1069
|
return (el instanceof FieldReference ||
|
|
1068
1070
|
el instanceof WildcardFieldReference ||
|
|
1069
|
-
el instanceof
|
|
1071
|
+
el instanceof FieldDeclaration);
|
|
1070
1072
|
}
|
|
1071
1073
|
exports.isFieldCollectionMember = isFieldCollectionMember;
|
|
1072
1074
|
class ProjectStatement extends ListOf {
|
|
@@ -1088,7 +1090,7 @@ class GroupBy extends ListOf {
|
|
|
1088
1090
|
constructor(members) {
|
|
1089
1091
|
super("groupBy", members);
|
|
1090
1092
|
for (const el of members) {
|
|
1091
|
-
if (el instanceof
|
|
1093
|
+
if (el instanceof FieldDeclaration) {
|
|
1092
1094
|
el.isMeasure = false;
|
|
1093
1095
|
}
|
|
1094
1096
|
}
|
|
@@ -1099,7 +1101,7 @@ class Aggregate extends ListOf {
|
|
|
1099
1101
|
constructor(members) {
|
|
1100
1102
|
super("aggregate", members);
|
|
1101
1103
|
for (const el of members) {
|
|
1102
|
-
if (el instanceof
|
|
1104
|
+
if (el instanceof FieldDeclaration) {
|
|
1103
1105
|
el.isMeasure = true;
|
|
1104
1106
|
}
|
|
1105
1107
|
}
|
|
@@ -1933,7 +1935,7 @@ class Top extends MalloyElement {
|
|
|
1933
1935
|
if (model.expressionIsAggregate(byExpr.expressionType)) {
|
|
1934
1936
|
this.log("top by expression must be an aggregate");
|
|
1935
1937
|
}
|
|
1936
|
-
return { by: "expression", e: (0,
|
|
1938
|
+
return { by: "expression", e: (0, ast_types_1.compressExpr)(byExpr.value) };
|
|
1937
1939
|
}
|
|
1938
1940
|
}
|
|
1939
1941
|
return undefined;
|
|
@@ -2183,4 +2185,674 @@ class SampleProperty extends MalloyElement {
|
|
|
2183
2185
|
}
|
|
2184
2186
|
}
|
|
2185
2187
|
exports.SampleProperty = SampleProperty;
|
|
2188
|
+
/**
|
|
2189
|
+
* Root node for any element in an expression. These essentially
|
|
2190
|
+
* create a sub-tree in the larger AST. Expression nodes know
|
|
2191
|
+
* how to write themselves as SQL (or rather, generate the
|
|
2192
|
+
* template for SQL required by the query writer)
|
|
2193
|
+
*/
|
|
2194
|
+
class ExpressionDef extends MalloyElement {
|
|
2195
|
+
constructor() {
|
|
2196
|
+
super(...arguments);
|
|
2197
|
+
this.legalChildTypes = ast_types_1.FT.anyAtomicT;
|
|
2198
|
+
}
|
|
2199
|
+
granular() {
|
|
2200
|
+
return false;
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Some operators want to give the right hand value a chance to
|
|
2204
|
+
* rewrite itself. This requests a translation for a rewrite,
|
|
2205
|
+
* or returns undefined if that request should be denied.
|
|
2206
|
+
* @param fs FieldSpace
|
|
2207
|
+
* @returns Translated expression or undefined
|
|
2208
|
+
*/
|
|
2209
|
+
requestExpression(fs) {
|
|
2210
|
+
return this.getExpression(fs);
|
|
2211
|
+
}
|
|
2212
|
+
defaultFieldName() {
|
|
2213
|
+
return undefined;
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Check an expression for type compatibility
|
|
2217
|
+
* @param _eNode currently unused, will be used to get error location
|
|
2218
|
+
* @param eVal ...list of expressions that must match legalChildTypes
|
|
2219
|
+
*/
|
|
2220
|
+
typeCheck(eNode, eVal) {
|
|
2221
|
+
if (!ast_types_1.FT.in(eVal, this.legalChildTypes)) {
|
|
2222
|
+
eNode.log(`'${this.elementType}' Can't use type ${ast_types_1.FT.inspect(eVal)}`);
|
|
2223
|
+
return false;
|
|
2224
|
+
}
|
|
2225
|
+
return true;
|
|
2226
|
+
}
|
|
2227
|
+
/**
|
|
2228
|
+
* This is the operation which makes partial comparison and value trees work
|
|
2229
|
+
* The default implemention merely constructs LEFT OP RIGHT, but specialized
|
|
2230
|
+
* nodes like alternation trees or or partial comparison can control how
|
|
2231
|
+
* the appplication gets generated
|
|
2232
|
+
* @param fs The symbol table
|
|
2233
|
+
* @param op The operator being applied
|
|
2234
|
+
* @param expr The "other" (besdies 'this') value
|
|
2235
|
+
* @returns The translated expression
|
|
2236
|
+
*/
|
|
2237
|
+
apply(fs, op, left) {
|
|
2238
|
+
return applyBinary(fs, left, op, this);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
exports.ExpressionDef = ExpressionDef;
|
|
2242
|
+
class ConstantSubExpression extends ExpressionDef {
|
|
2243
|
+
constructor(expr) {
|
|
2244
|
+
super({ expr });
|
|
2245
|
+
this.expr = expr;
|
|
2246
|
+
this.elementType = "constantExpression";
|
|
2247
|
+
}
|
|
2248
|
+
getExpression(_fs) {
|
|
2249
|
+
return this.constantValue();
|
|
2250
|
+
}
|
|
2251
|
+
get constantFs() {
|
|
2252
|
+
if (!this.cfs) {
|
|
2253
|
+
this.cfs = new ConstantFieldSpace();
|
|
2254
|
+
}
|
|
2255
|
+
return this.cfs;
|
|
2256
|
+
}
|
|
2257
|
+
constantValue() {
|
|
2258
|
+
return this.expr.getExpression(this.constantFs);
|
|
2259
|
+
}
|
|
2260
|
+
constantCondition(type) {
|
|
2261
|
+
const compareAndContrast = new ExprCompare(new DollarReference(type), "=", this.expr);
|
|
2262
|
+
const application = compareAndContrast.getExpression(this.constantFs);
|
|
2263
|
+
return { ...application, value: (0, ast_types_1.compressExpr)(application.value) };
|
|
2264
|
+
}
|
|
2265
|
+
apply(fs, op, expr) {
|
|
2266
|
+
return this.expr.apply(fs, op, expr);
|
|
2267
|
+
}
|
|
2268
|
+
requestExpression(fs) {
|
|
2269
|
+
return this.expr.requestExpression(fs);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
exports.ConstantSubExpression = ConstantSubExpression;
|
|
2273
|
+
class DollarReference extends ExpressionDef {
|
|
2274
|
+
constructor(refType) {
|
|
2275
|
+
super();
|
|
2276
|
+
this.refType = refType;
|
|
2277
|
+
this.elementType = "$";
|
|
2278
|
+
}
|
|
2279
|
+
getExpression(_fs) {
|
|
2280
|
+
return {
|
|
2281
|
+
dataType: this.refType,
|
|
2282
|
+
value: [{ type: "applyVal" }],
|
|
2283
|
+
expressionType: "scalar",
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
class ConstantFieldSpace {
|
|
2288
|
+
constructor() {
|
|
2289
|
+
this.type = "fieldSpace";
|
|
2290
|
+
}
|
|
2291
|
+
structDef() {
|
|
2292
|
+
throw new Error("ConstantFieldSpace cannot generate a structDef");
|
|
2293
|
+
}
|
|
2294
|
+
emptyStructDef() {
|
|
2295
|
+
throw new Error("ConstantFieldSpace cannot generate a structDef");
|
|
2296
|
+
}
|
|
2297
|
+
lookup(_name) {
|
|
2298
|
+
return {
|
|
2299
|
+
error: "Only constants allowed in parameter expressions",
|
|
2300
|
+
found: undefined,
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
dialectObj() {
|
|
2304
|
+
return undefined;
|
|
2305
|
+
}
|
|
2306
|
+
whenComplete(step) {
|
|
2307
|
+
step();
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
class FieldDeclaration extends MalloyElement {
|
|
2311
|
+
constructor(expr, defineName, exprSrc) {
|
|
2312
|
+
super({ expr });
|
|
2313
|
+
this.expr = expr;
|
|
2314
|
+
this.defineName = defineName;
|
|
2315
|
+
this.exprSrc = exprSrc;
|
|
2316
|
+
this.elementType = "fieldDeclaration";
|
|
2317
|
+
}
|
|
2318
|
+
fieldDef(fs, exprName) {
|
|
2319
|
+
/*
|
|
2320
|
+
* In an explore we cannot reference the thing we are defining, you need
|
|
2321
|
+
* to use rename. In a query, the output space is a new thing, and expressions
|
|
2322
|
+
* can reference the outer value in order to make a value with the new name,
|
|
2323
|
+
* and it feels wrong that this is HERE and not somehow in the QueryOperation.
|
|
2324
|
+
* For now, this stops the stack overflow, and passes all tests, but I think
|
|
2325
|
+
* a refactor of QueryFieldSpace might someday be the place where this should
|
|
2326
|
+
* happen.
|
|
2327
|
+
*/
|
|
2328
|
+
return this.queryFieldDef(new field_space_1.DefSpace(fs, this), exprName);
|
|
2329
|
+
}
|
|
2330
|
+
queryFieldDef(exprFS, exprName) {
|
|
2331
|
+
let exprValue;
|
|
2332
|
+
try {
|
|
2333
|
+
exprValue = this.expr.getExpression(exprFS);
|
|
2334
|
+
}
|
|
2335
|
+
catch (error) {
|
|
2336
|
+
this.log(`Cannot define '${exprName}', ${error.message}`);
|
|
2337
|
+
return {
|
|
2338
|
+
name: `error_defining_${exprName}`,
|
|
2339
|
+
type: "string",
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
const compressValue = (0, ast_types_1.compressExpr)(exprValue.value);
|
|
2343
|
+
const retType = exprValue.dataType;
|
|
2344
|
+
if ((0, malloy_types_1.isAtomicFieldType)(retType)) {
|
|
2345
|
+
const template = {
|
|
2346
|
+
name: exprName,
|
|
2347
|
+
type: retType,
|
|
2348
|
+
location: this.location,
|
|
2349
|
+
};
|
|
2350
|
+
if (compressValue.length > 0) {
|
|
2351
|
+
template.e = compressValue;
|
|
2352
|
+
}
|
|
2353
|
+
if (exprValue.expressionType) {
|
|
2354
|
+
template.expressionType = exprValue.expressionType;
|
|
2355
|
+
}
|
|
2356
|
+
if (this.exprSrc) {
|
|
2357
|
+
template.code = this.exprSrc;
|
|
2358
|
+
}
|
|
2359
|
+
// TODO this should work for dates too
|
|
2360
|
+
if ((0, ast_types_1.isGranularResult)(exprValue) && template.type === "timestamp") {
|
|
2361
|
+
template.timeframe = exprValue.timeframe;
|
|
2362
|
+
}
|
|
2363
|
+
return template;
|
|
2364
|
+
}
|
|
2365
|
+
const circularDef = exprFS instanceof field_space_1.DefSpace && exprFS.foundCircle;
|
|
2366
|
+
if (!circularDef) {
|
|
2367
|
+
if (exprValue.dataType == "unknown") {
|
|
2368
|
+
this.log(`Cannot define '${exprName}', value has unknown type`);
|
|
2369
|
+
}
|
|
2370
|
+
else {
|
|
2371
|
+
const badType = ast_types_1.FT.inspect(exprValue);
|
|
2372
|
+
this.log(`Cannot define '${exprName}', unexpected type: ${badType}`);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
return {
|
|
2376
|
+
name: `error_defining_${exprName}`,
|
|
2377
|
+
type: "string",
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
exports.FieldDeclaration = FieldDeclaration;
|
|
2382
|
+
/**
|
|
2383
|
+
* All of the magic of malloy expressions eventually flows to here,
|
|
2384
|
+
* where an operator is applied to two values. Depending on the
|
|
2385
|
+
* operator and value types this may involve transformations of
|
|
2386
|
+
* the values or even the operator.
|
|
2387
|
+
* @param fs FieldSpace for the symbols
|
|
2388
|
+
* @param left Left value
|
|
2389
|
+
* @param op The operator
|
|
2390
|
+
* @param right Right Value
|
|
2391
|
+
* @returns ExprValue of the expression
|
|
2392
|
+
*/
|
|
2393
|
+
function applyBinary(fs, left, op, right) {
|
|
2394
|
+
if ((0, ast_types_1.isEquality)(op)) {
|
|
2395
|
+
return equality(fs, left, op, right);
|
|
2396
|
+
}
|
|
2397
|
+
if ((0, ast_types_1.isComparison)(op)) {
|
|
2398
|
+
return compare(fs, left, op, right);
|
|
2399
|
+
}
|
|
2400
|
+
if (oneOf(op, "+", "-")) {
|
|
2401
|
+
return delta(fs, left, op, right);
|
|
2402
|
+
}
|
|
2403
|
+
if (oneOf(op, "*", "%")) {
|
|
2404
|
+
return numeric(fs, left, op, right);
|
|
2405
|
+
}
|
|
2406
|
+
if (oneOf(op, "/")) {
|
|
2407
|
+
const num = left.getExpression(fs);
|
|
2408
|
+
const denom = right.getExpression(fs);
|
|
2409
|
+
if (num.dataType != "number") {
|
|
2410
|
+
left.log("Numerator for division must be a number");
|
|
2411
|
+
}
|
|
2412
|
+
else if (denom.dataType != "number") {
|
|
2413
|
+
right.log("Denominator for division must be a number");
|
|
2414
|
+
}
|
|
2415
|
+
else {
|
|
2416
|
+
const div = {
|
|
2417
|
+
type: "dialect",
|
|
2418
|
+
function: "div",
|
|
2419
|
+
numerator: num.value,
|
|
2420
|
+
denominator: denom.value,
|
|
2421
|
+
};
|
|
2422
|
+
return {
|
|
2423
|
+
dataType: "number",
|
|
2424
|
+
expressionType: (0, malloy_types_1.maxExpressionType)(num.expressionType, denom.expressionType),
|
|
2425
|
+
value: [div],
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
return (0, ast_types_1.errorFor)("divide type mismatch");
|
|
2429
|
+
}
|
|
2430
|
+
left.log(`Canot use ${op} operator here`);
|
|
2431
|
+
return (0, ast_types_1.errorFor)("applybinary bad operator");
|
|
2432
|
+
}
|
|
2433
|
+
exports.applyBinary = applyBinary;
|
|
2434
|
+
class BinaryBoolean extends ExpressionDef {
|
|
2435
|
+
constructor(left, op, right) {
|
|
2436
|
+
super({ left, right });
|
|
2437
|
+
this.left = left;
|
|
2438
|
+
this.op = op;
|
|
2439
|
+
this.right = right;
|
|
2440
|
+
this.elementType = "abstract boolean binary";
|
|
2441
|
+
this.legalChildTypes = [ast_types_1.FT.boolT];
|
|
2442
|
+
}
|
|
2443
|
+
getExpression(fs) {
|
|
2444
|
+
const left = this.left.getExpression(fs);
|
|
2445
|
+
const right = this.right.getExpression(fs);
|
|
2446
|
+
if (this.typeCheck(this.left, left) && this.typeCheck(this.right, right)) {
|
|
2447
|
+
return {
|
|
2448
|
+
dataType: "boolean",
|
|
2449
|
+
expressionType: (0, malloy_types_1.maxExpressionType)(left.expressionType, right.expressionType),
|
|
2450
|
+
value: (0, ast_types_1.compose)(left.value, this.op, right.value),
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
return (0, ast_types_1.errorFor)("logial required boolean");
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
exports.BinaryBoolean = BinaryBoolean;
|
|
2457
|
+
class ExprCompare extends BinaryBoolean {
|
|
2458
|
+
constructor(left, op, right) {
|
|
2459
|
+
super(left, op, right);
|
|
2460
|
+
this.elementType = "a<=>b";
|
|
2461
|
+
this.legalChildTypes = compareTypes[op];
|
|
2462
|
+
}
|
|
2463
|
+
getExpression(fs) {
|
|
2464
|
+
if (!this.right.granular()) {
|
|
2465
|
+
const rhs = this.right.requestExpression(fs);
|
|
2466
|
+
if (rhs && (0, ast_types_1.isGranularResult)(rhs)) {
|
|
2467
|
+
const newRight = new ExprGranularTime(this.right, rhs.timeframe, false);
|
|
2468
|
+
return newRight.apply(fs, this.op, this.left);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
return this.right.apply(fs, this.op, this.left);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
exports.ExprCompare = ExprCompare;
|
|
2475
|
+
function equality(fs, left, op, right) {
|
|
2476
|
+
const lhs = left.getExpression(fs);
|
|
2477
|
+
const rhs = right.getExpression(fs);
|
|
2478
|
+
let value = timeCompare(lhs, op, rhs) || (0, ast_types_1.compose)(lhs.value, op, rhs.value);
|
|
2479
|
+
if (lhs.dataType != "unknown" && rhs.dataType != "unknown") {
|
|
2480
|
+
switch (op) {
|
|
2481
|
+
case "~":
|
|
2482
|
+
case "!~": {
|
|
2483
|
+
if (lhs.dataType === "string" && rhs.dataType === "string") {
|
|
2484
|
+
value = (0, ast_types_1.compose)(lhs.value, "LIKE", rhs.value);
|
|
2485
|
+
}
|
|
2486
|
+
else {
|
|
2487
|
+
const regexCmp = regexEqual(lhs, rhs);
|
|
2488
|
+
if (regexCmp === undefined) {
|
|
2489
|
+
throw new TypeMistmatch("Incompatible types for match('~') operator");
|
|
2490
|
+
}
|
|
2491
|
+
value = regexCmp;
|
|
2492
|
+
}
|
|
2493
|
+
value = (0, apply_expr_1.nullsafeNot)(value, op);
|
|
2494
|
+
break;
|
|
2495
|
+
}
|
|
2496
|
+
case "=":
|
|
2497
|
+
case "!=": {
|
|
2498
|
+
const nullCmp = nullCompare(lhs, op, rhs);
|
|
2499
|
+
if (nullCmp) {
|
|
2500
|
+
value = nullCmp;
|
|
2501
|
+
}
|
|
2502
|
+
else {
|
|
2503
|
+
value = (0, apply_expr_1.nullsafeNot)(regexEqual(lhs, rhs) || (0, ast_types_1.compose)(lhs.value, "=", rhs.value), op);
|
|
2504
|
+
}
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
return {
|
|
2510
|
+
dataType: "boolean",
|
|
2511
|
+
expressionType: (0, malloy_types_1.maxExpressionType)(lhs.expressionType, rhs.expressionType),
|
|
2512
|
+
value,
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
function compare(fs, left, op, right) {
|
|
2516
|
+
const lhs = left.getExpression(fs);
|
|
2517
|
+
const rhs = right.getExpression(fs);
|
|
2518
|
+
const expressionType = (0, malloy_types_1.maxExpressionType)(lhs.expressionType, rhs.expressionType);
|
|
2519
|
+
const value = timeCompare(lhs, op, rhs) || (0, ast_types_1.compose)(lhs.value, op, rhs.value);
|
|
2520
|
+
return {
|
|
2521
|
+
dataType: "boolean",
|
|
2522
|
+
expressionType,
|
|
2523
|
+
value: value,
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
function oneOf(op, ...operators) {
|
|
2527
|
+
return operators.includes(op);
|
|
2528
|
+
}
|
|
2529
|
+
function delta(fs, left, op, right) {
|
|
2530
|
+
const lhs = left.getExpression(fs);
|
|
2531
|
+
const rhs = right.getExpression(fs);
|
|
2532
|
+
if ((0, malloy_types_1.isTimeFieldType)(lhs.dataType)) {
|
|
2533
|
+
let duration = right;
|
|
2534
|
+
if (rhs.dataType !== "duration") {
|
|
2535
|
+
if ((0, ast_types_1.isGranularResult)(lhs)) {
|
|
2536
|
+
duration = new ExprDuration(right, lhs.timeframe);
|
|
2537
|
+
}
|
|
2538
|
+
else if (lhs.dataType === "date") {
|
|
2539
|
+
duration = new ExprDuration(right, "day");
|
|
2540
|
+
}
|
|
2541
|
+
else {
|
|
2542
|
+
left.log(`Can not offset time by '${rhs.dataType}'`);
|
|
2543
|
+
return (0, ast_types_1.errorFor)(`time plus ${rhs.dataType}`);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
return duration.apply(fs, op, left);
|
|
2547
|
+
}
|
|
2548
|
+
return numeric(fs, left, op, right);
|
|
2549
|
+
}
|
|
2550
|
+
function numeric(fs, left, op, right) {
|
|
2551
|
+
const lhs = left.getExpression(fs);
|
|
2552
|
+
const rhs = right.getExpression(fs);
|
|
2553
|
+
const expressionType = (0, malloy_types_1.maxExpressionType)(lhs.expressionType, rhs.expressionType);
|
|
2554
|
+
if (allAre("number", lhs, rhs)) {
|
|
2555
|
+
return {
|
|
2556
|
+
dataType: "number",
|
|
2557
|
+
expressionType,
|
|
2558
|
+
value: (0, ast_types_1.compose)(lhs.value, op, rhs.value),
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2561
|
+
left.log(`Non numeric('${lhs.dataType},${rhs.dataType}') value with '${op}'`);
|
|
2562
|
+
return (0, ast_types_1.errorFor)("numbers required");
|
|
2563
|
+
}
|
|
2564
|
+
const compareTypes = {
|
|
2565
|
+
"~": [ast_types_1.FT.stringT],
|
|
2566
|
+
"!~": [ast_types_1.FT.stringT],
|
|
2567
|
+
"<": [ast_types_1.FT.numberT, ast_types_1.FT.stringT, ast_types_1.FT.dateT, ast_types_1.FT.timestampT],
|
|
2568
|
+
"<=": [ast_types_1.FT.numberT, ast_types_1.FT.stringT, ast_types_1.FT.dateT, ast_types_1.FT.timestampT],
|
|
2569
|
+
"=": [ast_types_1.FT.numberT, ast_types_1.FT.stringT, ast_types_1.FT.dateT, ast_types_1.FT.timestampT],
|
|
2570
|
+
"!=": [ast_types_1.FT.numberT, ast_types_1.FT.stringT, ast_types_1.FT.dateT, ast_types_1.FT.timestampT],
|
|
2571
|
+
">=": [ast_types_1.FT.numberT, ast_types_1.FT.stringT, ast_types_1.FT.dateT, ast_types_1.FT.timestampT],
|
|
2572
|
+
">": [ast_types_1.FT.numberT, ast_types_1.FT.stringT, ast_types_1.FT.dateT, ast_types_1.FT.timestampT],
|
|
2573
|
+
};
|
|
2574
|
+
/**
|
|
2575
|
+
* GranularTime is a moment in time which ALSO has a "granularity"
|
|
2576
|
+
* commonly this are created by applying ".datePart" to an expression
|
|
2577
|
+
* 1) They have a value, which is the moment in time
|
|
2578
|
+
* 2) When used in a comparison, they act like a range, for the
|
|
2579
|
+
* duration of 1 unit of granularity
|
|
2580
|
+
*/
|
|
2581
|
+
class ExprGranularTime extends ExpressionDef {
|
|
2582
|
+
constructor(expr, units, truncate) {
|
|
2583
|
+
super({ expr });
|
|
2584
|
+
this.expr = expr;
|
|
2585
|
+
this.units = units;
|
|
2586
|
+
this.truncate = truncate;
|
|
2587
|
+
this.elementType = "granularTime";
|
|
2588
|
+
this.legalChildTypes = [ast_types_1.FT.timestampT, ast_types_1.FT.dateT];
|
|
2589
|
+
}
|
|
2590
|
+
granular() {
|
|
2591
|
+
return true;
|
|
2592
|
+
}
|
|
2593
|
+
getExpression(fs) {
|
|
2594
|
+
const timeframe = this.units;
|
|
2595
|
+
const exprVal = this.expr.getExpression(fs);
|
|
2596
|
+
if ((0, malloy_types_1.isTimeFieldType)(exprVal.dataType)) {
|
|
2597
|
+
const tsVal = {
|
|
2598
|
+
...exprVal,
|
|
2599
|
+
dataType: exprVal.dataType,
|
|
2600
|
+
timeframe: timeframe,
|
|
2601
|
+
};
|
|
2602
|
+
if (this.truncate) {
|
|
2603
|
+
tsVal.value = [
|
|
2604
|
+
{
|
|
2605
|
+
type: "dialect",
|
|
2606
|
+
function: "trunc",
|
|
2607
|
+
expr: { value: exprVal.value, valueType: exprVal.dataType },
|
|
2608
|
+
units: timeframe,
|
|
2609
|
+
},
|
|
2610
|
+
];
|
|
2611
|
+
}
|
|
2612
|
+
return tsVal;
|
|
2613
|
+
}
|
|
2614
|
+
this.log(`Cannot do time truncation on type '${exprVal.dataType}'`);
|
|
2615
|
+
return (0, ast_types_1.errorFor)(`granularity typecheck`);
|
|
2616
|
+
}
|
|
2617
|
+
apply(fs, op, left) {
|
|
2618
|
+
const rangeType = this.getExpression(fs).dataType;
|
|
2619
|
+
const _valueType = left.getExpression(fs).dataType;
|
|
2620
|
+
const granularityType = (0, malloy_types_1.isDateUnit)(this.units) ? "date" : "timestamp";
|
|
2621
|
+
if (rangeType == "date" && granularityType == "date") {
|
|
2622
|
+
return this.dateRange(fs, op, left);
|
|
2623
|
+
}
|
|
2624
|
+
return this.timestampRange(fs, op, left);
|
|
2625
|
+
/*
|
|
2626
|
+
write tests for each of these cases ....
|
|
2627
|
+
|
|
2628
|
+
vt rt gt use
|
|
2629
|
+
dt dt dt dateRange
|
|
2630
|
+
dt dt ts == or timeStampRange
|
|
2631
|
+
dt ts dt timestampRange
|
|
2632
|
+
dt ts ts timeStampRange
|
|
2633
|
+
|
|
2634
|
+
ts ts ts timestampRange
|
|
2635
|
+
ts ts dt timestampRange
|
|
2636
|
+
ts dt ts timestampRange
|
|
2637
|
+
ts dt dt either
|
|
2638
|
+
|
|
2639
|
+
*/
|
|
2640
|
+
}
|
|
2641
|
+
timestampRange(fs, op, expr) {
|
|
2642
|
+
const begin = this.getExpression(fs);
|
|
2643
|
+
const beginTime = ExprTime.fromValue("timestamp", begin);
|
|
2644
|
+
const endTime = new ExprTime("timestamp", (0, time_utils_1.timeOffset)("timestamp", begin.value, "+", (0, malloy_types_1.mkExpr) `1`, this.units), begin.expressionType);
|
|
2645
|
+
const range = new Range(beginTime, endTime);
|
|
2646
|
+
return range.apply(fs, op, expr);
|
|
2647
|
+
}
|
|
2648
|
+
dateRange(fs, op, expr) {
|
|
2649
|
+
const begin = this.getExpression(fs);
|
|
2650
|
+
const beginTime = new ExprTime("date", begin.value, begin.expressionType);
|
|
2651
|
+
const endAt = (0, time_utils_1.timeOffset)("date", begin.value, "+", ["1"], this.units);
|
|
2652
|
+
const end = new ExprTime("date", endAt, begin.expressionType);
|
|
2653
|
+
const range = new Range(beginTime, end);
|
|
2654
|
+
return range.apply(fs, op, expr);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
exports.ExprGranularTime = ExprGranularTime;
|
|
2658
|
+
function timeCompare(lhs, op, rhs) {
|
|
2659
|
+
if ((0, malloy_types_1.isTimeFieldType)(lhs.dataType) && (0, malloy_types_1.isTimeFieldType)(rhs.dataType)) {
|
|
2660
|
+
if (lhs.dataType !== rhs.dataType) {
|
|
2661
|
+
let lval = lhs.value;
|
|
2662
|
+
let rval = rhs.value;
|
|
2663
|
+
if (lhs.dataType === "timestamp") {
|
|
2664
|
+
lval = (0, time_utils_1.castTimestampToDate)(lval);
|
|
2665
|
+
}
|
|
2666
|
+
else {
|
|
2667
|
+
rval = (0, time_utils_1.castTimestampToDate)(rval);
|
|
2668
|
+
}
|
|
2669
|
+
return (0, ast_types_1.compose)(lval, op, rval);
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
return undefined;
|
|
2673
|
+
}
|
|
2674
|
+
function regexEqual(left, right) {
|
|
2675
|
+
if (left.dataType === "string") {
|
|
2676
|
+
if (right.dataType === "regular expression") {
|
|
2677
|
+
return [
|
|
2678
|
+
{
|
|
2679
|
+
type: "dialect",
|
|
2680
|
+
function: "regexpMatch",
|
|
2681
|
+
expr: left.value,
|
|
2682
|
+
regexp: right.value[0].replace(/^r'/, "'"),
|
|
2683
|
+
},
|
|
2684
|
+
];
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
else if (right.dataType === "string") {
|
|
2688
|
+
if (left.dataType === "regular expression") {
|
|
2689
|
+
return [
|
|
2690
|
+
{
|
|
2691
|
+
type: "dialect",
|
|
2692
|
+
function: "regexpMatch",
|
|
2693
|
+
expr: right.value,
|
|
2694
|
+
regexp: left.value[0].replace(/^r'/, "'"),
|
|
2695
|
+
},
|
|
2696
|
+
];
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
return undefined;
|
|
2700
|
+
}
|
|
2701
|
+
class TypeMistmatch extends Error {
|
|
2702
|
+
}
|
|
2703
|
+
exports.TypeMistmatch = TypeMistmatch;
|
|
2704
|
+
function nullCompare(left, op, right) {
|
|
2705
|
+
const not = op === "!=" || op === "!~";
|
|
2706
|
+
if (left.dataType === "null" || right.dataType === "null") {
|
|
2707
|
+
const maybeNot = not ? " NOT" : "";
|
|
2708
|
+
if (left.dataType !== "null") {
|
|
2709
|
+
return [...left.value, ` IS${maybeNot} NULL`];
|
|
2710
|
+
}
|
|
2711
|
+
if (right.dataType !== "null") {
|
|
2712
|
+
return [...right.value, `IS${maybeNot} NULL`];
|
|
2713
|
+
}
|
|
2714
|
+
return [not ? "false" : "true"];
|
|
2715
|
+
}
|
|
2716
|
+
return undefined;
|
|
2717
|
+
}
|
|
2718
|
+
class ExprDuration extends ExpressionDef {
|
|
2719
|
+
constructor(n, timeframe) {
|
|
2720
|
+
super({ n });
|
|
2721
|
+
this.n = n;
|
|
2722
|
+
this.timeframe = timeframe;
|
|
2723
|
+
this.elementType = "duration";
|
|
2724
|
+
this.legalChildTypes = [ast_types_1.FT.timestampT, ast_types_1.FT.dateT];
|
|
2725
|
+
}
|
|
2726
|
+
apply(fs, op, left) {
|
|
2727
|
+
const lhs = left.getExpression(fs);
|
|
2728
|
+
this.typeCheck(this, lhs);
|
|
2729
|
+
if ((0, malloy_types_1.isTimeFieldType)(lhs.dataType) && (op === "+" || op === "-")) {
|
|
2730
|
+
const num = this.n.getExpression(fs);
|
|
2731
|
+
if (!ast_types_1.FT.typeEq(num, ast_types_1.FT.numberT)) {
|
|
2732
|
+
this.log(`Duration units needs number not '${num.dataType}`);
|
|
2733
|
+
return (0, ast_types_1.errorFor)("illegal unit expression");
|
|
2734
|
+
}
|
|
2735
|
+
let resultGranularity;
|
|
2736
|
+
// Only allow the output of this to be granular if the
|
|
2737
|
+
// granularities match, this is still an area where
|
|
2738
|
+
// more thought is required.
|
|
2739
|
+
if ((0, ast_types_1.isGranularResult)(lhs) && lhs.timeframe == this.timeframe) {
|
|
2740
|
+
resultGranularity = lhs.timeframe;
|
|
2741
|
+
}
|
|
2742
|
+
if (lhs.dataType === "timestamp") {
|
|
2743
|
+
const result = (0, time_utils_1.timeOffset)("timestamp", lhs.value, op, num.value, this.timeframe);
|
|
2744
|
+
return (0, time_utils_1.timeResult)({
|
|
2745
|
+
dataType: "timestamp",
|
|
2746
|
+
expressionType: (0, malloy_types_1.maxExpressionType)(lhs.expressionType, num.expressionType),
|
|
2747
|
+
value: result,
|
|
2748
|
+
}, resultGranularity);
|
|
2749
|
+
}
|
|
2750
|
+
return (0, time_utils_1.timeResult)({
|
|
2751
|
+
dataType: "date",
|
|
2752
|
+
expressionType: (0, malloy_types_1.maxExpressionType)(lhs.expressionType, num.expressionType),
|
|
2753
|
+
value: (0, time_utils_1.timeOffset)("date", lhs.value, op, num.value, this.timeframe),
|
|
2754
|
+
}, resultGranularity);
|
|
2755
|
+
}
|
|
2756
|
+
return super.apply(fs, op, left);
|
|
2757
|
+
}
|
|
2758
|
+
getExpression(_fs) {
|
|
2759
|
+
return {
|
|
2760
|
+
dataType: "duration",
|
|
2761
|
+
expressionType: "scalar",
|
|
2762
|
+
value: ["__ERROR_DURATION_IS_NOT_A_VALUE__"],
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
exports.ExprDuration = ExprDuration;
|
|
2767
|
+
function allAre(oneType, ...values) {
|
|
2768
|
+
for (const v of values) {
|
|
2769
|
+
if (v.dataType !== oneType) {
|
|
2770
|
+
return false;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
return true;
|
|
2774
|
+
}
|
|
2775
|
+
class ExprTime extends ExpressionDef {
|
|
2776
|
+
constructor(timeType, value, expressionType = "scalar") {
|
|
2777
|
+
super();
|
|
2778
|
+
this.elementType = "timestampOrDate";
|
|
2779
|
+
this.elementType = timeType;
|
|
2780
|
+
this.translationValue = {
|
|
2781
|
+
dataType: timeType,
|
|
2782
|
+
expressionType,
|
|
2783
|
+
value: typeof value === "string" ? [value] : value,
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
getExpression(_fs) {
|
|
2787
|
+
return this.translationValue;
|
|
2788
|
+
}
|
|
2789
|
+
static fromValue(timeType, expr) {
|
|
2790
|
+
let value = expr.value;
|
|
2791
|
+
if (timeType != expr.dataType) {
|
|
2792
|
+
const toTs = {
|
|
2793
|
+
type: "dialect",
|
|
2794
|
+
function: "cast",
|
|
2795
|
+
safe: false,
|
|
2796
|
+
dstType: timeType,
|
|
2797
|
+
expr: expr.value,
|
|
2798
|
+
};
|
|
2799
|
+
if ((0, malloy_types_1.isTimeFieldType)(expr.dataType)) {
|
|
2800
|
+
toTs.srcType = expr.dataType;
|
|
2801
|
+
}
|
|
2802
|
+
value = (0, ast_types_1.compressExpr)([toTs]);
|
|
2803
|
+
}
|
|
2804
|
+
return new ExprTime(timeType, value, expr.expressionType);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
exports.ExprTime = ExprTime;
|
|
2808
|
+
class Range extends ExpressionDef {
|
|
2809
|
+
constructor(first, last) {
|
|
2810
|
+
super({ first, last });
|
|
2811
|
+
this.first = first;
|
|
2812
|
+
this.last = last;
|
|
2813
|
+
this.elementType = "range";
|
|
2814
|
+
}
|
|
2815
|
+
apply(fs, op, expr) {
|
|
2816
|
+
switch (op) {
|
|
2817
|
+
case "=":
|
|
2818
|
+
case "!=": {
|
|
2819
|
+
const op1 = op === "=" ? ">=" : "<";
|
|
2820
|
+
const op2 = op === "=" ? "and" : "or";
|
|
2821
|
+
const op3 = op === "=" ? "<" : ">=";
|
|
2822
|
+
const fromValue = this.first.apply(fs, op1, expr);
|
|
2823
|
+
const toValue = this.last.apply(fs, op3, expr);
|
|
2824
|
+
return {
|
|
2825
|
+
dataType: "boolean",
|
|
2826
|
+
expressionType: (0, malloy_types_1.maxExpressionType)(fromValue.expressionType, toValue.expressionType),
|
|
2827
|
+
value: (0, ast_types_1.compose)(fromValue.value, op2, toValue.value),
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
/**
|
|
2831
|
+
* This is a little surprising, but is actually how you comapre a
|
|
2832
|
+
* value to a range ...
|
|
2833
|
+
*
|
|
2834
|
+
* val > begin to end val >= end
|
|
2835
|
+
* val >= begin to end val >= begin
|
|
2836
|
+
* val < begin to end val < begin
|
|
2837
|
+
* val <= begin to end val < end
|
|
2838
|
+
*/
|
|
2839
|
+
case ">":
|
|
2840
|
+
return this.last.apply(fs, ">=", expr);
|
|
2841
|
+
case ">=":
|
|
2842
|
+
return this.first.apply(fs, ">=", expr);
|
|
2843
|
+
case "<":
|
|
2844
|
+
return this.first.apply(fs, "<", expr);
|
|
2845
|
+
case "<=":
|
|
2846
|
+
return this.last.apply(fs, "<", expr);
|
|
2847
|
+
}
|
|
2848
|
+
throw new Error("mysterious error in range computation");
|
|
2849
|
+
}
|
|
2850
|
+
requestExpression(_fs) {
|
|
2851
|
+
return undefined;
|
|
2852
|
+
}
|
|
2853
|
+
getExpression(_fs) {
|
|
2854
|
+
return (0, ast_types_1.errorFor)("a range is not a value");
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
exports.Range = Range;
|
|
2186
2858
|
//# sourceMappingURL=ast-main.js.map
|