@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
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.SQLServerDialect = void 0;
|
|
8
|
+
const malloy_types_1 = require("../../model/malloy_types");
|
|
9
|
+
const functions_1 = require("../functions");
|
|
10
|
+
const dialect_1 = require("../dialect");
|
|
11
|
+
const dialect_functions_1 = require("./dialect_functions");
|
|
12
|
+
const function_overrides_1 = require("./function_overrides");
|
|
13
|
+
const windows_zones_1 = require("./windows_zones");
|
|
14
|
+
/** DATEPART / DATEADD / DATEDIFF spellings of Malloy's units */
|
|
15
|
+
const datePartMap = {
|
|
16
|
+
second: 'second',
|
|
17
|
+
minute: 'minute',
|
|
18
|
+
hour: 'hour',
|
|
19
|
+
day: 'day',
|
|
20
|
+
week: 'week',
|
|
21
|
+
month: 'month',
|
|
22
|
+
quarter: 'quarter',
|
|
23
|
+
year: 'year',
|
|
24
|
+
day_of_year: 'dayofyear',
|
|
25
|
+
};
|
|
26
|
+
const inSeconds = {
|
|
27
|
+
second: 1,
|
|
28
|
+
minute: 60,
|
|
29
|
+
hour: 3600,
|
|
30
|
+
day: 24 * 3600,
|
|
31
|
+
week: 7 * 24 * 3600,
|
|
32
|
+
};
|
|
33
|
+
// Malloy types by SQL Server type name, spelled as sys.types and the
|
|
34
|
+
// system_type_name of sys.dm_exec_describe_first_result_set spell them.
|
|
35
|
+
// DECIMAL and NUMERIC are absent: their scale decides (sqlTypeToMalloyType).
|
|
36
|
+
const sqlServerToMalloyTypes = {
|
|
37
|
+
// A bit is an integer: SQL Server has no boolean values
|
|
38
|
+
'bit': { type: 'number', numberType: 'integer' },
|
|
39
|
+
'tinyint': { type: 'number', numberType: 'integer' },
|
|
40
|
+
'smallint': { type: 'number', numberType: 'integer' },
|
|
41
|
+
'int': { type: 'number', numberType: 'integer' },
|
|
42
|
+
'bigint': { type: 'number', numberType: 'bigint' },
|
|
43
|
+
'real': { type: 'number', numberType: 'float' },
|
|
44
|
+
'float': { type: 'number', numberType: 'float' },
|
|
45
|
+
'money': { type: 'number', numberType: 'float' },
|
|
46
|
+
'smallmoney': { type: 'number', numberType: 'float' },
|
|
47
|
+
'char': { type: 'string' },
|
|
48
|
+
'varchar': { type: 'string' },
|
|
49
|
+
'nchar': { type: 'string' },
|
|
50
|
+
'nvarchar': { type: 'string' },
|
|
51
|
+
// text and ntext take no GROUP BY, MAX or comparison, so they stay native
|
|
52
|
+
// uniqueidentifier stays native: a GUID is neither text nor a number to Malloy
|
|
53
|
+
'date': { type: 'date' },
|
|
54
|
+
'datetime': { type: 'timestamp' },
|
|
55
|
+
'datetime2': { type: 'timestamp' },
|
|
56
|
+
'smalldatetime': { type: 'timestamp' },
|
|
57
|
+
};
|
|
58
|
+
function parseSQLServerType(sqlType) {
|
|
59
|
+
var _a, _b;
|
|
60
|
+
const text = sqlType.trim().toLowerCase();
|
|
61
|
+
const base = (_b = (_a = text.match(/^\w+/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : text;
|
|
62
|
+
const params = text.match(/^\w+\s*\((\d+(?:\s*,\s*\d+)*)\)/);
|
|
63
|
+
return {
|
|
64
|
+
base,
|
|
65
|
+
params: params ? params[1].split(',').map(p => parseInt(p, 10)) : [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A table path with T-SQL's `[name]` segments rewritten as ANSI-quoted ones
|
|
70
|
+
* (`]]` inside a segment is one `]`); a `"name"` segment passes through
|
|
71
|
+
* untouched. `undefined` for a `[` that is never closed.
|
|
72
|
+
*/
|
|
73
|
+
function bracketsToAnsi(input, quote) {
|
|
74
|
+
let ansi = '';
|
|
75
|
+
let i = 0;
|
|
76
|
+
while (i < input.length) {
|
|
77
|
+
const c = input[i];
|
|
78
|
+
if (c === '"') {
|
|
79
|
+
let j = i + 1;
|
|
80
|
+
while (j < input.length) {
|
|
81
|
+
if (input[j] === '"') {
|
|
82
|
+
if (input[j + 1] === '"') {
|
|
83
|
+
j += 2;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
j++;
|
|
89
|
+
}
|
|
90
|
+
ansi += input.slice(i, j + 1);
|
|
91
|
+
i = j + 1;
|
|
92
|
+
}
|
|
93
|
+
else if (c === '[') {
|
|
94
|
+
let body = '';
|
|
95
|
+
let j = i + 1;
|
|
96
|
+
let closed = false;
|
|
97
|
+
while (j < input.length) {
|
|
98
|
+
if (input[j] === ']') {
|
|
99
|
+
if (input[j + 1] === ']') {
|
|
100
|
+
body += ']';
|
|
101
|
+
j += 2;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
closed = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
body += input[j];
|
|
108
|
+
j++;
|
|
109
|
+
}
|
|
110
|
+
if (!closed) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
ansi += quote(body);
|
|
114
|
+
i = j + 1;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
ansi += c;
|
|
118
|
+
i++;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return ansi;
|
|
122
|
+
}
|
|
123
|
+
/** T-SQL for SQL Server 2017 and later, and Azure SQL Database. */
|
|
124
|
+
class SQLServerDialect extends dialect_1.Dialect {
|
|
125
|
+
constructor() {
|
|
126
|
+
super(...arguments);
|
|
127
|
+
this.name = 'sqlserver';
|
|
128
|
+
this.experimental = true;
|
|
129
|
+
// The connection's batch prefix sets QUOTED_IDENTIFIER ON, so an identifier
|
|
130
|
+
// is double-quoted and a string literal single-quoted; a quote inside
|
|
131
|
+
// either is escaped by doubling it.
|
|
132
|
+
this.stringLiteralStyle = dialect_1.EscapeStyle.Doubled;
|
|
133
|
+
this.identifierEscapeStyle = dialect_1.EscapeStyle.Doubled;
|
|
134
|
+
this.identifierQuoteChar = '"';
|
|
135
|
+
this.defaultNumberType = 'FLOAT(53)';
|
|
136
|
+
this.defaultDecimalType = 'DECIMAL(38, 9)';
|
|
137
|
+
this.udfPrefix = '__udf';
|
|
138
|
+
this.hasFinalStage = false;
|
|
139
|
+
this.divisionIsInteger = true;
|
|
140
|
+
this.supportsSumDistinctFunction = true;
|
|
141
|
+
this.unnestWithNumbers = false;
|
|
142
|
+
this.defaultSampling = { rows: 50000 };
|
|
143
|
+
this.supportUnnestArrayAgg = false;
|
|
144
|
+
this.supportsAggDistinct = false;
|
|
145
|
+
// A CTE is a statement prefix and cannot appear inside a subquery.
|
|
146
|
+
this.supportsCTEinCoorelatedSubQueries = false;
|
|
147
|
+
this.dontUnionIndex = false;
|
|
148
|
+
this.supportsQualify = false;
|
|
149
|
+
this.supportsSafeCast = true;
|
|
150
|
+
this.supportsNesting = false;
|
|
151
|
+
this.supportsPipelinesInViews = false;
|
|
152
|
+
this.supportsFullJoin = true;
|
|
153
|
+
this.readsNestedData = false;
|
|
154
|
+
this.supportsComplexFilteredSources = false;
|
|
155
|
+
this.supportsArraysInData = false;
|
|
156
|
+
this.compoundObjectInSchema = false;
|
|
157
|
+
this.supportsSelectReplace = false;
|
|
158
|
+
this.supportsTempTables = false;
|
|
159
|
+
// SQL Server has no boolean values: a comparison is legal only where a
|
|
160
|
+
// condition is expected, `true` and `false` are (1=1) and (1=0), a `bit`
|
|
161
|
+
// column is an integer, and a cast to boolean is refused.
|
|
162
|
+
this.booleanType = 'none';
|
|
163
|
+
this.hasTimestamptz = false;
|
|
164
|
+
// A bigint arrives from the driver as text and is read as a JavaScript number
|
|
165
|
+
this.supportsBigIntPrecision = false;
|
|
166
|
+
this.maxIdentifierLength = 128;
|
|
167
|
+
this.likeEscape = true;
|
|
168
|
+
this.likeExtraWildcards = ['['];
|
|
169
|
+
this.groupByClause = 'expression';
|
|
170
|
+
// A leading `#` or `@` names a temp table or a variable, not a table.
|
|
171
|
+
this.tablePathBareIdentRegex = /^[A-Za-z_][A-Za-z0-9_$#@]*/;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* `[schema].[table]` is read as the ANSI-quoted path it stands for, and the
|
|
175
|
+
* canonical form is the ANSI spelling the base validates.
|
|
176
|
+
*/
|
|
177
|
+
sqlValidateTableName(input) {
|
|
178
|
+
const ansi = bracketsToAnsi(input, body => this.sqlQuoteIdentifier(body));
|
|
179
|
+
if (ansi === undefined) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
error: `Invalid ${this.name} table path: ${JSON.stringify(input)} — unterminated bracketed segment`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return super.sqlValidateTableName(ansi);
|
|
186
|
+
}
|
|
187
|
+
malloyTypeToSQLType(malloyType) {
|
|
188
|
+
switch (malloyType.type) {
|
|
189
|
+
case 'number':
|
|
190
|
+
if (malloyType.numberType === 'integer') {
|
|
191
|
+
return 'INT';
|
|
192
|
+
}
|
|
193
|
+
else if (malloyType.numberType === 'bigint') {
|
|
194
|
+
return 'BIGINT';
|
|
195
|
+
}
|
|
196
|
+
return this.defaultNumberType;
|
|
197
|
+
case 'string':
|
|
198
|
+
return 'NVARCHAR(MAX)';
|
|
199
|
+
case 'boolean':
|
|
200
|
+
return 'BIT';
|
|
201
|
+
case 'date':
|
|
202
|
+
return 'DATE';
|
|
203
|
+
case 'timestamp':
|
|
204
|
+
return 'DATETIME2';
|
|
205
|
+
case 'record':
|
|
206
|
+
case 'array':
|
|
207
|
+
return 'NVARCHAR(MAX)';
|
|
208
|
+
default:
|
|
209
|
+
return malloyType.type;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
sqlTypeToMalloyType(sqlType) {
|
|
213
|
+
var _a, _b, _c;
|
|
214
|
+
const { base, params } = parseSQLServerType(sqlType);
|
|
215
|
+
if (base === 'decimal' || base === 'numeric') {
|
|
216
|
+
// Scale decides float vs exact; precision decides whether an exact
|
|
217
|
+
// value survives a JS double. A bare DECIMAL is (18, 0).
|
|
218
|
+
const [precision, scale] = [(_a = params[0]) !== null && _a !== void 0 ? _a : 18, (_b = params[1]) !== null && _b !== void 0 ? _b : 0];
|
|
219
|
+
if (scale > 0) {
|
|
220
|
+
return { type: 'number', numberType: 'float' };
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
type: 'number',
|
|
224
|
+
numberType: precision <= 15 ? 'integer' : 'bigint',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return ((_c = sqlServerToMalloyTypes[base]) !== null && _c !== void 0 ? _c : {
|
|
228
|
+
type: 'sql native',
|
|
229
|
+
rawType: base,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
validateTypeName(sqlType) {
|
|
233
|
+
// The spellings the type map reads: letters, digits, spaces, parentheses
|
|
234
|
+
// and commas, as in BIGINT, FLOAT(53) and DECIMAL(5, 2).
|
|
235
|
+
return sqlType.match(/^[A-Za-z\s(),0-9]*$/) !== null;
|
|
236
|
+
}
|
|
237
|
+
sqlGroupSetTable(groupSetCount) {
|
|
238
|
+
const rows = Array.from({ length: groupSetCount + 1 }, (_, i) => `(${i})`);
|
|
239
|
+
return `CROSS JOIN (VALUES ${rows.join(',')}) AS group_set(group_set)`;
|
|
240
|
+
}
|
|
241
|
+
sqlAnyValue(_groupSet, fieldName) {
|
|
242
|
+
return `MAX(${fieldName})`;
|
|
243
|
+
}
|
|
244
|
+
// Nests, arrays and records are not supported: supportsNesting,
|
|
245
|
+
// supportsArraysInData and compoundObjectInSchema are false. A call here
|
|
246
|
+
// comes from a literal, a source the translator let through, or an
|
|
247
|
+
// ungrouped aggregate (all(), exclude()), which shares the nest machinery.
|
|
248
|
+
sqlAggregateTurtle() {
|
|
249
|
+
return this.unsupported('nesting');
|
|
250
|
+
}
|
|
251
|
+
sqlAnyValueTurtle() {
|
|
252
|
+
return this.unsupported('nesting');
|
|
253
|
+
}
|
|
254
|
+
sqlAnyValueLastTurtle() {
|
|
255
|
+
return this.unsupported('nesting');
|
|
256
|
+
}
|
|
257
|
+
sqlCoaleseMeasuresInline() {
|
|
258
|
+
return this.unsupported('nesting');
|
|
259
|
+
}
|
|
260
|
+
sqlUnnestAlias() {
|
|
261
|
+
return this.unsupported('arrays');
|
|
262
|
+
}
|
|
263
|
+
sqlSelectAliasAsStruct() {
|
|
264
|
+
return this.unsupported('records');
|
|
265
|
+
}
|
|
266
|
+
sqlLiteralRecord() {
|
|
267
|
+
return this.unsupported('record literals');
|
|
268
|
+
}
|
|
269
|
+
sqlLiteralArray() {
|
|
270
|
+
return this.unsupported('array literals');
|
|
271
|
+
}
|
|
272
|
+
sqlFieldReference(parentAlias, _parentType, childName, _childType) {
|
|
273
|
+
return `${parentAlias}.${this.sqlQuoteIdentifier(childName)}`;
|
|
274
|
+
}
|
|
275
|
+
unsupported(what) {
|
|
276
|
+
throw new Error(`SQL Server dialect does not support ${what}`);
|
|
277
|
+
}
|
|
278
|
+
sqlUnnestPipelineHead(_isSingleton, _sourceSQLExpression, _fieldList) {
|
|
279
|
+
return this.unsupported('a nested pipeline');
|
|
280
|
+
}
|
|
281
|
+
sqlCreateFunction(_id, _funcText) {
|
|
282
|
+
return this.unsupported('a nested pipeline');
|
|
283
|
+
}
|
|
284
|
+
sqlCreateFunctionCombineLastStage(_lastStageName, _fieldList, _orderBy) {
|
|
285
|
+
return this.unsupported('a nested pipeline');
|
|
286
|
+
}
|
|
287
|
+
sqlCreateTableAsSelect(tableName, sql) {
|
|
288
|
+
return `SELECT * INTO ${tableName} FROM (\n${sql}\n) AS __malloy_ctas`;
|
|
289
|
+
}
|
|
290
|
+
// The row key a symmetric aggregate is keyed on: NEWID() is evaluated again
|
|
291
|
+
// for every joined row and double-counts, a row number is one per source row
|
|
292
|
+
sqlGenerateUUID() {
|
|
293
|
+
return 'ROW_NUMBER() OVER (ORDER BY (SELECT NULL))';
|
|
294
|
+
}
|
|
295
|
+
// A stage's SQL is indented when it becomes a CTE, which would indent the
|
|
296
|
+
// text of a literal spanning lines.
|
|
297
|
+
sqlLiteralString(literal) {
|
|
298
|
+
// N'...' is Unicode; a bare '...' is read in the database's code page
|
|
299
|
+
if (!/[\r\n]/.test(literal)) {
|
|
300
|
+
return `N${super.sqlLiteralString(literal)}`;
|
|
301
|
+
}
|
|
302
|
+
const parts = literal
|
|
303
|
+
.split(/(\r|\n)/)
|
|
304
|
+
.filter(p => p !== '')
|
|
305
|
+
.map(p => p === '\r'
|
|
306
|
+
? 'CHAR(13)'
|
|
307
|
+
: p === '\n'
|
|
308
|
+
? 'CHAR(10)'
|
|
309
|
+
: `N${super.sqlLiteralString(p)}`);
|
|
310
|
+
return `(${parts.join(' + ')})`;
|
|
311
|
+
}
|
|
312
|
+
// The MD5 of the key as a 64-bit integer, widened so that hash + value is
|
|
313
|
+
// exact at the value's scale.
|
|
314
|
+
sqlSumDistinctHashedKey(sqlDistinctKey) {
|
|
315
|
+
return `CAST(CAST(HASHBYTES('MD5', CAST(${sqlDistinctKey} AS NVARCHAR(MAX))) AS BIGINT) AS ${this.defaultDecimalType})`;
|
|
316
|
+
}
|
|
317
|
+
sqlSumDistinct(key, value, funcName) {
|
|
318
|
+
const hashKey = this.sqlSumDistinctHashedKey(key);
|
|
319
|
+
const v = `CAST(COALESCE(${value}, 0) AS ${this.defaultDecimalType})`;
|
|
320
|
+
const sqlSum = `CAST((SUM(DISTINCT ${hashKey} + ${v}) - SUM(DISTINCT ${hashKey})) AS ${this.defaultNumberType})`;
|
|
321
|
+
if (funcName === 'SUM') {
|
|
322
|
+
return sqlSum;
|
|
323
|
+
}
|
|
324
|
+
else if (funcName === 'AVG') {
|
|
325
|
+
return `(${sqlSum})/NULLIF(COUNT(DISTINCT CASE WHEN ${value} IS NOT NULL THEN ${key} END), 0)`;
|
|
326
|
+
}
|
|
327
|
+
throw new Error(`Unknown Symmetric Aggregate function ${funcName}`);
|
|
328
|
+
}
|
|
329
|
+
sqlAggDistinct(_key, _values, _func) {
|
|
330
|
+
return this.unsupported('distinct aggregates');
|
|
331
|
+
}
|
|
332
|
+
// OFFSET/FETCH is ORDER BY's row limit, and an ORDER BY carrying an OFFSET
|
|
333
|
+
// is legal inside a CTE or derived table where a bare one is not; the server
|
|
334
|
+
// still sorts it. A limit with no ordering orders by (SELECT NULL), the one
|
|
335
|
+
// constant ORDER BY accepts.
|
|
336
|
+
sqlOrderByLimit(orderTerms, limit) {
|
|
337
|
+
if (orderTerms.length === 0 && limit === undefined) {
|
|
338
|
+
return '';
|
|
339
|
+
}
|
|
340
|
+
const orderBy = orderTerms.length > 0
|
|
341
|
+
? this.sqlOrderBy(orderTerms, 'query')
|
|
342
|
+
: 'ORDER BY (SELECT NULL)';
|
|
343
|
+
const fetch = limit === undefined ? '' : ` FETCH NEXT ${limit} ROWS ONLY`;
|
|
344
|
+
return `${orderBy} OFFSET 0 ROWS${fetch}\n`;
|
|
345
|
+
}
|
|
346
|
+
// TABLESAMPLE reads whole pages of a base table and nothing else, so a
|
|
347
|
+
// sample on a derived table is ignored rather than taken as its first rows.
|
|
348
|
+
sqlSampleTable(tableSQL, sample, onBaseTable = true) {
|
|
349
|
+
if (sample === undefined || !onBaseTable) {
|
|
350
|
+
return tableSQL;
|
|
351
|
+
}
|
|
352
|
+
if ((0, malloy_types_1.isSamplingEnable)(sample) && sample.enable) {
|
|
353
|
+
sample = this.defaultSampling;
|
|
354
|
+
}
|
|
355
|
+
if ((0, malloy_types_1.isSamplingRows)(sample)) {
|
|
356
|
+
return `(SELECT * FROM ${tableSQL} TABLESAMPLE (${sample.rows} ROWS))`;
|
|
357
|
+
}
|
|
358
|
+
if ((0, malloy_types_1.isSamplingPercent)(sample)) {
|
|
359
|
+
return `(SELECT * FROM ${tableSQL} TABLESAMPLE (${sample.percent} PERCENT))`;
|
|
360
|
+
}
|
|
361
|
+
return tableSQL;
|
|
362
|
+
}
|
|
363
|
+
castToString(expression) {
|
|
364
|
+
return `CAST(${expression} AS NVARCHAR(MAX))`;
|
|
365
|
+
}
|
|
366
|
+
concat(...values) {
|
|
367
|
+
return `CONCAT(${values.join(', ')})`;
|
|
368
|
+
}
|
|
369
|
+
sqlDateToString(sqlDateExp) {
|
|
370
|
+
return `CONVERT(NVARCHAR(10), ${sqlDateExp}, 23)`;
|
|
371
|
+
}
|
|
372
|
+
// A Malloy timestamp is a UTC wall clock: a datetime2 is read as UTC, and
|
|
373
|
+
// now is the server's UTC clock
|
|
374
|
+
sqlNowExpr() {
|
|
375
|
+
return 'SYSUTCDATETIME()';
|
|
376
|
+
}
|
|
377
|
+
sqlTimeExtractExpr(qi, from) {
|
|
378
|
+
let extractFrom = from.e.sql;
|
|
379
|
+
if (malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
|
|
380
|
+
const tz = (0, dialect_1.qtz)(qi);
|
|
381
|
+
if (tz) {
|
|
382
|
+
extractFrom = this.sqlConvertToCivilTime(extractFrom !== null && extractFrom !== void 0 ? extractFrom : '', tz, from.e.typeDef).sql;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (from.units === 'day_of_week') {
|
|
386
|
+
// Days since a Sunday, so the answer does not follow DATEFIRST
|
|
387
|
+
return `((DATEDIFF(day, '19000107', ${extractFrom}) % 7 + 7) % 7 + 1)`;
|
|
388
|
+
}
|
|
389
|
+
// Malloy's week of the year is the ISO week; DATEPART(week) counts from
|
|
390
|
+
// January 1st and follows DATEFIRST
|
|
391
|
+
const part = from.units === 'week' ? 'iso_week' : datePartMap[from.units];
|
|
392
|
+
if (part === undefined) {
|
|
393
|
+
throw new Error(`Unknown SQL Server date part '${from.units}'`);
|
|
394
|
+
}
|
|
395
|
+
return `DATEPART(${part}, ${extractFrom})`;
|
|
396
|
+
}
|
|
397
|
+
sqlCast(qi, cast) {
|
|
398
|
+
const expr = cast.e.sql || '';
|
|
399
|
+
const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
|
|
400
|
+
if ((dstTypeDef === null || dstTypeDef === void 0 ? void 0 : dstTypeDef.type) === 'boolean') {
|
|
401
|
+
return this.unsupported('a cast to boolean: there are no boolean values');
|
|
402
|
+
}
|
|
403
|
+
const tz = (0, dialect_1.qtz)(qi);
|
|
404
|
+
if (tz && srcTypeDef && dstTypeDef) {
|
|
405
|
+
if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
|
|
406
|
+
const civil = this.sqlConvertToCivilTime(expr, tz, srcTypeDef).sql;
|
|
407
|
+
return `CAST(${civil} AS DATE)`;
|
|
408
|
+
}
|
|
409
|
+
if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
|
|
410
|
+
return this.sqlConvertFromCivilTime(`CAST(${expr} AS DATETIME2)`, tz, dstTypeDef);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
|
|
414
|
+
const castFunc = cast.safe ? 'TRY_CAST' : 'CAST';
|
|
415
|
+
return `${castFunc}(${expr} AS ${dstSQLType})`;
|
|
416
|
+
}
|
|
417
|
+
return expr;
|
|
418
|
+
}
|
|
419
|
+
sqlRegexpMatch(_df) {
|
|
420
|
+
return this.unsupported('regular expressions before SQL Server 2025');
|
|
421
|
+
}
|
|
422
|
+
sqlDateLiteral(_qi, literal) {
|
|
423
|
+
return `CAST('${literal}' AS DATE)`;
|
|
424
|
+
}
|
|
425
|
+
sqlTimestampLiteral(qi, literal, timezone) {
|
|
426
|
+
const tz = timezone || (0, dialect_1.qtz)(qi);
|
|
427
|
+
const ts = `CAST('${literal}' AS DATETIME2)`;
|
|
428
|
+
if (tz) {
|
|
429
|
+
return this.sqlConvertFromCivilTime(ts, tz, { type: 'timestamp' });
|
|
430
|
+
}
|
|
431
|
+
return ts;
|
|
432
|
+
}
|
|
433
|
+
sqlTimestamptzLiteral(_qi, _literal, _timezone) {
|
|
434
|
+
throw new Error('SQL Server dialect does not support timestamptz');
|
|
435
|
+
}
|
|
436
|
+
// AT TIME ZONE takes a Windows time zone name
|
|
437
|
+
sqlTimezoneLiteral(timezone) {
|
|
438
|
+
const windowsName = windows_zones_1.WINDOWS_TIME_ZONES[timezone];
|
|
439
|
+
if (windowsName === undefined) {
|
|
440
|
+
throw new Error(`SQL Server dialect has no Windows time zone for '${timezone}'`);
|
|
441
|
+
}
|
|
442
|
+
return this.sqlLiteralString(windowsName);
|
|
443
|
+
}
|
|
444
|
+
sqlConvertToCivilTime(expr, timezone, _typeDef) {
|
|
445
|
+
if (timezone === 'UTC') {
|
|
446
|
+
return { sql: expr, typeDef: { type: 'timestamp' } };
|
|
447
|
+
}
|
|
448
|
+
const tz = this.sqlTimezoneLiteral(timezone);
|
|
449
|
+
return {
|
|
450
|
+
sql: `CAST((${expr} AT TIME ZONE 'UTC') AT TIME ZONE ${tz} AS DATETIME2)`,
|
|
451
|
+
typeDef: { type: 'timestamp' },
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
sqlConvertFromCivilTime(expr, timezone, _destTypeDef) {
|
|
455
|
+
if (timezone === 'UTC') {
|
|
456
|
+
return expr;
|
|
457
|
+
}
|
|
458
|
+
const tz = this.sqlTimezoneLiteral(timezone);
|
|
459
|
+
return `CAST((${expr} AT TIME ZONE ${tz}) AT TIME ZONE 'UTC' AS DATETIME2)`;
|
|
460
|
+
}
|
|
461
|
+
sqlTruncate(expr, unit, typeDef, _inCivilTime, _timezone) {
|
|
462
|
+
// Truncation is DATEADD of the whole units since an anchor: 1900-01-01
|
|
463
|
+
// for a calendar unit, the value's own midnight for a clock unit, so the
|
|
464
|
+
// count fits an int. SQL Server 2017 has no DATETRUNC.
|
|
465
|
+
const midnight = `CAST(CAST(${expr} AS DATE) AS DATETIME2)`;
|
|
466
|
+
if (unit === 'week') {
|
|
467
|
+
// Malloy weeks start on Sunday whatever DATEFIRST says, so count days
|
|
468
|
+
// from a known Sunday.
|
|
469
|
+
const day = malloy_types_1.TD.isDate(typeDef) ? expr : midnight;
|
|
470
|
+
return `DATEADD(day, -((DATEDIFF(day, '19000107', ${expr}) % 7 + 7) % 7), ${day})`;
|
|
471
|
+
}
|
|
472
|
+
const part = datePartMap[unit];
|
|
473
|
+
if (part === undefined) {
|
|
474
|
+
throw new Error(`Unknown SQL Server date part '${unit}'`);
|
|
475
|
+
}
|
|
476
|
+
if (unit === 'day' && !malloy_types_1.TD.isDate(typeDef)) {
|
|
477
|
+
return midnight;
|
|
478
|
+
}
|
|
479
|
+
if (unit === 'hour' || unit === 'minute' || unit === 'second') {
|
|
480
|
+
return `DATEADD(${part}, DATEDIFF(${part}, ${midnight}, ${expr}), ${midnight})`;
|
|
481
|
+
}
|
|
482
|
+
const truncated = `DATEADD(${part}, DATEDIFF(${part}, '19000101', ${expr}), '19000101')`;
|
|
483
|
+
return malloy_types_1.TD.isDate(typeDef)
|
|
484
|
+
? `CAST(${truncated} AS DATE)`
|
|
485
|
+
: `CAST(${truncated} AS DATETIME2)`;
|
|
486
|
+
}
|
|
487
|
+
sqlOffsetTime(expr, op, magnitude, unit, _typeDef, _inCivilTime, _timezone) {
|
|
488
|
+
const part = datePartMap[unit];
|
|
489
|
+
if (part === undefined) {
|
|
490
|
+
throw new Error(`Unknown SQL Server date part '${unit}'`);
|
|
491
|
+
}
|
|
492
|
+
const n = op === '-' ? `-(${magnitude})` : `(${magnitude})`;
|
|
493
|
+
return `DATEADD(${part}, ${n}, ${expr})`;
|
|
494
|
+
}
|
|
495
|
+
// Malloy measures a range in whole units completed, truncated toward zero;
|
|
496
|
+
// DATEDIFF counts boundaries crossed.
|
|
497
|
+
sqlMeasureTimeExpr(df) {
|
|
498
|
+
const from = df.kids.left;
|
|
499
|
+
const to = df.kids.right;
|
|
500
|
+
const lVal = from.sql;
|
|
501
|
+
const rVal = to.sql;
|
|
502
|
+
if (malloy_types_1.TD.isDate(from.typeDef) &&
|
|
503
|
+
malloy_types_1.TD.isDate(to.typeDef) &&
|
|
504
|
+
['week', 'month', 'quarter', 'year'].includes(df.units)) {
|
|
505
|
+
// Integer division truncates toward zero, as Malloy measures
|
|
506
|
+
if (df.units === 'week') {
|
|
507
|
+
return `(DATEDIFF(day, ${lVal}, ${rVal}) / 7)`;
|
|
508
|
+
}
|
|
509
|
+
const earlier = `(CASE WHEN ${rVal} >= ${lVal} THEN ${lVal} ELSE ${rVal} END)`;
|
|
510
|
+
const later = `(CASE WHEN ${rVal} >= ${lVal} THEN ${rVal} ELSE ${lVal} END)`;
|
|
511
|
+
const months = `(DATEDIFF(month, ${earlier}, ${later}) - CASE WHEN DAY(${later}) < DAY(${earlier}) THEN 1 ELSE 0 END)`;
|
|
512
|
+
const measured = df.units === 'month'
|
|
513
|
+
? months
|
|
514
|
+
: df.units === 'quarter'
|
|
515
|
+
? `(${months} / 3)`
|
|
516
|
+
: `(${months} / 12)`;
|
|
517
|
+
return `CASE
|
|
518
|
+
WHEN ${lVal} IS NULL OR ${rVal} IS NULL THEN NULL
|
|
519
|
+
WHEN ${rVal} >= ${lVal} THEN ${measured}
|
|
520
|
+
ELSE -(${measured})
|
|
521
|
+
END`;
|
|
522
|
+
}
|
|
523
|
+
if (inSeconds[df.units]) {
|
|
524
|
+
const ms = `DATEDIFF_BIG(millisecond, ${lVal}, ${rVal})`;
|
|
525
|
+
const divisor = inSeconds[df.units] * 1000;
|
|
526
|
+
return `CAST(${ms} / ${divisor} AS BIGINT)`;
|
|
527
|
+
}
|
|
528
|
+
throw new Error(`Unknown or unhandled SQL Server time unit: ${df.units}`);
|
|
529
|
+
}
|
|
530
|
+
getDialectFunctionOverrides() {
|
|
531
|
+
return (0, functions_1.expandOverrideMap)(function_overrides_1.SQLSERVER_MALLOY_STANDARD_OVERLOADS);
|
|
532
|
+
}
|
|
533
|
+
getDialectFunctions() {
|
|
534
|
+
return (0, functions_1.expandBlueprintMap)(dialect_functions_1.SQLSERVER_DIALECT_FUNCTIONS);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
exports.SQLServerDialect = SQLServerDialect;
|
|
538
|
+
//# sourceMappingURL=sqlserver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const WINDOWS_TIME_ZONES: Record<string, string>;
|