@malloydata/malloy 0.0.435 → 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 (76) 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/core.d.ts +16 -4
  4. package/dist/api/foundation/core.js +23 -23
  5. package/dist/api/foundation/index.d.ts +1 -1
  6. package/dist/api/foundation/runtime.d.ts +21 -2
  7. package/dist/api/foundation/runtime.js +44 -31
  8. package/dist/api/foundation/types.d.ts +43 -3
  9. package/dist/connection/dbmeta.d.ts +29 -0
  10. package/dist/connection/dbmeta.js +46 -0
  11. package/dist/connection/index.d.ts +1 -0
  12. package/dist/connection/index.js +1 -0
  13. package/dist/dialect/databricks/databricks.d.ts +11 -12
  14. package/dist/dialect/databricks/databricks.js +24 -36
  15. package/dist/dialect/dialect.d.ts +56 -170
  16. package/dist/dialect/dialect.js +89 -90
  17. package/dist/dialect/duckdb/duckdb.d.ts +2 -2
  18. package/dist/dialect/duckdb/duckdb.js +1 -1
  19. package/dist/dialect/index.d.ts +1 -1
  20. package/dist/dialect/index.js +2 -2
  21. package/dist/dialect/mysql/mysql.d.ts +11 -12
  22. package/dist/dialect/mysql/mysql.js +20 -32
  23. package/dist/dialect/pg_impl.d.ts +9 -10
  24. package/dist/dialect/pg_impl.js +27 -73
  25. package/dist/dialect/postgres/postgres.d.ts +4 -4
  26. package/dist/dialect/postgres/postgres.js +4 -12
  27. package/dist/dialect/snowflake/snowflake.d.ts +11 -13
  28. package/dist/dialect/snowflake/snowflake.js +30 -82
  29. package/dist/dialect/sqlserver/sqlserver.d.ts +12 -13
  30. package/dist/dialect/sqlserver/sqlserver.js +46 -47
  31. package/dist/dialect/standardsql/standardsql.d.ts +11 -16
  32. package/dist/dialect/standardsql/standardsql.js +22 -73
  33. package/dist/dialect/trino/trino.d.ts +16 -18
  34. package/dist/dialect/trino/trino.js +127 -181
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.js +2 -2
  37. package/dist/lang/ast/expressions/boolean-value.d.ts +24 -0
  38. package/dist/lang/ast/expressions/boolean-value.js +41 -0
  39. package/dist/lang/ast/expressions/case.js +7 -0
  40. package/dist/lang/ast/expressions/expr-alternation-tree.js +11 -0
  41. package/dist/lang/ast/expressions/expr-cast.js +5 -0
  42. package/dist/lang/ast/expressions/expr-coalesce.js +4 -0
  43. package/dist/lang/ast/expressions/expr-func.js +13 -2
  44. package/dist/lang/ast/expressions/expr-minus.js +5 -2
  45. package/dist/lang/ast/expressions/expr-null.js +4 -0
  46. package/dist/lang/ast/expressions/pick-when.js +7 -0
  47. package/dist/lang/ast/field-space/index-field-space.js +13 -1
  48. package/dist/lang/ast/field-space/query-spaces.js +11 -0
  49. package/dist/lang/ast/query-builders/reduce-builder.js +1 -1
  50. package/dist/lang/ast/query-items/field-declaration.d.ts +5 -0
  51. package/dist/lang/ast/query-items/field-declaration.js +22 -16
  52. package/dist/lang/ast/query-items/field-references.d.ts +6 -0
  53. package/dist/lang/ast/query-items/field-references.js +23 -0
  54. package/dist/lang/ast/query-properties/filters.js +3 -1
  55. package/dist/lang/ast/statements/import-statement.js +13 -0
  56. package/dist/lang/ast/types/expression-def.js +17 -2
  57. package/dist/lang/ast/types/malloy-element.d.ts +24 -1
  58. package/dist/lang/ast/types/malloy-element.js +63 -26
  59. package/dist/lang/ast/view-elements/reference-view.js +4 -0
  60. package/dist/lang/ast/view-elements/refine-utils.js +1 -0
  61. package/dist/lang/composite-source-utils.js +10 -17
  62. package/dist/lang/parse-log.d.ts +12 -0
  63. package/dist/lang/parse-log.js +2 -0
  64. package/dist/lang/test/test-translator.d.ts +2 -1
  65. package/dist/lang/test/test-translator.js +37 -3
  66. package/dist/model/expression_compiler.js +8 -7
  67. package/dist/model/filter_compilers.js +13 -12
  68. package/dist/model/given_binding.d.ts +8 -0
  69. package/dist/model/given_binding.js +13 -0
  70. package/dist/model/index.d.ts +1 -1
  71. package/dist/model/index.js +2 -1
  72. package/dist/model/sql_compiled.d.ts +19 -20
  73. package/dist/model/sql_compiled.js +29 -17
  74. package/dist/version.d.ts +1 -1
  75. package/dist/version.js +1 -1
  76. package/package.json +4 -4
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.validateDottedTablePath = exports.decodeDottedTablePath = exports.TinyParser = exports.getMalloyStandardFunctions = exports.registerDialect = exports.getDialect = exports.SQLServerDialect = exports.DatabricksDialect = exports.MySQLDialect = exports.TrinoDialect = exports.SnowflakeDialect = exports.DuckDBDialect = exports.PostgresDialect = exports.StandardSQLDialect = exports.qtz = exports.Dialect = exports.sql = exports.literal = exports.variadicParam = exports.param = exports.spread = exports.maxScalar = exports.minAggregate = exports.minScalar = exports.overload = exports.makeParam = exports.anyExprType = exports.arg = void 0;
7
+ exports.validateDottedTablePath = exports.decodeDottedTablePath = exports.TinyParser = exports.getMalloyStandardFunctions = exports.registerDialect = exports.getDialect = exports.SQLServerDialect = exports.DatabricksDialect = exports.MySQLDialect = exports.TrinoDialect = exports.SnowflakeDialect = exports.DuckDBDialect = exports.PostgresDialect = exports.StandardSQLDialect = exports.queryZone = exports.Dialect = exports.sql = exports.literal = exports.variadicParam = exports.param = exports.spread = exports.maxScalar = exports.minAggregate = exports.minScalar = exports.overload = exports.makeParam = exports.anyExprType = exports.arg = void 0;
8
8
  var util_1 = require("./functions/util");
