@malloydata/malloy 0.0.433 → 0.0.435
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/foundation/compile.d.ts +2 -2
- package/dist/api/foundation/compile.js +8 -9
- package/dist/api/foundation/config.js +6 -15
- package/dist/api/foundation/runtime.d.ts +2 -8
- package/dist/api/foundation/runtime.js +11 -39
- package/dist/api/foundation/types.d.ts +1 -1
- package/dist/connection/validate_table_path.d.ts +1 -2
- package/dist/connection/validate_table_path.js +1 -2
- package/dist/dialect/databricks/databricks.js +6 -6
- package/dist/dialect/dialect.d.ts +35 -3
- package/dist/dialect/dialect.js +67 -5
- package/dist/dialect/dialect_map.js +2 -0
- package/dist/dialect/duckdb/duckdb.js +7 -21
- package/dist/dialect/index.d.ts +2 -1
- package/dist/dialect/index.js +3 -1
- package/dist/dialect/mysql/mysql.js +8 -8
- package/dist/dialect/pg_impl.js +12 -12
- package/dist/dialect/postgres/postgres.js +3 -3
- package/dist/dialect/snowflake/snowflake.js +12 -12
- package/dist/dialect/sqlserver/dialect_functions.d.ts +2 -0
- package/dist/dialect/sqlserver/dialect_functions.js +42 -0
- package/dist/dialect/sqlserver/function_overrides.d.ts +2 -0
- package/dist/dialect/sqlserver/function_overrides.js +55 -0
- package/dist/dialect/sqlserver/index.d.ts +1 -0
- package/dist/dialect/sqlserver/index.js +22 -0
- package/dist/dialect/sqlserver/sqlserver.d.ts +102 -0
- package/dist/dialect/sqlserver/sqlserver.js +538 -0
- package/dist/dialect/sqlserver/windows_zones.d.ts +1 -0
- package/dist/dialect/sqlserver/windows_zones.js +613 -0
- package/dist/dialect/standardsql/standardsql.d.ts +1 -1
- package/dist/dialect/standardsql/standardsql.js +16 -13
- package/dist/dialect/trino/trino.d.ts +3 -3
- package/dist/dialect/trino/trino.js +32 -28
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/lang/ast/source-elements/named-source.js +0 -2
- package/dist/lang/ast/source-elements/refined-source.js +1 -1
- package/dist/lang/ast/source-elements/sql-source.js +2 -4
- package/dist/lang/ast/source-elements/table-source.js +1 -2
- package/dist/lang/ast/source-properties/timezone-statement.d.ts +4 -3
- package/dist/lang/ast/source-properties/timezone-statement.js +14 -12
- package/dist/lang/ast/statements/import-statement.js +4 -0
- package/dist/lang/ast/types/malloy-element.d.ts +0 -7
- package/dist/lang/ast/types/malloy-element.js +0 -18
- package/dist/lang/composite-source-utils.js +10 -3
- package/dist/lang/malloy-to-ast.js +2 -8
- package/dist/lang/parse-malloy.d.ts +20 -4
- package/dist/lang/parse-malloy.js +50 -15
- package/dist/lang/parse-tree-walkers/find-external-references.d.ts +3 -3
- package/dist/lang/parse-tree-walkers/find-external-references.js +17 -16
- package/dist/lang/test/test-translator.d.ts +12 -2
- package/dist/lang/test/test-translator.js +16 -2
- package/dist/model/expression_compiler.js +1 -1
- package/dist/model/join_instance.d.ts +1 -0
- package/dist/model/join_instance.js +9 -1
- package/dist/model/query_query.d.ts +28 -1
- package/dist/model/query_query.js +299 -156
- package/dist/model/source_def_utils.d.ts +7 -1
- package/dist/model/source_def_utils.js +20 -0
- package/dist/model/sql_block.d.ts +2 -2
- package/dist/model/sql_block.js +11 -47
- package/dist/model/sql_compiled.d.ts +3 -1
- package/dist/model/sql_compiled.js +8 -6
- package/dist/model/stage_writer.js +3 -3
- package/dist/model/utils.d.ts +2 -2
- package/dist/model/utils.js +2 -3
- package/dist/test/index.js +16 -8
- package/dist/test/matchers.d.ts +1 -1
- package/dist/test/matchers.js +1 -1
- package/dist/test/test-models.js +6 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/femto-config.motly +15 -1
- package/package.json +7 -5
|
@@ -34,14 +34,57 @@ function fieldIsAnalytic(fi) {
|
|
|
34
34
|
return ((0, malloy_types_1.hasExpression)(fi.f.fieldDef) &&
|
|
35
35
|
(0, malloy_types_1.expressionIsAnalytic)(fi.f.fieldDef.expressionType));
|
|
36
36
|
}
|
|
37
|
-
//
|
|
38
|
-
function
|
|
39
|
-
|
|
37
|
+
// `<expr> as <name>`
|
|
38
|
+
function aliasedColumn(expr, name, role) {
|
|
39
|
+
var _a;
|
|
40
|
+
return {
|
|
41
|
+
sql: `${expr} as ${name}`,
|
|
42
|
+
expr,
|
|
43
|
+
name,
|
|
44
|
+
isDimension: role.isDimension,
|
|
45
|
+
constant: (_a = role.constant) !== null && _a !== void 0 ? _a : false,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Does a field's expression read any column of its source?
|
|
49
|
+
function readsColumn(fieldDef) {
|
|
50
|
+
if (!(0, malloy_types_1.hasExpression)(fieldDef)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
for (const node of (0, utils_1.exprWalk)(fieldDef.e)) {
|
|
54
|
+
switch (node.node) {
|
|
55
|
+
case 'field':
|
|
56
|
+
case 'outputField':
|
|
57
|
+
case 'source-reference':
|
|
58
|
+
case 'genericSQLExpr':
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
// The dimension columns as GROUP BY terms: 1-based SELECT positions, or the
|
|
65
|
+
// expressions for a dialect without ordinals in GROUP BY.
|
|
66
|
+
function groupByTerms(dialect, columns) {
|
|
67
|
+
if (dialect.groupByClause === 'ordinal') {
|
|
68
|
+
return columns.flatMap((c, i) => (c.isDimension ? [`${i + 1}`] : []));
|
|
69
|
+
}
|
|
70
|
+
return columns.flatMap(c => (c.isDimension && !c.constant ? [c.expr] : []));
|
|
71
|
+
}
|
|
72
|
+
function sqlGroupBy(dialect, columns) {
|
|
73
|
+
const terms = groupByTerms(dialect, columns);
|
|
74
|
+
if (terms.length > 0) {
|
|
75
|
+
return `GROUP BY ${terms.join(',')}\n`;
|
|
76
|
+
}
|
|
77
|
+
// Dimensions which are all constants still make one group of the input
|
|
78
|
+
if (dialect.groupByClause === 'expression' &&
|
|
79
|
+
columns.some(c => c.isDimension)) {
|
|
80
|
+
return 'GROUP BY ()\n';
|
|
81
|
+
}
|
|
82
|
+
return '';
|
|
40
83
|
}
|
|
41
84
|
function pushDialectField(dl, f) {
|
|
42
|
-
const { sqlExpression,
|
|
85
|
+
const { sqlExpression, rawName } = f;
|
|
43
86
|
if ((0, malloy_types_1.isAtomic)(f.fieldDef)) {
|
|
44
|
-
dl.push({ typeDef: f.fieldDef, sqlExpression,
|
|
87
|
+
dl.push({ typeDef: f.fieldDef, sqlExpression, rawName });
|
|
45
88
|
}
|
|
46
89
|
}
|
|
47
90
|
/** Query builder object. */
|
|
@@ -539,13 +582,23 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
539
582
|
case 'virtual': {
|
|
540
583
|
const virtualMap = (_a = qs.prepareResultOptions) === null || _a === void 0 ? void 0 : _a.virtualMap;
|
|
541
584
|
const tablePath = (_b = virtualMap === null || virtualMap === void 0 ? void 0 : virtualMap.get(qs.structDef.connection)) === null || _b === void 0 ? void 0 : _b.get(qs.structDef.name);
|
|
542
|
-
if (
|
|
585
|
+
if (tablePath === undefined) {
|
|
543
586
|
throw new malloy_compile_error_1.MalloyCompileError(`No virtual-map entry for virtual source '${qs.structDef.name}' ` +
|
|
544
587
|
`on connection '${qs.structDef.connection}'. ` +
|
|
545
|
-
'
|
|
588
|
+
'Supply the entry in `virtualMap` when compiling or running the model.', 'runtime-virtual-map-missing', qs.structDef.location);
|
|
589
|
+
}
|
|
590
|
+
// Check the binding immediately before SQL substitution, using the
|
|
591
|
+
// executing query's dialect. Other entries in the map may be unused.
|
|
592
|
+
const dialect = this.parent.dialect;
|
|
593
|
+
const checked = dialect.sqlValidateTableName(tablePath);
|
|
594
|
+
if (!checked.ok || checked.canonical !== tablePath) {
|
|
595
|
+
const reason = checked.ok
|
|
596
|
+
? `use the canonical path ${checked.canonical}`
|
|
597
|
+
: checked.error;
|
|
598
|
+
throw new malloy_compile_error_1.MalloyCompileError(`virtualMap entry '${qs.structDef.connection}.${qs.structDef.name}' ` +
|
|
599
|
+
`has table path ${JSON.stringify(tablePath)}, which is not a ` +
|
|
600
|
+
`canonical table path for ${dialect.name}: ${reason}`, 'runtime-virtual-map-invalid', qs.structDef.location);
|
|
546
601
|
}
|
|
547
|
-
// virtualMap entries are application-supplied — assumed already
|
|
548
|
-
// canonical SQL.
|
|
549
602
|
return tablePath;
|
|
550
603
|
}
|
|
551
604
|
case 'composite':
|
|
@@ -676,9 +729,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
676
729
|
return tempInstance.generateExpression();
|
|
677
730
|
});
|
|
678
731
|
}
|
|
679
|
-
if (ji.
|
|
680
|
-
conditions === undefined ||
|
|
681
|
-
!this.parent.dialect.supportsComplexFilteredSources) {
|
|
732
|
+
if (!ji.isSubquery()) {
|
|
682
733
|
// LTNOTE: need a check here to see the children's where: conditions are local
|
|
683
734
|
// to the source and not to any of it's joined children.
|
|
684
735
|
// In Presto, we're going to get a SQL error if in this case
|
|
@@ -700,9 +751,27 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
700
751
|
else {
|
|
701
752
|
let select = `SELECT ${ji.alias}.*`;
|
|
702
753
|
let joins = '';
|
|
754
|
+
// Every join below this one lives inside the subquery, so every one of
|
|
755
|
+
// them has to be projected out of it, not just the direct children.
|
|
756
|
+
// A child which became a subquery of its own has already flattened its
|
|
757
|
+
// whole subtree into columns, so everything under it is read from that
|
|
758
|
+
// child rather than packed a second time.
|
|
759
|
+
const projectJoin = (j, from) => {
|
|
760
|
+
const fieldList = this.joinProjectionFieldList(j);
|
|
761
|
+
if (fieldList.length > 0) {
|
|
762
|
+
select +=
|
|
763
|
+
from === undefined
|
|
764
|
+
? `, ${this.parent.dialect.sqlSelectAliasAsStruct(j.alias, fieldList)} AS ${j.alias}`
|
|
765
|
+
: `, ${from}.${j.alias} AS ${j.alias}`;
|
|
766
|
+
}
|
|
767
|
+
const readFrom = from !== null && from !== void 0 ? from : (j.isSubquery() ? j.alias : undefined);
|
|
768
|
+
for (const below of j.children) {
|
|
769
|
+
projectJoin(below, readFrom);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
703
772
|
for (const childJoin of ji.children) {
|
|
704
773
|
joins += this.generateSQLJoinBlock(stageWriter, childJoin, depth + 1);
|
|
705
|
-
|
|
774
|
+
projectJoin(childJoin, undefined);
|
|
706
775
|
}
|
|
707
776
|
select += `\nFROM ${structSQL} AS ${ji.alias}\n${joins}\nWHERE ${conditions === null || conditions === void 0 ? void 0 : conditions.join(' AND ')}\n`;
|
|
708
777
|
s += `${matrixOperation} JOIN (\n${(0, utils_1.indent)(select)}) AS ${ji.alias}\n ON ${onCondition}\n`;
|
|
@@ -746,6 +815,111 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
746
815
|
}
|
|
747
816
|
return s;
|
|
748
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Which column of which join a reference needs, or undefined when it needs
|
|
820
|
+
* none. Walking `path` from `from`, the answer is the member of the
|
|
821
|
+
* innermost join along it -- a record is kept whole, since a reference into
|
|
822
|
+
* one is a reference to the column holding it.
|
|
823
|
+
*
|
|
824
|
+
* Two kinds of path need no column. One ending at a computed field
|
|
825
|
+
* contributes that field's name, which is no column of the source; the
|
|
826
|
+
* columns the computation reads arrive as references of their own. And a
|
|
827
|
+
* `having:` or `calculate:` names an *output* field, so its path is rooted
|
|
828
|
+
* in the query's output rather than its source and need not resolve here at
|
|
829
|
+
* all. An output name which happens to match a source column resolves as
|
|
830
|
+
* though it were one, which costs nothing: a query's own source is never
|
|
831
|
+
* packed, only the joins beneath a filtered one.
|
|
832
|
+
*/
|
|
833
|
+
packedColumnFor(from, path) {
|
|
834
|
+
let struct = from;
|
|
835
|
+
let alias = struct.getIdentifier();
|
|
836
|
+
let column = undefined;
|
|
837
|
+
for (const name of path) {
|
|
838
|
+
const child = struct.getChildByName(name);
|
|
839
|
+
if (child === undefined)
|
|
840
|
+
return undefined;
|
|
841
|
+
if (!(child instanceof query_node_1.QueryFieldStruct)) {
|
|
842
|
+
column !== null && column !== void 0 ? column : (column = name);
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
struct = child.queryStruct;
|
|
846
|
+
if (struct.getJoinableParent() === struct) {
|
|
847
|
+
// A join: what the path named above it says nothing about what this
|
|
848
|
+
// one has to pack.
|
|
849
|
+
alias = struct.getIdentifier();
|
|
850
|
+
column = undefined;
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
column !== null && column !== void 0 ? column : (column = name);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return column === undefined ? undefined : [alias, column];
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Which columns of each join have to survive being packed into a struct:
|
|
860
|
+
* every column the segment's field usage names, plus each join's primary
|
|
861
|
+
* key, which the compiler reaches for as a distinct key without any
|
|
862
|
+
* expression in the query naming it.
|
|
863
|
+
*/
|
|
864
|
+
packedColumnsByJoin() {
|
|
865
|
+
var _a;
|
|
866
|
+
if (this.packedColumns === undefined) {
|
|
867
|
+
const packed = new Map();
|
|
868
|
+
const needed = (found) => {
|
|
869
|
+
if (found === undefined)
|
|
870
|
+
return;
|
|
871
|
+
const [alias, column] = found;
|
|
872
|
+
const columns = packed.get(alias);
|
|
873
|
+
if (columns) {
|
|
874
|
+
columns.add(column);
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
packed.set(alias, new Set([column]));
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
const usage = (0, malloy_types_1.isRawSegment)(this.firstSegment)
|
|
881
|
+
? []
|
|
882
|
+
: ((_a = this.firstSegment.expandedFieldUsage) !== null && _a !== void 0 ? _a : []);
|
|
883
|
+
for (const { path } of usage) {
|
|
884
|
+
needed(this.packedColumnFor(this.parent, path));
|
|
885
|
+
}
|
|
886
|
+
// What a primary key needs is what it reads: a computed key's name is
|
|
887
|
+
// not a column, and the columns its expression reads are named nowhere
|
|
888
|
+
// else in the query.
|
|
889
|
+
for (const ji of this.rootResult.joins.values()) {
|
|
890
|
+
const primaryKey = ji.queryStruct.primaryKey();
|
|
891
|
+
if (primaryKey === undefined)
|
|
892
|
+
continue;
|
|
893
|
+
const keyDef = primaryKey.fieldDef;
|
|
894
|
+
if ((0, malloy_types_1.hasExpression)(keyDef)) {
|
|
895
|
+
for (const { path } of (0, malloy_types_1.fieldUsageFrom)(keyDef.refSummary)) {
|
|
896
|
+
needed(this.packedColumnFor(ji.queryStruct, path));
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
needed(this.packedColumnFor(ji.queryStruct, [(0, malloy_types_1.activeName)(keyDef)]));
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
this.packedColumns = packed;
|
|
904
|
+
}
|
|
905
|
+
return this.packedColumns;
|
|
906
|
+
}
|
|
907
|
+
// What a join contributes to the SELECT of a filtered join's subquery: every
|
|
908
|
+
// column named outside the subquery, and the generated __distinct_key when
|
|
909
|
+
// the join needed one.
|
|
910
|
+
joinProjectionFieldList(ji) {
|
|
911
|
+
const packed = this.packedColumnsByJoin().get(ji.alias);
|
|
912
|
+
const dialect = ji.queryStruct.dialect;
|
|
913
|
+
const fieldList = (0, utils_1.getDialectFieldList)(ji.queryStruct.structDef, dialect).filter(d => packed === null || packed === void 0 ? void 0 : packed.has(d.rawName));
|
|
914
|
+
if (ji.makeUniqueKey) {
|
|
915
|
+
fieldList.push({
|
|
916
|
+
typeDef: { type: 'string' },
|
|
917
|
+
sqlExpression: dialect.sqlQuoteIdentifier('__distinct_key'),
|
|
918
|
+
rawName: '__distinct_key',
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
return fieldList;
|
|
922
|
+
}
|
|
749
923
|
// BigQuery has wildcard psudo columns that are treated differently
|
|
750
924
|
// SELECT * FROM xxx doesn't include these psuedo columns but we need them so
|
|
751
925
|
// filters can get pushed down properly when generating a UNIQUE key.
|
|
@@ -773,9 +947,10 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
773
947
|
// Joins
|
|
774
948
|
let structSQL = this.getStructSourceSQL(qs, stageWriter);
|
|
775
949
|
if ((0, malloy_types_1.isIndexSegment)(this.firstSegment)) {
|
|
776
|
-
structSQL = this.parent.dialect.sqlSampleTable(structSQL, this.firstSegment.sample);
|
|
950
|
+
structSQL = this.parent.dialect.sqlSampleTable(structSQL, this.firstSegment.sample, qs.structDef.type === 'table');
|
|
777
951
|
if (this.firstSegment.sample) {
|
|
778
|
-
|
|
952
|
+
const d = this.parent.dialect;
|
|
953
|
+
structSQL = stageWriter.addStage(`SELECT * from ${structSQL} as x ${d.sqlOrderByLimit([], 100000)}`);
|
|
779
954
|
}
|
|
780
955
|
}
|
|
781
956
|
if ((0, malloy_types_1.isBaseTable)(qs.structDef)) {
|
|
@@ -819,7 +994,6 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
819
994
|
return result;
|
|
820
995
|
}
|
|
821
996
|
genereateSQLOrderBy(queryDef, resultStruct) {
|
|
822
|
-
let s = '';
|
|
823
997
|
// Collect array joins for dialects that need explicit ordering.
|
|
824
998
|
// Only for project queries - reduce queries aggregate rows so individual
|
|
825
999
|
// row order doesn't matter, and we can't ORDER BY columns not in GROUP BY.
|
|
@@ -833,24 +1007,24 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
833
1007
|
// For project without explicit ordering, we still need array ordinality
|
|
834
1008
|
// ordering if the dialect requires it
|
|
835
1009
|
if (arrayJoins.length === 0) {
|
|
836
|
-
return
|
|
1010
|
+
return []; // No default ordering for project.
|
|
837
1011
|
}
|
|
838
1012
|
}
|
|
839
1013
|
// Intermediate results (in a pipeline or join) that have no limit, don't need an orderby
|
|
840
1014
|
// Some database don't have this optimization.
|
|
841
1015
|
if (this.fieldDef.pipeline.length > 1 && queryDef.limit === undefined) {
|
|
842
|
-
return
|
|
1016
|
+
return [];
|
|
843
1017
|
}
|
|
844
1018
|
// ignore orderby if all aggregates.
|
|
845
1019
|
if (resultStruct.getRepeatedResultType() === 'inline_all_numbers') {
|
|
846
|
-
return
|
|
1020
|
+
return [];
|
|
847
1021
|
}
|
|
848
1022
|
// if we are in the last stage of a query and the query is a subquery
|
|
849
1023
|
// and has no limit, ORDER BY is superfluous
|
|
850
1024
|
if (this.isJoinedSubquery &&
|
|
851
1025
|
this.fieldDef.pipeline.length === 1 &&
|
|
852
1026
|
queryDef.limit === undefined) {
|
|
853
|
-
return
|
|
1027
|
+
return [];
|
|
854
1028
|
}
|
|
855
1029
|
const orderBy = queryDef.orderBy || resultStruct.calculateDefaultOrderBy();
|
|
856
1030
|
const o = [];
|
|
@@ -893,10 +1067,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
893
1067
|
for (const aj of arrayJoins) {
|
|
894
1068
|
o.push(`${aj.alias}_outer.__row_id ASC`);
|
|
895
1069
|
}
|
|
896
|
-
|
|
897
|
-
s = this.parent.dialect.sqlOrderBy(o, 'query') + '\n';
|
|
898
|
-
}
|
|
899
|
-
return s;
|
|
1070
|
+
return o;
|
|
900
1071
|
}
|
|
901
1072
|
/**
|
|
902
1073
|
* The single-scan form, with no `group_set` fan-out: one `SELECT` over the
|
|
@@ -909,45 +1080,36 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
909
1080
|
*/
|
|
910
1081
|
generateSimpleSQL(stageWriter) {
|
|
911
1082
|
this.rootResult.emitsGroupSet = false;
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1083
|
+
const limit = !(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit
|
|
1084
|
+
? this.firstSegment.limit
|
|
1085
|
+
: undefined;
|
|
1086
|
+
let s = 'SELECT \n';
|
|
1087
|
+
// A projection groups nothing; a reduce groups its scalar result fields.
|
|
1088
|
+
const grouped = this.firstSegment.type === 'reduce';
|
|
1089
|
+
const columns = [];
|
|
915
1090
|
const outputPipelinedSQL = [];
|
|
916
1091
|
for (const [name, fi] of this.rootResult.allFields) {
|
|
917
1092
|
const sqlName = this.parent.dialect.sqlQuoteIdentifier(name);
|
|
918
1093
|
if (fi instanceof field_instance_1.FieldInstanceField) {
|
|
919
1094
|
if (fi.fieldUsage.type === 'result') {
|
|
920
|
-
|
|
1095
|
+
columns.push(aliasedColumn(fi.generateExpression(), sqlName, {
|
|
1096
|
+
isDimension: grouped && (0, query_node_1.isScalarField)(fi.f),
|
|
1097
|
+
constant: !readsColumn(fi.f.fieldDef),
|
|
1098
|
+
}));
|
|
921
1099
|
}
|
|
922
1100
|
}
|
|
923
1101
|
else if (fi instanceof field_instance_1.FieldInstanceResult) {
|
|
924
1102
|
const turtle = this.generateTurtleSQL(fi, stageWriter, sqlName, outputPipelinedSQL, true);
|
|
925
|
-
|
|
1103
|
+
columns.push(aliasedColumn(turtle, sqlName, { isDimension: false }));
|
|
926
1104
|
}
|
|
927
1105
|
}
|
|
928
|
-
s += (0, utils_1.indent)(
|
|
1106
|
+
s += (0, utils_1.indent)(columns.map(c => ` ${c.sql}`).join(',\n')) + '\n';
|
|
929
1107
|
s += this.generateSQLJoins(stageWriter);
|
|
930
1108
|
s += this.generateSQLFilters(this.rootResult, 'where').sql('where');
|
|
931
|
-
|
|
932
|
-
if (this.firstSegment.type === 'reduce') {
|
|
933
|
-
const n = [];
|
|
934
|
-
for (const field of this.rootResult.fields()) {
|
|
935
|
-
const fi = field;
|
|
936
|
-
if (fi.fieldUsage.type === 'result' && (0, query_node_1.isScalarField)(fi.f)) {
|
|
937
|
-
n.push(fi.fieldUsage.resultIndex.toString());
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
if (n.length > 0) {
|
|
941
|
-
s += `GROUP BY ${n.join(',')}\n`;
|
|
942
|
-
}
|
|
943
|
-
}
|
|
1109
|
+
s += sqlGroupBy(this.parent.dialect, columns);
|
|
944
1110
|
s += this.generateSQLFilters(this.rootResult, 'having').sql('having');
|
|
945
|
-
// order by
|
|
946
|
-
s += this.genereateSQLOrderBy(this.firstSegment, this.rootResult);
|
|
947
|
-
// limit
|
|
948
|
-
if (!(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit) {
|
|
949
|
-
s += `LIMIT ${this.firstSegment.limit}\n`;
|
|
950
|
-
}
|
|
1111
|
+
// order by, limit
|
|
1112
|
+
s += this.parent.dialect.sqlOrderByLimit(this.genereateSQLOrderBy(this.firstSegment, this.rootResult), limit);
|
|
951
1113
|
this.resultStage = stageWriter.addStage(s);
|
|
952
1114
|
// Consumes any pipelined turtle output. The nests that reach here are
|
|
953
1115
|
// single-stage (canUseSingleGroupSetSQL), so outputPipelinedSQL is empty and
|
|
@@ -1059,6 +1221,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1059
1221
|
});
|
|
1060
1222
|
output.columns.push({
|
|
1061
1223
|
sql: outputFieldName,
|
|
1224
|
+
expr: outputFieldName,
|
|
1062
1225
|
name: outputName,
|
|
1063
1226
|
isDimension: true,
|
|
1064
1227
|
});
|
|
@@ -1067,6 +1230,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1067
1230
|
const outputFieldNameString = `__lateral_join_bag.${outputNameString}`;
|
|
1068
1231
|
output.columns.push({
|
|
1069
1232
|
sql: outputFieldNameString,
|
|
1233
|
+
expr: outputFieldNameString,
|
|
1070
1234
|
name: outputNameString,
|
|
1071
1235
|
isDimension: true,
|
|
1072
1236
|
});
|
|
@@ -1079,19 +1243,14 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1079
1243
|
}
|
|
1080
1244
|
else {
|
|
1081
1245
|
// just treat it like a regular field.
|
|
1082
|
-
output.columns.push({
|
|
1083
|
-
sql: `${exp} as ${outputName}`,
|
|
1084
|
-
name: outputName,
|
|
1246
|
+
output.columns.push(aliasedColumn(exp, outputName, {
|
|
1085
1247
|
isDimension: true,
|
|
1086
|
-
|
|
1248
|
+
constant: !readsColumn(fi.f.fieldDef),
|
|
1249
|
+
}));
|
|
1087
1250
|
}
|
|
1088
1251
|
}
|
|
1089
1252
|
else if ((0, query_node_1.isBasicCalculation)(fi.f)) {
|
|
1090
|
-
output.columns.push({
|
|
1091
|
-
sql: `${exp} as ${outputName}`,
|
|
1092
|
-
name: outputName,
|
|
1093
|
-
isDimension: false,
|
|
1094
|
-
});
|
|
1253
|
+
output.columns.push(aliasedColumn(exp, outputName, { isDimension: false }));
|
|
1095
1254
|
}
|
|
1096
1255
|
}
|
|
1097
1256
|
}
|
|
@@ -1101,11 +1260,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1101
1260
|
}
|
|
1102
1261
|
else if (fi.firstSegment.type === 'project') {
|
|
1103
1262
|
const s = this.generateTurtleSQL(fi, stageWriter, outputName, output.outputPipelinedSQL);
|
|
1104
|
-
output.columns.push({
|
|
1105
|
-
sql: `${s} as ${outputName}`,
|
|
1106
|
-
name: outputName,
|
|
1107
|
-
isDimension: false,
|
|
1108
|
-
});
|
|
1263
|
+
output.columns.push(aliasedColumn(s, outputName, { isDimension: false }));
|
|
1109
1264
|
}
|
|
1110
1265
|
}
|
|
1111
1266
|
}
|
|
@@ -1123,11 +1278,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1123
1278
|
}
|
|
1124
1279
|
else {
|
|
1125
1280
|
resultSet.hasHaving = true;
|
|
1126
|
-
output.columns.push({
|
|
1127
|
-
sql: `CASE WHEN group_set=${resultSet.groupSet} THEN CASE WHEN ${having.sql()} THEN 0 ELSE 1 END END as __delete__${resultSet.groupSet}`,
|
|
1128
|
-
name: `__delete__${resultSet.groupSet}`,
|
|
1129
|
-
isDimension: false,
|
|
1130
|
-
});
|
|
1281
|
+
output.columns.push(aliasedColumn(`CASE WHEN group_set=${resultSet.groupSet} THEN CASE WHEN ${having.sql()} THEN 0 ELSE 1 END END`, `__delete__${resultSet.groupSet}`, { isDimension: false }));
|
|
1131
1282
|
}
|
|
1132
1283
|
}
|
|
1133
1284
|
}
|
|
@@ -1306,7 +1457,14 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1306
1457
|
let from = this.generateSQLJoins(stageWriter);
|
|
1307
1458
|
const wheres = this.generateSQLWhereTurtled();
|
|
1308
1459
|
const f = {
|
|
1309
|
-
columns: [
|
|
1460
|
+
columns: [
|
|
1461
|
+
{
|
|
1462
|
+
sql: 'group_set',
|
|
1463
|
+
expr: 'group_set',
|
|
1464
|
+
name: 'group_set',
|
|
1465
|
+
isDimension: true,
|
|
1466
|
+
},
|
|
1467
|
+
],
|
|
1310
1468
|
lateralJoinSQLExpressions: [],
|
|
1311
1469
|
groupsAggregated: [],
|
|
1312
1470
|
outputPipelinedSQL: [],
|
|
@@ -1316,7 +1474,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1316
1474
|
throw new malloy_compile_error_1.MalloyCompileError("Cannot use 'select:' in a stage that contains nested views. " +
|
|
1317
1475
|
'Use `group_by:` or restructure the pipeline.', 'compiler-project-with-turtles', this.fieldDef.location);
|
|
1318
1476
|
}
|
|
1319
|
-
const groupBy =
|
|
1477
|
+
const groupBy = sqlGroupBy(this.parent.dialect, f.columns);
|
|
1320
1478
|
from += this.parent.dialect.sqlGroupSetTable(this.maxGroupSet) + '\n';
|
|
1321
1479
|
s += (0, utils_1.indent)(f.columns.map(c => c.sql).join(',\n')) + '\n';
|
|
1322
1480
|
if (f.lateralJoinSQLExpressions.length > 0) {
|
|
@@ -1338,19 +1496,11 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1338
1496
|
if (fi.fieldUsage.type === 'result') {
|
|
1339
1497
|
if ((0, query_node_1.isScalarField)(fi.f)) {
|
|
1340
1498
|
const exp = (0, utils_1.caseGroup)(resultSet.groupSet > 0 ? resultSet.childGroups : [], sqlFieldName);
|
|
1341
|
-
output.columns.push({
|
|
1342
|
-
sql: `${exp} as ${sqlFieldName}`,
|
|
1343
|
-
name: sqlFieldName,
|
|
1344
|
-
isDimension: true,
|
|
1345
|
-
});
|
|
1499
|
+
output.columns.push(aliasedColumn(exp, sqlFieldName, { isDimension: true }));
|
|
1346
1500
|
}
|
|
1347
1501
|
else if ((0, query_node_1.isBasicCalculation)(fi.f)) {
|
|
1348
1502
|
const exp = this.parent.dialect.sqlAnyValue(resultSet.groupSet, sqlFieldName);
|
|
1349
|
-
output.columns.push({
|
|
1350
|
-
sql: `${exp} as ${sqlFieldName}`,
|
|
1351
|
-
name: sqlFieldName,
|
|
1352
|
-
isDimension: false,
|
|
1353
|
-
});
|
|
1503
|
+
output.columns.push(aliasedColumn(exp, sqlFieldName, { isDimension: false }));
|
|
1354
1504
|
}
|
|
1355
1505
|
}
|
|
1356
1506
|
}
|
|
@@ -1365,11 +1515,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1365
1515
|
toGroup: resultSet.groupSet,
|
|
1366
1516
|
});
|
|
1367
1517
|
groupsToMap.push(fi.groupSet);
|
|
1368
|
-
output.columns.push({
|
|
1369
|
-
sql: `${s} as ${sqlFieldName}`,
|
|
1370
|
-
name: sqlFieldName,
|
|
1371
|
-
isDimension: false,
|
|
1372
|
-
});
|
|
1518
|
+
output.columns.push(aliasedColumn(s, sqlFieldName, { isDimension: false }));
|
|
1373
1519
|
}
|
|
1374
1520
|
else {
|
|
1375
1521
|
this.generateDepthNFields(depth, fi, output, stageWriter);
|
|
@@ -1386,12 +1532,10 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1386
1532
|
for (const m of output.groupsAggregated) {
|
|
1387
1533
|
groupSetSQL += `WHEN group_set=${m.fromGroup} THEN ${m.toGroup} `;
|
|
1388
1534
|
}
|
|
1389
|
-
groupSetSQL += 'ELSE group_set END
|
|
1390
|
-
output.columns[0] = {
|
|
1391
|
-
sql: groupSetSQL,
|
|
1392
|
-
name: 'group_set',
|
|
1535
|
+
groupSetSQL += 'ELSE group_set END';
|
|
1536
|
+
output.columns[0] = aliasedColumn(groupSetSQL, 'group_set', {
|
|
1393
1537
|
isDimension: true,
|
|
1394
|
-
};
|
|
1538
|
+
});
|
|
1395
1539
|
}
|
|
1396
1540
|
// For hasLateralColumnAliasInSelect, the remap is handled in
|
|
1397
1541
|
// generateSQLDepthN to avoid adding it multiple times from recursion.
|
|
@@ -1400,7 +1544,14 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1400
1544
|
generateSQLDepthN(depth, stageWriter, stageName) {
|
|
1401
1545
|
let s = 'SELECT \n';
|
|
1402
1546
|
const f = {
|
|
1403
|
-
columns: [
|
|
1547
|
+
columns: [
|
|
1548
|
+
{
|
|
1549
|
+
sql: 'group_set',
|
|
1550
|
+
expr: 'group_set',
|
|
1551
|
+
name: 'group_set',
|
|
1552
|
+
isDimension: true,
|
|
1553
|
+
},
|
|
1554
|
+
],
|
|
1404
1555
|
lateralJoinSQLExpressions: [],
|
|
1405
1556
|
groupsAggregated: [],
|
|
1406
1557
|
outputPipelinedSQL: [],
|
|
@@ -1420,8 +1571,13 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1420
1571
|
for (const m of f.groupsAggregated) {
|
|
1421
1572
|
groupSetSQL += `WHEN group_set=${m.fromGroup} THEN ${m.toGroup} `;
|
|
1422
1573
|
}
|
|
1423
|
-
groupSetSQL += 'ELSE group_set END
|
|
1424
|
-
f.columns[0] = {
|
|
1574
|
+
groupSetSQL += 'ELSE group_set END';
|
|
1575
|
+
f.columns[0] = {
|
|
1576
|
+
sql: `${groupSetSQL} as __remapped_group_set`,
|
|
1577
|
+
expr: groupSetSQL,
|
|
1578
|
+
name: 'group_set',
|
|
1579
|
+
isDimension: true,
|
|
1580
|
+
};
|
|
1425
1581
|
}
|
|
1426
1582
|
s += (0, utils_1.indent)(f.columns.map(c => c.sql).join(',\n')) + '\n';
|
|
1427
1583
|
s += `FROM ${stageName}\n`;
|
|
@@ -1429,10 +1585,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1429
1585
|
if (where.length > 0) {
|
|
1430
1586
|
s += `WHERE ${where}\n`;
|
|
1431
1587
|
}
|
|
1432
|
-
|
|
1433
|
-
if (dimensionPositions.length > 0) {
|
|
1434
|
-
s += `GROUP BY ${dimensionPositions.join(',')}\n`;
|
|
1435
|
-
}
|
|
1588
|
+
s += sqlGroupBy(this.parent.dialect, f.columns);
|
|
1436
1589
|
this.resultStage = stageWriter.addStage(s);
|
|
1437
1590
|
if (needsRemapStage) {
|
|
1438
1591
|
const cols = f.columns.slice(1).map(c => c.name);
|
|
@@ -1443,6 +1596,9 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1443
1596
|
return this.resultStage;
|
|
1444
1597
|
}
|
|
1445
1598
|
genereateSQLCombineTurtles(stageWriter, stage0Name) {
|
|
1599
|
+
const limit = !(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit
|
|
1600
|
+
? this.firstSegment.limit
|
|
1601
|
+
: undefined;
|
|
1446
1602
|
let s = 'SELECT\n';
|
|
1447
1603
|
// Columns this combine stage emits. Unlike the grouped stages above, these
|
|
1448
1604
|
// are the final output names (unsuffixed), not the group-set-suffixed form.
|
|
@@ -1454,15 +1610,13 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1454
1610
|
if (fi instanceof field_instance_1.FieldInstanceField) {
|
|
1455
1611
|
if (fi.fieldUsage.type === 'result') {
|
|
1456
1612
|
if ((0, query_node_1.isScalarField)(fi.f)) {
|
|
1457
|
-
columns.push({
|
|
1458
|
-
sql: this.parent.dialect.sqlQuoteIdentifier(`${name}__${this.rootResult.groupSet}`) + ` as ${sqlName}`,
|
|
1459
|
-
name: sqlName,
|
|
1460
|
-
isDimension: true,
|
|
1461
|
-
});
|
|
1613
|
+
columns.push(aliasedColumn(this.parent.dialect.sqlQuoteIdentifier(`${name}__${this.rootResult.groupSet}`), sqlName, { isDimension: true }));
|
|
1462
1614
|
}
|
|
1463
1615
|
else if ((0, query_node_1.isBasicCalculation)(fi.f)) {
|
|
1616
|
+
const lastTurtle = this.parent.dialect.sqlAnyValueLastTurtle(this.parent.dialect.sqlQuoteIdentifier(`${name}__${this.rootResult.groupSet}`), this.rootResult.groupSet, sqlName);
|
|
1464
1617
|
columns.push({
|
|
1465
|
-
sql:
|
|
1618
|
+
sql: lastTurtle,
|
|
1619
|
+
expr: lastTurtle,
|
|
1466
1620
|
name: sqlName,
|
|
1467
1621
|
isDimension: false,
|
|
1468
1622
|
});
|
|
@@ -1471,15 +1625,13 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1471
1625
|
}
|
|
1472
1626
|
else if (fi instanceof field_instance_1.FieldInstanceResult) {
|
|
1473
1627
|
if (fi.firstSegment.type === 'reduce') {
|
|
1474
|
-
columns.push({
|
|
1475
|
-
sql: `${this.generateTurtleSQL(fi, stageWriter, sqlName, outputPipelinedSQL)} as ${sqlName}`,
|
|
1476
|
-
name: sqlName,
|
|
1477
|
-
isDimension: false,
|
|
1478
|
-
});
|
|
1628
|
+
columns.push(aliasedColumn(this.generateTurtleSQL(fi, stageWriter, sqlName, outputPipelinedSQL), sqlName, { isDimension: false }));
|
|
1479
1629
|
}
|
|
1480
1630
|
else if (fi.firstSegment.type === 'project') {
|
|
1631
|
+
const lastTurtle = this.parent.dialect.sqlAnyValueLastTurtle(this.parent.dialect.sqlQuoteIdentifier(`${name}__${this.rootResult.groupSet}`), this.rootResult.groupSet, sqlName);
|
|
1481
1632
|
columns.push({
|
|
1482
|
-
sql:
|
|
1633
|
+
sql: lastTurtle,
|
|
1634
|
+
expr: lastTurtle,
|
|
1483
1635
|
name: sqlName,
|
|
1484
1636
|
isDimension: false,
|
|
1485
1637
|
});
|
|
@@ -1491,27 +1643,18 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1491
1643
|
if (where.length > 0) {
|
|
1492
1644
|
s += `WHERE ${where}\n`;
|
|
1493
1645
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
// order by
|
|
1499
|
-
const orderBySQL = this.genereateSQLOrderBy(this.firstSegment, this.rootResult);
|
|
1500
|
-
s += orderBySQL;
|
|
1501
|
-
// limit
|
|
1502
|
-
if (!(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit) {
|
|
1503
|
-
s += `LIMIT ${this.firstSegment.limit}\n`;
|
|
1504
|
-
}
|
|
1646
|
+
s += sqlGroupBy(this.parent.dialect, columns);
|
|
1647
|
+
// order by, limit
|
|
1648
|
+
const orderTerms = this.genereateSQLOrderBy(this.firstSegment, this.rootResult);
|
|
1649
|
+
s += this.parent.dialect.sqlOrderByLimit(orderTerms, limit);
|
|
1505
1650
|
this.resultStage = stageWriter.addStage(s);
|
|
1506
|
-
this.resultStage = this.generatePipelinedStages(outputPipelinedSQL, this.resultStage, stageWriter, columns,
|
|
1651
|
+
this.resultStage = this.generatePipelinedStages(outputPipelinedSQL, this.resultStage, stageWriter, columns, this.parent.dialect.sqlOrderByLimit(orderTerms, undefined));
|
|
1507
1652
|
return this.resultStage;
|
|
1508
1653
|
}
|
|
1509
1654
|
// create a simplified version of the StructDef for dialects.
|
|
1510
1655
|
buildDialectFieldList(resultStruct) {
|
|
1511
1656
|
const dialectFieldList = [];
|
|
1512
1657
|
for (const [name, field] of resultStruct.allFields) {
|
|
1513
|
-
const sqlName = this.parent.dialect.sqlQuoteIdentifier(name);
|
|
1514
|
-
//
|
|
1515
1658
|
if (resultStruct.firstSegment.type === 'reduce' &&
|
|
1516
1659
|
field instanceof field_instance_1.FieldInstanceResult) {
|
|
1517
1660
|
const { structDef, repeatedResultType } = this.generateTurtlePipelineSQL(field, new stage_writer_1.StageWriter(true, undefined), '<nosource>');
|
|
@@ -1527,7 +1670,6 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1527
1670
|
typeDef: multiLineNest,
|
|
1528
1671
|
sqlExpression: this.parent.dialect.sqlQuoteIdentifier(`${name}__${resultStruct.groupSet}`),
|
|
1529
1672
|
rawName: name,
|
|
1530
|
-
sqlOutputName: sqlName,
|
|
1531
1673
|
});
|
|
1532
1674
|
}
|
|
1533
1675
|
else {
|
|
@@ -1541,7 +1683,6 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1541
1683
|
typeDef: oneLineNest,
|
|
1542
1684
|
sqlExpression: this.parent.dialect.sqlQuoteIdentifier(`${name}__${resultStruct.groupSet}`),
|
|
1543
1685
|
rawName: name,
|
|
1544
|
-
sqlOutputName: sqlName,
|
|
1545
1686
|
});
|
|
1546
1687
|
}
|
|
1547
1688
|
}
|
|
@@ -1552,7 +1693,6 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1552
1693
|
fieldDef: field.f.fieldDef,
|
|
1553
1694
|
sqlExpression: this.parent.dialect.sqlQuoteIdentifier(`${name}__${resultStruct.groupSet}`),
|
|
1554
1695
|
rawName: name,
|
|
1555
|
-
sqlOutputName: sqlName,
|
|
1556
1696
|
});
|
|
1557
1697
|
}
|
|
1558
1698
|
else if (resultStruct.firstSegment.type === 'project' &&
|
|
@@ -1562,7 +1702,6 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1562
1702
|
fieldDef: field.f.fieldDef,
|
|
1563
1703
|
sqlExpression: field.generateExpression(),
|
|
1564
1704
|
rawName: name,
|
|
1565
|
-
sqlOutputName: sqlName,
|
|
1566
1705
|
});
|
|
1567
1706
|
}
|
|
1568
1707
|
}
|
|
@@ -1684,7 +1823,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1684
1823
|
const inputStruct = {
|
|
1685
1824
|
type: 'nest_source',
|
|
1686
1825
|
name: '~pipe~',
|
|
1687
|
-
pipeSQL: this.parent.dialect.sqlUnnestPipelineHead(repeatedResultType === 'inline_all_numbers', sourceSQLExpression, (0, utils_1.getDialectFieldList)(structDef)),
|
|
1826
|
+
pipeSQL: this.parent.dialect.sqlUnnestPipelineHead(repeatedResultType === 'inline_all_numbers', sourceSQLExpression, (0, utils_1.getDialectFieldList)(structDef, this.parent.dialect)),
|
|
1688
1827
|
fields: structDef.fields,
|
|
1689
1828
|
connection: structDef.connection,
|
|
1690
1829
|
dialect: structDef.dialect,
|
|
@@ -1822,30 +1961,37 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
1822
1961
|
fields.push({ name, path, type: fi.f.fieldDef.type, expression });
|
|
1823
1962
|
}
|
|
1824
1963
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
s += ` END as ${fieldNameColumn},\n`;
|
|
1831
|
-
s += ' CASE group_set\n';
|
|
1832
|
-
for (let i = 0; i < fields.length; i++) {
|
|
1833
|
-
const path = pathToCol(fields[i].path);
|
|
1834
|
-
s += ` WHEN ${i} THEN '${path}'\n`;
|
|
1835
|
-
}
|
|
1836
|
-
s += ` END as ${fieldPathColumn},\n`;
|
|
1837
|
-
s += ' CASE group_set\n';
|
|
1838
|
-
for (let i = 0; i < fields.length; i++) {
|
|
1839
|
-
s += ` WHEN ${i} THEN '${fields[i].type}'\n`;
|
|
1840
|
-
}
|
|
1841
|
-
s += ` END as ${fieldTypeColumn},`;
|
|
1842
|
-
s += ` CASE group_set WHEN 99999 THEN ${dialect.castToString('NULL')}\n`;
|
|
1843
|
-
for (let i = 0; i < fields.length; i++) {
|
|
1844
|
-
if (fields[i].type === 'string') {
|
|
1845
|
-
s += ` WHEN ${i} THEN ${fields[i].expression}\n`;
|
|
1964
|
+
// The grouped columns: group_set and one CASE per index column.
|
|
1965
|
+
const caseOnGroupSet = (when, firstWhen) => {
|
|
1966
|
+
let c = 'CASE group_set\n';
|
|
1967
|
+
if (firstWhen !== undefined) {
|
|
1968
|
+
c += ` ${firstWhen}\n`;
|
|
1846
1969
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1970
|
+
for (let i = 0; i < fields.length; i++) {
|
|
1971
|
+
const value = when(i);
|
|
1972
|
+
if (value !== undefined) {
|
|
1973
|
+
c += ` WHEN ${i} THEN ${value}\n`;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
return c + ' END';
|
|
1977
|
+
};
|
|
1978
|
+
const grouped = [
|
|
1979
|
+
{
|
|
1980
|
+
sql: 'group_set',
|
|
1981
|
+
expr: 'group_set',
|
|
1982
|
+
name: 'group_set',
|
|
1983
|
+
isDimension: true,
|
|
1984
|
+
},
|
|
1985
|
+
aliasedColumn(caseOnGroupSet(i => `'${fields[i].name}'`), fieldNameColumn, { isDimension: true }),
|
|
1986
|
+
aliasedColumn(caseOnGroupSet(i => `'${pathToCol(fields[i].path)}'`), fieldPathColumn, { isDimension: true }),
|
|
1987
|
+
aliasedColumn(caseOnGroupSet(i => `'${fields[i].type}'`), fieldTypeColumn, { isDimension: true }),
|
|
1988
|
+
aliasedColumn(caseOnGroupSet(i => (fields[i].type === 'string' ? fields[i].expression : undefined), `WHEN 99999 THEN ${dialect.castToString('NULL')}`), fieldValueColumn, { isDimension: true }),
|
|
1989
|
+
];
|
|
1990
|
+
const limit = !(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit
|
|
1991
|
+
? this.firstSegment.limit
|
|
1992
|
+
: undefined;
|
|
1993
|
+
let s = 'SELECT\n';
|
|
1994
|
+
s += grouped.map(c => ` ${c.sql},\n`).join('');
|
|
1849
1995
|
s += ` ${measureSQL} as ${weightColumn},\n`;
|
|
1850
1996
|
// just in case we don't have any field types, force the case statement to have at least one value.
|
|
1851
1997
|
s += " CASE group_set\n WHEN 99999 THEN ''";
|
|
@@ -1868,11 +2014,8 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
1868
2014
|
s += this.generateSQLJoins(stageWriter);
|
|
1869
2015
|
s += dialect.sqlGroupSetTable(fields.length) + '\n';
|
|
1870
2016
|
s += this.generateSQLFilters(this.rootResult, 'where').sql('where');
|
|
1871
|
-
s +=
|
|
1872
|
-
|
|
1873
|
-
if (!(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit) {
|
|
1874
|
-
s += `LIMIT ${this.firstSegment.limit}\n`;
|
|
1875
|
-
}
|
|
2017
|
+
s += sqlGroupBy(dialect, grouped);
|
|
2018
|
+
s += dialect.sqlOrderByLimit([], limit);
|
|
1876
2019
|
// console.log(s);
|
|
1877
2020
|
const resultStage = stageWriter.addStage(s);
|
|
1878
2021
|
this.resultStage = stageWriter.addStage(`SELECT
|