@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
@@ -150,7 +150,7 @@ class StandardSQLDialect extends dialect_1.Dialect {
150
150
  // can array agg or any_value a struct...
151
151
  sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
152
152
  const fields = fieldList
153
- .map(f => `\n ${f.sqlExpression} as ${f.sqlOutputName}`)
153
+ .map(f => `\n ${f.sqlExpression} as ${this.sqlQuoteIdentifier(f.rawName)}`)
154
154
  .join(', ');
155
155
  const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
156
156
  // BigQuery ARRAY_AGG takes LIMIT as its final sub-clause, after ORDER BY.
@@ -162,7 +162,7 @@ class StandardSQLDialect extends dialect_1.Dialect {
162
162
  }
163
163
  sqlAnyValueTurtle(groupSet, fieldList) {
164
164
  const fields = fieldList
165
- .map(f => `${f.sqlExpression} as ${f.sqlOutputName}`)
165
+ .map(f => `${f.sqlExpression} as ${this.sqlQuoteIdentifier(f.rawName)}`)
166
166
  .join(', ');
167
167
  return `ANY_VALUE(CASE WHEN group_set=${groupSet} THEN STRUCT(${fields}) END)`;
168
168
  }
@@ -171,10 +171,10 @@ class StandardSQLDialect extends dialect_1.Dialect {
171
171
  }
172
172
  sqlCoaleseMeasuresInline(groupSet, fieldList) {
173
173
  const fields = fieldList
174
- .map(f => `${f.sqlExpression} as ${f.sqlOutputName}`)
174
+ .map(f => `${f.sqlExpression} as ${this.sqlQuoteIdentifier(f.rawName)}`)
175
175
  .join(', ');
176
176
  const nullValues = fieldList
177
- .map(f => `NULL as ${f.sqlOutputName}`)
177
+ .map(f => `NULL as ${this.sqlQuoteIdentifier(f.rawName)}`)
178
178
  .join(', ');
179
179
  return `COALESCE(ANY_VALUE(CASE WHEN group_set=${groupSet} THEN STRUCT(${fields}) END), STRUCT(${nullValues}))`;
180
180
  }
@@ -240,8 +240,11 @@ ${(0, utils_1.indent)(sql)}
240
240
  sqlCreateFunctionCombineLastStage(lastStageName) {
241
241
  return `SELECT ARRAY((SELECT AS STRUCT * FROM ${lastStageName}))\n`;
242
242
  }
243
- sqlSelectAliasAsStruct(alias) {
244
- return `(SELECT AS STRUCT ${alias}.*)`;
243
+ sqlSelectAliasAsStruct(alias, dialectFieldList) {
244
+ const fields = dialectFieldList
245
+ .map(d => `${alias}.${this.sqlQuoteIdentifier(d.rawName)}`)
246
+ .join(', ');
247
+ return `(SELECT AS STRUCT ${fields})`;
245
248
  }
246
249
  sqlNowExpr() {
247
250
  return 'CURRENT_TIMESTAMP()';
@@ -249,18 +252,18 @@ ${(0, utils_1.indent)(sql)}
249
252
  sqlTimeExtractExpr(qi, te) {
250
253
  const extractTo = extractMap[te.units] || te.units;
251
254
  const tz = malloy_types_1.TD.isAnyTimestamp(te.e.typeDef) && qtz(qi);
252
- const tzAdd = tz ? ` AT TIME ZONE '${tz}'` : '';
255
+ const tzAdd = tz ? ` AT TIME ZONE ${this.sqlTimezoneLiteral(tz)}` : '';
253
256
  return `EXTRACT(${extractTo} FROM ${te.e.sql}${tzAdd})`;
254
257
  }
255
258
  sqlConvertToCivilTime(expr, timezone, _typeDef) {
256
259
  // BigQuery has no timestamptz type, so typeDef.timestamptz will never be true
257
260
  return {
258
- sql: `DATETIME(${expr}, '${timezone}')`,
261
+ sql: `DATETIME(${expr}, ${this.sqlTimezoneLiteral(timezone)})`,
259
262
  typeDef: { type: 'timestamp' },
260
263
  };
261
264
  }
262
265
  sqlConvertFromCivilTime(expr, timezone, _destTypeDef) {
263
- return `TIMESTAMP(${expr}, '${timezone}')`;
266
+ return `TIMESTAMP(${expr}, ${this.sqlTimezoneLiteral(timezone)})`;
264
267
  }
265
268
  sqlTruncate(expr, unit, typeDef, inCivilTime, timezone) {
266
269
  if (inCivilTime) {
@@ -272,7 +275,7 @@ ${(0, utils_1.indent)(sql)}
272
275
  return `DATE_TRUNC(${expr}, ${unit})`;
273
276
  }
274
277
  // TIMESTAMP truncation with optional timezone
275
- const tzParam = timezone ? `, '${timezone}'` : '';
278
+ const tzParam = timezone ? `, ${this.sqlTimezoneLiteral(timezone)}` : '';
276
279
  return `TIMESTAMP_TRUNC(${expr}, ${unit}${tzParam})`;
277
280
  }
278
281
  sqlOffsetTime(expr, op, magnitude, unit, typeDef, inCivilTime, _timezone) {
@@ -299,10 +302,10 @@ ${(0, utils_1.indent)(sql)}
299
302
  const tz = qtz(qi);
300
303
  const src = cast.e.sql || '';
301
304
  if (op === 'timestamp::date' && tz) {
302
- return `DATE(${src},'${tz}')`;
305
+ return `DATE(${src},${this.sqlTimezoneLiteral(tz)})`;
303
306
  }
304
307
  if (op === 'date::timestamp' && tz) {
305
- return `TIMESTAMP(${src}, '${tz}')`;
308
+ return `TIMESTAMP(${src}, ${this.sqlTimezoneLiteral(tz)})`;
306
309
  }
307
310
  if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
308
311
  const castFunc = cast.safe ? 'SAFE_CAST' : 'CAST';
@@ -320,7 +323,7 @@ ${(0, utils_1.indent)(sql)}
320
323
  let timestampArgs = `'${literal}'`;
321
324
  const tz = timezone || qtz(qi);
322
325
  if (tz && tz !== 'UTC') {
323
- timestampArgs += `,'${tz}'`;
326
+ timestampArgs += `,${this.sqlTimezoneLiteral(tz)}`;
324
327
  }
325
328
  return `TIMESTAMP(${timestampArgs})`;
326
329
  }
@@ -1,4 +1,4 @@
1
- import type { Expr, Sampling, AtomicTypeDef, ATimestampTypeDef, TypecastExpr, RegexMatchExpr, MeasureTimeExpr, TimeExtractExpr, BasicAtomicTypeDef, RecordLiteralNode } from '../../model/malloy_types';
1
+ import type { Expr, Sampling, AtomicTypeDef, ATimestampTypeDef, TypecastExpr, RegexMatchExpr, MeasureTimeExpr, TimeExtractExpr, BasicAtomicTypeDef, RecordLiteralNode, OrderBy } from '../../model/malloy_types';
2
2
  import type { DialectFunctionOverloadDef } from '../functions';
3
3
  import type { CompiledOrderBy, DialectFieldList, OrderByClauseType, QueryInfo } from '../dialect';
4
4
  import { PostgresBase } from '../pg_impl';
@@ -49,8 +49,8 @@ export declare class TrinoDialect extends PostgresBase {
49
49
  sqlUnnestPipelineHead(isSingleton: boolean, sourceSQLExpression: string): string;
50
50
  sqlCreateFunction(id: string, funcText: string): string;
51
51
  sqlCreateTableAsSelect(tableName: string, sql: string): string;
52
- sqlCreateFunctionCombineLastStage(lastStageName: string, fieldList: DialectFieldList): string;
53
- sqlSelectAliasAsStruct(alias: string, fieldList: any): string;
52
+ sqlCreateFunctionCombineLastStage(lastStageName: string, fieldList: DialectFieldList, orderBy: OrderBy[] | undefined): string;
53
+ sqlSelectAliasAsStruct(alias: string, fieldList: DialectFieldList): string;
54
54
  keywords: string[];
55
55
  sqlConvertToCivilTime(expr: string, timezone: string, typeDef: AtomicTypeDef): {
56
56
  sql: string;
@@ -82,7 +82,7 @@ class TrinoDialect extends pg_impl_1.PostgresBase {
82
82
  this.orderByClause = 'output_name';
83
83
  this.nullMatchesFunctionSignature = false;
84
84
  this.supportsSelectReplace = false;
85
- this.supportsComplexFilteredSources = false;
85
+ this.supportsComplexFilteredSources = true;
86
86
  this.supportsTempTables = false;
87
87
  this.supportsCountApprox = true;
88
88
  this.supportsHyperLogLog = true;
@@ -223,7 +223,8 @@ class TrinoDialect extends pg_impl_1.PostgresBase {
223
223
  }
224
224
  buildTypeExpression(fieldList) {
225
225
  return fieldList
226
- .map(dlf => `${dlf.sqlOutputName} ${this.malloyTypeToSQLType(dlf.typeDef)}`)
226
+ .map(dlf => `${this.sqlQuoteIdentifier(dlf.rawName)} ` +
227
+ this.malloyTypeToSQLType(dlf.typeDef))
227
228
  .join(', \n');
228
229
  }
229
230
  // can array agg or any_value a struct...
@@ -325,15 +326,18 @@ ${(0, utils_1.indent)(sql)}
325
326
  );
326
327
  `;
327
328
  }
328
- sqlCreateFunctionCombineLastStage(lastStageName, fieldList) {
329
+ sqlCreateFunctionCombineLastStage(lastStageName, fieldList, orderBy) {
329
330
  const fields = fieldList.map(f => f.sqlExpression).join(', ');
330
331
  const definitions = this.buildTypeExpression(fieldList);
331
- return `SELECT ARRAY_AGG(CAST(ROW(${fields}) as ROW(${definitions}))) FROM ${lastStageName}\n`;
332
+ const o = this.sqlCombineLastStageOrderBy(orderBy, fieldList);
333
+ return `SELECT ARRAY_AGG(CAST(ROW(${fields}) as ROW(${definitions}))${o}) FROM ${lastStageName}\n`;
332
334
  }
333
335
  sqlSelectAliasAsStruct(alias, fieldList) {
334
- const fields = fieldList.map(f => f.sqlExpression).join(', ');
336
+ const fields = fieldList
337
+ .map(f => `${alias}.${this.sqlQuoteIdentifier(f.rawName)}`)
338
+ .join(', ');
335
339
  const definitions = this.buildTypeExpression(fieldList);
336
- return `CAST(ROW(${fields}) as ROW(${definitions})`;
340
+ return `CAST(ROW(${fields}) as ROW(${definitions}))`;
337
341
  }
338
342
  sqlConvertToCivilTime(expr, timezone, typeDef) {
339
343
  // Trino civil time = TIMESTAMPTZ with query timezone as stored timezone
@@ -341,13 +345,13 @@ ${(0, utils_1.indent)(sql)}
341
345
  if (typeDef.type === 'timestamp') {
342
346
  // TIMESTAMP (UTC wall clock) → TIMESTAMPTZ in query timezone
343
347
  return {
344
- sql: `at_timezone(with_timezone(${expr}, 'UTC'), '${timezone}')`,
348
+ sql: `at_timezone(with_timezone(${expr}, 'UTC'), ${this.sqlTimezoneLiteral(timezone)})`,
345
349
  typeDef: { type: 'timestamptz' },
346
350
  };
347
351
  }
348
352
  // TIMESTAMPTZ → TIMESTAMPTZ in query timezone (same instant, different stored tz)
349
353
  return {
350
- sql: `at_timezone(${expr}, '${timezone}')`,
354
+ sql: `at_timezone(${expr}, ${this.sqlTimezoneLiteral(timezone)})`,
351
355
  typeDef: { type: 'timestamptz' },
352
356
  };
353
357
  }
@@ -392,27 +396,27 @@ ${(0, utils_1.indent)(sql)}
392
396
  if (tz && srcTypeDef && dstTypeDef) {
393
397
  // TIMESTAMP → DATE: interpret as UTC, convert to query timezone
394
398
  if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
395
- return `CAST(at_timezone(with_timezone(${expr}, 'UTC'), '${tz}') AS DATE)`;
399
+ return `CAST(at_timezone(with_timezone(${expr}, 'UTC'), ${this.sqlTimezoneLiteral(tz)}) AS DATE)`;
396
400
  }
397
401
  // TIMESTAMPTZ → DATE: convert to query timezone
398
402
  if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
399
- return `CAST(at_timezone(${expr}, '${tz}') AS DATE)`;
403
+ return `CAST(at_timezone(${expr}, ${this.sqlTimezoneLiteral(tz)}) AS DATE)`;
400
404
  }
401
405
  // DATE → TIMESTAMP: interpret date in query timezone, return UTC wall clock
402
406
  if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
403
- return `CAST(at_timezone(with_timezone(CAST(${expr} AS TIMESTAMP), '${tz}'), 'UTC') AS TIMESTAMP)`;
407
+ return `CAST(at_timezone(with_timezone(CAST(${expr} AS TIMESTAMP), ${this.sqlTimezoneLiteral(tz)}), 'UTC') AS TIMESTAMP)`;
404
408
  }
405
409
  // DATE → TIMESTAMPTZ: interpret date in query timezone
406
410
  if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
407
- return `with_timezone(CAST(${expr} AS TIMESTAMP), '${tz}')`;
411
+ return `with_timezone(CAST(${expr} AS TIMESTAMP), ${this.sqlTimezoneLiteral(tz)})`;
408
412
  }
409
413
  // TIMESTAMPTZ → TIMESTAMP: convert to query timezone wall clock
410
414
  if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
411
- return `CAST(at_timezone(${expr}, '${tz}') AS TIMESTAMP)`;
415
+ return `CAST(at_timezone(${expr}, ${this.sqlTimezoneLiteral(tz)}) AS TIMESTAMP)`;
412
416
  }
413
417
  // TIMESTAMP → TIMESTAMPTZ: interpret TIMESTAMP as being in query timezone
414
418
  if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
415
- return `with_timezone(${expr}, '${tz}')`;
419
+ return `with_timezone(${expr}, ${this.sqlTimezoneLiteral(tz)})`;
416
420
  }
417
421
  }
418
422
  // No special handling needed, or no query timezone
@@ -567,13 +571,13 @@ ${(0, utils_1.indent)(sql)}
567
571
  const tz = timezone || qtz(qi);
568
572
  if (tz) {
569
573
  // Interpret wall clock time in timezone, convert to UTC wall clock, cast to TIMESTAMP
570
- return `CAST(at_timezone(with_timezone(TIMESTAMP '${literal}', '${tz}'), 'UTC') AS TIMESTAMP)`;
574
+ return `CAST(at_timezone(with_timezone(TIMESTAMP '${literal}', ${this.sqlTimezoneLiteral(tz)}), 'UTC') AS TIMESTAMP)`;
571
575
  }
572
576
  return `TIMESTAMP '${literal}'`;
573
577
  }
574
578
  sqlTimestamptzLiteral(_qi, literal, timezone) {
575
579
  // Use with_timezone to create a TIMESTAMP WITH TIME ZONE
576
- return `with_timezone(TIMESTAMP '${literal}', '${timezone}')`;
580
+ return `with_timezone(TIMESTAMP '${literal}', ${this.sqlTimezoneLiteral(timezone)})`;
577
581
  }
578
582
  sqlTimeExtractExpr(qi, from) {
579
583
  const pgUnits = pg_impl_1.timeExtractMap[from.units] || from.units;
@@ -584,11 +588,11 @@ ${(0, utils_1.indent)(sql)}
584
588
  // Convert both TIMESTAMP and TIMESTAMPTZ to query timezone for extraction
585
589
  if (from.e.typeDef.type === 'timestamp') {
586
590
  // TIMESTAMP: interpret as UTC, convert to query timezone
587
- extractFrom = `at_timezone(with_timezone(${extractFrom}, 'UTC'), '${tz}')`;
591
+ extractFrom = `at_timezone(with_timezone(${extractFrom}, 'UTC'), ${this.sqlTimezoneLiteral(tz)})`;
588
592
  }
589
593
  else {
590
594
  // TIMESTAMPTZ: convert to query timezone
591
- extractFrom = `at_timezone(${extractFrom}, '${tz}')`;
595
+ extractFrom = `at_timezone(${extractFrom}, ${this.sqlTimezoneLiteral(tz)})`;
592
596
  }
593
597
  }
594
598
  }
@@ -628,18 +632,18 @@ class PrestoDialect extends TrinoDialect {
628
632
  sqlTimestampLiteral(qi, literal, timezone) {
629
633
  const tz = timezone || qtz(qi);
630
634
  if (tz) {
631
- return `CAST(TIMESTAMP '${literal} ${tz}' AT TIME ZONE 'UTC' AS TIMESTAMP)`;
635
+ return `CAST(TIMESTAMP '${literal} ${this.checkedTimezoneName(tz)}' AT TIME ZONE 'UTC' AS TIMESTAMP)`;
632
636
  }
633
637
  return `TIMESTAMP '${literal}'`;
634
638
  }
635
639
  sqlTimestamptzLiteral(_qi, literal, timezone) {
636
- return `TIMESTAMP '${literal} ${timezone}'`;
640
+ return `TIMESTAMP '${literal} ${this.checkedTimezoneName(timezone)}'`;
637
641
  }
638
642
  sqlConvertToCivilTime(expr, timezone, _typeDef) {
639
643
  // Presto's AT TIME ZONE operator (not function) produces TIMESTAMPTZ
640
644
  // Reinterprets the instant in the target timezone
641
645
  return {
642
- sql: `${expr} AT TIME ZONE '${timezone}'`,
646
+ sql: `${expr} AT TIME ZONE ${this.sqlTimezoneLiteral(timezone)}`,
643
647
  typeDef: { type: 'timestamptz' },
644
648
  };
645
649
  }
@@ -658,26 +662,26 @@ class PrestoDialect extends TrinoDialect {
658
662
  if (tz && srcTypeDef && dstTypeDef) {
659
663
  // TIMESTAMP → DATE: interpret as UTC, convert to query timezone
660
664
  if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
661
- return `CAST((${expr} AT TIME ZONE 'UTC') AT TIME ZONE '${tz}' AS DATE)`;
665
+ return `CAST((${expr} AT TIME ZONE 'UTC') AT TIME ZONE ${this.sqlTimezoneLiteral(tz)} AS DATE)`;
662
666
  }
663
667
  // TIMESTAMPTZ → DATE: convert to query timezone
664
668
  if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
665
- return `CAST(${expr} AT TIME ZONE '${tz}' AS DATE)`;
669
+ return `CAST(${expr} AT TIME ZONE ${this.sqlTimezoneLiteral(tz)} AS DATE)`;
666
670
  }
667
671
  // DATE → TIMESTAMP: interpret date in query timezone, return UTC wall clock
668
672
  // Presto doesn't have a way to interpret TIMESTAMP in a non-UTC timezone,
669
673
  // so we build a TIMESTAMPTZ literal string and cast it
670
674
  if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
671
- const tstzLiteral = `CAST(CAST(${expr} AS VARCHAR) || ' 00:00:00 ${tz}' AS TIMESTAMP WITH TIME ZONE)`;
675
+ const tstzLiteral = `CAST(CAST(${expr} AS VARCHAR) || ' 00:00:00 ${this.checkedTimezoneName(tz)}' AS TIMESTAMP WITH TIME ZONE)`;
672
676
  return `CAST(${tstzLiteral} AS TIMESTAMP)`;
673
677
  }
674
678
  // DATE → TIMESTAMPTZ: interpret date in query timezone
675
679
  if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
676
- return `CAST(CAST(${expr} AS VARCHAR) || ' 00:00:00 ${tz}' AS TIMESTAMP WITH TIME ZONE)`;
680
+ return `CAST(CAST(${expr} AS VARCHAR) || ' 00:00:00 ${this.checkedTimezoneName(tz)}' AS TIMESTAMP WITH TIME ZONE)`;
677
681
  }
678
682
  // TIMESTAMPTZ → TIMESTAMP: convert to query timezone wall clock
679
683
  if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
680
- return `CAST(${expr} AT TIME ZONE '${tz}' AS TIMESTAMP)`;
684
+ return `CAST(${expr} AT TIME ZONE ${this.sqlTimezoneLiteral(tz)} AS TIMESTAMP)`;
681
685
  }
682
686
  // TIMESTAMP → TIMESTAMPTZ: interpret TIMESTAMP as UTC
683
687
  if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
@@ -701,11 +705,11 @@ class PrestoDialect extends TrinoDialect {
701
705
  if (from.e.typeDef.type === 'timestamp') {
702
706
  // TIMESTAMP: interpret as UTC, convert to query timezone
703
707
  // Presto uses AT TIME ZONE operator
704
- extractFrom = `(${extractFrom} AT TIME ZONE 'UTC') AT TIME ZONE '${tz}'`;
708
+ extractFrom = `(${extractFrom} AT TIME ZONE 'UTC') AT TIME ZONE ${this.sqlTimezoneLiteral(tz)}`;
705
709
  }
706
710
  else {
707
711
  // TIMESTAMPTZ: convert to query timezone
708
- extractFrom = `${extractFrom} AT TIME ZONE '${tz}'`;
712
+ extractFrom = `${extractFrom} AT TIME ZONE ${this.sqlTimezoneLiteral(tz)}`;
709
713
  }
710
714
  }
711
715
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { DuckDBDialect, StandardSQLDialect, TrinoDialect, PostgresDialect, SnowflakeDialect, MySQLDialect, DatabricksDialect, registerDialect, arg, qtz, overload, minScalar, anyExprType, minAggregate, maxScalar, sql, makeParam, param, variadicParam, literal, spread, Dialect, decodeDottedTablePath, validateDottedTablePath, } from './dialect';
1
+ export { DuckDBDialect, StandardSQLDialect, TrinoDialect, PostgresDialect, SnowflakeDialect, MySQLDialect, DatabricksDialect, SQLServerDialect, registerDialect, getDialect, arg, qtz, overload, minScalar, anyExprType, minAggregate, maxScalar, sql, makeParam, param, variadicParam, literal, spread, Dialect, decodeDottedTablePath, validateDottedTablePath, } from './dialect';
2
2
  export type { DialectFieldList, DialectFunctionOverloadDef, QueryInfo, MalloyStandardFunctionImplementations, DefinitionBlueprint, DefinitionBlueprintMap, OverloadedDefinitionBlueprint, DecodeDottedTablePathResult, DottedTablePathOptions, TablePathEscapeStyle, TablePathSegment, ValidateTablePathResult, } from './dialect';
3
3
  export type { QueryRecord, StructDef, TableSourceDef, SQLSourceDef, SourceDef, JoinFieldDef, NamedSourceDefs, MalloyQueryData, DateUnit, ExtractUnit, TimestampUnit, TemporalFieldType, QueryData, QueryValue, Expr, FilterCondition, Argument, Parameter, FieldDef, PipeSegment, QueryFieldDef, IndexFieldDef, TurtleDef, SearchValueMapResult, SearchIndexResult, ModelDef, Query, QueryResult, QueryResultDef, QueryRunStats, QueryScalar, NamedQueryDef, NamedModelObject, ExpressionType, FunctionDef, FunctionOverloadDef, FunctionParameterDef, ExpressionValueType, TypeDesc, FunctionParamTypeDesc, DocumentLocation, DocumentRange, DocumentPosition, Sampling, BasicAtomicTypeDef, BasicAtomicDef, AtomicTypeDef, AtomicFieldDef, ArrayDef, ArrayTypeDef, RecordTypeDef, RepeatedRecordTypeDef, RecordDef, RepeatedRecordDef, RecordLiteralNode, StringLiteralNode, ArrayLiteralNode, SourceComponentInfo, DateLiteralNode, TimestampLiteralNode, TimestamptzLiteralNode, TimeLiteralExpr, TypecastExpr, BuildID, BuildManifest, BuildManifestEntry, GivenValue, VirtualMap, } from './model';
4
4
  export { isSourceDef, isAtomic, isBasicAtomic, isCompoundArrayData, isJoined, isJoinedSource, isSamplingEnable, isSamplingPercent, isSamplingRows, isRepeatedRecord, isBasicArray, mkArrayDef, mkFieldDef, expressionIsAggregate, expressionIsAnalytic, expressionIsCalculation, expressionIsScalar, expressionIsUngroupedAggregate, indent, composeSQLExpr, isTimestampUnit, isDateUnit, constantExprToSQL, } from './model';
package/dist/index.js CHANGED
@@ -37,9 +37,9 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  };
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.Model = exports.MalloyTranslator = exports.malloyToQuery = exports.constantExprToSQL = exports.isDateUnit = exports.isTimestampUnit = exports.composeSQLExpr = exports.indent = exports.expressionIsUngroupedAggregate = exports.expressionIsScalar = exports.expressionIsCalculation = exports.expressionIsAnalytic = exports.expressionIsAggregate = exports.mkFieldDef = exports.mkArrayDef = exports.isBasicArray = exports.isRepeatedRecord = exports.isSamplingRows = exports.isSamplingPercent = exports.isSamplingEnable = exports.isJoinedSource = exports.isJoined = exports.isCompoundArrayData = exports.isBasicAtomic = exports.isAtomic = exports.isSourceDef = exports.validateDottedTablePath = exports.decodeDottedTablePath = exports.Dialect = exports.spread = exports.literal = exports.variadicParam = exports.param = exports.makeParam = exports.sql = exports.maxScalar = exports.minAggregate = exports.anyExprType = exports.minScalar = exports.overload = exports.qtz = exports.arg = exports.registerDialect = exports.DatabricksDialect = exports.MySQLDialect = exports.SnowflakeDialect = exports.PostgresDialect = exports.TrinoDialect = exports.StandardSQLDialect = exports.DuckDBDialect = void 0;
41
- exports.Reference = exports.EMPTY_BUILD_MANIFEST = exports.PersistSource = exports.annotationToTaglines = exports.annotationToTag = exports.tagFromAnnotations = exports.annotationsForRoute = exports.payloadOf = exports.routeOf = exports.RoutedNote = exports.Annotations = exports.sqlKey = exports.API = exports.sourceDefToSourceInfo = exports.modelDefToModelInfo = exports.toAsyncGenerator = exports.createConnectionsFromConfig = exports.getRegisteredConnectionTypes = exports.getConnectionTypeDisplayName = exports.getConnectionProperties = exports.registerConnectionType = exports.discoverConfig = exports.defaultConfigOverlays = exports.contextOverlay = exports.envOverlay = exports.MalloyConfig = exports.Manifest = exports.CacheManager = exports.InMemoryModelCache = exports.Explore = exports.DataWriter = exports.Parse = exports.JSONWriter = exports.CSVWriter = exports.QueryMaterializer = exports.Result = exports.PreparedResult = exports.TimestampTimeframe = exports.DateTimeframe = exports.SourceRelationship = exports.JoinRelationship = exports.MalloyError = exports.FixedConnectionMap = exports.InMemoryURLReader = exports.EmptyURLReader = exports.SingleConnectionRuntime = exports.ConnectionRuntime = exports.AtomicFieldType = exports.Runtime = exports.Malloy = void 0;
42
- exports.makeDigest = void 0;
40
+ exports.malloyToQuery = exports.constantExprToSQL = exports.isDateUnit = exports.isTimestampUnit = exports.composeSQLExpr = exports.indent = exports.expressionIsUngroupedAggregate = exports.expressionIsScalar = exports.expressionIsCalculation = exports.expressionIsAnalytic = exports.expressionIsAggregate = exports.mkFieldDef = exports.mkArrayDef = exports.isBasicArray = exports.isRepeatedRecord = exports.isSamplingRows = exports.isSamplingPercent = exports.isSamplingEnable = exports.isJoinedSource = exports.isJoined = exports.isCompoundArrayData = exports.isBasicAtomic = exports.isAtomic = exports.isSourceDef = exports.validateDottedTablePath = exports.decodeDottedTablePath = exports.Dialect = exports.spread = exports.literal = exports.variadicParam = exports.param = exports.makeParam = exports.sql = exports.maxScalar = exports.minAggregate = exports.anyExprType = exports.minScalar = exports.overload = exports.qtz = exports.arg = exports.getDialect = exports.registerDialect = exports.SQLServerDialect = exports.DatabricksDialect = exports.MySQLDialect = exports.SnowflakeDialect = exports.PostgresDialect = exports.TrinoDialect = exports.StandardSQLDialect = exports.DuckDBDialect = void 0;
41
+ exports.PersistSource = exports.annotationToTaglines = exports.annotationToTag = exports.tagFromAnnotations = exports.annotationsForRoute = exports.payloadOf = exports.routeOf = exports.RoutedNote = exports.Annotations = exports.sqlKey = exports.API = exports.sourceDefToSourceInfo = exports.modelDefToModelInfo = exports.toAsyncGenerator = exports.createConnectionsFromConfig = exports.getRegisteredConnectionTypes = exports.getConnectionTypeDisplayName = exports.getConnectionProperties = exports.registerConnectionType = exports.discoverConfig = exports.defaultConfigOverlays = exports.contextOverlay = exports.envOverlay = exports.MalloyConfig = exports.Manifest = exports.CacheManager = exports.InMemoryModelCache = exports.Explore = exports.DataWriter = exports.Parse = exports.JSONWriter = exports.CSVWriter = exports.QueryMaterializer = exports.Result = exports.PreparedResult = exports.TimestampTimeframe = exports.DateTimeframe = exports.SourceRelationship = exports.JoinRelationship = exports.MalloyError = exports.FixedConnectionMap = exports.InMemoryURLReader = exports.EmptyURLReader = exports.SingleConnectionRuntime = exports.ConnectionRuntime = exports.AtomicFieldType = exports.Runtime = exports.Malloy = exports.Model = exports.MalloyTranslator = void 0;
42
+ exports.makeDigest = exports.Reference = exports.EMPTY_BUILD_MANIFEST = void 0;
43
43
  var dialect_1 = require("./dialect");
44
44
  Object.defineProperty(exports, "DuckDBDialect", { enumerable: true, get: function () { return dialect_1.DuckDBDialect; } });
45
45
  Object.defineProperty(exports, "StandardSQLDialect", { enumerable: true, get: function () { return dialect_1.StandardSQLDialect; } });
@@ -48,7 +48,9 @@ Object.defineProperty(exports, "PostgresDialect", { enumerable: true, get: funct
48
48
  Object.defineProperty(exports, "SnowflakeDialect", { enumerable: true, get: function () { return dialect_1.SnowflakeDialect; } });
49
49
  Object.defineProperty(exports, "MySQLDialect", { enumerable: true, get: function () { return dialect_1.MySQLDialect; } });
50
50
  Object.defineProperty(exports, "DatabricksDialect", { enumerable: true, get: function () { return dialect_1.DatabricksDialect; } });
51
+ Object.defineProperty(exports, "SQLServerDialect", { enumerable: true, get: function () { return dialect_1.SQLServerDialect; } });
51
52
  Object.defineProperty(exports, "registerDialect", { enumerable: true, get: function () { return dialect_1.registerDialect; } });
53
+ Object.defineProperty(exports, "getDialect", { enumerable: true, get: function () { return dialect_1.getDialect; } });
52
54
  Object.defineProperty(exports, "arg", { enumerable: true, get: function () { return dialect_1.arg; } });
53
55
  Object.defineProperty(exports, "qtz", { enumerable: true, get: function () { return dialect_1.qtz; } });
54
56
  Object.defineProperty(exports, "overload", { enumerable: true, get: function () { return dialect_1.overload; } });
@@ -52,7 +52,6 @@ class NamedSource extends source_1.Source {
52
52
  }
53
53
  }
54
54
  modelStruct() {
55
- var _a;
56
55
  const modelEnt = this.modelEntry(this.ref);
57
56
  const entry = modelEnt === null || modelEnt === void 0 ? void 0 : modelEnt.entry;
58
57
  if (!entry) {
@@ -89,7 +88,6 @@ class NamedSource extends source_1.Source {
89
88
  return;
90
89
  }
91
90
  else {
92
- (_a = this.document()) === null || _a === void 0 ? void 0 : _a.checkExperimentalDialect(this, entry.dialect);
93
91
  if ((0, malloy_types_1.isSourceDef)(entry)) {
94
92
  // This struct is created as an unmodified reference to `entry`. Mark it
95
93
  // with entry's own identity; the modification path (DynamicSpace) clears
@@ -81,7 +81,7 @@ class RefinedSource extends source_1.Source {
81
81
  filters.push(el);
82
82
  }
83
83
  else if (el instanceof timezone_statement_1.TimezoneStatement) {
84
- newTimezone = el.tz;
84
+ newTimezone = el.timezoneName();
85
85
  }
86
86
  else {
87
87
  errTo.logError('unexpected-source-property', `Unexpected source property: '${errTo.elementType}'`);
@@ -19,12 +19,13 @@ class SQLSource extends source_1.Source {
19
19
  this.connectionNameInvalid = false;
20
20
  }
21
21
  sqlSourceRequest(doc) {
22
+ var _a;
22
23
  const partialModel = this.select.containsQueries
23
24
  ? doc.modelDef()
24
25
  : undefined;
25
26
  const [valid, phrases] = this.select.sqlPhrases(this.connectionName.refString);
26
27
  if (valid) {
27
- return (0, sql_block_1.getSourceRequest)(phrases, this.connectionName.refString, partialModel);
28
+ return (0, sql_block_1.getSourceRequest)(phrases, this.connectionName.refString, partialModel, (_a = this.translator()) === null || _a === void 0 ? void 0 : _a.root.virtualMap);
28
29
  }
29
30
  return undefined;
30
31
  }
@@ -77,9 +78,6 @@ class SQLSource extends source_1.Source {
77
78
  compileSQL: sql,
78
79
  };
79
80
  }
80
- else if (lookup.status === 'present') {
81
- doc.checkExperimentalDialect(this, lookup.value.dialect);
82
- }
83
81
  }
84
82
  getSourceDef() {
85
83
  var _a;
@@ -12,7 +12,7 @@ const error_factory_1 = require("../error-factory");
12
12
  const dialect_1 = require("../../../dialect");
13
13
  class TableSource extends source_1.Source {
14
14
  getSourceDef() {
15
- var _a, _b, _c;
15
+ var _a, _b;
16
16
  const info = this.getTableInfo();
17
17
  if (info === undefined) {
18
18
  return error_factory_1.ErrorFactory.structDef;
@@ -36,7 +36,6 @@ class TableSource extends source_1.Source {
36
36
  let msg = `Schema read failure for table '${tablePath}' for connection '${connectionName}'`;
37
37
  if (tableDefEntry) {
38
38
  if (tableDefEntry.status === 'present') {
39
- (_c = this.document()) === null || _c === void 0 ? void 0 : _c.checkExperimentalDialect(this, tableDefEntry.value.dialect);
40
39
  tableDefEntry.value.location = this.location;
41
40
  tableDefEntry.value.fields.forEach(field => {
42
41
  field.location = this.location;
@@ -1,12 +1,13 @@
1
+ import type { ExprString } from '../expressions/expr-string';
1
2
  import type { QueryBuilder } from '../types/query-builder';
2
3
  import { MalloyElement } from '../types/malloy-element';
3
4
  import type { QueryPropertyInterface } from '../types/query-property-interface';
4
5
  export declare class TimezoneStatement extends MalloyElement implements QueryPropertyInterface {
5
- readonly tz: string;
6
+ private readonly timezone;
6
7
  elementType: string;
7
8
  forceQueryClass: undefined;
8
9
  queryRefinementStage: undefined;
9
- constructor(tz: string);
10
- get isValid(): boolean;
10
+ constructor(timezone: ExprString);
11
+ timezoneName(): string | undefined;
11
12
  queryExecute(executeFor: QueryBuilder): void;
12
13
  }
@@ -5,27 +5,29 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.TimezoneStatement = void 0;
8
+ const dialect_1 = require("../../../dialect/dialect");
8
9
  const malloy_element_1 = require("../types/malloy-element");
9
- const luxon_1 = require("luxon");
10
10
  class TimezoneStatement extends malloy_element_1.MalloyElement {
11
- constructor(tz) {
12
- super();
13
- this.tz = tz;
11
+ constructor(timezone) {
12
+ super({ timezone });
13
+ this.timezone = timezone;
14
14
  this.elementType = 'timezone';
15
15
  this.forceQueryClass = undefined;
16
16
  this.queryRefinementStage = undefined;
17
17
  }
18
- get isValid() {
19
- try {
20
- luxon_1.DateTime.fromISO('2020-02-19T00:00:00', { zone: this.tz });
21
- return true;
22
- }
23
- catch {
24
- return false;
18
+ timezoneName() {
19
+ const name = this.timezone.value;
20
+ if (!dialect_1.Dialect.isSafeTimezoneName(name)) {
21
+ this.logError('invalid-timezone', { timezone: name }, { at: this.timezone.location });
22
+ return undefined;
25
23
  }
24
+ return name;
26
25
  }
27
26
  queryExecute(executeFor) {
28
- executeFor.resultFS.setTimezone(this.tz);
27
+ const timezone = this.timezoneName();
28
+ if (timezone !== undefined) {
29
+ executeFor.resultFS.setTimezone(timezone);
30
+ }
29
31
  }
30
32
  }
31
33
  exports.TimezoneStatement = TimezoneStatement;
@@ -152,6 +152,10 @@ class ImportStatement extends malloy_element_1.ListOf {
152
152
  }
153
153
  const importMe = { ...sourceEntry };
154
154
  importMe.as = dstName;
155
+ const dialect = (0, source_def_utils_1.dialectOfEntry)(importMe, importedModel);
156
+ if (dialect !== undefined) {
157
+ trans.checkExperimentalDialect(dialect, this.location);
158
+ }
155
159
  doc.setEntry(dstName, { entry: importMe, exported: false });
156
160
  // Every source the walk touched, routes included — not just the
157
161
  // persistent ones. A `#@ -persist` wrapper that adds a join is
@@ -152,13 +152,6 @@ export declare class Document extends MalloyElement implements NameSpace {
152
152
  modelDef(): ModelDef;
153
153
  getEntry(str: string): ModelEntry | undefined;
154
154
  setEntry(str: string, ent: ModelEntry): void;
155
- /**
156
- * Return an error message if this dialect is the first reference to this particular
157
- * dialect, and the dialect is marked as experimental, and we are not running tests.
158
- * @param dialect The dialect name
159
- * @returns The error message or undefined
160
- */
161
- checkExperimentalDialect(me: MalloyElement, dialect: string): void;
162
155
  private readonly dialectNameSpaces;
163
156
  getDialectNamespace(dialectName: string): NameSpace | undefined;
164
157
  }
@@ -656,9 +656,6 @@ class Document extends MalloyElement {
656
656
  if (this.globalNameSpace.getEntry(str) !== undefined) {
657
657
  this.logError('name-conflict-with-global', `Cannot redefine '${str}', which is in global namespace`);
658
658
  }
659
- if ((0, malloy_types_1.isSourceDef)(ent.entry)) {
660
- this.checkExperimentalDialect(this, ent.entry.dialect);
661
- }
662
659
  // Track if the model was modified after initialization
663
660
  if (this.didInitModel) {
664
661
  this.modelWasModified = true;
@@ -675,21 +672,6 @@ class Document extends MalloyElement {
675
672
  };
676
673
  }
677
674
  }
678
- /**
679
- * Return an error message if this dialect is the first reference to this particular
680
- * dialect, and the dialect is marked as experimental, and we are not running tests.
681
- * @param dialect The dialect name
682
- * @returns The error message or undefined
683
- */
684
- checkExperimentalDialect(me, dialect) {
685
- const t = this.translator();
686
- if (t &&
687
- t.firstReferenceToDialect(dialect) &&
688
- (0, dialect_1.getDialect)(dialect).experimental &&
689
- !t.experimentalDialectEnabled(dialect)) {
690
- me.logError('experimental-dialect-not-enabled', { dialect });
691
- }
692
- }
693
675
  getDialectNamespace(dialectName) {
694
676
  if (this.dialectNameSpaces.has(dialectName)) {
695
677
  return this.dialectNameSpaces.get(dialectName);
@@ -407,9 +407,11 @@ function expandRefUsage(seed, fields, alwaysJoinNames = []) {
407
407
  // given usage into the accumulator. Used both in-loop (joins reached
408
408
  // via reference paths) and upfront (always-on segment-level joins).
409
409
  function activateJoin(joinDef, joinPath) {
410
+ if (!(0, malloy_types_1.isJoined)(joinDef))
411
+ return;
410
412
  const joinKey = (0, utils_2.pathToKey)('join', joinPath);
411
413
  const thisDep = getJoin(activeJoinGraph, joinKey, joinPath);
412
- if (!(0, malloy_types_1.isJoined)(joinDef) || thisDep.checked)
414
+ if (thisDep.checked)
413
415
  return;
414
416
  thisDep.checked = true;
415
417
  const joinFieldUsage = getJoinFieldUsage(joinDef, joinPath);
@@ -462,8 +464,13 @@ function expandRefUsage(seed, fields, alwaysJoinNames = []) {
462
464
  ungroupings.push(...joinedUngroupings(refPath, def.ungroupings));
463
465
  }
464
466
  }
465
- // For paths through joins, additionaly track join relationships
466
- for (let joinLen = 1; joinLen < reference.path.length; joinLen++) {
467
+ // Track the joins along the path. A path which ends at a join and carries
468
+ // a unique key requirement is an aggregate computed over that join's rows,
469
+ // which should then include the join's on-clause fields in the field usage.
470
+ // A path which merely names a join-typed value does not.
471
+ const endsInTraversedJoin = reference.uniqueKeyRequirement !== undefined;
472
+ const joinDepth = reference.path.length - (endsInTraversedJoin ? 0 : 1);
473
+ for (let joinLen = 1; joinLen <= joinDepth; joinLen++) {
467
474
  const joinPath = reference.path.slice(0, joinLen);
468
475
  const joinDef = inNamespace(joinPath, fieldNameSpace);
469
476
  if (!joinDef)
@@ -803,14 +803,8 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
803
803
  return this.visitTimezoneStatement(cx.timezoneStatement());
804
804
  }
805
805
  visitTimezoneStatement(cx) {
806
- const timezone = this.getPlainStringFrom(cx);
807
- const timezoneStatement = this.astAt(new ast.TimezoneStatement(timezone), cx.string());
808
- if (!timezoneStatement.isValid) {
809
- this.astError(timezoneStatement, 'invalid-timezone', {
810
- timezone: timezoneStatement.tz,
811
- });
812
- }
813
- return this.astAt(timezoneStatement, cx);
806
+ const timezone = this.astAt(new ast.ExprString(this.getPlainStringFrom(cx)), cx.string());
807
+ return this.astAt(new ast.TimezoneStatement(timezone), cx);
814
808
  }
815
809
  visitQueryProperties(pcx) {
816
810
  const qProps = this.only(pcx.queryStatement().map(qcx => this.astAt(this.visit(qcx), qcx)), x => ast.isQueryProperty(x) && x, 'query statement');