9
9
  Object.defineProperty(exports, "arg", { enumerable: true, get: function () { return util_1.arg; } });
10
10
  Object.defineProperty(exports, "anyExprType", { enumerable: true, get: function () { return util_1.anyExprType; } });
@@ -20,7 +20,7 @@ Object.defineProperty(exports, "literal", { enumerable: true, get: function () {
20
20
  Object.defineProperty(exports, "sql", { enumerable: true, get: function () { return util_1.sql; } });
21
21
  var dialect_1 = require("./dialect");
22
22
  Object.defineProperty(exports, "Dialect", { enumerable: true, get: function () { return dialect_1.Dialect; } });
23
- Object.defineProperty(exports, "qtz", { enumerable: true, get: function () { return dialect_1.qtz; } });
23
+ Object.defineProperty(exports, "queryZone", { enumerable: true, get: function () { return dialect_1.queryZone; } });
24
24
  var standardsql_1 = require("./standardsql");
25
25
  Object.defineProperty(exports, "StandardSQLDialect", { enumerable: true, get: function () { return standardsql_1.StandardSQLDialect; } });
26
26
  var postgres_1 = require("./postgres");
@@ -1,5 +1,5 @@
1
- import type { Sampling, MeasureTimeExpr, RegexMatchExpr, TimeExtractExpr, TypecastExpr, BasicAtomicTypeDef, AtomicTypeDef, TimestampTypeDef, ArrayLiteralNode, RecordLiteralNode } from '../../model/malloy_types';
2
- import type { BooleanTypeSupport, CompiledOrderBy, DialectFieldList, FieldReferenceType, OrderByClauseType, QueryInfo } from '../dialect';
1
+ import type { Sampling, MeasureTimeExpr, RegexMatchExpr, TypecastExpr, BasicAtomicTypeDef, AtomicTypeDef, ArrayLiteralNode, RecordLiteralNode, ATimestampTypeDef, TimestampUnit, ExtractUnit } from '../../model/malloy_types';
2
+ import type { CivilTime, BooleanTypeSupport, CompiledOrderBy, DialectFieldList, FieldReferenceType, OrderByClauseType, QueryInfo } from '../dialect';
3
3
  import { Dialect } from '../dialect';
4
4
  import type { DialectFunctionOverloadDef } from '../functions';
5
5
  export declare class MySQLDialect extends Dialect {
@@ -60,18 +60,17 @@ export declare class MySQLDialect extends Dialect {
60
60
  sqlSelectAliasAsStruct(_alias: string, _fieldList: DialectFieldList): string;
61
61
  sqlCreateTableAsSelect(_tableName: string, _sql: string): string;
62
62
  sqlNowExpr(): string;
63
- sqlConvertToCivilTime(expr: string, timezone: string, _typeDef: AtomicTypeDef): {
64
- sql: string;
65
- typeDef: AtomicTypeDef;
66
- };
67
- sqlConvertFromCivilTime(expr: string, timezone: string, _destTypeDef: TimestampTypeDef): string;
68
- sqlTruncate(expr: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
69
- sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
70
- sqlTimeExtractExpr(qi: QueryInfo, te: TimeExtractExpr): string;
71
- sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
63
+ sqlConvertToCivilTime(expr: string, timezone: string, _typeDef: ATimestampTypeDef): string;
64
+ sqlConvertFromCivilTime(expr: string, timezone: string, _destTypeDef: ATimestampTypeDef): string;
65
+ sqlTruncate(expr: string, unit: TimestampUnit, _civilType: CivilTime): string;
66
+ sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, _civilType: CivilTime): string;
67
+ sqlCivilExtract(expr: string, units: ExtractUnit, _civilType: CivilTime): string;
68
+ sqlCivilToDate(expr: string): string;
69
+ sqlDateToCivil(expr: string): string;
70
+ sqlCast(_qi: QueryInfo, cast: TypecastExpr): string;
72
71
  sqlRegexpMatch(df: RegexMatchExpr): string;
73
72
  sqlDateLiteral(_qi: QueryInfo, literal: string): string;
74
- sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
73
+ sqlCivilLiteral(literal: string): string;
75
74
  sqlTimestamptzLiteral(_qi: QueryInfo, _literal: string, _timezone: string): string;
76
75
  sqlMeasureTimeExpr(df: MeasureTimeExpr): string;
77
76
  sqlAggDistinct(_key: string, _values: string[], _func: (valNames: string[]) => string): string;
@@ -351,17 +351,18 @@ class MySQLDialect extends dialect_1.Dialect {
351
351
  sqlNowExpr() {
352
352
  return 'LOCALTIMESTAMP';
353
353
  }
354
+ // Civil time is a DATETIME, which has no zone
354
355
  sqlConvertToCivilTime(expr, timezone, _typeDef) {
355
- // MySQL has no timestamptz type, so typeDef.timestamptz will never be true
356
- return {
357
- sql: `CONVERT_TZ(${expr}, 'UTC', ${this.sqlTimezoneLiteral(timezone)})`,
358
- typeDef: { type: 'timestamp' },
359
- };
356
+ return timezone === 'UTC'
357
+ ? expr
358
+ : `CONVERT_TZ(${expr}, 'UTC', ${this.sqlTimezoneLiteral(timezone)})`;
360
359
  }
361
360
  sqlConvertFromCivilTime(expr, timezone, _destTypeDef) {
362
- return `CONVERT_TZ(${expr}, ${this.sqlTimezoneLiteral(timezone)}, 'UTC')`;
361
+ return timezone === 'UTC'
362
+ ? expr
363
+ : `CONVERT_TZ(${expr}, ${this.sqlTimezoneLiteral(timezone)}, 'UTC')`;
363
364
  }
364
- sqlTruncate(expr, unit, _typeDef, _inCivilTime, _timezone) {
365
+ sqlTruncate(expr, unit, _civilType) {
365
366
  // For week truncation, adjust to Sunday first
366
367
  // DAYOFWEEK returns 1=Sunday, 2=Monday, etc., so subtract (DAYOFWEEK-1) days
367
368
  const adjustedExpr = unit === 'week'
@@ -392,7 +393,7 @@ class MySQLDialect extends dialect_1.Dialect {
392
393
  }
393
394
  return `TIMESTAMP(DATE_FORMAT(${adjustedExpr}, ${format}))`;
394
395
  }
395
- sqlOffsetTime(expr, op, magnitude, unit, _typeDef, _inCivilTime, _timezone) {
396
+ sqlOffsetTime(expr, op, magnitude, unit, _civilType) {
396
397
  // Convert quarter/week to supported units
397
398
  let offsetUnit = unit;
398
399
  let offsetMag = magnitude;
@@ -407,27 +408,18 @@ class MySQLDialect extends dialect_1.Dialect {
407
408
  const interval = `INTERVAL ${offsetMag} ${offsetUnit}`;
408
409
  return `(${expr} ${op} ${interval})`;
409
410
  }
410
- sqlTimeExtractExpr(qi, te) {
411
- const msUnits = msExtractionMap[te.units] || te.units;
412
- let extractFrom = te.e.sql;
413
- if (malloy_types_1.TD.isTimestamp(te.e.typeDef)) {
414
- const tz = (0, dialect_1.qtz)(qi);
415
- if (tz) {
416
- extractFrom = `CONVERT_TZ(${extractFrom}, 'UTC', ${this.sqlTimezoneLiteral(tz)})`;
417
- }
418
- }
419
- return `${msUnits}(${extractFrom})`;
411
+ sqlCivilExtract(expr, units, _civilType) {
412
+ return `${msExtractionMap[units] || units}(${expr})`;
413
+ }
414
+ sqlCivilToDate(expr) {
415
+ return `CAST(${expr} AS DATE)`;
416
+ }
417
+ sqlDateToCivil(expr) {
418
+ return `CAST(${expr} AS DATETIME)`;
420
419
  }
421
- sqlCast(qi, cast) {
420
+ sqlCast(_qi, cast) {
422
421
  const srcSQL = cast.e.sql || 'internal-error-in-sql-generation';
423
- const { op, srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
424
- const tz = (0, dialect_1.qtz)(qi);
425
- if (op === 'timestamp::date' && tz) {
426
- return `CAST(CONVERT_TZ(${srcSQL}, 'UTC', ${this.sqlTimezoneLiteral(tz)}) AS DATE) `;
427
- }
428
- else if (op === 'date::timestamp' && tz) {
429
- return ` CONVERT_TZ(${srcSQL}, ${this.sqlTimezoneLiteral(tz)}, 'UTC')`;
430
- }
422
+ const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
431
423
  if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
432
424
  if (cast.safe) {
433
425
  throw new Error("Mysql dialect doesn't support Safe Cast");
@@ -445,11 +437,7 @@ class MySQLDialect extends dialect_1.Dialect {
445
437
  sqlDateLiteral(_qi, literal) {
446
438
  return `DATE '${literal}'`;
447
439
  }
448
- sqlTimestampLiteral(qi, literal, timezone) {
449
- const tz = timezone || (0, dialect_1.qtz)(qi);
450
- if (tz) {
451
- return `CONVERT_TZ('${literal}', ${this.sqlTimezoneLiteral(tz)}, 'UTC')`;
452
- }
440
+ sqlCivilLiteral(literal) {
453
441
  return `TIMESTAMP '${literal}'`;
454
442
  }
455
443
  sqlTimestamptzLiteral(_qi, _literal, _timezone) {
@@ -1,5 +1,5 @@
1
- import type { ArrayLiteralNode, AtomicTypeDef, ATimestampTypeDef, RecordLiteralNode, RegexMatchExpr, TimeExtractExpr, TimestampUnit, TypecastExpr } from '../model/malloy_types';
2
- import type { QueryInfo } from './dialect';
1
+ import type { ArrayLiteralNode, ATimestampTypeDef, RecordLiteralNode, ExtractUnit, RegexMatchExpr, TimestampUnit, TypecastExpr } from '../model/malloy_types';
2
+ import type { CivilTime, QueryInfo } from './dialect';
3
3
  import { Dialect } from './dialect';
4
4
  export declare const timeExtractMap: Record<string, string>;
5
5
  /**
@@ -14,18 +14,17 @@ export declare abstract class PostgresBase extends Dialect {
14
14
  identifierQuoteChar: string;
15
15
  sqlLiteralString(literal: string): string;
16
16
  sqlNowExpr(): string;
17
- sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
18
- sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
17
+ sqlCivilExtract(expr: string, units: ExtractUnit, _civilType: CivilTime): string;
18
+ sqlCast(_qi: QueryInfo, cast: TypecastExpr): string;
19
19
  sqlRegexpMatch(df: RegexMatchExpr): string;
20
20
  sqlDateLiteral(_qi: QueryInfo, literal: string): string;
21
- sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
21
+ sqlCivilLiteral(literal: string): string;
22
22
  sqlTimestamptzLiteral(_qi: QueryInfo, literal: string, timezone: string): string;
23
23
  sqlLiteralRecord(_lit: RecordLiteralNode): string;
24
24
  sqlLiteralArray(lit: ArrayLiteralNode): string;
25
- sqlConvertToCivilTime(expr: string, timezone: string, typeDef: AtomicTypeDef): {
26
- sql: string;
27
- typeDef: AtomicTypeDef;
28
- };
25
+ sqlConvertToCivilTime(expr: string, timezone: string, typeDef: ATimestampTypeDef): string;
29
26
  sqlConvertFromCivilTime(expr: string, timezone: string, destTypeDef: ATimestampTypeDef): string;
30
- sqlTruncate(expr: string, unit: TimestampUnit, _typeDef: AtomicTypeDef, inCivilTime: boolean, _timezone?: string): string;
27
+ sqlTruncate(expr: string, unit: TimestampUnit, _civilType: CivilTime): string;
28
+ sqlCivilToDate(expr: string): string;
29
+ sqlDateToCivil(expr: string): string;
31
30
  }
@@ -39,46 +39,13 @@ class PostgresBase extends dialect_1.Dialect {
39
39
  sqlNowExpr() {
40
40
  return 'LOCALTIMESTAMP';
41
41
  }
42
- sqlTimeExtractExpr(qi, from) {
43
- const units = exports.timeExtractMap[from.units] || from.units;
44
- let extractFrom = from.e.sql;
45
- if (malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
46
- const tz = (0, dialect_1.qtz)(qi);
47
- if (tz) {
48
- extractFrom = `(${extractFrom}::TIMESTAMPTZ AT TIME ZONE ${this.sqlTimezoneLiteral(tz)})`;
49
- }
50
- }
51
- const extracted = `EXTRACT(${units} FROM ${extractFrom})`;
52
- return from.units === 'day_of_week' ? `(${extracted}+1)` : extracted;
42
+ sqlCivilExtract(expr, units, _civilType) {
43
+ const extracted = `EXTRACT(${exports.timeExtractMap[units] || units} FROM ${expr})`;
44
+ return units === 'day_of_week' ? `(${extracted}+1)` : extracted;
53
45
  }
54
- sqlCast(qi, cast) {
46
+ sqlCast(_qi, cast) {
55
47
  const expr = cast.e.sql || '';
56
48
  const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
57
- const tz = (0, dialect_1.qtz)(qi);
58
- // Timezone-aware casts when query timezone is set
59
- if (tz && srcTypeDef && dstTypeDef) {
60
- // TIMESTAMP → DATE: convert via TIMESTAMPTZ to query timezone
61
- if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
62
- return `CAST((${expr}::TIMESTAMPTZ) AT TIME ZONE ${this.sqlTimezoneLiteral(tz)} AS DATE)`;
63
- }
64
- // TIMESTAMPTZ → DATE: convert to query timezone
65
- if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
66
- return `CAST((${expr}) AT TIME ZONE ${this.sqlTimezoneLiteral(tz)} AS DATE)`;
67
- }
68
- // DATE → TIMESTAMP: interpret date in query timezone, return UTC timestamp
69
- if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
70
- return `CAST((${expr})::TIMESTAMP AT TIME ZONE ${this.sqlTimezoneLiteral(tz)} AS TIMESTAMP)`;
71
- }
72
- // DATE → TIMESTAMPTZ: interpret date in query timezone
73
- if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
74
- return `(${expr})::TIMESTAMP AT TIME ZONE ${this.sqlTimezoneLiteral(tz)}`;
75
- }
76
- // TIMESTAMPTZ → TIMESTAMP: convert to query timezone (returns TIMESTAMP)
77
- if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
78
- return `(${expr}) AT TIME ZONE ${this.sqlTimezoneLiteral(tz)}`;
79
- }
80
- }
81
- // No special handling needed, or no query timezone
82
49
  if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
83
50
  const castFunc = cast.safe ? 'TRY_CAST' : 'CAST';
84
51
  return `${castFunc}(${expr} AS ${dstSQLType})`;
@@ -91,11 +58,7 @@ class PostgresBase extends dialect_1.Dialect {
91
58
  sqlDateLiteral(_qi, literal) {
92
59
  return `DATE '${literal}'`;
93
60
  }
94
- sqlTimestampLiteral(qi, literal, timezone) {
95
- const tz = timezone || (0, dialect_1.qtz)(qi);
96
- if (tz) {
97
- return `TIMESTAMPTZ '${literal} ${this.checkedTimezoneName(tz)}'::TIMESTAMP`;
98
- }
61
+ sqlCivilLiteral(literal) {
99
62
  return `TIMESTAMP '${literal}'`;
100
63
  }
101
64
  sqlTimestamptzLiteral(_qi, literal, timezone) {
@@ -109,44 +72,35 @@ class PostgresBase extends dialect_1.Dialect {
109
72
  return 'ARRAY[' + array.join(',') + ']';
110
73
  }
111
74
  sqlConvertToCivilTime(expr, timezone, typeDef) {
112
- // PostgreSQL/DuckDB: AT TIME ZONE is polymorphic
113
- // For timestamptz (TIMESTAMPTZ): AT TIME ZONE converts to plain TIMESTAMP (civil in timezone)
114
- if (typeDef.type === 'timestamptz') {
115
- return {
116
- sql: `(${expr}) AT TIME ZONE ${this.sqlTimezoneLiteral(timezone)}`,
117
- typeDef: { type: 'timestamp' },
118
- };
75
+ const tz = this.sqlTimezoneLiteral(timezone);
76
+ if (malloy_types_1.TD.isTimestamptz(typeDef)) {
77
+ return `(${expr}) AT TIME ZONE ${tz}`;
119
78
  }
120
- // For plain timestamps: cast to TIMESTAMPTZ (interprets as UTC)
121
- // Then AT TIME ZONE converts to plain TIMESTAMP (civil in timezone)
122
- return {
123
- sql: `(${expr})::TIMESTAMPTZ AT TIME ZONE ${this.sqlTimezoneLiteral(timezone)}`,
124
- typeDef: { type: 'timestamp' },
125
- };
79
+ return timezone === 'UTC'
80
+ ? expr
81
+ : `(${expr})::TIMESTAMPTZ AT TIME ZONE ${tz}`;
126
82
  }
127
83
  sqlConvertFromCivilTime(expr, timezone, destTypeDef) {
128
- if (destTypeDef.type === 'timestamptz') {
129
- return `(${expr}) AT TIME ZONE ${this.sqlTimezoneLiteral(timezone)}`;
84
+ const tz = this.sqlTimezoneLiteral(timezone);
85
+ if (malloy_types_1.TD.isTimestamptz(destTypeDef)) {
86
+ return `(${expr}) AT TIME ZONE ${tz}`;
130
87
  }
131
- return `((${expr}) AT TIME ZONE ${this.sqlTimezoneLiteral(timezone)})::TIMESTAMP`;
88
+ return timezone === 'UTC'
89
+ ? expr
90
+ : `((${expr}) AT TIME ZONE ${tz})::TIMESTAMP`;
132
91
  }
133
- sqlTruncate(expr, unit, _typeDef, inCivilTime, _timezone) {
134
- // PostgreSQL/DuckDB starts weeks on Monday, Malloy wants Sunday
135
- // Add 1 day before truncating, subtract 1 day after
136
- let truncated;
92
+ sqlTruncate(expr, unit, _civilType) {
93
+ // Weeks start on Monday here, so truncate the following day
137
94
  if (unit === 'week') {
138
- truncated = `(DATE_TRUNC('${unit}', (${expr} + INTERVAL '1' DAY)) - INTERVAL '1' DAY)`;
139
- }
140
- else {
141
- truncated = `DATE_TRUNC('${unit}', ${expr})`;
95
+ return `(DATE_TRUNC('${unit}', (${expr} + INTERVAL '1' DAY)) - INTERVAL '1' DAY)`;
142
96
  }
143
- // DATE_TRUNC returns DATE for calendar units (day/week/month/quarter/year)
144
- // When in civil time (plain TIMESTAMP), cast DATE back to TIMESTAMP to continue operations
145
- const calendarUnits = ['day', 'week', 'month', 'quarter', 'year'];
146
- if (inCivilTime && calendarUnits.includes(unit)) {
147
- return `(${truncated})::TIMESTAMP`;
148
- }
149
- return truncated;
97
+ return `DATE_TRUNC('${unit}', ${expr})`;
98
+ }
99
+ sqlCivilToDate(expr) {
100
+ return `CAST(${expr} AS DATE)`;
101
+ }
102
+ sqlDateToCivil(expr) {
103
+ return `CAST(${expr} AS TIMESTAMP)`;
150
104
  }
151
105
  }
152
106
  exports.PostgresBase = PostgresBase;
@@ -1,6 +1,6 @@
1
- import type { Sampling, AtomicTypeDef, TypecastExpr, MeasureTimeExpr, BasicAtomicTypeDef, RecordLiteralNode, ArrayLiteralNode, TimeExtractExpr, TimestampUnit } from '../../model/malloy_types';
1
+ import type { Sampling, AtomicTypeDef, TypecastExpr, MeasureTimeExpr, BasicAtomicTypeDef, RecordLiteralNode, ArrayLiteralNode, TimestampUnit, ExtractUnit } from '../../model/malloy_types';
2
2
  import type { DialectFunctionOverloadDef } from '../functions';
3
- import { type CompiledOrderBy, type DialectFieldList, type FieldReferenceType, type QueryInfo } from '../dialect';
3
+ import { type CompiledOrderBy, type DialectFieldList, type FieldReferenceType, type QueryInfo, type CivilTime } from '../dialect';
4
4
  import { PostgresBase } from '../pg_impl';
5
5
  export declare class PostgresDialect extends PostgresBase {
6
6
  name: string;
@@ -46,7 +46,7 @@ export declare class PostgresDialect extends PostgresBase {
46
46
  sqlFinalStage(lastStageName: string, _fields: string[]): string;
47
47
  sqlSelectAliasAsStruct(alias: string): string;
48
48
  sqlCreateTableAsSelect(_tableName: string, _sql: string): string;
49
- sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
49
+ sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, _civilType: CivilTime): string;
50
50
  sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
51
51
  sqlMeasureTimeExpr(df: MeasureTimeExpr): string;
52
52
  sqlSumDistinct(key: string, value: string, funcName: string): string;
@@ -66,5 +66,5 @@ export declare class PostgresDialect extends PostgresBase {
66
66
  validateTypeName(sqlType: string): boolean;
67
67
  sqlLiteralRecord(lit: RecordLiteralNode): string;
68
68
  sqlLiteralArray(lit: ArrayLiteralNode): string;
69
- sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
69
+ sqlCivilExtract(expr: string, units: ExtractUnit, _civilType: CivilTime): string;
70
70
  }
@@ -200,7 +200,7 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
200
200
  sqlCreateTableAsSelect(_tableName, _sql) {
201
201
  throw new Error('Not implemented Yet');
202
202
  }
203
- sqlOffsetTime(expr, op, magnitude, unit, _typeDef, _inCivilTime, _timezone) {
203
+ sqlOffsetTime(expr, op, magnitude, unit, _civilType) {
204
204
  // Convert quarter/week to supported units
205
205
  let offsetUnit = unit;
206
206
  let offsetMag = magnitude;
@@ -387,19 +387,11 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
387
387
  const array = lit.kids.values.map(val => val.sql);
388
388
  return 'JSONB_BUILD_ARRAY(' + array.join(',') + ')';
389
389
  }
390
- sqlTimeExtractExpr(qi, from) {
391
- const units = pg_impl_1.timeExtractMap[from.units] || from.units;
392
- let extractFrom = from.e.sql;
393
- if (malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
394
- const tz = (0, dialect_1.qtz)(qi);
395
- if (tz) {
396
- extractFrom = `(${extractFrom}::TIMESTAMPTZ AT TIME ZONE ${this.sqlTimezoneLiteral(tz)})`;
397
- }
398
- }
390
+ sqlCivilExtract(expr, units, _civilType) {
399
391
  // PostgreSQL before 14 returns a double precision for EXTRACT, cast to integer
400
392
  // since it is common to pass an extraction to mod ( like in fitler expressions )
401
- const extracted = `EXTRACT(${units} FROM ${extractFrom})::integer`;
402
- return from.units === 'day_of_week' ? `(${extracted}+1)` : `(${extracted})`;
393
+ const extracted = `EXTRACT(${pg_impl_1.timeExtractMap[units] || units} FROM ${expr})::integer`;
394
+ return units === 'day_of_week' ? `(${extracted}+1)` : `(${extracted})`;
403
395
  }
404
396
  }
405
397
  exports.PostgresDialect = PostgresDialect;
@@ -1,7 +1,7 @@
1
- import type { Sampling, AtomicTypeDef, TimeExtractExpr, TypecastExpr, MeasureTimeExpr, RegexMatchExpr, BasicAtomicTypeDef, TimestampTypeDef, ArrayLiteralNode, RecordLiteralNode } from '../../model/malloy_types';
1
+ import type { Sampling, AtomicTypeDef, TypecastExpr, MeasureTimeExpr, RegexMatchExpr, BasicAtomicTypeDef, ArrayLiteralNode, RecordLiteralNode, ATimestampTypeDef, TimestampUnit, ExtractUnit } from '../../model/malloy_types';
2
2
  import type { DialectFunctionOverloadDef } from '../functions';
3
3
  import type { CompiledOrderBy, DialectFieldList, FieldReferenceType, IntegerTypeMapping, QueryInfo } from '../dialect';
4
- import { Dialect } from '../dialect';
4
+ import { CivilTime, Dialect } from '../dialect';
5
5
  export declare class SnowflakeDialect extends Dialect {
6
6
  name: string;
7
7
  experimental: boolean;
@@ -50,19 +50,17 @@ export declare class SnowflakeDialect extends Dialect {
50
50
  sqlCreateFunctionCombineLastStage(_lastStageName: string): string;
51
51
  sqlSelectAliasAsStruct(alias: string): string;
52
52
  sqlCreateTableAsSelect(tableName: string, sql: string): string;
53
- sqlConvertToCivilTime(expr: string, timezone: string, typeDef: AtomicTypeDef): {
54
- sql: string;
55
- typeDef: AtomicTypeDef;
56
- };
57
- sqlConvertFromCivilTime(expr: string, timezone: string, _destTypeDef: TimestampTypeDef): string;
58
- sqlTruncate(expr: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
59
- sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: string, typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
60
- sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
61
- private atTz;
53
+ sqlConvertToCivilTime(expr: string, timezone: string, typeDef: ATimestampTypeDef): string;
54
+ sqlConvertFromCivilTime(expr: string, timezone: string, destTypeDef: ATimestampTypeDef): string;
55
+ sqlTruncate(expr: string, unit: TimestampUnit, _civilType: CivilTime): string;
56
+ sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, civilType: CivilTime): string;
57
+ sqlCivilExtract(expr: string, units: ExtractUnit, _civilType: CivilTime): string;
58
+ sqlCivilToDate(expr: string): string;
59
+ sqlDateToCivil(expr: string): string;
62
60
  sqlNowExpr(): string;
63
- sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
61
+ sqlCast(_qi: QueryInfo, cast: TypecastExpr): string;
64
62
  sqlDateLiteral(_qi: QueryInfo, literal: string): string;
65
- sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
63
+ sqlCivilLiteral(literal: string): string;
66
64
  sqlTimestamptzLiteral(_qi: QueryInfo, literal: string, timezone: string): string;
67
65
  sqlMeasureTimeExpr(df: MeasureTimeExpr): string;
68
66
  sqlRegexpMatch(compare: RegexMatchExpr): string;
@@ -249,61 +249,45 @@ ${(0, utils_1.indent)(sql)}
249
249
  );
250
250
  `;
251
251
  }
252
+ // Civil time is a TIMESTAMP_NTZ, which has no zone
252
253
  sqlConvertToCivilTime(expr, timezone, typeDef) {
253
- // For timestamptz (TIMESTAMP_TZ): use 2-arg form
254
- // Returns TIMESTAMP_TZ with timezone preserved
255
- if (typeDef.type === 'timestamptz') {
256
- return {
257
- sql: `CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(timezone)}, ${expr})`,
258
- typeDef: { type: 'timestamptz' },
259
- };
254
+ const tz = this.sqlTimezoneLiteral(timezone);
255
+ if (malloy_types_1.TD.isTimestamptz(typeDef)) {
256
+ return `CONVERT_TIMEZONE(${tz}, ${expr})::TIMESTAMP_NTZ`;
260
257
  }
261
- // For plain timestamps (TIMESTAMP_NTZ): use 3-arg form
262
- // Must cast to TIMESTAMP_NTZ first, returns TIMESTAMP_NTZ
263
- return {
264
- sql: `CONVERT_TIMEZONE('UTC', ${this.sqlTimezoneLiteral(timezone)}, (${expr})::TIMESTAMP_NTZ)`,
265
- typeDef: { type: 'timestamp' },
266
- };
267
- }
268
- sqlConvertFromCivilTime(expr, timezone, _destTypeDef) {
269
- // After civil time operations, we have a TIMESTAMP_NTZ in the target timezone
270
- // Convert from timezone to UTC, returning TIMESTAMP_NTZ
271
- return `CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(timezone)}, 'UTC', (${expr})::TIMESTAMP_NTZ)`;
272
- }
273
- sqlTruncate(expr, unit, _typeDef, _inCivilTime, _timezone) {
274
- // Snowflake session is configured with WEEK_START=7 (Sunday)
275
- // so DATE_TRUNC already truncates to Sunday - no adjustment needed
276
- // Unlike PostgreSQL/DuckDB, Snowflake's DATE_TRUNC preserves the input type
277
- // (TIMESTAMP_NTZ → TIMESTAMP_NTZ, TIMESTAMP_TZ → TIMESTAMP_TZ)
258
+ return timezone === 'UTC'
259
+ ? expr
260
+ : `CONVERT_TIMEZONE('UTC', ${tz}, (${expr})::TIMESTAMP_NTZ)`;
261
+ }
262
+ sqlConvertFromCivilTime(expr, timezone, destTypeDef) {
263
+ const utc = timezone === 'UTC'
264
+ ? expr
265
+ : `CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(timezone)}, 'UTC', (${expr})::TIMESTAMP_NTZ)`;
266
+ // The session time zone is UTC
267
+ return malloy_types_1.TD.isTimestamptz(destTypeDef) ? `(${utc})::TIMESTAMP_TZ` : utc;
268
+ }
269
+ sqlTruncate(expr, unit, _civilType) {
270
+ // The session sets WEEK_START to Sunday
278
271
  return `DATE_TRUNC('${unit}', ${expr})`;
279
272
  }
280
- sqlOffsetTime(expr, op, magnitude, unit, typeDef, _inCivilTime, _timezone) {
281
- const funcName = typeDef.type === 'date' ? 'DATEADD' : 'TIMESTAMPADD';
273
+ sqlOffsetTime(expr, op, magnitude, unit, civilType) {
274
+ const funcName = civilType === dialect_1.CivilTime.Date ? 'DATEADD' : 'TIMESTAMPADD';
282
275
  const n = op === '+' ? magnitude : `-(${magnitude})`;
283
276
  return `${funcName}(${unit}, ${n}, ${expr})`;
284
277
  }
285
- sqlTimeExtractExpr(qi, from) {
286
- const extractUnits = extractionMap[from.units] || from.units;
287
- let extractFrom = from.e.sql;
288
- const tz = (0, dialect_1.qtz)(qi);
289
- if (tz && malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
290
- extractFrom = `CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(tz)}, ${extractFrom})`;
291
- }
292
- return `EXTRACT(${extractUnits} FROM ${extractFrom})`;
293
- }
294
- atTz(sqlExpr, tz) {
295
- if (tz !== undefined) {
296
- return `(
297
- TO_CHAR(${sqlExpr}::TIMESTAMP_NTZ, 'YYYY-MM-DD HH24:MI:SS.FF9') ||
298
- TO_CHAR(CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(tz)}, '1970-01-01 00:00:00'), 'TZHTZM')
299
- )::TIMESTAMP_TZ`;
300
- }
301
- return `${sqlExpr}::TIMESTAMP_NTZ`;
278
+ sqlCivilExtract(expr, units, _civilType) {
279
+ return `EXTRACT(${extractionMap[units] || units} FROM ${expr})`;
280
+ }
281
+ sqlCivilToDate(expr) {
282
+ return `TO_DATE(${expr})`;
283
+ }
284
+ sqlDateToCivil(expr) {
285
+ return `(${expr})::TIMESTAMP_NTZ`;
302
286
  }
303
287
  sqlNowExpr() {
304
288
  return 'CURRENT_TIMESTAMP()';
305
289
  }
306
- sqlCast(qi, cast) {
290
+ sqlCast(_qi, cast) {
307
291
  const src = cast.e.sql || '';
308
292
  const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
309
293
  if (malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
@@ -316,50 +300,14 @@ ${(0, utils_1.indent)(sql)}
316
300
  throw new Error(`Snowflake dialect doesn't support safe cast for a few types:
317
301
  refer to: https://docs.snowflake.com/en/sql-reference/functions/try_cast`);
318
302
  }
319
- const tz = (0, dialect_1.qtz)(qi);
320
- // Timezone-aware casts when query timezone is set
321
- if (tz && srcTypeDef && dstTypeDef) {
322
- // TIMESTAMP → DATE: convert to query timezone, then to date
323
- if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
324
- return `TO_DATE(CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(tz)}, ${src}))`;
325
- }
326
- // TIMESTAMPTZ → DATE: convert to query timezone, then to date
327
- if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
328
- return `TO_DATE(CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(tz)}, ${src}))`;
329
- }
330
- // DATE → TIMESTAMP: interpret date in query timezone, return UTC timestamp
331
- if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
332
- const retExpr = `TO_TIMESTAMP(${src})`;
333
- return this.atTz(retExpr, tz);
334
- }
335
- // DATE → TIMESTAMPTZ: interpret date in query timezone
336
- if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
337
- const retExpr = `TO_TIMESTAMP(${src})`;
338
- return this.atTz(retExpr, tz);
339
- }
340
- // TIMESTAMPTZ → TIMESTAMP: convert to query timezone, get UTC wall clock
341
- if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
342
- return `CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(tz)}, ${src})::TIMESTAMP_NTZ`;
343
- }
344
- // TIMESTAMP → TIMESTAMPTZ: interpret as UTC, convert to TIMESTAMPTZ
345
- if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
346
- return this.atTz(src, tz);
347
- }
348
- }
349
303
  const castFunc = cast.safe ? 'TRY_CAST' : 'CAST';
350
304
  return `${castFunc}(${src} AS ${dstSQLType})`;
351
305
  }
352
306
  sqlDateLiteral(_qi, literal) {
353
307
  return `TO_DATE('${literal}')`;
354
308
  }
355
- sqlTimestampLiteral(qi, literal, timezone) {
356
- const tz = timezone || (0, dialect_1.qtz)(qi);
357
- let ret = `'${literal}'::TIMESTAMP_NTZ`;
358
- if (tz) {
359
- // Interpret the literal as being in query timezone, convert to UTC
360
- ret = `CONVERT_TIMEZONE(${this.sqlTimezoneLiteral(tz)}, 'UTC', ${ret})`;
361
- }
362
- return ret;
309
+ sqlCivilLiteral(literal) {
310
+ return `'${literal}'::TIMESTAMP_NTZ`;
363
311
  }
364
312
  sqlTimestamptzLiteral(_qi, literal, timezone) {
365
313
  // Use TIMESTAMP_TZ_FROM_PARTS to create timestamptz
@@ -1,7 +1,7 @@
1
- import type { Sampling, AtomicTypeDef, ATimestampTypeDef, TypecastExpr, MeasureTimeExpr, BasicAtomicTypeDef, RegexMatchExpr, TimeExtractExpr, TimestampUnit, OrderBy } from '../../model/malloy_types';
1
+ import type { Sampling, AtomicTypeDef, ATimestampTypeDef, TypecastExpr, MeasureTimeExpr, BasicAtomicTypeDef, RegexMatchExpr, TimestampUnit, OrderBy, ExtractUnit } from '../../model/malloy_types';
2
2
  import type { DialectFunctionOverloadDef } from '../functions';
3
3
  import type { BooleanTypeSupport, DialectFieldList, FieldReferenceType, GroupByClauseType, QueryInfo } from '../dialect';
4
- import { Dialect } from '../dialect';
4
+ import { CivilTime, Dialect } from '../dialect';
5
5
  import type { ValidateTablePathResult } from '../table-path';
6
6
  /** T-SQL for SQL Server 2017 and later, and Azure SQL Database. */
7
7
  export declare class SQLServerDialect extends Dialect {
@@ -78,20 +78,19 @@ export declare class SQLServerDialect extends Dialect {
78
78
  concat(...values: string[]): string;
79
79
  sqlDateToString(sqlDateExp: string): string;
80
80
  sqlNowExpr(): string;
81
- sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
82
- sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
81
+ sqlCivilExtract(expr: string, units: ExtractUnit, _civilType: CivilTime): string;
82
+ sqlCivilToDate(expr: string): string;
83
+ sqlDateToCivil(expr: string): string;
84
+ sqlCast(_qi: QueryInfo, cast: TypecastExpr): string;
83
85
  sqlRegexpMatch(_df: RegexMatchExpr): string;
84
86
  sqlDateLiteral(_qi: QueryInfo, literal: string): string;
85
- sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
86
- sqlTimestamptzLiteral(_qi: QueryInfo, _literal: string, _timezone: string): string;
87
+ sqlCivilLiteral(literal: string): string;
88
+ sqlTimestamptzLiteral(_qi: QueryInfo, literal: string, timezone: string): string;
87
89
  sqlTimezoneLiteral(timezone: string): string;
88
- sqlConvertToCivilTime(expr: string, timezone: string, _typeDef: AtomicTypeDef): {
89
- sql: string;
90
- typeDef: AtomicTypeDef;
91
- };
92
- sqlConvertFromCivilTime(expr: string, timezone: string, _destTypeDef: ATimestampTypeDef): string;
93
- sqlTruncate(expr: string, unit: TimestampUnit, typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
94
- sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
90
+ sqlConvertToCivilTime(expr: string, timezone: string, typeDef: ATimestampTypeDef): string;
91
+ sqlConvertFromCivilTime(expr: string, timezone: string, destTypeDef: ATimestampTypeDef): string;
92
+ sqlTruncate(expr: string, unit: TimestampUnit, civilType: CivilTime): string;
93
+ sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, _civilType: CivilTime): string;
95
94
  sqlMeasureTimeExpr(df: MeasureTimeExpr): string;
96
95
  getDialectFunctionOverrides(): {
97
96
  [name: string]: DialectFunctionOverloadDef[];