@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,46 @@
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.dbmetaProperty = exports.DBMETA_ROUTE = void 0;
8
+ exports.dbmetaAnnotations = dbmetaAnnotations;
9
+ const malloy_tag_1 = require("@malloydata/malloy-tag");
10
+ /**
11
+ * The annotation route a connection writes database metadata to:
12
+ * `#(dbmeta) description="..."`.
13
+ */
14
+ exports.DBMETA_ROUTE = 'dbmeta';
15
+ const DBMETA_LOCATION = {
16
+ url: `internal://${exports.DBMETA_ROUTE}`,
17
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
18
+ };
19
+ /**
20
+ * The connection property that turns on database metadata. Connections that
21
+ * support it register this definition, so the option has the same name and
22
+ * meaning on every backend. Off unless set.
23
+ */
24
+ exports.dbmetaProperty = {
25
+ name: 'dbmeta',
26
+ displayName: 'Database Metadata',
27
+ type: 'boolean',
28
+ optional: true,
29
+ advanced: true,
30
+ description: 'Copy table and column descriptions from the database onto the schema, ' +
31
+ 'as #(dbmeta) description annotations.',
32
+ };
33
+ /**
34
+ * The `#(dbmeta)` annotation for a table or column, as a fragment to spread
35
+ * into its field or table def: `{annotations}`, or `{}` when there is nothing
36
+ * to say.
37
+ */
38
+ function dbmetaAnnotations(meta) {
39
+ var _a;
40
+ const description = (_a = meta.description) === null || _a === void 0 ? void 0 : _a.trim();
41
+ if (!description)
42
+ return {};
43
+ const text = `#(${exports.DBMETA_ROUTE}) description=${(0, malloy_tag_1.quoteString)(description)}\n`;
44
+ return { annotations: { notes: [{ text, at: DBMETA_LOCATION }] } };
45
+ }
46
+ //# sourceMappingURL=dbmeta.js.map
@@ -1,3 +1,4 @@
1
1
  export * from './types';
2
2
  export * from './base_connection';
3
3
  export * from './registry';
4
+ export * from './dbmeta';
@@ -17,4 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./types"), exports);
18
18
  __exportStar(require("./base_connection"), exports);
19
19
  __exportStar(require("./registry"), exports);
20
+ __exportStar(require("./dbmeta"), exports);
20
21
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
- import type { Sampling, MeasureTimeExpr, RegexMatchExpr, TimeExtractExpr, TypecastExpr, BasicAtomicTypeDef, AtomicTypeDef, ArrayLiteralNode, RecordLiteralNode } from '../../model/malloy_types';
2
- import type { BooleanTypeSupport, CompiledOrderBy, DialectFieldList, FieldReferenceType, LateralJoinExpression, OrderByClauseType, OrderByRequest, 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, LateralJoinExpression, OrderByClauseType, OrderByRequest, QueryInfo } from '../dialect';
3
3
  import { Dialect } from '../dialect';
4
4
  import type { DialectFunctionOverloadDef } from '../functions';
