@malloydata/malloy 0.0.119-dev240123183113 → 0.0.119-dev240124170348
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/index.d.ts +2 -2
- package/dist/index.js +1 -3
- package/dist/lang/ast/error-factory.js +3 -3
- package/dist/lang/ast/expressions/expr-aggregate-function.d.ts +6 -4
- package/dist/lang/ast/expressions/expr-aggregate-function.js +24 -13
- package/dist/lang/ast/expressions/expr-count.js +1 -1
- package/dist/lang/ast/expressions/expr-func.js +4 -4
- package/dist/lang/ast/expressions/expr-id-reference.js +2 -2
- package/dist/lang/ast/field-space/dynamic-space.js +1 -1
- package/dist/lang/ast/field-space/index-field-space.d.ts +5 -3
- package/dist/lang/ast/field-space/index-field-space.js +85 -16
- package/dist/lang/ast/field-space/query-input-space.d.ts +3 -3
- package/dist/lang/ast/field-space/query-spaces.d.ts +15 -12
- package/dist/lang/ast/field-space/query-spaces.js +60 -57
- package/dist/lang/ast/field-space/reference-field.d.ts +0 -5
- package/dist/lang/ast/field-space/reference-field.js +16 -46
- package/dist/lang/ast/query-builders/index-builder.d.ts +2 -2
- package/dist/lang/ast/query-builders/partial-builder.js +28 -1
- package/dist/lang/ast/query-builders/project-builder.d.ts +8 -4
- package/dist/lang/ast/query-builders/project-builder.js +5 -6
- package/dist/lang/ast/query-builders/reduce-builder.d.ts +13 -8
- package/dist/lang/ast/query-builders/reduce-builder.js +12 -9
- package/dist/lang/ast/query-items/field-declaration.d.ts +1 -1
- package/dist/lang/ast/query-items/field-references.d.ts +5 -1
- package/dist/lang/ast/query-items/field-references.js +17 -1
- package/dist/lang/ast/query-properties/qop-desc.d.ts +2 -2
- package/dist/lang/ast/source-properties/joins.js +1 -1
- package/dist/lang/ast/types/field-space.d.ts +2 -2
- package/dist/lang/ast/types/query-builder.d.ts +2 -2
- package/dist/lang/ast/view-elements/qop-desc-view.d.ts +3 -3
- package/dist/lang/ast/view-elements/reference-view.d.ts +3 -3
- package/dist/lang/ast/view-elements/reference-view.js +1 -1
- package/dist/lang/ast/view-elements/refine-utils.js +20 -18
- package/dist/lang/ast/view-elements/view-arrow.d.ts +2 -2
- package/dist/lang/ast/view-elements/view-refine.d.ts +3 -3
- package/dist/lang/ast/view-elements/view.d.ts +4 -4
- package/dist/lang/field-utils.d.ts +3 -3
- package/dist/lang/field-utils.js +9 -8
- package/dist/lang/test/annotation.spec.js +26 -27
- package/dist/lang/test/expressions.spec.js +43 -40
- package/dist/lang/test/field-symbols.spec.js +4 -4
- package/dist/lang/test/locations.spec.js +13 -6
- package/dist/lang/test/query.spec.js +44 -56
- package/dist/lang/test/test-translator.d.ts +3 -2
- package/dist/lang/test/test-translator.js +30 -24
- package/dist/malloy.js +1 -1
- package/dist/model/malloy_query.d.ts +14 -36
- package/dist/model/malloy_query.js +145 -312
- package/dist/model/malloy_types.d.ts +18 -23
- package/dist/model/malloy_types.js +2 -15
- package/dist/tags.js +19 -4
- package/package.json +1 -1
|
@@ -27,6 +27,9 @@ const dialect_1 = require("../dialect");
|
|
|
27
27
|
const standardsql_1 = require("../dialect/standardsql/standardsql");
|
|
28
28
|
const malloy_types_1 = require("./malloy_types");
|
|
29
29
|
const utils_1 = require("./utils");
|
|
30
|
+
function pathToCol(path) {
|
|
31
|
+
return path.map(el => encodeURIComponent(el)).join('/');
|
|
32
|
+
}
|
|
30
33
|
// quote a string for SQL use. Perhaps should be in dialect.
|
|
31
34
|
function generateSQLStringLiteral(sourceString) {
|
|
32
35
|
return `'${sourceString}'`;
|
|
@@ -330,17 +333,30 @@ class QueryField extends QueryNode {
|
|
|
330
333
|
generateSpread(_resultSet, _context, _frag, _state) {
|
|
331
334
|
throw new Error('Unexpanded spread encountered during SQL generation');
|
|
332
335
|
}
|
|
333
|
-
generateParameterFragment(resultSet, context, expr,
|
|
336
|
+
generateParameterFragment(resultSet, context, expr, _state) {
|
|
337
|
+
/*
|
|
338
|
+
mtoy todo parameters and paths figure this out
|
|
339
|
+
|
|
334
340
|
// find the structDef and return the path to the field...
|
|
335
341
|
const param = context.parameters()[expr.path];
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
if (isValueParameter(param)) {
|
|
343
|
+
if (param.value) {
|
|
344
|
+
return this.generateExpressionFromExpr(
|
|
345
|
+
resultSet,
|
|
346
|
+
context,
|
|
347
|
+
param.value,
|
|
348
|
+
state
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
} else if (param.condition) {
|
|
352
|
+
return this.generateExpressionFromExpr(
|
|
353
|
+
resultSet,
|
|
354
|
+
context,
|
|
355
|
+
param.condition,
|
|
356
|
+
state
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
*/
|
|
344
360
|
throw new Error(`Can't generate SQL, no value for ${expr.path}`);
|
|
345
361
|
}
|
|
346
362
|
generateFilterFragment(resultSet, context, expr, state) {
|
|
@@ -479,9 +495,6 @@ class QueryField extends QueryNode {
|
|
|
479
495
|
generateDialect(resultSet, context, expr, state) {
|
|
480
496
|
return this.generateExpressionFromExpr(resultSet, context, context.dialect.dialectExpr(resultSet.getQueryInfo(), expr), state);
|
|
481
497
|
}
|
|
482
|
-
generateFieldReference(resultSet, context, expr, state) {
|
|
483
|
-
return this.generateFieldFragment(resultSet, context, { type: 'field', path: expr.path }, state);
|
|
484
|
-
}
|
|
485
498
|
generateSqlString(resultSet, context, expr, state) {
|
|
486
499
|
return expr.e
|
|
487
500
|
.map(part => typeof part === 'string'
|
|
@@ -667,9 +680,6 @@ class QueryField extends QueryNode {
|
|
|
667
680
|
else if (expr.type === 'source-reference') {
|
|
668
681
|
s += this.generateSourceReference(resultSet, context, expr);
|
|
669
682
|
}
|
|
670
|
-
else if (expr.type === 'field-reference') {
|
|
671
|
-
s += this.generateFieldReference(resultSet, context, expr, state);
|
|
672
|
-
}
|
|
673
683
|
else {
|
|
674
684
|
throw new Error(`Internal Error: Unknown expression fragment ${JSON.stringify(expr, undefined, 2)}`);
|
|
675
685
|
}
|
|
@@ -699,7 +709,7 @@ function isAggregateField(f) {
|
|
|
699
709
|
return f instanceof QueryAtomicField && f.isAggregate();
|
|
700
710
|
}
|
|
701
711
|
function isScalarField(f) {
|
|
702
|
-
return f instanceof QueryAtomicField && !f.isCalculated();
|
|
712
|
+
return f instanceof QueryAtomicField && !f.isCalculated() && !f.isAggregate();
|
|
703
713
|
}
|
|
704
714
|
class QueryAtomicField extends QueryField {
|
|
705
715
|
includeInWildcard() {
|
|
@@ -750,10 +760,10 @@ class QueryFieldStruct extends QueryAtomicField {
|
|
|
750
760
|
onExpression: [
|
|
751
761
|
{
|
|
752
762
|
type: 'field',
|
|
753
|
-
path: this.primaryKey,
|
|
763
|
+
path: [this.primaryKey],
|
|
754
764
|
},
|
|
755
765
|
'=',
|
|
756
|
-
{ type: 'field', path: foreignKeyName },
|
|
766
|
+
{ type: 'field', path: [foreignKeyName] },
|
|
757
767
|
],
|
|
758
768
|
},
|
|
759
769
|
};
|
|
@@ -1411,136 +1421,14 @@ class QueryQuery extends QueryField {
|
|
|
1411
1421
|
return (this.parent.fieldDef.structSource.type === 'sql' &&
|
|
1412
1422
|
this.parent.fieldDef.structSource.method === 'nested');
|
|
1413
1423
|
}
|
|
1414
|
-
getFieldList() {
|
|
1415
|
-
switch (this.firstSegment.type) {
|
|
1416
|
-
// case "index":
|
|
1417
|
-
// return this.firstSegment.fields || [];
|
|
1418
|
-
case 'reduce':
|
|
1419
|
-
return this.firstSegment.fields;
|
|
1420
|
-
// probably need some way of checking type class of field here...
|
|
1421
|
-
// project should only contain scalars
|
|
1422
|
-
case 'project':
|
|
1423
|
-
return this.firstSegment.fields;
|
|
1424
|
-
default:
|
|
1425
|
-
throw new Error(`Query contains no fields ${JSON.stringify(this.fieldDef)}`);
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
1424
|
// get a field ref and expand it.
|
|
1429
1425
|
expandField(f) {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
field = this.parent.getQueryFieldByName(f);
|
|
1435
|
-
}
|
|
1436
|
-
else if ('type' in f) {
|
|
1437
|
-
field = this.parent.makeQueryField(f);
|
|
1438
|
-
}
|
|
1439
|
-
// or FilteredAliasedName or a hacked timestamp field.
|
|
1440
|
-
else if ('name' in f && 'as' in f) {
|
|
1441
|
-
field = this.parent.getQueryFieldByName(f.name);
|
|
1442
|
-
// QueryFieldStructs return new names...
|
|
1443
|
-
as = field.fieldDef.as || f.as;
|
|
1444
|
-
if (field instanceof QueryFieldStruct) {
|
|
1445
|
-
throw new Error('Syntax currently disallowed. Semantics up for discussion');
|
|
1446
|
-
}
|
|
1447
|
-
// Types of aliased fields.
|
|
1448
|
-
// turtles
|
|
1449
|
-
// Timestamps and Dates (are just fine to leave as is).
|
|
1450
|
-
// measures
|
|
1451
|
-
// let e: Expr;
|
|
1452
|
-
if (field instanceof QueryQuery) {
|
|
1453
|
-
const newFieldDef = structuredClone(field.fieldDef);
|
|
1454
|
-
newFieldDef.as = f.name;
|
|
1455
|
-
newFieldDef.filterList = f.filterList;
|
|
1456
|
-
field = QueryQuery.makeQuery(newFieldDef, this.parent, undefined, this.isJoinedSubquery);
|
|
1457
|
-
}
|
|
1458
|
-
else if (!(field instanceof QueryFieldTimestamp ||
|
|
1459
|
-
field instanceof QueryFieldDate)) {
|
|
1460
|
-
throw new Error(`No longer generate code this way. \n ${JSON.stringify(f, undefined, 2)}`);
|
|
1461
|
-
// // its a measure
|
|
1462
|
-
// e = [{ type: "field", path: field.getFullOutputName() }];
|
|
1463
|
-
// if ("filterList" in f && f.filterList) {
|
|
1464
|
-
// e = [{ type: "filterExpression", filterList: f.filterList, e: e }];
|
|
1465
|
-
// }
|
|
1466
|
-
// const newFieldDef = {
|
|
1467
|
-
// type: field.fieldDef.type,
|
|
1468
|
-
// name: f.as,
|
|
1469
|
-
// e,
|
|
1470
|
-
// };
|
|
1471
|
-
// field = this.parent.makeQueryField(newFieldDef as FieldDef);
|
|
1472
|
-
}
|
|
1473
|
-
// or inline field FieldTypeDef
|
|
1474
|
-
}
|
|
1475
|
-
else {
|
|
1476
|
-
throw new Error(`Unrecognized field definition ${JSON.stringify(f, undefined, 2)}`);
|
|
1477
|
-
}
|
|
1478
|
-
if (!as) {
|
|
1479
|
-
as = field.getIdentifier();
|
|
1480
|
-
}
|
|
1426
|
+
const field = f.type === 'fieldref'
|
|
1427
|
+
? this.parent.getQueryFieldReference(f.path, f.annotation)
|
|
1428
|
+
: this.parent.makeQueryField(f);
|
|
1429
|
+
const as = field.getIdentifier();
|
|
1481
1430
|
return { as, field };
|
|
1482
1431
|
}
|
|
1483
|
-
expandDependantField(resultStruct, fieldRef) {
|
|
1484
|
-
this.expandField(fieldRef);
|
|
1485
|
-
}
|
|
1486
|
-
// find all the fieldNames in the struct (and children)
|
|
1487
|
-
// that match the filter
|
|
1488
|
-
expandWildCardStruct(struct, expandChildren, filter = undefined) {
|
|
1489
|
-
let fieldNames = [];
|
|
1490
|
-
const structs = [];
|
|
1491
|
-
for (const [_name, f] of struct.nameMap) {
|
|
1492
|
-
if (f instanceof QueryAtomicField &&
|
|
1493
|
-
isScalarField(f) &&
|
|
1494
|
-
f.includeInWildcard() &&
|
|
1495
|
-
!this.parent.dialect.ignoreInProject(f.fieldDef.name) &&
|
|
1496
|
-
(!filter || filter(f))) {
|
|
1497
|
-
// fieldNames.push(`${struct.getFullOutputName()}${name}`);
|
|
1498
|
-
fieldNames.push(f.getFullOutputName());
|
|
1499
|
-
}
|
|
1500
|
-
else if (f instanceof QueryStruct && expandChildren) {
|
|
1501
|
-
structs.push(f);
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
for (const s of structs) {
|
|
1505
|
-
fieldNames = fieldNames.concat(this.expandWildCardStruct(s, expandChildren, filter));
|
|
1506
|
-
}
|
|
1507
|
-
return fieldNames;
|
|
1508
|
-
}
|
|
1509
|
-
// Do any '*' expansion.
|
|
1510
|
-
expandWildCards(fields, filter = undefined) {
|
|
1511
|
-
let ret = [];
|
|
1512
|
-
for (const f of fields) {
|
|
1513
|
-
if (typeof f !== 'string') {
|
|
1514
|
-
ret.push(f);
|
|
1515
|
-
}
|
|
1516
|
-
else {
|
|
1517
|
-
const fieldName = f;
|
|
1518
|
-
const path = fieldName.split('.');
|
|
1519
|
-
if (!path[path.length - 1].startsWith('*')) {
|
|
1520
|
-
ret.push(f);
|
|
1521
|
-
}
|
|
1522
|
-
else {
|
|
1523
|
-
const expandChildren = path.pop() === '**';
|
|
1524
|
-
let struct = this.parent;
|
|
1525
|
-
let pathElementName;
|
|
1526
|
-
while (path.length > 0 && (pathElementName = path.shift())) {
|
|
1527
|
-
const structNode = struct.getChildByName(pathElementName);
|
|
1528
|
-
if (structNode === undefined) {
|
|
1529
|
-
throw new Error(`Nested source not found '${pathElementName}'`);
|
|
1530
|
-
}
|
|
1531
|
-
if (structNode instanceof QueryStruct) {
|
|
1532
|
-
struct = structNode;
|
|
1533
|
-
}
|
|
1534
|
-
else {
|
|
1535
|
-
throw new Error(`'${pathElementName}' is not a source object`);
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
ret = ret.concat(this.expandWildCardStruct(struct, expandChildren, filter));
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
return ret;
|
|
1543
|
-
}
|
|
1544
1432
|
addDependantPath(resultStruct, context, path, uniqueKeyPossibleUse, joinStack) {
|
|
1545
1433
|
const node = context.getFieldByName(path);
|
|
1546
1434
|
let struct;
|
|
@@ -1668,9 +1556,17 @@ class QueryQuery extends QueryField {
|
|
|
1668
1556
|
this.addDependantExpr(resultStruct, field.parent, field.fieldDef.e, []);
|
|
1669
1557
|
}
|
|
1670
1558
|
}
|
|
1559
|
+
getSegmentFields(resultStruct) {
|
|
1560
|
+
const fs = resultStruct.firstSegment;
|
|
1561
|
+
return fs.type === 'index'
|
|
1562
|
+
? fs.indexFields
|
|
1563
|
+
: (0, malloy_types_1.isQuerySegment)(fs)
|
|
1564
|
+
? fs.queryFields
|
|
1565
|
+
: [];
|
|
1566
|
+
}
|
|
1671
1567
|
expandFields(resultStruct) {
|
|
1672
1568
|
let resultIndex = 1;
|
|
1673
|
-
for (const f of this.
|
|
1569
|
+
for (const f of this.getSegmentFields(resultStruct)) {
|
|
1674
1570
|
const { as, field } = this.expandField(f);
|
|
1675
1571
|
if (field instanceof QueryTurtle || field instanceof QueryQuery) {
|
|
1676
1572
|
if (this.firstSegment.type === 'project') {
|
|
@@ -1834,7 +1730,7 @@ class QueryQuery extends QueryField {
|
|
|
1834
1730
|
const resultType = fi.getRepeatedResultType() === 'nested' ? 'nested' : 'inline';
|
|
1835
1731
|
structDef.name = name;
|
|
1836
1732
|
structDef.structRelationship = {
|
|
1837
|
-
|
|
1733
|
+
fieldName: name,
|
|
1838
1734
|
type: resultType,
|
|
1839
1735
|
isArray: false,
|
|
1840
1736
|
};
|
|
@@ -1998,7 +1894,7 @@ class QueryQuery extends QueryField {
|
|
|
1998
1894
|
if (qs.parent === undefined || ji.parent === undefined) {
|
|
1999
1895
|
throw new Error('Internal Error, nested structure with no parent.');
|
|
2000
1896
|
}
|
|
2001
|
-
const fieldExpression = this.parent.dialect.sqlFieldReference(qs.parent.getSQLIdentifier(), structRelationship.
|
|
1897
|
+
const fieldExpression = this.parent.dialect.sqlFieldReference(qs.parent.getSQLIdentifier(), structRelationship.fieldName, 'struct', qs.parent.fieldDef.structRelationship.type === 'nested', this.parent.fieldDef.structRelationship.type === 'nested' &&
|
|
2002
1898
|
this.parent.fieldDef.structRelationship.isArray);
|
|
2003
1899
|
// we need to generate primary key. If parent has a primary key combine
|
|
2004
1900
|
// console.log(ji.alias, fieldExpression, this.inNestedPipeline());
|
|
@@ -2622,20 +2518,21 @@ class QueryQueryProject extends QueryQuery {
|
|
|
2622
2518
|
class QueryQueryIndexStage extends QueryQuery {
|
|
2623
2519
|
constructor(fieldDef, parent, stageWriter, isJoinedSubquery) {
|
|
2624
2520
|
super(fieldDef, parent, stageWriter, isJoinedSubquery);
|
|
2521
|
+
this.indexPaths = {};
|
|
2625
2522
|
this.fieldDef = fieldDef;
|
|
2626
2523
|
}
|
|
2627
|
-
// get a field ref and expand it.
|
|
2628
2524
|
expandField(f) {
|
|
2629
|
-
const
|
|
2630
|
-
|
|
2525
|
+
const as = f.path.join('.');
|
|
2526
|
+
const field = this.parent.getQueryFieldByName(f.path);
|
|
2527
|
+
return { as, field };
|
|
2631
2528
|
}
|
|
2632
2529
|
expandFields(resultStruct) {
|
|
2633
2530
|
let resultIndex = 1;
|
|
2634
2531
|
const groupIndex = resultStruct.groupSet;
|
|
2635
2532
|
this.maxGroupSet = groupIndex;
|
|
2636
|
-
const
|
|
2637
|
-
for (const f of fieldNames) {
|
|
2533
|
+
for (const f of this.firstSegment.indexFields) {
|
|
2638
2534
|
const { as, field } = this.expandField(f);
|
|
2535
|
+
this.indexPaths[as] = f.path;
|
|
2639
2536
|
resultStruct.addField(as, field, {
|
|
2640
2537
|
resultIndex,
|
|
2641
2538
|
type: 'result',
|
|
@@ -2647,7 +2544,7 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
2647
2544
|
}
|
|
2648
2545
|
const measure = this.firstSegment.weightMeasure;
|
|
2649
2546
|
if (measure !== undefined) {
|
|
2650
|
-
const f = this.parent.getFieldByName(measure);
|
|
2547
|
+
const f = this.parent.getFieldByName([measure]);
|
|
2651
2548
|
resultStruct.addField(measure, f, {
|
|
2652
2549
|
resultIndex,
|
|
2653
2550
|
type: 'result',
|
|
@@ -2660,6 +2557,7 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
2660
2557
|
let measureSQL = 'COUNT(*)';
|
|
2661
2558
|
const dialect = this.parent.dialect;
|
|
2662
2559
|
const fieldNameColumn = dialect.sqlMaybeQuoteIdentifier('fieldName');
|
|
2560
|
+
const fieldPathColumn = dialect.sqlMaybeQuoteIdentifier('fieldPath');
|
|
2663
2561
|
const fieldValueColumn = dialect.sqlMaybeQuoteIdentifier('fieldValue');
|
|
2664
2562
|
const fieldTypeColumn = dialect.sqlMaybeQuoteIdentifier('fieldType');
|
|
2665
2563
|
const fieldRangeColumn = dialect.sqlMaybeQuoteIdentifier('fieldRange');
|
|
@@ -2674,7 +2572,8 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
2674
2572
|
const fi = field;
|
|
2675
2573
|
if (fi.fieldUsage.type === 'result' && isScalarField(fi.f)) {
|
|
2676
2574
|
const expression = fi.f.generateExpression(this.rootResult);
|
|
2677
|
-
|
|
2575
|
+
const path = this.indexPaths[name] || [];
|
|
2576
|
+
fields.push({ name, path, type: fi.f.fieldDef.type, expression });
|
|
2678
2577
|
}
|
|
2679
2578
|
}
|
|
2680
2579
|
let s = 'SELECT\n group_set,\n';
|
|
@@ -2682,13 +2581,19 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
2682
2581
|
for (let i = 0; i < fields.length; i++) {
|
|
2683
2582
|
s += ` WHEN ${i} THEN '${fields[i].name}'\n`;
|
|
2684
2583
|
}
|
|
2685
|
-
s += ` END as ${fieldNameColumn}
|
|
2584
|
+
s += ` END as ${fieldNameColumn},\n`;
|
|
2585
|
+
s += ' CASE group_set\n';
|
|
2586
|
+
for (let i = 0; i < fields.length; i++) {
|
|
2587
|
+
const path = pathToCol(fields[i].path);
|
|
2588
|
+
s += ` WHEN ${i} THEN '${path}'\n`;
|
|
2589
|
+
}
|
|
2590
|
+
s += ` END as ${fieldPathColumn},\n`;
|
|
2686
2591
|
s += ' CASE group_set\n';
|
|
2687
2592
|
for (let i = 0; i < fields.length; i++) {
|
|
2688
2593
|
s += ` WHEN ${i} THEN '${fields[i].type}'\n`;
|
|
2689
2594
|
}
|
|
2690
2595
|
s += ` END as ${fieldTypeColumn},`;
|
|
2691
|
-
s +=
|
|
2596
|
+
s += ` CASE group_set WHEN 99999 THEN ${dialect.castToString('NULL')}\n`;
|
|
2692
2597
|
for (let i = 0; i < fields.length; i++) {
|
|
2693
2598
|
if (fields[i].type === 'string') {
|
|
2694
2599
|
s += ` WHEN ${i} THEN ${fields[i].expression}\n`;
|
|
@@ -2717,7 +2622,7 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
2717
2622
|
s += this.generateSQLJoins(stageWriter);
|
|
2718
2623
|
s += dialect.sqlGroupSetTable(fields.length) + '\n';
|
|
2719
2624
|
s += this.generateSQLFilters(this.rootResult, 'where').sql('where');
|
|
2720
|
-
s += 'GROUP BY 1,2,3,4\n';
|
|
2625
|
+
s += 'GROUP BY 1,2,3,4,5\n';
|
|
2721
2626
|
// limit
|
|
2722
2627
|
if (!(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit) {
|
|
2723
2628
|
s += `LIMIT ${this.firstSegment.limit}\n`;
|
|
@@ -2726,6 +2631,7 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
2726
2631
|
const resultStage = stageWriter.addStage(s);
|
|
2727
2632
|
this.resultStage = stageWriter.addStage(`SELECT
|
|
2728
2633
|
${fieldNameColumn},
|
|
2634
|
+
${fieldPathColumn},
|
|
2729
2635
|
${fieldTypeColumn},
|
|
2730
2636
|
COALESCE(${fieldValueColumn}, ${fieldRangeColumn}) as ${fieldValueColumn},
|
|
2731
2637
|
weight
|
|
@@ -2755,90 +2661,57 @@ class QueryQueryRaw extends QueryQuery {
|
|
|
2755
2661
|
class QueryQueryIndex extends QueryQuery {
|
|
2756
2662
|
constructor(fieldDef, parent, stageWriter, isJoinedSubquery) {
|
|
2757
2663
|
super(fieldDef, parent, stageWriter, isJoinedSubquery);
|
|
2758
|
-
this.
|
|
2759
|
-
this.fanPrefixMap = {};
|
|
2664
|
+
this.stages = [];
|
|
2760
2665
|
this.fieldDef = fieldDef;
|
|
2761
|
-
this.
|
|
2762
|
-
}
|
|
2763
|
-
// we want to generate a different query for each
|
|
2764
|
-
// nested structure so we don't do a crazy cross product.
|
|
2765
|
-
findFanPrefexes(qs) {
|
|
2766
|
-
for (const [_name, f] of qs.nameMap) {
|
|
2767
|
-
if (f instanceof QueryStruct &&
|
|
2768
|
-
(f.fieldDef.structRelationship.type === 'many' ||
|
|
2769
|
-
f.fieldDef.structRelationship.type === 'nested') &&
|
|
2770
|
-
f.fieldDef.fields.length > 1 && // leave arrays in parent.
|
|
2771
|
-
this.parent.dialect.dontUnionIndex === false) {
|
|
2772
|
-
const key = f.getFullOutputName();
|
|
2773
|
-
this.fanPrefixMap[key] = [];
|
|
2774
|
-
this.findFanPrefexes(f);
|
|
2775
|
-
}
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
expandIndexWildCards() {
|
|
2779
|
-
// if no fields were specified, look in the parent struct for strings.
|
|
2780
|
-
let fieldNames = this.firstSegment.fields || [];
|
|
2781
|
-
if (fieldNames.length === 0) {
|
|
2782
|
-
fieldNames.push('**');
|
|
2783
|
-
}
|
|
2784
|
-
fieldNames = this.expandWildCards(fieldNames, qf => ['string', 'number', 'timestamp', 'date'].indexOf(qf.fieldDef.type) !==
|
|
2785
|
-
-1);
|
|
2786
|
-
return fieldNames;
|
|
2666
|
+
this.fieldsToStages();
|
|
2787
2667
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
}
|
|
2794
|
-
for (const fieldList of Object.values(this.fanPrefixMap)) {
|
|
2795
|
-
if (fieldList.length > 0) {
|
|
2796
|
-
s.push(fieldList);
|
|
2797
|
-
}
|
|
2668
|
+
fieldsToStages() {
|
|
2669
|
+
const indexSeg = this.firstSegment;
|
|
2670
|
+
if (this.parent.dialect.dontUnionIndex) {
|
|
2671
|
+
this.stages = [indexSeg.indexFields];
|
|
2672
|
+
return;
|
|
2798
2673
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2674
|
+
// Collect the field references by unique path, the final
|
|
2675
|
+
// index will be a union indexes from each unique path
|
|
2676
|
+
const stageMap = {};
|
|
2677
|
+
for (const fref of indexSeg.indexFields) {
|
|
2678
|
+
if (fref.path.length > 1) {
|
|
2679
|
+
const stageRoot = pathToCol(fref.path.slice(0, fref.path.length - 1));
|
|
2680
|
+
const stage = stageMap[stageRoot];
|
|
2681
|
+
if (stage === undefined) {
|
|
2682
|
+
const f = this.parent.nameMap.get(fref.path[0]);
|
|
2683
|
+
if (f instanceof QueryStruct &&
|
|
2684
|
+
(f.fieldDef.structRelationship.type === 'many' ||
|
|
2685
|
+
f.fieldDef.structRelationship.type === 'nested') &&
|
|
2686
|
+
f.fieldDef.fields.length > 1) {
|
|
2687
|
+
const toStage = [fref];
|
|
2688
|
+
stageMap[stageRoot] = toStage;
|
|
2689
|
+
this.stages.push(toStage);
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
else {
|
|
2694
|
+
stage.push(fref);
|
|
2695
|
+
continue;
|
|
2821
2696
|
}
|
|
2822
2697
|
}
|
|
2823
|
-
if (
|
|
2824
|
-
this.
|
|
2698
|
+
if (this.stages[0] === undefined) {
|
|
2699
|
+
this.stages[0] = [];
|
|
2825
2700
|
}
|
|
2701
|
+
this.stages[0].push(fref);
|
|
2826
2702
|
}
|
|
2827
2703
|
}
|
|
2828
|
-
expandFields(_resultStruct) {
|
|
2829
|
-
const fieldNames = this.expandIndexWildCards();
|
|
2830
|
-
this.mapFieldsIntoStages(fieldNames);
|
|
2831
|
-
}
|
|
2704
|
+
expandFields(_resultStruct) { }
|
|
2832
2705
|
generateSQL(stageWriter) {
|
|
2833
|
-
const
|
|
2706
|
+
const indexSeg = this.firstSegment;
|
|
2834
2707
|
const outputStageNames = [];
|
|
2835
|
-
for (const fields of stages) {
|
|
2708
|
+
for (const fields of this.stages) {
|
|
2836
2709
|
const q = new QueryQueryIndexStage({
|
|
2837
2710
|
...this.fieldDef,
|
|
2838
2711
|
pipeline: [
|
|
2839
2712
|
{
|
|
2840
|
-
...
|
|
2841
|
-
|
|
2713
|
+
...indexSeg,
|
|
2714
|
+
indexFields: fields,
|
|
2842
2715
|
},
|
|
2843
2716
|
],
|
|
2844
2717
|
}, this.parent, stageWriter, this.isJoinedSubquery);
|
|
@@ -2854,7 +2727,11 @@ class QueryQueryIndex extends QueryQuery {
|
|
|
2854
2727
|
}
|
|
2855
2728
|
return this.resultStage;
|
|
2856
2729
|
}
|
|
2857
|
-
/**
|
|
2730
|
+
/**
|
|
2731
|
+
* All Indexes have the same output schema.
|
|
2732
|
+
* fieldName is deprecated, dots in fieldName may or may not be join nodes
|
|
2733
|
+
* fieldPath is a URL encoded slash separated path
|
|
2734
|
+
*/
|
|
2858
2735
|
getResultStructDef() {
|
|
2859
2736
|
const ret = {
|
|
2860
2737
|
type: 'struct',
|
|
@@ -2862,6 +2739,7 @@ class QueryQueryIndex extends QueryQuery {
|
|
|
2862
2739
|
dialect: this.parent.fieldDef.dialect,
|
|
2863
2740
|
fields: [
|
|
2864
2741
|
{ type: 'string', name: 'fieldName' },
|
|
2742
|
+
{ type: 'string', name: 'fieldPath' },
|
|
2865
2743
|
{ type: 'string', name: 'fieldValue' },
|
|
2866
2744
|
{ type: 'string', name: 'fieldType' },
|
|
2867
2745
|
{ type: 'number', name: 'weight', numberType: 'integer' },
|
|
@@ -2963,32 +2841,6 @@ class QueryStruct extends QueryNode {
|
|
|
2963
2841
|
return ret;
|
|
2964
2842
|
}
|
|
2965
2843
|
}
|
|
2966
|
-
// when structs are referenced in queries, incorporate the
|
|
2967
|
-
// primary key of struct and add the struct as a join to the result.
|
|
2968
|
-
getAsQueryField() {
|
|
2969
|
-
if (this.fieldDef.primaryKey === undefined) {
|
|
2970
|
-
throw new Error(`Joined explores can only be included in queries if a primary key is defined: '${this.getFullOutputName()}' has no primary key`);
|
|
2971
|
-
}
|
|
2972
|
-
const pkField = this.getPrimaryKeyField(this.fieldDef);
|
|
2973
|
-
const pkType = pkField.fieldDef.type;
|
|
2974
|
-
if (pkType !== 'string' && pkType !== 'number') {
|
|
2975
|
-
throw new Error(`Unknown Primary key data type for ${pkField.fieldDef.name}`);
|
|
2976
|
-
}
|
|
2977
|
-
const aliasName = (0, malloy_types_1.getIdentifier)(this.fieldDef);
|
|
2978
|
-
const pkName = this.fieldDef.primaryKey;
|
|
2979
|
-
const fieldDef = {
|
|
2980
|
-
type: pkType,
|
|
2981
|
-
name: `${aliasName}_id`,
|
|
2982
|
-
e: [
|
|
2983
|
-
{
|
|
2984
|
-
type: 'field',
|
|
2985
|
-
// path: pkField.getFullOutputName(),
|
|
2986
|
-
path: pkField.getIdentifier(),
|
|
2987
|
-
},
|
|
2988
|
-
],
|
|
2989
|
-
};
|
|
2990
|
-
return new QueryFieldStruct(fieldDef, this, `${aliasName}.${pkName}`);
|
|
2991
|
-
}
|
|
2992
2844
|
getSQLIdentifier() {
|
|
2993
2845
|
if (this.unnestWithNumbers() && this.parent !== undefined) {
|
|
2994
2846
|
const x = this.parent.getSQLIdentifier() +
|
|
@@ -3149,27 +3001,6 @@ class QueryStruct extends QueryNode {
|
|
|
3149
3001
|
throw new Error(`unknown field definition ${JSON.stringify(field)}`);
|
|
3150
3002
|
}
|
|
3151
3003
|
}
|
|
3152
|
-
/**
|
|
3153
|
-
* return a field if it exists, make one if we are passed a field definition.
|
|
3154
|
-
*/
|
|
3155
|
-
getOrMakeField(fieldRef) {
|
|
3156
|
-
if (typeof fieldRef === 'string') {
|
|
3157
|
-
return this.getFieldByName(fieldRef);
|
|
3158
|
-
}
|
|
3159
|
-
else {
|
|
3160
|
-
return this.makeQueryField(fieldRef);
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
/** returns a dimension for the given name or make one. */
|
|
3164
|
-
getOrMakeDimension(fieldRef) {
|
|
3165
|
-
const dim = this.getOrMakeField(fieldRef);
|
|
3166
|
-
if (dim instanceof QueryAtomicField && isScalarField(dim)) {
|
|
3167
|
-
return dim;
|
|
3168
|
-
}
|
|
3169
|
-
else {
|
|
3170
|
-
throw new Error(`${fieldRef} is not of type a scalar'`);
|
|
3171
|
-
}
|
|
3172
|
-
}
|
|
3173
3004
|
structSourceSQL(stageWriter) {
|
|
3174
3005
|
switch (this.fieldDef.structSource.type) {
|
|
3175
3006
|
case 'table': {
|
|
@@ -3221,37 +3052,43 @@ class QueryStruct extends QueryNode {
|
|
|
3221
3052
|
}
|
|
3222
3053
|
primaryKey() {
|
|
3223
3054
|
if (this.fieldDef.primaryKey) {
|
|
3224
|
-
return this.getDimensionByName(this.fieldDef.primaryKey);
|
|
3055
|
+
return this.getDimensionByName([this.fieldDef.primaryKey]);
|
|
3225
3056
|
}
|
|
3226
3057
|
else {
|
|
3227
3058
|
return undefined;
|
|
3228
3059
|
}
|
|
3229
3060
|
}
|
|
3230
|
-
/** get the componennts of a field path */
|
|
3231
|
-
static resolvePath(name) {
|
|
3232
|
-
return name.split('.');
|
|
3233
|
-
}
|
|
3234
3061
|
getChildByName(name) {
|
|
3235
3062
|
return this.nameMap.get(name);
|
|
3236
3063
|
}
|
|
3237
|
-
/** convert a
|
|
3238
|
-
getFieldByName(
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
for (const n of path) {
|
|
3242
|
-
const r = ret.getChildByName(n);
|
|
3064
|
+
/** convert a path into a field reference */
|
|
3065
|
+
getFieldByName(path) {
|
|
3066
|
+
return path.reduce((lookIn, childName) => {
|
|
3067
|
+
const r = lookIn.getChildByName(childName);
|
|
3243
3068
|
if (r === undefined) {
|
|
3244
|
-
throw new Error(
|
|
3069
|
+
throw new Error(path.length === 1
|
|
3070
|
+
? `'${childName}' not found`
|
|
3071
|
+
: `'${childName}' not found in '${path.join('.')}'`);
|
|
3245
3072
|
}
|
|
3246
|
-
|
|
3247
|
-
}
|
|
3248
|
-
return ret;
|
|
3073
|
+
return r;
|
|
3074
|
+
}, this);
|
|
3249
3075
|
}
|
|
3250
3076
|
// structs referenced in queries are converted to fields.
|
|
3251
3077
|
getQueryFieldByName(name) {
|
|
3252
|
-
|
|
3078
|
+
const field = this.getFieldByName(name);
|
|
3253
3079
|
if (field instanceof QueryStruct) {
|
|
3254
|
-
|
|
3080
|
+
throw new Error(`Cannot reference ${name} as a scalar'`);
|
|
3081
|
+
}
|
|
3082
|
+
return field;
|
|
3083
|
+
}
|
|
3084
|
+
getQueryFieldReference(name, refAnnoatation) {
|
|
3085
|
+
const field = this.getQueryFieldByName(name);
|
|
3086
|
+
if (refAnnoatation) {
|
|
3087
|
+
// Made the field object from the source, but the annotations were computed by the compiler
|
|
3088
|
+
// and have noth the source and reference annotations included, use those.
|
|
3089
|
+
const newDef = { ...field.fieldDef };
|
|
3090
|
+
newDef.annotation = refAnnoatation;
|
|
3091
|
+
field.fieldDef = newDef;
|
|
3255
3092
|
}
|
|
3256
3093
|
return field;
|
|
3257
3094
|
}
|
|
@@ -3281,12 +3118,12 @@ class QueryStruct extends QueryNode {
|
|
|
3281
3118
|
return struct;
|
|
3282
3119
|
}
|
|
3283
3120
|
else {
|
|
3284
|
-
throw new Error(`Error: Path to structure not found '${name}'`);
|
|
3121
|
+
throw new Error(`Error: Path to structure not found '${name.join('.')}'`);
|
|
3285
3122
|
}
|
|
3286
3123
|
}
|
|
3287
3124
|
getDistinctKey() {
|
|
3288
3125
|
if (this.fieldDef.structRelationship.type !== 'inline') {
|
|
3289
|
-
return this.getDimensionByName('__distinct_key');
|
|
3126
|
+
return this.getDimensionByName(['__distinct_key']);
|
|
3290
3127
|
}
|
|
3291
3128
|
else if (this.parent) {
|
|
3292
3129
|
return this.parent.getDistinctKey();
|
|
@@ -3341,22 +3178,6 @@ class QueryModel {
|
|
|
3341
3178
|
}
|
|
3342
3179
|
}
|
|
3343
3180
|
}
|
|
3344
|
-
parseQueryPath(name) {
|
|
3345
|
-
const path = name.split('.');
|
|
3346
|
-
let struct;
|
|
3347
|
-
if ((struct = this.structs.get(path[0]))) {
|
|
3348
|
-
if (path.length > 1) {
|
|
3349
|
-
path.shift();
|
|
3350
|
-
}
|
|
3351
|
-
else {
|
|
3352
|
-
throw new Error(`No query specified in Struct '${path[0]}'`);
|
|
3353
|
-
}
|
|
3354
|
-
return { queryName: path.join('.'), struct };
|
|
3355
|
-
}
|
|
3356
|
-
else {
|
|
3357
|
-
throw new Error(`Cannot find Struct '${path[0]}' Model`);
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
3181
|
getStructByName(name) {
|
|
3361
3182
|
let s;
|
|
3362
3183
|
if ((s = this.structs.get(name))) {
|
|
@@ -3443,17 +3264,27 @@ class QueryModel {
|
|
|
3443
3264
|
}
|
|
3444
3265
|
// make a search index if one isn't modelled.
|
|
3445
3266
|
const struct = this.getStructByName(explore);
|
|
3267
|
+
let indexStar = [];
|
|
3268
|
+
for (const [fn, fv] of struct.nameMap) {
|
|
3269
|
+
if (!(fv instanceof QueryStruct)) {
|
|
3270
|
+
if (isScalarField(fv) && fv.includeInWildcard()) {
|
|
3271
|
+
indexStar.push({ type: 'fieldref', path: [fn] });
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
indexStar = indexStar.sort((a, b) => a.path[0].localeCompare(b.path[0]));
|
|
3446
3276
|
const indexQuery = {
|
|
3447
3277
|
structRef: explore,
|
|
3448
3278
|
pipeline: [
|
|
3449
3279
|
{
|
|
3450
3280
|
type: 'index',
|
|
3451
|
-
|
|
3281
|
+
indexFields: indexStar,
|
|
3452
3282
|
sample: struct.dialect.defaultSampling,
|
|
3453
3283
|
},
|
|
3454
3284
|
],
|
|
3455
3285
|
};
|
|
3456
3286
|
const fieldNameColumn = struct.dialect.sqlMaybeQuoteIdentifier('fieldName');
|
|
3287
|
+
const fieldPathColumn = struct.dialect.sqlMaybeQuoteIdentifier('fieldPath');
|
|
3457
3288
|
const fieldValueColumn = struct.dialect.sqlMaybeQuoteIdentifier('fieldValue');
|
|
3458
3289
|
const fieldTypeColumn = struct.dialect.sqlMaybeQuoteIdentifier('fieldType');
|
|
3459
3290
|
// if we've compiled the SQL before use it otherwise
|
|
@@ -3464,10 +3295,11 @@ class QueryModel {
|
|
|
3464
3295
|
}
|
|
3465
3296
|
let query = `SELECT
|
|
3466
3297
|
${fieldNameColumn},
|
|
3298
|
+
${fieldPathColumn},
|
|
3467
3299
|
${fieldValueColumn},
|
|
3468
3300
|
${fieldTypeColumn},
|
|
3469
3301
|
weight,
|
|
3470
|
-
CASE WHEN lower(${fieldValueColumn}) LIKE
|
|
3302
|
+
CASE WHEN lower(${fieldValueColumn}) LIKE lower(${generateSQLStringLiteral(searchValue + '%')}) THEN 1 ELSE 0 END as match_first
|
|
3471
3303
|
FROM ${await connection.manifestTemporaryTable(sqlPDT)}
|
|
3472
3304
|
WHERE lower(${fieldValueColumn}) LIKE lower(${generateSQLStringLiteral('%' + searchValue + '%')}) ${searchField !== undefined
|
|
3473
3305
|
? ` AND ${fieldNameColumn} = '` + searchField + "' \n"
|
|
@@ -3478,6 +3310,7 @@ class QueryModel {
|
|
|
3478
3310
|
if (struct.dialect.hasFinalStage) {
|
|
3479
3311
|
query = `WITH __stage0 AS(\n${query}\n)\n${struct.dialect.sqlFinalStage('__stage0', [
|
|
3480
3312
|
fieldNameColumn,
|
|
3313
|
+
fieldPathColumn,
|
|
3481
3314
|
fieldValueColumn,
|
|
3482
3315
|
fieldTypeColumn,
|
|
3483
3316
|
'weight',
|