@malloydata/malloy 0.0.434 → 0.0.436

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