5
5
  export declare class DatabricksDialect extends Dialect {
@@ -66,18 +66,17 @@ export declare class DatabricksDialect extends Dialect {
66
66
  sqlSelectAliasAsStruct(alias: string, fieldList: DialectFieldList): string;
67
67
  sqlCreateTableAsSelect(tableName: string, sql: string): string;
68
68
  sqlNowExpr(): string;
69
- sqlConvertToCivilTime(expr: string, timezone: string, _typeDef: AtomicTypeDef): {
70
- sql: string;
71
- typeDef: AtomicTypeDef;
72
- };
73
- sqlConvertFromCivilTime(expr: string, timezone: string, _destTypeDef: AtomicTypeDef): string;
74
- sqlTruncate(expr: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
75
- sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
76
- sqlTimeExtractExpr(qi: QueryInfo, te: TimeExtractExpr): string;
77
- sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
69
+ sqlConvertToCivilTime(expr: string, timezone: string, _typeDef: ATimestampTypeDef): string;
70
+ sqlConvertFromCivilTime(expr: string, timezone: string, _destTypeDef: ATimestampTypeDef): string;
71
+ sqlTruncate(expr: string, unit: TimestampUnit, _civilType: CivilTime): string;
72
+ sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, _civilType: CivilTime): string;
73
+ sqlCivilExtract(expr: string, units: ExtractUnit, _civilType: CivilTime): string;
74
+ sqlCivilToDate(expr: string): string;
75
+ sqlDateToCivil(expr: string): string;
76
+ sqlCast(_qi: QueryInfo, cast: TypecastExpr): string;
78
77
  sqlRegexpMatch(df: RegexMatchExpr): string;
79
78
  sqlDateLiteral(_qi: QueryInfo, literal: string): string;
80
- sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
79
+ sqlCivilLiteral(literal: string): string;
81
80
  sqlTimestamptzLiteral(_qi: QueryInfo, _literal: string, _timezone: string): string;
82
81
  sqlMeasureTimeExpr(df: MeasureTimeExpr): string;
83
82
  sqlSampleTable(tableSQL: string, sample: Sampling | undefined): string;
@@ -300,19 +300,19 @@ class DatabricksDialect extends dialect_1.Dialect {
300
300
  sqlNowExpr() {
301
301
  return 'CURRENT_TIMESTAMP()';
302
302
  }
303
+ // Civil time is a TIMESTAMP whose reading in the session zone, UTC, is
304
+ // the civil time
303
305
  sqlConvertToCivilTime(expr, timezone, _typeDef) {
304
- // Databricks has no timestamptz type; timestamps are stored as UTC
305
- // Convert from UTC to local timezone for civil time operations
306
- return {
307
- sql: `FROM_UTC_TIMESTAMP(${expr}, ${this.sqlTimezoneLiteral(timezone)})`,
308
- typeDef: { type: 'timestamp' },
309
- };
306
+ return timezone === 'UTC'
307
+ ? expr
308
+ : `FROM_UTC_TIMESTAMP(${expr}, ${this.sqlTimezoneLiteral(timezone)})`;
310
309
  }
311
310
  sqlConvertFromCivilTime(expr, timezone, _destTypeDef) {
312
- // Convert from local timezone back to UTC
313
- return `TO_UTC_TIMESTAMP(${expr}, ${this.sqlTimezoneLiteral(timezone)})`;
311
+ return timezone === 'UTC'
312
+ ? expr
313
+ : `TO_UTC_TIMESTAMP(${expr}, ${this.sqlTimezoneLiteral(timezone)})`;
314
314
  }
315
- sqlTruncate(expr, unit, _typeDef, _inCivilTime, _timezone) {
315
+ sqlTruncate(expr, unit, _civilType) {
316
316
  // Databricks DATE_TRUNC starts weeks on Monday; Malloy wants Sunday.
317
317
  // Add 1 day before truncating, subtract 1 day after.
318
318
  if (unit === 'week') {
@@ -320,7 +320,7 @@ class DatabricksDialect extends dialect_1.Dialect {
320
320
  }
321
321
  return `DATE_TRUNC('${unit}', ${expr})`;
322
322
  }
323
- sqlOffsetTime(expr, op, magnitude, unit, _typeDef, _inCivilTime, _timezone) {
323
+ sqlOffsetTime(expr, op, magnitude, unit, _civilType) {
324
324
  // Use TIMESTAMPADD which accepts expressions for magnitude,
325
325
  // unlike INTERVAL which only accepts literals in Databricks.
326
326
  let offsetUnit = unit.toUpperCase();
@@ -336,31 +336,23 @@ class DatabricksDialect extends dialect_1.Dialect {
336
336
  const n = op === '+' ? offsetMag : `-(${offsetMag})`;
337
337
  return `TIMESTAMPADD(${offsetUnit}, ${n}, ${expr})`;
338
338
  }
339
- sqlTimeExtractExpr(qi, te) {
340
- const units = extractionMap[te.units] || te.units;
341
- let extractFrom = te.e.sql;
342
- if (malloy_types_1.TD.isTimestamp(te.e.typeDef)) {
343
- const tz = (0, dialect_1.qtz)(qi);
344
- if (tz) {
345
- extractFrom = `FROM_UTC_TIMESTAMP(${extractFrom}, ${this.sqlTimezoneLiteral(tz)})`;
346
- }
347
- }
348
- if (extractionMap[te.units]) {
339
+ sqlCivilExtract(expr, units, _civilType) {
340
+ const extractUnits = extractionMap[units];
341
+ if (extractUnits) {
349
342
  // DAYOFWEEK, DAYOFYEAR are functions
350
- return `${units}(${extractFrom})`;
343
+ return `${extractUnits}(${expr})`;
351
344
  }
352
- return `EXTRACT(${units} FROM ${extractFrom})`;
345
+ return `EXTRACT(${units} FROM ${expr})`;
346
+ }
347
+ sqlCivilToDate(expr) {
348
+ return `CAST(${expr} AS DATE)`;
349
+ }
350
+ sqlDateToCivil(expr) {
351
+ return `CAST(${expr} AS TIMESTAMP)`;
353
352
  }
354
- sqlCast(qi, cast) {
353
+ sqlCast(_qi, cast) {
355
354
  const srcSQL = cast.e.sql || 'internal-error-in-sql-generation';
356
- const { op, srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
357
- const tz = (0, dialect_1.qtz)(qi);
358
- if (op === 'timestamp::date' && tz) {
359
- return `CAST(FROM_UTC_TIMESTAMP(${srcSQL}, ${this.sqlTimezoneLiteral(tz)}) AS DATE)`;
360
- }
361
- else if (op === 'date::timestamp' && tz) {
362
- return `TO_UTC_TIMESTAMP(CAST(${srcSQL} AS TIMESTAMP_NTZ), ${this.sqlTimezoneLiteral(tz)})`;
363
- }
355
+ const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
364
356
  if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
365
357
  if (cast.safe) {
366
358
  return `TRY_CAST(${srcSQL} AS ${dstSQLType})`;
@@ -378,11 +370,7 @@ class DatabricksDialect extends dialect_1.Dialect {
378
370
  sqlDateLiteral(_qi, literal) {
379
371
  return `DATE '${literal}'`;
380
372
  }
381
- sqlTimestampLiteral(qi, literal, timezone) {
382
- const tz = timezone || (0, dialect_1.qtz)(qi);
383
- if (tz) {
384
- return `TO_UTC_TIMESTAMP(TIMESTAMP_NTZ '${literal}', ${this.sqlTimezoneLiteral(tz)})`;
385
- }
373
+ sqlCivilLiteral(literal) {
386
374
  return `TIMESTAMP '${literal}'`;
387
375
  }
388
376
  sqlTimestamptzLiteral(_qi, _literal, _timezone) {
@@ -1,4 +1,4 @@
1
- import type { Expr, Sampling, AtomicTypeDef, MeasureTimeExpr, TimeExtractExpr, TypecastExpr, RegexMatchExpr, TimeLiteralExpr, RecordLiteralNode, ArrayLiteralNode, BasicAtomicTypeDef, OrderBy, TimestampUnit, ATimestampTypeDef, TimeExpr, TemporalFieldType } from '../model/malloy_types';
1
+ import type { Expr, Sampling, AtomicTypeDef, MeasureTimeExpr, TimeExtractExpr, TypecastExpr, RegexMatchExpr, TimeLiteralExpr, RecordLiteralNode, ArrayLiteralNode, BasicAtomicTypeDef, OrderBy, TimestampUnit, ATimestampTypeDef, TimeExpr, TemporalFieldType, ExtractUnit } from '../model/malloy_types';
2
2
  import type { DialectFunctionOverloadDef } from './functions';
3
3
  import type { ValidateTablePathResult } from './table-path';
4
4
  /**
@@ -68,10 +68,26 @@ export interface QueryInfo {
68
68
  queryTimezone?: string;
69
69
  systemTimezone?: string;
70
70
  }
71
+ /**
72
+ * The two kinds of civil time: a calendar date, and a date with a time of
73
+ * day. Neither has a zone.
74
+ */
75
+ export declare const CivilTime: {
76
+ readonly Date: "date";
77
+ readonly DateAndTime: "date_and_time";
78
+ };
79
+ export type CivilTime = (typeof CivilTime)[keyof typeof CivilTime];
80
+ /** Add or subtract `magnitude` units of time */
81
+ export interface TimeOffset {
82
+ op: '+' | '-';
83
+ magnitude: string;
84
+ unit: TimestampUnit;
85
+ }
71
86
  export type FieldReferenceType = 'table' | 'nest source' | 'array[scalar]' | 'array[record]' | 'record';
72
87
  export declare const dayIndex: number;
73
88
  export declare function inDays(units: string): boolean;
74
- export declare function qtz(qi: QueryInfo): string | undefined;
89
+ /** The query time zone, or UTC when the query has none. */
90
+ export declare function queryZone(qi: QueryInfo): string;
75
91
  /**
76
92
  * The per-element condition for an aggregate turtle (sqlAggregateTurtle). On
77
93
  * the normal path it gates each row by its group_set (and a projection's own
@@ -83,6 +99,7 @@ export declare function qtz(qi: QueryInfo): string | undefined;
83
99
  */
84
100
  export declare function turtleGroupSetCondition(groupSet: number | undefined, filterSQL: string | undefined): string | undefined;
85
101
  export type OrderByClauseType = 'output_name' | 'ordinal' | 'expression';
102
+ export type GroupByClauseType = 'ordinal' | 'expression';
86
103
  export type OrderByRequest = 'query' | 'turtle' | 'analytical';
87
104
  export type BooleanTypeSupport = 'supported' | 'simulated' | 'none';
88
105
  /**
@@ -115,6 +132,14 @@ export declare abstract class Dialect {
115
132
  abstract supportsSafeCast: boolean;
116
133
  abstract supportsNesting: boolean;
117
134
  abstract experimental: boolean;
135
+ /**
136
+ * What the compiler consults, not `experimental` itself. The test harness
137
+ * calls `acceptExperimental()` on the dialect under test so the shared
138
+ * suites can run on an experimental dialect without every test enabling it.
139
+ */
140
+ private experimentalAccepted;
141
+ isExperimental(): boolean;
142
+ acceptExperimental(): void;
118
143
  cantPartitionWindowFunctionsOnExpressions: boolean;
119
144
  sqlLateralJoinBag(_expressions: LateralJoinExpression[]): string;
120
145
  supportsPipelinesInViews: boolean;
@@ -123,6 +148,9 @@ export declare abstract class Dialect {
123
148
  hasTimestamptz: boolean;
124
149
  readsNestedData: boolean;
125
150
  orderByClause: OrderByClauseType;
151
+ groupByClause: GroupByClauseType;
152
+ /** The ordering and row limit which end a SELECT stage */
153
+ sqlOrderByLimit(orderTerms: string[], limit: number | undefined): string;
126
154
  nullMatchesFunctionSignature: boolean;
127
155
  supportsSelectReplace: boolean;
128
156
  supportsBigIntPrecision: boolean;
@@ -140,6 +168,7 @@ export declare abstract class Dialect {
140
168
  compoundObjectInSchema: boolean;
141
169
  booleanType: BooleanTypeSupport;
142
170
  likeEscape: boolean;
171
+ likeExtraWildcards: string[];
143
172
  /**
144
173
  * Mappings from integer value ranges to Malloy number types.
145
174
  *
@@ -270,175 +299,43 @@ export declare abstract class Dialect {
270
299
  sqlLiteralNumber(literal: string): string;
271
300
  ignoreInProject(_fieldName: string): boolean;
272
301
  abstract sqlNowExpr(): string;
273
- abstract sqlTimeExtractExpr(qi: QueryInfo, xFrom: TimeExtractExpr): string;
274
302
  abstract sqlMeasureTimeExpr(e: MeasureTimeExpr): string;
275
303
  /**
276
- * Generate SQL for type casting expressions.
277
- *
278
- * Most casts are simple: `CAST(expr AS type)` or `TRY_CAST(expr AS type)` for safe casts.
279
- *
280
- * However, when a query timezone is set, casts between temporal types (date, timestamp, timestamptz)
281
- * require special handling to ensure correct timezone semantics:
282
- *
283
- * **Timezone-Aware Cast Semantics:**
284
- *
285
- * 1. **TIMESTAMP → DATE**:
286
- * - TIMESTAMP represents UTC wall clock
287
- * - Convert to query timezone, then extract date
288
- * - Example: TIMESTAMP '2020-02-20 00:00:00' with tz 'America/Mexico_City' → '2020-02-19'
289
- *
290
- * 2. **TIMESTAMPTZ → DATE**:
291
- * - TIMESTAMPTZ represents absolute instant
292
- * - Convert to query timezone, then extract date
293
- * - Example: TIMESTAMPTZ '2020-02-20 00:00:00 UTC' with tz 'America/Mexico_City' → '2020-02-19'
294
- *
295
- * 3. **DATE → TIMESTAMP**:
296
- * - DATE represents civil date
297
- * - Interpret as midnight in query timezone, return UTC wall clock
298
- * - Example: DATE '2020-02-20' with tz 'America/Mexico_City' → TIMESTAMP '2020-02-20 06:00:00' (UTC)
299
- *
300
- * 4. **DATE → TIMESTAMPTZ**:
301
- * - DATE represents civil date
302
- * - Interpret as midnight in query timezone, create instant
303
- * - Example: DATE '2020-02-20' with tz 'America/Mexico_City' → instant at 2020-02-20 06:00:00 UTC
304
- *
305
- * 5. **TIMESTAMPTZ → TIMESTAMP**:
306
- * - TIMESTAMPTZ represents absolute instant
307
- * - Extract wall clock in query timezone, return as TIMESTAMP
308
- * - Example: TIMESTAMPTZ '2020-02-20 00:00:00 UTC' with tz 'America/Mexico_City' → TIMESTAMP '2020-02-19 18:00:00'
309
- *
310
- * 6. **TIMESTAMP → TIMESTAMPTZ**:
311
- * - TIMESTAMP represents UTC wall clock
312
- * - Interpret as being in query timezone
313
- * - Example: TIMESTAMP '2020-02-20 00:00:00' with tz 'America/Mexico_City' → instant at 2020-02-20 06:00:00 UTC
314
- *
315
- * **Implementation Notes:**
316
- *
317
- * - Dialects without timestamptz support (MySQL, BigQuery, StandardSQL) only need cases 1-3
318
- * - Without query timezone, most casts are simple `CAST(expr AS type)`
319
- *
320
- * @param qi - Query info containing timezone and other context
321
- * @param cast - The typecast expression to generate SQL for
322
- * @returns SQL string for the cast operation
304
+ * Generate SQL for a cast. Casts between date, timestamp and timestamptz
305
+ * never reach here; they are made by sqlTimeCast.
323
306
  */
324
307
  abstract sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
325
308
  abstract sqlRegexpMatch(df: RegexMatchExpr): string;
309
+ /** The civil time which `timezone` shows at the instant `expr`. */
310
+ abstract sqlConvertToCivilTime(expr: string, timezone: string, typeDef: ATimestampTypeDef): string;
326
311
  /**
327
- * Converts a Malloy timestamp to "civil time" for calendar operations in a timezone.
328
- *
329
- * Each dialect selects its own SQL type to represent civil time (e.g., plain TIMESTAMP,
330
- * TIMESTAMP WITH TIME ZONE, or DATETIME). The civil space is where timezone-aware
331
- * truncation and interval arithmetic happen. Operations like sqlTruncate and sqlOffsetTime
332
- * are aware of the civil space and work correctly within it.
333
- *
334
- * @param expr The SQL expression for the Malloy timestamp (plain or timestamptz)
335
- * @param timezone The target timezone for civil operations
336
- * @param typeDef The Malloy type of the input expression
337
- * @returns Object with SQL expression and the SQL type it evaluates to (the civil type)
338
- */
339
- abstract sqlConvertToCivilTime(expr: string, timezone: string, typeDef: AtomicTypeDef): {
340
- sql: string;
341
- typeDef: AtomicTypeDef;
342
- };
343
- /**
344
- * Converts from civil time back to a Malloy timestamp type.
345
- *
346
- * This is the inverse of sqlConvertToCivilTime. Takes a value in the dialect's
347
- * civil space and converts it back to either a plain timestamp (UTC) or a
348
- * timestamptz, depending on the destination type.
349
- *
350
- * @param expr The SQL expression in civil time
351
- * @param timezone The timezone of the civil time
352
- * @param destTypeDef The destination Malloy timestamp type (plain or timestamptz)
353
- * @returns SQL expression representing the Malloy timestamp
312
+ * The instant at which `timezone` shows the civil time `expr`, as a
313
+ * value of `destTypeDef`.
354
314
  */
355
315
  abstract sqlConvertFromCivilTime(expr: string, timezone: string, destTypeDef: ATimestampTypeDef): string;
316
+ /** The start of the unit containing `expr`. A week starts on Sunday. */
317
+ abstract sqlTruncate(expr: string, unit: TimestampUnit, civilType: CivilTime): string;
318
+ abstract sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, civilType: CivilTime): string;
319
+ /** One field of `expr`. A day_of_week counts from 1 on Sunday. */
320
+ abstract sqlCivilExtract(expr: string, units: ExtractUnit, civilType: CivilTime): string;
321
+ /** The date part of the civil time `expr`. */
322
+ abstract sqlCivilToDate(expr: string): string;
323
+ /** Midnight at the start of the date `expr`, as civil time. */
324
+ abstract sqlDateToCivil(expr: string): string;
325
+ /** The civil time written as `literal`, 'YYYY-MM-DD HH:MM:SS'. */
326
+ abstract sqlCivilLiteral(literal: string): string;
327
+ sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
356
328
  /**
357
- * Truncates a time expression to the specified unit.
358
- *
359
- * @param expr The SQL expression to truncate
360
- * @param unit The unit to truncate to (year, month, day, hour, etc.)
361
- * @param typeDef The Malloy type of the expression (date, timestamp, etc.)
362
- * @param inCivilTime If true, the expression is already in civil (local) time and should not
363
- * be converted. If false, may need timezone conversion for timestamps.
364
- * @param timezone Optional timezone for the operation. Only provided when timezone-aware
365
- * truncation is needed but inCivilTime is false.
366
- * @returns SQL expression representing the truncated time
367
- */
368
- abstract sqlTruncate(expr: string, unit: TimestampUnit, typeDef: AtomicTypeDef, inCivilTime: boolean, timezone?: string): string;
369
- /**
370
- * Adds or subtracts a time interval from a time expression.
371
- *
372
- * @param expr The SQL expression to offset
373
- * @param op The operation: '+' for addition, '-' for subtraction
374
- * @param magnitude The SQL expression for the interval magnitude (e.g., '6', '(delta_val)')
375
- * @param unit The interval unit (year, month, day, hour, etc.)
376
- * @param typeDef The Malloy type of the expression (date, timestamp, etc.)
377
- * @param inCivilTime If true, the expression is already in civil (local) time and should not
378
- * be converted. If false, may need timezone conversion for timestamps.
379
- * @param timezone Optional timezone for the operation. Only provided when timezone-aware
380
- * offset is needed but inCivilTime is false.
381
- * @returns SQL expression representing the offset time
382
- */
383
- abstract sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, typeDef: AtomicTypeDef, inCivilTime: boolean, timezone?: string): string;
384
- /**
385
- * Determines whether a truncation and/or offset operation needs to be performed in
386
- * civil (local) time, and if so, which timezone to use.
387
- *
388
- * Calendar-based operations (day, week, month, quarter, year) typically need civil time
389
- * computation because these units can cross DST boundaries, changing the UTC offset.
390
- *
391
- * Default implementation:
392
- * - Returns true for timestamps with truncation or calendar-unit offsets
393
- * - Uses the query timezone from QueryInfo, or 'UTC' if none specified
394
- * - Returns false for dates or sub-day offsets (hour, minute, second)
395
- *
396
- * Dialects can override this if they have different rules about which operations
397
- * require civil time computation.
398
- *
399
- * @param typeDef The Malloy type of the base expression
400
- * @param truncateTo The truncation unit, if any
401
- * @param offsetUnit The offset unit, if any
402
- * @param qi Query information including timezone settings
403
- * @returns Object with `needed` boolean and optional `tz` string
329
+ * Truncation and/or offset, the offset applied after the truncation.
330
+ * Doing both in one pass converts to and from civil time only once.
404
331
  */
405
- needsCivilTimeComputation(typeDef: AtomicTypeDef, truncateTo: TimestampUnit | undefined, offsetUnit: TimestampUnit | undefined, qi: QueryInfo): {
406
- needed: boolean;
407
- tz: string | undefined;
408
- };
332
+ sqlTruncAndOffset(baseExpr: TimeExpr, qi: QueryInfo, truncateTo?: TimestampUnit, offset?: TimeOffset): string;
333
+ private civilTruncAndOffset;
409
334
  /**
410
- * Unified function for truncation and/or offset operations. This turns out to
411
- * be a very common operation and one which can be optimized by doing it together.
412
- *
413
- * Much of the complexity has to do with getting timezone values set up so that
414
- * they will truncate/offset in the query timezone instead of UTC.
415
- *
416
- * The intention is that this implementation will work for all dialects, and all
417
- * the dialect peculiarities are handled with the new primitives introduced to
418
- * support this function:
419
- * - needsCivilTimeComputation: Determines if operation needs civil time
420
- * - sqlConvertToCivilTime/sqlConvertFromCivilTime: Timezone conversion
421
- * - sqlTruncate: Truncation operation
422
- * - sqlOffsetTime: Interval arithmetic
423
- *
424
- * OFFSET TIMESTAMP BEHAVIOR:
425
- * - Plain timestamps (offset=false): Always return plain TIMESTAMP in UTC
426
- * - Offset timestamps (offset=true):
427
- * - Simple path: Operate in native embedded timezone, return TIMESTAMPTZ
428
- * - Civil path: Convert to query timezone (preserving instant), operate there,
429
- * return TIMESTAMPTZ in query timezone via sqlConvertFromCivilTime
430
- * - BigQuery/MySQL: No offset timestamp support, this logic never runs
431
- *
432
- * @param baseExpr The time expression to operate on (already compiled, with .sql populated)
433
- * @param qi Query information including timezone
434
- * @param truncateTo Optional truncation unit (year, month, day, etc.)
435
- * @param offset Optional offset to apply (after truncation if both present)
335
+ * A cast between date, timestamp and timestamptz, or undefined for any
336
+ * other cast.
436
337
  */
437
- sqlTruncAndOffset(baseExpr: TimeExpr, qi: QueryInfo, truncateTo?: TimestampUnit, offset?: {
438
- op: '+' | '-';
439
- magnitude: string;
440
- unit: TimestampUnit;
441
- }): string;
338
+ sqlTimeCast(qi: QueryInfo, cast: TypecastExpr): string | undefined;
442
339
  /**
443
340
  * Generate SQL for a DATE literal.
444
341
  * @param literal - The date string in format 'YYYY-MM-DD'
@@ -446,18 +343,10 @@ export declare abstract class Dialect {
446
343
  */
447
344
  abstract sqlDateLiteral(qi: QueryInfo, literal: string): string;
448
345
  /**
449
- * Generate SQL for a plain TIMESTAMP literal (without timezone offset).
450
- * @param literal - The timestamp string in format 'YYYY-MM-DD HH:MM:SS'
451
- * @param timezone - Optional timezone name (e.g., 'America/Los_Angeles')
452
- * - If undefined: Create plain timestamp literal from the literal string
453
- * - If defined: The literal string represents a civil time in the given timezone.
454
- * Convert it to a plain timestamp (typically by interpreting as timestamptz
455
- * in that timezone, then casting to plain timestamp). This happens when:
456
- * 1. A constant with timezone is used (constants don't have dialect context)
457
- * 2. A literal with timezone is used in a dialect that doesn't support offset timestamps
458
- * @returns SQL that produces a plain TIMESTAMP value
346
+ * The timestamp at which `timezone`, or the query time zone when there is
347
+ * none, shows the civil time `literal`.
459
348
  */
460
- abstract sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
349
+ sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
461
350
  /**
462
351
  * Generate SQL for an offset TIMESTAMP literal (TIMESTAMP WITH TIME ZONE).
463
352
  * Only called for dialects where hasOffsetTimestamp = true.
@@ -503,7 +392,7 @@ export declare abstract class Dialect {
503
392
  exprToSQL(qi: QueryInfo, df: Expr): string | undefined;
504
393
  sqlSumDistinct(_key: string, _value: string, _funcName: string): string;
505
394
  sqlAggDistinct(_key: string, _values: string[], _func: (valNames: string[]) => string): string;
506
- sqlSampleTable(tableSQL: string, sample: Sampling | undefined): string;
395
+ sqlSampleTable(tableSQL: string, sample: Sampling | undefined, _onBaseTable?: boolean): string;
507
396
  /**
508
397
  * MySQL is NULLs first, all other dialects have a way to make NULLs last.
509
398
  * isBaseOrdering is a hack to allow the MySQL dialect to partially implement
@@ -535,6 +424,16 @@ export declare abstract class Dialect {
535
424
  * SQL to generate to get a boolean value for a boolean expression
536
425
  */
537
426
  sqlBoolean(bv: boolean): string;
427
+ /**
428
+ * Malloy's `not`: of NULL, true. A dialect with booleanType 'none' has no
429
+ * boolean value for COALESCE to return, so the test is written as a
430
+ * condition.
431
+ */
432
+ sqlNot(condition: string): string;
433
+ /**
434
+ * A condition Malloy defines as true when SQL says NULL: `!=` and `!~`
435
+ */
436
+ sqlNullIsTrue(condition: string): string;
538
437
  /**
539
438
  * What a boolean value looks like in a query result
540
439
  */