@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.
Files changed (74) hide show
  1. package/dist/api/foundation/compile.d.ts +2 -2
  2. package/dist/api/foundation/compile.js +8 -9
  3. package/dist/api/foundation/config.js +6 -15
  4. package/dist/api/foundation/runtime.d.ts +2 -8
  5. package/dist/api/foundation/runtime.js +11 -39
  6. package/dist/api/foundation/types.d.ts +1 -1
  7. package/dist/connection/validate_table_path.d.ts +1 -2
  8. package/dist/connection/validate_table_path.js +1 -2
  9. package/dist/dialect/databricks/databricks.js +6 -6
  10. package/dist/dialect/dialect.d.ts +35 -3
  11. package/dist/dialect/dialect.js +67 -5
  12. package/dist/dialect/dialect_map.js +2 -0
  13. package/dist/dialect/duckdb/duckdb.js +7 -21
  14. package/dist/dialect/index.d.ts +2 -1
  15. package/dist/dialect/index.js +3 -1
  16. package/dist/dialect/mysql/mysql.js +8 -8
  17. package/dist/dialect/pg_impl.js +12 -12
  18. package/dist/dialect/postgres/postgres.js +3 -3
  19. package/dist/dialect/snowflake/snowflake.js +12 -12
  20. package/dist/dialect/sqlserver/dialect_functions.d.ts +2 -0
  21. package/dist/dialect/sqlserver/dialect_functions.js +42 -0
  22. package/dist/dialect/sqlserver/function_overrides.d.ts +2 -0
  23. package/dist/dialect/sqlserver/function_overrides.js +55 -0
  24. package/dist/dialect/sqlserver/index.d.ts +1 -0
  25. package/dist/dialect/sqlserver/index.js +22 -0
  26. package/dist/dialect/sqlserver/sqlserver.d.ts +102 -0
  27. package/dist/dialect/sqlserver/sqlserver.js +538 -0
  28. package/dist/dialect/sqlserver/windows_zones.d.ts +1 -0
  29. package/dist/dialect/sqlserver/windows_zones.js +613 -0
  30. package/dist/dialect/standardsql/standardsql.d.ts +1 -1
  31. package/dist/dialect/standardsql/standardsql.js +16 -13
  32. package/dist/dialect/trino/trino.d.ts +3 -3
  33. package/dist/dialect/trino/trino.js +32 -28
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +5 -3
  36. package/dist/lang/ast/source-elements/named-source.js +0 -2
  37. package/dist/lang/ast/source-elements/refined-source.js +1 -1
  38. package/dist/lang/ast/source-elements/sql-source.js +2 -4
  39. package/dist/lang/ast/source-elements/table-source.js +1 -2
  40. package/dist/lang/ast/source-properties/timezone-statement.d.ts +4 -3
  41. package/dist/lang/ast/source-properties/timezone-statement.js +14 -12
  42. package/dist/lang/ast/statements/import-statement.js +4 -0
  43. package/dist/lang/ast/types/malloy-element.d.ts +0 -7
  44. package/dist/lang/ast/types/malloy-element.js +0 -18
  45. package/dist/lang/composite-source-utils.js +10 -3
  46. package/dist/lang/malloy-to-ast.js +2 -8
  47. package/dist/lang/parse-malloy.d.ts +20 -4
  48. package/dist/lang/parse-malloy.js +50 -15
  49. package/dist/lang/parse-tree-walkers/find-external-references.d.ts +3 -3
  50. package/dist/lang/parse-tree-walkers/find-external-references.js +17 -16
  51. package/dist/lang/test/test-translator.d.ts +12 -2
  52. package/dist/lang/test/test-translator.js +16 -2
  53. package/dist/model/expression_compiler.js +1 -1
  54. package/dist/model/join_instance.d.ts +1 -0
  55. package/dist/model/join_instance.js +9 -1
  56. package/dist/model/query_query.d.ts +28 -1
  57. package/dist/model/query_query.js +299 -156
  58. package/dist/model/source_def_utils.d.ts +7 -1
  59. package/dist/model/source_def_utils.js +20 -0
  60. package/dist/model/sql_block.d.ts +2 -2
  61. package/dist/model/sql_block.js +11 -47
  62. package/dist/model/sql_compiled.d.ts +3 -1
  63. package/dist/model/sql_compiled.js +8 -6
  64. package/dist/model/stage_writer.js +3 -3
  65. package/dist/model/utils.d.ts +2 -2
  66. package/dist/model/utils.js +2 -3
  67. package/dist/test/index.js +16 -8
  68. package/dist/test/matchers.d.ts +1 -1
  69. package/dist/test/matchers.js +1 -1
  70. package/dist/test/test-models.js +6 -13
  71. package/dist/version.d.ts +1 -1
  72. package/dist/version.js +1 -1
  73. package/femto-config.motly +15 -1
  74. 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>;