@malloydata/malloy 0.0.434 → 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 +0 -1
- package/dist/api/foundation/compile.js +0 -3
- package/dist/api/foundation/runtime.d.ts +0 -1
- package/dist/api/foundation/runtime.js +1 -23
- package/dist/api/foundation/types.d.ts +0 -1
- package/dist/dialect/dialect.d.ts +14 -1
- package/dist/dialect/dialect.js +34 -2
- package/dist/dialect/dialect_map.js +2 -0
- package/dist/dialect/index.d.ts +2 -1
- package/dist/dialect/index.js +3 -1
- 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/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/sql-source.js +0 -3
- package/dist/lang/ast/source-elements/table-source.js +1 -2
- 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/parse-malloy.d.ts +17 -2
- package/dist/lang/parse-malloy.js +48 -14
- 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 +10 -1
- package/dist/lang/test/test-translator.js +15 -1
- package/dist/model/expression_compiler.js +1 -1
- package/dist/model/query_query.d.ts +3 -1
- package/dist/model/query_query.js +157 -139
- package/dist/model/source_def_utils.d.ts +7 -1
- package/dist/model/source_def_utils.js +20 -0
- 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 +13 -1
- package/package.json +7 -5
|
@@ -34,9 +34,52 @@ 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
85
|
const { sqlExpression, rawName } = f;
|
|
@@ -904,9 +947,10 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
904
947
|
// Joins
|
|
905
948
|
let structSQL = this.getStructSourceSQL(qs, stageWriter);
|
|
906
949
|
if ((0, malloy_types_1.isIndexSegment)(this.firstSegment)) {
|
|
907
|
-
structSQL = this.parent.dialect.sqlSampleTable(structSQL, this.firstSegment.sample);
|
|
950
|
+
structSQL = this.parent.dialect.sqlSampleTable(structSQL, this.firstSegment.sample, qs.structDef.type === 'table');
|
|
908
951
|
if (this.firstSegment.sample) {
|
|
909
|
-
|
|
952
|
+
const d = this.parent.dialect;
|
|
953
|
+
structSQL = stageWriter.addStage(`SELECT * from ${structSQL} as x ${d.sqlOrderByLimit([], 100000)}`);
|
|
910
954
|
}
|
|
911
955
|
}
|
|
912
956
|
if ((0, malloy_types_1.isBaseTable)(qs.structDef)) {
|
|
@@ -950,7 +994,6 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
950
994
|
return result;
|
|
951
995
|
}
|
|
952
996
|
genereateSQLOrderBy(queryDef, resultStruct) {
|
|
953
|
-
let s = '';
|
|
954
997
|
// Collect array joins for dialects that need explicit ordering.
|
|
955
998
|
// Only for project queries - reduce queries aggregate rows so individual
|
|
956
999
|
// row order doesn't matter, and we can't ORDER BY columns not in GROUP BY.
|
|
@@ -964,24 +1007,24 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
964
1007
|
// For project without explicit ordering, we still need array ordinality
|
|
965
1008
|
// ordering if the dialect requires it
|
|
966
1009
|
if (arrayJoins.length === 0) {
|
|
967
|
-
return
|
|
1010
|
+
return []; // No default ordering for project.
|
|
968
1011
|
}
|
|
969
1012
|
}
|
|
970
1013
|
// Intermediate results (in a pipeline or join) that have no limit, don't need an orderby
|
|
971
1014
|
// Some database don't have this optimization.
|
|
972
1015
|
if (this.fieldDef.pipeline.length > 1 && queryDef.limit === undefined) {
|
|
973
|
-
return
|
|
1016
|
+
return [];
|
|
974
1017
|
}
|
|
975
1018
|
// ignore orderby if all aggregates.
|
|
976
1019
|
if (resultStruct.getRepeatedResultType() === 'inline_all_numbers') {
|
|
977
|
-
return
|
|
1020
|
+
return [];
|
|
978
1021
|
}
|
|
979
1022
|
// if we are in the last stage of a query and the query is a subquery
|
|
980
1023
|
// and has no limit, ORDER BY is superfluous
|
|
981
1024
|
if (this.isJoinedSubquery &&
|
|
982
1025
|
this.fieldDef.pipeline.length === 1 &&
|
|
983
1026
|
queryDef.limit === undefined) {
|
|
984
|
-
return
|
|
1027
|
+
return [];
|
|
985
1028
|
}
|
|
986
1029
|
const orderBy = queryDef.orderBy || resultStruct.calculateDefaultOrderBy();
|
|
987
1030
|
const o = [];
|
|
@@ -1024,10 +1067,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1024
1067
|
for (const aj of arrayJoins) {
|
|
1025
1068
|
o.push(`${aj.alias}_outer.__row_id ASC`);
|
|
1026
1069
|
}
|
|
1027
|
-
|
|
1028
|
-
s = this.parent.dialect.sqlOrderBy(o, 'query') + '\n';
|
|
1029
|
-
}
|
|
1030
|
-
return s;
|
|
1070
|
+
return o;
|
|
1031
1071
|
}
|
|
1032
1072
|
/**
|
|
1033
1073
|
* The single-scan form, with no `group_set` fan-out: one `SELECT` over the
|
|
@@ -1040,45 +1080,36 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1040
1080
|
*/
|
|
1041
1081
|
generateSimpleSQL(stageWriter) {
|
|
1042
1082
|
this.rootResult.emitsGroupSet = false;
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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 = [];
|
|
1046
1090
|
const outputPipelinedSQL = [];
|
|
1047
1091
|
for (const [name, fi] of this.rootResult.allFields) {
|
|
1048
1092
|
const sqlName = this.parent.dialect.sqlQuoteIdentifier(name);
|
|
1049
1093
|
if (fi instanceof field_instance_1.FieldInstanceField) {
|
|
1050
1094
|
if (fi.fieldUsage.type === 'result') {
|
|
1051
|
-
|
|
1095
|
+
columns.push(aliasedColumn(fi.generateExpression(), sqlName, {
|
|
1096
|
+
isDimension: grouped && (0, query_node_1.isScalarField)(fi.f),
|
|
1097
|
+
constant: !readsColumn(fi.f.fieldDef),
|
|
1098
|
+
}));
|
|
1052
1099
|
}
|
|
1053
1100
|
}
|
|
1054
1101
|
else if (fi instanceof field_instance_1.FieldInstanceResult) {
|
|
1055
1102
|
const turtle = this.generateTurtleSQL(fi, stageWriter, sqlName, outputPipelinedSQL, true);
|
|
1056
|
-
|
|
1103
|
+
columns.push(aliasedColumn(turtle, sqlName, { isDimension: false }));
|
|
1057
1104
|
}
|
|
1058
1105
|
}
|
|
1059
|
-
s += (0, utils_1.indent)(
|
|
1106
|
+
s += (0, utils_1.indent)(columns.map(c => ` ${c.sql}`).join(',\n')) + '\n';
|
|
1060
1107
|
s += this.generateSQLJoins(stageWriter);
|
|
1061
1108
|
s += this.generateSQLFilters(this.rootResult, 'where').sql('where');
|
|
1062
|
-
|
|
1063
|
-
if (this.firstSegment.type === 'reduce') {
|
|
1064
|
-
const n = [];
|
|
1065
|
-
for (const field of this.rootResult.fields()) {
|
|
1066
|
-
const fi = field;
|
|
1067
|
-
if (fi.fieldUsage.type === 'result' && (0, query_node_1.isScalarField)(fi.f)) {
|
|
1068
|
-
n.push(fi.fieldUsage.resultIndex.toString());
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
if (n.length > 0) {
|
|
1072
|
-
s += `GROUP BY ${n.join(',')}\n`;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1109
|
+
s += sqlGroupBy(this.parent.dialect, columns);
|
|
1075
1110
|
s += this.generateSQLFilters(this.rootResult, 'having').sql('having');
|
|
1076
|
-
// order by
|
|
1077
|
-
s += this.genereateSQLOrderBy(this.firstSegment, this.rootResult);
|
|
1078
|
-
// limit
|
|
1079
|
-
if (!(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit) {
|
|
1080
|
-
s += `LIMIT ${this.firstSegment.limit}\n`;
|
|
1081
|
-
}
|
|
1111
|
+
// order by, limit
|
|
1112
|
+
s += this.parent.dialect.sqlOrderByLimit(this.genereateSQLOrderBy(this.firstSegment, this.rootResult), limit);
|
|
1082
1113
|
this.resultStage = stageWriter.addStage(s);
|
|
1083
1114
|
// Consumes any pipelined turtle output. The nests that reach here are
|
|
1084
1115
|
// single-stage (canUseSingleGroupSetSQL), so outputPipelinedSQL is empty and
|
|
@@ -1190,6 +1221,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1190
1221
|
});
|
|
1191
1222
|
output.columns.push({
|
|
1192
1223
|
sql: outputFieldName,
|
|
1224
|
+
expr: outputFieldName,
|
|
1193
1225
|
name: outputName,
|
|
1194
1226
|
isDimension: true,
|
|
1195
1227
|
});
|
|
@@ -1198,6 +1230,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1198
1230
|
const outputFieldNameString = `__lateral_join_bag.${outputNameString}`;
|
|
1199
1231
|
output.columns.push({
|
|
1200
1232
|
sql: outputFieldNameString,
|
|
1233
|
+
expr: outputFieldNameString,
|
|
1201
1234
|
name: outputNameString,
|
|
1202
1235
|
isDimension: true,
|
|
1203
1236
|
});
|
|
@@ -1210,19 +1243,14 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1210
1243
|
}
|
|
1211
1244
|
else {
|
|
1212
1245
|
// just treat it like a regular field.
|
|
1213
|
-
output.columns.push({
|
|
1214
|
-
sql: `${exp} as ${outputName}`,
|
|
1215
|
-
name: outputName,
|
|
1246
|
+
output.columns.push(aliasedColumn(exp, outputName, {
|
|
1216
1247
|
isDimension: true,
|
|
1217
|
-
|
|
1248
|
+
constant: !readsColumn(fi.f.fieldDef),
|
|
1249
|
+
}));
|
|
1218
1250
|
}
|
|
1219
1251
|
}
|
|
1220
1252
|
else if ((0, query_node_1.isBasicCalculation)(fi.f)) {
|
|
1221
|
-
output.columns.push({
|
|
1222
|
-
sql: `${exp} as ${outputName}`,
|
|
1223
|
-
name: outputName,
|
|
1224
|
-
isDimension: false,
|
|
1225
|
-
});
|
|
1253
|
+
output.columns.push(aliasedColumn(exp, outputName, { isDimension: false }));
|
|
1226
1254
|
}
|
|
1227
1255
|
}
|
|
1228
1256
|
}
|
|
@@ -1232,11 +1260,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1232
1260
|
}
|
|
1233
1261
|
else if (fi.firstSegment.type === 'project') {
|
|
1234
1262
|
const s = this.generateTurtleSQL(fi, stageWriter, outputName, output.outputPipelinedSQL);
|
|
1235
|
-
output.columns.push({
|
|
1236
|
-
sql: `${s} as ${outputName}`,
|
|
1237
|
-
name: outputName,
|
|
1238
|
-
isDimension: false,
|
|
1239
|
-
});
|
|
1263
|
+
output.columns.push(aliasedColumn(s, outputName, { isDimension: false }));
|
|
1240
1264
|
}
|
|
1241
1265
|
}
|
|
1242
1266
|
}
|
|
@@ -1254,11 +1278,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1254
1278
|
}
|
|
1255
1279
|
else {
|
|
1256
1280
|
resultSet.hasHaving = true;
|
|
1257
|
-
output.columns.push({
|
|
1258
|
-
sql: `CASE WHEN group_set=${resultSet.groupSet} THEN CASE WHEN ${having.sql()} THEN 0 ELSE 1 END END as __delete__${resultSet.groupSet}`,
|
|
1259
|
-
name: `__delete__${resultSet.groupSet}`,
|
|
1260
|
-
isDimension: false,
|
|
1261
|
-
});
|
|
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 }));
|
|
1262
1282
|
}
|
|
1263
1283
|
}
|
|
1264
1284
|
}
|
|
@@ -1437,7 +1457,14 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1437
1457
|
let from = this.generateSQLJoins(stageWriter);
|
|
1438
1458
|
const wheres = this.generateSQLWhereTurtled();
|
|
1439
1459
|
const f = {
|
|
1440
|
-
columns: [
|
|
1460
|
+
columns: [
|
|
1461
|
+
{
|
|
1462
|
+
sql: 'group_set',
|
|
1463
|
+
expr: 'group_set',
|
|
1464
|
+
name: 'group_set',
|
|
1465
|
+
isDimension: true,
|
|
1466
|
+
},
|
|
1467
|
+
],
|
|
1441
1468
|
lateralJoinSQLExpressions: [],
|
|
1442
1469
|
groupsAggregated: [],
|
|
1443
1470
|
outputPipelinedSQL: [],
|
|
@@ -1447,7 +1474,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1447
1474
|
throw new malloy_compile_error_1.MalloyCompileError("Cannot use 'select:' in a stage that contains nested views. " +
|
|
1448
1475
|
'Use `group_by:` or restructure the pipeline.', 'compiler-project-with-turtles', this.fieldDef.location);
|
|
1449
1476
|
}
|
|
1450
|
-
const groupBy =
|
|
1477
|
+
const groupBy = sqlGroupBy(this.parent.dialect, f.columns);
|
|
1451
1478
|
from += this.parent.dialect.sqlGroupSetTable(this.maxGroupSet) + '\n';
|
|
1452
1479
|
s += (0, utils_1.indent)(f.columns.map(c => c.sql).join(',\n')) + '\n';
|
|
1453
1480
|
if (f.lateralJoinSQLExpressions.length > 0) {
|
|
@@ -1469,19 +1496,11 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1469
1496
|
if (fi.fieldUsage.type === 'result') {
|
|
1470
1497
|
if ((0, query_node_1.isScalarField)(fi.f)) {
|
|
1471
1498
|
const exp = (0, utils_1.caseGroup)(resultSet.groupSet > 0 ? resultSet.childGroups : [], sqlFieldName);
|
|
1472
|
-
output.columns.push({
|
|
1473
|
-
sql: `${exp} as ${sqlFieldName}`,
|
|
1474
|
-
name: sqlFieldName,
|
|
1475
|
-
isDimension: true,
|
|
1476
|
-
});
|
|
1499
|
+
output.columns.push(aliasedColumn(exp, sqlFieldName, { isDimension: true }));
|
|
1477
1500
|
}
|
|
1478
1501
|
else if ((0, query_node_1.isBasicCalculation)(fi.f)) {
|
|
1479
1502
|
const exp = this.parent.dialect.sqlAnyValue(resultSet.groupSet, sqlFieldName);
|
|
1480
|
-
output.columns.push({
|
|
1481
|
-
sql: `${exp} as ${sqlFieldName}`,
|
|
1482
|
-
name: sqlFieldName,
|
|
1483
|
-
isDimension: false,
|
|
1484
|
-
});
|
|
1503
|
+
output.columns.push(aliasedColumn(exp, sqlFieldName, { isDimension: false }));
|
|
1485
1504
|
}
|
|
1486
1505
|
}
|
|
1487
1506
|
}
|
|
@@ -1496,11 +1515,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1496
1515
|
toGroup: resultSet.groupSet,
|
|
1497
1516
|
});
|
|
1498
1517
|
groupsToMap.push(fi.groupSet);
|
|
1499
|
-
output.columns.push({
|
|
1500
|
-
sql: `${s} as ${sqlFieldName}`,
|
|
1501
|
-
name: sqlFieldName,
|
|
1502
|
-
isDimension: false,
|
|
1503
|
-
});
|
|
1518
|
+
output.columns.push(aliasedColumn(s, sqlFieldName, { isDimension: false }));
|
|
1504
1519
|
}
|
|
1505
1520
|
else {
|
|
1506
1521
|
this.generateDepthNFields(depth, fi, output, stageWriter);
|
|
@@ -1517,12 +1532,10 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1517
1532
|
for (const m of output.groupsAggregated) {
|
|
1518
1533
|
groupSetSQL += `WHEN group_set=${m.fromGroup} THEN ${m.toGroup} `;
|
|
1519
1534
|
}
|
|
1520
|
-
groupSetSQL += 'ELSE group_set END
|
|
1521
|
-
output.columns[0] = {
|
|
1522
|
-
sql: groupSetSQL,
|
|
1523
|
-
name: 'group_set',
|
|
1535
|
+
groupSetSQL += 'ELSE group_set END';
|
|
1536
|
+
output.columns[0] = aliasedColumn(groupSetSQL, 'group_set', {
|
|
1524
1537
|
isDimension: true,
|
|
1525
|
-
};
|
|
1538
|
+
});
|
|
1526
1539
|
}
|
|
1527
1540
|
// For hasLateralColumnAliasInSelect, the remap is handled in
|
|
1528
1541
|
// generateSQLDepthN to avoid adding it multiple times from recursion.
|
|
@@ -1531,7 +1544,14 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1531
1544
|
generateSQLDepthN(depth, stageWriter, stageName) {
|
|
1532
1545
|
let s = 'SELECT \n';
|
|
1533
1546
|
const f = {
|
|
1534
|
-
columns: [
|
|
1547
|
+
columns: [
|
|
1548
|
+
{
|
|
1549
|
+
sql: 'group_set',
|
|
1550
|
+
expr: 'group_set',
|
|
1551
|
+
name: 'group_set',
|
|
1552
|
+
isDimension: true,
|
|
1553
|
+
},
|
|
1554
|
+
],
|
|
1535
1555
|
lateralJoinSQLExpressions: [],
|
|
1536
1556
|
groupsAggregated: [],
|
|
1537
1557
|
outputPipelinedSQL: [],
|
|
@@ -1551,8 +1571,13 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1551
1571
|
for (const m of f.groupsAggregated) {
|
|
1552
1572
|
groupSetSQL += `WHEN group_set=${m.fromGroup} THEN ${m.toGroup} `;
|
|
1553
1573
|
}
|
|
1554
|
-
groupSetSQL += 'ELSE group_set END
|
|
1555
|
-
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
|
+
};
|
|
1556
1581
|
}
|
|
1557
1582
|
s += (0, utils_1.indent)(f.columns.map(c => c.sql).join(',\n')) + '\n';
|
|
1558
1583
|
s += `FROM ${stageName}\n`;
|
|
@@ -1560,10 +1585,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1560
1585
|
if (where.length > 0) {
|
|
1561
1586
|
s += `WHERE ${where}\n`;
|
|
1562
1587
|
}
|
|
1563
|
-
|
|
1564
|
-
if (dimensionPositions.length > 0) {
|
|
1565
|
-
s += `GROUP BY ${dimensionPositions.join(',')}\n`;
|
|
1566
|
-
}
|
|
1588
|
+
s += sqlGroupBy(this.parent.dialect, f.columns);
|
|
1567
1589
|
this.resultStage = stageWriter.addStage(s);
|
|
1568
1590
|
if (needsRemapStage) {
|
|
1569
1591
|
const cols = f.columns.slice(1).map(c => c.name);
|
|
@@ -1574,6 +1596,9 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1574
1596
|
return this.resultStage;
|
|
1575
1597
|
}
|
|
1576
1598
|
genereateSQLCombineTurtles(stageWriter, stage0Name) {
|
|
1599
|
+
const limit = !(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit
|
|
1600
|
+
? this.firstSegment.limit
|
|
1601
|
+
: undefined;
|
|
1577
1602
|
let s = 'SELECT\n';
|
|
1578
1603
|
// Columns this combine stage emits. Unlike the grouped stages above, these
|
|
1579
1604
|
// are the final output names (unsuffixed), not the group-set-suffixed form.
|
|
@@ -1585,15 +1610,13 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1585
1610
|
if (fi instanceof field_instance_1.FieldInstanceField) {
|
|
1586
1611
|
if (fi.fieldUsage.type === 'result') {
|
|
1587
1612
|
if ((0, query_node_1.isScalarField)(fi.f)) {
|
|
1588
|
-
columns.push({
|
|
1589
|
-
sql: this.parent.dialect.sqlQuoteIdentifier(`${name}__${this.rootResult.groupSet}`) + ` as ${sqlName}`,
|
|
1590
|
-
name: sqlName,
|
|
1591
|
-
isDimension: true,
|
|
1592
|
-
});
|
|
1613
|
+
columns.push(aliasedColumn(this.parent.dialect.sqlQuoteIdentifier(`${name}__${this.rootResult.groupSet}`), sqlName, { isDimension: true }));
|
|
1593
1614
|
}
|
|
1594
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);
|
|
1595
1617
|
columns.push({
|
|
1596
|
-
sql:
|
|
1618
|
+
sql: lastTurtle,
|
|
1619
|
+
expr: lastTurtle,
|
|
1597
1620
|
name: sqlName,
|
|
1598
1621
|
isDimension: false,
|
|
1599
1622
|
});
|
|
@@ -1602,15 +1625,13 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1602
1625
|
}
|
|
1603
1626
|
else if (fi instanceof field_instance_1.FieldInstanceResult) {
|
|
1604
1627
|
if (fi.firstSegment.type === 'reduce') {
|
|
1605
|
-
columns.push({
|
|
1606
|
-
sql: `${this.generateTurtleSQL(fi, stageWriter, sqlName, outputPipelinedSQL)} as ${sqlName}`,
|
|
1607
|
-
name: sqlName,
|
|
1608
|
-
isDimension: false,
|
|
1609
|
-
});
|
|
1628
|
+
columns.push(aliasedColumn(this.generateTurtleSQL(fi, stageWriter, sqlName, outputPipelinedSQL), sqlName, { isDimension: false }));
|
|
1610
1629
|
}
|
|
1611
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);
|
|
1612
1632
|
columns.push({
|
|
1613
|
-
sql:
|
|
1633
|
+
sql: lastTurtle,
|
|
1634
|
+
expr: lastTurtle,
|
|
1614
1635
|
name: sqlName,
|
|
1615
1636
|
isDimension: false,
|
|
1616
1637
|
});
|
|
@@ -1622,19 +1643,12 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1622
1643
|
if (where.length > 0) {
|
|
1623
1644
|
s += `WHERE ${where}\n`;
|
|
1624
1645
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
// order by
|
|
1630
|
-
const orderBySQL = this.genereateSQLOrderBy(this.firstSegment, this.rootResult);
|
|
1631
|
-
s += orderBySQL;
|
|
1632
|
-
// limit
|
|
1633
|
-
if (!(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit) {
|
|
1634
|
-
s += `LIMIT ${this.firstSegment.limit}\n`;
|
|
1635
|
-
}
|
|
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);
|
|
1636
1650
|
this.resultStage = stageWriter.addStage(s);
|
|
1637
|
-
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));
|
|
1638
1652
|
return this.resultStage;
|
|
1639
1653
|
}
|
|
1640
1654
|
// create a simplified version of the StructDef for dialects.
|
|
@@ -1947,30 +1961,37 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
1947
1961
|
fields.push({ name, path, type: fi.f.fieldDef.type, expression });
|
|
1948
1962
|
}
|
|
1949
1963
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
s += ` END as ${fieldNameColumn},\n`;
|
|
1956
|
-
s += ' CASE group_set\n';
|
|
1957
|
-
for (let i = 0; i < fields.length; i++) {
|
|
1958
|
-
const path = pathToCol(fields[i].path);
|
|
1959
|
-
s += ` WHEN ${i} THEN '${path}'\n`;
|
|
1960
|
-
}
|
|
1961
|
-
s += ` END as ${fieldPathColumn},\n`;
|
|
1962
|
-
s += ' CASE group_set\n';
|
|
1963
|
-
for (let i = 0; i < fields.length; i++) {
|
|
1964
|
-
s += ` WHEN ${i} THEN '${fields[i].type}'\n`;
|
|
1965
|
-
}
|
|
1966
|
-
s += ` END as ${fieldTypeColumn},`;
|
|
1967
|
-
s += ` CASE group_set WHEN 99999 THEN ${dialect.castToString('NULL')}\n`;
|
|
1968
|
-
for (let i = 0; i < fields.length; i++) {
|
|
1969
|
-
if (fields[i].type === 'string') {
|
|
1970
|
-
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`;
|
|
1971
1969
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
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('');
|
|
1974
1995
|
s += ` ${measureSQL} as ${weightColumn},\n`;
|
|
1975
1996
|
// just in case we don't have any field types, force the case statement to have at least one value.
|
|
1976
1997
|
s += " CASE group_set\n WHEN 99999 THEN ''";
|
|
@@ -1993,11 +2014,8 @@ class QueryQueryIndexStage extends QueryQuery {
|
|
|
1993
2014
|
s += this.generateSQLJoins(stageWriter);
|
|
1994
2015
|
s += dialect.sqlGroupSetTable(fields.length) + '\n';
|
|
1995
2016
|
s += this.generateSQLFilters(this.rootResult, 'where').sql('where');
|
|
1996
|
-
s +=
|
|
1997
|
-
|
|
1998
|
-
if (!(0, malloy_types_1.isRawSegment)(this.firstSegment) && this.firstSegment.limit) {
|
|
1999
|
-
s += `LIMIT ${this.firstSegment.limit}\n`;
|
|
2000
|
-
}
|
|
2017
|
+
s += sqlGroupBy(dialect, grouped);
|
|
2018
|
+
s += dialect.sqlOrderByLimit([], limit);
|
|
2001
2019
|
// console.log(s);
|
|
2002
2020
|
const resultStage = stageWriter.addStage(s);
|
|
2003
2021
|
this.resultStage = stageWriter.addStage(`SELECT
|
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
* fields they need, never using spread, so identity is not propagated onto a
|
|
8
8
|
* freshly built source.
|
|
9
9
|
*/
|
|
10
|
-
import type { BuildID, FieldDef, GivenID, ModelDef, PersistableSourceDef, Query, QuerySourceDef, SourceDef, SourceID, SourceRegistryEntry, SourceRegistryValue, SQLPhraseSegment, SQLSourceDef, TableSourceDef } from './malloy_types';
|
|
10
|
+
import type { BuildID, FieldDef, GivenID, ModelDef, NamedModelObject, PersistableSourceDef, Query, QuerySourceDef, SourceDef, SourceID, SourceRegistryEntry, SourceRegistryValue, SQLPhraseSegment, SQLSourceDef, TableSourceDef } from './malloy_types';
|
|
11
|
+
/**
|
|
12
|
+
* The dialect an entry commits whoever imports it to, if any. A query begins
|
|
13
|
+
* at a source, which is either inline in the query or named in the model the
|
|
14
|
+
* query came from.
|
|
15
|
+
*/
|
|
16
|
+
export declare function dialectOfEntry(entry: NamedModelObject, from: ModelDef): string | undefined;
|
|
11
17
|
export declare function mkSourceID(name: string, url: string | undefined): SourceID;
|
|
12
18
|
export declare function mkGivenID(name: string, url: string | undefined): GivenID;
|
|
13
19
|
/**
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.dialectOfEntry = dialectOfEntry;
|
|
7
8
|
exports.mkSourceID = mkSourceID;
|
|
8
9
|
exports.mkGivenID = mkGivenID;
|
|
9
10
|
exports.mkBuildID = mkBuildID;
|
|
@@ -17,6 +18,25 @@ exports.registerSource = registerSource;
|
|
|
17
18
|
exports.hasSourceRegistryEntry = hasSourceRegistryEntry;
|
|
18
19
|
const utils_1 = require("./utils");
|
|
19
20
|
const malloy_types_1 = require("./malloy_types");
|
|
21
|
+
/**
|
|
22
|
+
* The dialect an entry commits whoever imports it to, if any. A query begins
|
|
23
|
+
* at a source, which is either inline in the query or named in the model the
|
|
24
|
+
* query came from.
|
|
25
|
+
*/
|
|
26
|
+
function dialectOfEntry(entry, from) {
|
|
27
|
+
if ((0, malloy_types_1.isSourceDef)(entry)) {
|
|
28
|
+
return entry.dialect;
|
|
29
|
+
}
|
|
30
|
+
if (entry.type === 'query') {
|
|
31
|
+
const head = entry.structRef;
|
|
32
|
+
if (typeof head === 'string') {
|
|
33
|
+
const named = (0, malloy_types_1.safeRecordGet)(from.contents, head);
|
|
34
|
+
return named && (0, malloy_types_1.isSourceDef)(named) ? named.dialect : undefined;
|
|
35
|
+
}
|
|
36
|
+
return head.dialect;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
20
40
|
function mkSourceID(name, url) {
|
|
21
41
|
return `${name}@${url !== null && url !== void 0 ? url : 'unknown'}`;
|
|
22
42
|
}
|
package/dist/test/index.js
CHANGED
|
@@ -113,14 +113,20 @@ function databasesFromEnvironmentOr(defaultDatabases) {
|
|
|
113
113
|
: defaultDatabases;
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
|
-
* A replacement for [describe()] that mimics [describe.skip()]
|
|
116
|
+
* A replacement for [describe()] that mimics [describe.skip()].
|
|
117
|
+
*
|
|
118
|
+
* Built on demand, not at module scope: this module is also imported outside
|
|
119
|
+
* jest (scripts that construct a test connection), where the jest globals do
|
|
120
|
+
* not exist.
|
|
117
121
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
})
|
|
122
|
+
function makeDescribeSkip() {
|
|
123
|
+
return Object.assign((name, fn) => describe.skip(name, fn), {
|
|
124
|
+
skip: describe.skip,
|
|
125
|
+
// eslint-disable-next-line no-restricted-properties
|
|
126
|
+
only: describe.only,
|
|
127
|
+
each: (() => () => it.skip('skipped', () => { })),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
124
130
|
/**
|
|
125
131
|
* Confirms that one or more of the databases being tested overlaps with
|
|
126
132
|
* the databases a test suite can accept. If there is overlap, return a tuple
|
|
@@ -130,7 +136,9 @@ const describeSkip = Object.assign((name, fn) => describe.skip(name, fn), {
|
|
|
130
136
|
function describeIfDatabaseAvailable(acceptableDatabases) {
|
|
131
137
|
const currentDatabases = databasesFromEnvironmentOr(acceptableDatabases);
|
|
132
138
|
const overlap = acceptableDatabases.filter(d => currentDatabases.includes(d));
|
|
133
|
-
return overlap.length > 0
|
|
139
|
+
return overlap.length > 0
|
|
140
|
+
? [describe, overlap]
|
|
141
|
+
: [makeDescribeSkip(), overlap];
|
|
134
142
|
}
|
|
135
143
|
function brokenIn(dialectName, connectionName) {
|
|
136
144
|
return dialectName === connectionName;
|
package/dist/test/matchers.d.ts
CHANGED
package/dist/test/matchers.js
CHANGED
|
@@ -7,7 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
/**
|
|
8
8
|
* Import this module to register Jest matchers (toMatchResult, toEqualResult).
|
|
9
9
|
*
|
|
10
|
-
* Usage in jest.config.
|
|
10
|
+
* Usage in jest.config.ts:
|
|
11
11
|
* setupFilesAfterEnv: ['@malloydata/malloy/test/matchers']
|
|
12
12
|
*
|
|
13
13
|
* Or import directly in a test file:
|
package/dist/test/test-models.js
CHANGED
|
@@ -229,19 +229,12 @@ function generateSQL(dialect, rows) {
|
|
|
229
229
|
.join(', ');
|
|
230
230
|
const innerQuery = selects.join('\nUNION ALL ');
|
|
231
231
|
// Generate ORDER BY based on dialect preference
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
orderByClause = `ORDER BY ${rowIdColumn}`;
|
|
241
|
-
}
|
|
242
|
-
// Presto/Trino ignores ORDER BY on a subquery without LIMIT
|
|
243
|
-
orderByClause += ` LIMIT ${rows.length}`;
|
|
244
|
-
const sql = `SELECT ${quotedColumns}\nFROM (\n SELECT *\n FROM (\n${innerQuery}\n ) AS t_sorted\n ${orderByClause}\n) AS t_result\n`;
|
|
232
|
+
const orderTerm = dialect.orderByClause === 'ordinal'
|
|
233
|
+
? `${columnList.length + 1}`
|
|
234
|
+
: rowIdColumn;
|
|
235
|
+
// Presto/Trino ignores ORDER BY on a subquery without a row limit
|
|
236
|
+
const orderByClause = dialect.sqlOrderByLimit([orderTerm], rows.length);
|
|
237
|
+
const sql = `SELECT ${quotedColumns}\nFROM (\n SELECT *\n FROM (\n${innerQuery}\n ) AS t_sorted\n ${orderByClause}) AS t_result\n`;
|
|
245
238
|
return sql;
|
|
246
239
|
}
|
|
247
240
|
/**
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MALLOY_VERSION = "0.0.
|
|
1
|
+
export declare const MALLOY_VERSION = "0.0.435";
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MALLOY_VERSION = void 0;
|
|
4
4
|
// generated with 'generate-version-file' script; do not edit manually
|
|
5
|
-
exports.MALLOY_VERSION = '0.0.
|
|
5
|
+
exports.MALLOY_VERSION = '0.0.435';
|
|
6
6
|
//# sourceMappingURL=version.js.map
|