@malloydata/malloy 0.0.323 → 0.0.325

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 (54) hide show
  1. package/CONTEXT.md +57 -0
  2. package/dist/api/core.js +2 -0
  3. package/dist/api/util.js +16 -24
  4. package/dist/dialect/dialect.d.ts +113 -23
  5. package/dist/dialect/dialect.js +83 -13
  6. package/dist/dialect/duckdb/duckdb.d.ts +1 -4
  7. package/dist/dialect/duckdb/duckdb.js +13 -17
  8. package/dist/dialect/mysql/mysql.d.ts +9 -4
  9. package/dist/dialect/mysql/mysql.js +18 -11
  10. package/dist/dialect/pg_impl.d.ts +11 -2
  11. package/dist/dialect/pg_impl.js +79 -15
  12. package/dist/dialect/postgres/postgres.d.ts +1 -4
  13. package/dist/dialect/postgres/postgres.js +6 -18
  14. package/dist/dialect/snowflake/snowflake.d.ts +10 -4
  15. package/dist/dialect/snowflake/snowflake.js +80 -31
  16. package/dist/dialect/standardsql/standardsql.d.ts +9 -4
  17. package/dist/dialect/standardsql/standardsql.js +21 -19
  18. package/dist/dialect/tiny_parser.js +1 -1
  19. package/dist/dialect/trino/trino.d.ts +19 -6
  20. package/dist/dialect/trino/trino.js +163 -31
  21. package/dist/index.d.ts +1 -1
  22. package/dist/lang/ast/expressions/expr-granular-time.js +26 -8
  23. package/dist/lang/ast/expressions/expr-props.d.ts +24 -0
  24. package/dist/lang/ast/expressions/for-range.d.ts +1 -1
  25. package/dist/lang/ast/expressions/for-range.js +5 -4
  26. package/dist/lang/ast/expressions/time-literal.d.ts +9 -7
  27. package/dist/lang/ast/expressions/time-literal.js +43 -50
  28. package/dist/lang/ast/query-items/field-declaration.js +1 -2
  29. package/dist/lang/ast/time-utils.js +1 -1
  30. package/dist/lang/ast/typedesc-utils.d.ts +1 -0
  31. package/dist/lang/ast/typedesc-utils.js +14 -1
  32. package/dist/lang/ast/types/expression-def.js +1 -1
  33. package/dist/lang/ast/types/granular-result.js +2 -1
  34. package/dist/lang/composite-source-utils.js +1 -1
  35. package/dist/lang/lib/Malloy/MalloyLexer.d.ts +76 -75
  36. package/dist/lang/lib/Malloy/MalloyLexer.js +1252 -1243
  37. package/dist/lang/lib/Malloy/MalloyParser.d.ts +77 -75
  38. package/dist/lang/lib/Malloy/MalloyParser.js +515 -510
  39. package/dist/lang/malloy-to-stable-query.js +13 -14
  40. package/dist/lang/test/expr-to-str.js +5 -1
  41. package/dist/malloy.d.ts +3 -2
  42. package/dist/malloy.js +6 -0
  43. package/dist/model/field_instance.js +1 -1
  44. package/dist/model/filter_compilers.d.ts +2 -1
  45. package/dist/model/filter_compilers.js +8 -5
  46. package/dist/model/malloy_types.d.ts +31 -9
  47. package/dist/model/malloy_types.js +49 -6
  48. package/dist/model/query_node.d.ts +2 -2
  49. package/dist/model/query_node.js +1 -0
  50. package/dist/model/query_query.js +15 -3
  51. package/dist/to_stable.js +13 -1
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/package.json +6 -6
@@ -67,9 +67,6 @@ const trinoToMalloyTypes = {
67
67
  'FLOAT64': {type: 'number', numberType: 'float'},
68
68
  'NUMERIC': {type: 'number', numberType: 'float'},
69
69
  'BIGNUMERIC': {type: 'number', numberType: 'float'},
70
- 'TIMESTAMP': {type: 'timestamp'},
71
- 'BOOLEAN': {type: 'boolean'},
72
- 'BOOL': {type: 'boolean'},
73
70
  'JSON': {type: 'json'},*/
74
71
  // TODO (https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#tablefieldschema):
75
72
  // BYTES
@@ -330,10 +327,29 @@ ${(0, utils_1.indent)(sql)}
330
327
  const definitions = this.buildTypeExpression(fieldList);
331
328
  return `CAST(ROW(${fields}) as ROW(${definitions})`;
332
329
  }
333
- sqlConvertToCivilTime(expr, timezone) {
334
- return `${expr} AT TIME ZONE '${timezone}'`;
330
+ sqlConvertToCivilTime(expr, timezone, typeDef) {
331
+ // Trino civil time = TIMESTAMPTZ with query timezone as stored timezone
332
+ // Operations (extract, truncate, etc.) happen in the stored timezone
333
+ if (typeDef.type === 'timestamp') {
334
+ // TIMESTAMP (UTC wall clock) → TIMESTAMPTZ in query timezone
335
+ return {
336
+ sql: `at_timezone(with_timezone(${expr}, 'UTC'), '${timezone}')`,
337
+ typeDef: { type: 'timestamptz' },
338
+ };
339
+ }
340
+ // TIMESTAMPTZ → TIMESTAMPTZ in query timezone (same instant, different stored tz)
341
+ return {
342
+ sql: `at_timezone(${expr}, '${timezone}')`,
343
+ typeDef: { type: 'timestamptz' },
344
+ };
335
345
  }
336
- sqlConvertFromCivilTime(expr, _timezone) {
346
+ sqlConvertFromCivilTime(expr, _timezone, destTypeDef) {
347
+ // From civil TIMESTAMPTZ (in query timezone) to destination type
348
+ if (destTypeDef.type === 'timestamptz') {
349
+ // Already TIMESTAMPTZ, keep as-is
350
+ return expr;
351
+ }
352
+ // To TIMESTAMP: convert to UTC and cast to plain TIMESTAMP
337
353
  return `CAST(at_timezone(${expr}, 'UTC') AS TIMESTAMP)`;
338
354
  }
339
355
  sqlTruncate(expr, unit, _typeDef, _inCivilTime, _timezone) {
@@ -361,16 +377,37 @@ ${(0, utils_1.indent)(sql)}
361
377
  return `DATE_ADD('${offsetUnit}', ${n}, ${expr})`;
362
378
  }
363
379
  sqlCast(qi, cast) {
364
- const { op, srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
380
+ const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
365
381
  const tz = qtz(qi);
366
382
  const expr = cast.e.sql || '';
367
- if (op === 'timestamp::date' && tz) {
368
- const tstz = `CAST(${expr} as TIMESTAMP)`;
369
- return `CAST((${tstz}) AT TIME ZONE '${tz}' AS DATE)`;
370
- }
371
- else if (op === 'date::timestamp' && tz) {
372
- return `CAST(CONCAT(CAST(CAST(${expr} AS TIMESTAMP) AS VARCHAR), ' ${tz}') AS TIMESTAMP WITH TIME ZONE)`;
383
+ // Timezone-aware casts when query timezone is set
384
+ if (tz && srcTypeDef && dstTypeDef) {
385
+ // TIMESTAMP DATE: interpret as UTC, convert to query timezone
386
+ if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
387
+ return `CAST(at_timezone(with_timezone(${expr}, 'UTC'), '${tz}') AS DATE)`;
388
+ }
389
+ // TIMESTAMPTZ → DATE: convert to query timezone
390
+ if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
391
+ return `CAST(at_timezone(${expr}, '${tz}') AS DATE)`;
392
+ }
393
+ // DATE → TIMESTAMP: interpret date in query timezone, return UTC wall clock
394
+ if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
395
+ return `CAST(at_timezone(with_timezone(CAST(${expr} AS TIMESTAMP), '${tz}'), 'UTC') AS TIMESTAMP)`;
396
+ }
397
+ // DATE → TIMESTAMPTZ: interpret date in query timezone
398
+ if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
399
+ return `with_timezone(CAST(${expr} AS TIMESTAMP), '${tz}')`;
400
+ }
401
+ // TIMESTAMPTZ → TIMESTAMP: convert to query timezone wall clock
402
+ if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
403
+ return `CAST(at_timezone(${expr}, '${tz}') AS TIMESTAMP)`;
404
+ }
405
+ // TIMESTAMP → TIMESTAMPTZ: interpret TIMESTAMP as being in query timezone
406
+ if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
407
+ return `with_timezone(${expr}, '${tz}')`;
408
+ }
373
409
  }
410
+ // No special handling needed, or no query timezone
374
411
  if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
375
412
  const castFunc = cast.safe ? 'TRY_CAST' : 'CAST';
376
413
  return `${castFunc}(${expr} AS ${dstSQLType})`;
@@ -446,6 +483,8 @@ ${(0, utils_1.indent)(sql)}
446
483
  return malloyType.numberType === 'integer' ? 'BIGINT' : 'DOUBLE';
447
484
  case 'string':
448
485
  return 'VARCHAR';
486
+ case 'timestamptz':
487
+ return 'TIMESTAMP WITH TIME ZONE';
449
488
  case 'record': {
450
489
  const typeSpec = [];
451
490
  for (const f of malloyType.fields) {
@@ -475,7 +514,11 @@ ${(0, utils_1.indent)(sql)}
475
514
  }
476
515
  sqlTypeToMalloyType(sqlType) {
477
516
  var _a, _b, _c;
478
- const baseSqlType = (_b = (_a = sqlType.match(/^(\w+)/)) === null || _a === void 0 ? void 0 : _a.at(0)) !== null && _b !== void 0 ? _b : sqlType;
517
+ const matchType = sqlType.toLowerCase();
518
+ if (matchType.startsWith('timestamp with time zone')) {
519
+ return { type: 'timestamptz' };
520
+ }
521
+ const baseSqlType = (_b = (_a = matchType.match(/^\w+/)) === null || _a === void 0 ? void 0 : _a.at(0)) !== null && _b !== void 0 ? _b : matchType;
479
522
  return ((_c = trinoToMalloyTypes[baseSqlType]) !== null && _c !== void 0 ? _c : {
480
523
  type: 'sql native',
481
524
  rawType: sqlType,
@@ -502,24 +545,36 @@ ${(0, utils_1.indent)(sql)}
502
545
  // Angle Brackets: ARRAY<INT64>
503
546
  return sqlType.match(/^[A-Za-z\s(),<>0-9]*$/) !== null;
504
547
  }
505
- sqlLiteralTime(qi, lit) {
506
- if (malloy_types_1.TD.isDate(lit.typeDef)) {
507
- return `DATE '${lit.literal}'`;
508
- }
509
- const tz = lit.timezone || qtz(qi);
548
+ sqlDateLiteral(_qi, literal) {
549
+ return `DATE '${literal}'`;
550
+ }
551
+ sqlTimestampLiteral(qi, literal, timezone) {
552
+ const tz = timezone || qtz(qi);
510
553
  if (tz) {
511
554
  // Interpret wall clock time in timezone, convert to UTC wall clock, cast to TIMESTAMP
512
- return `CAST(at_timezone(with_timezone(TIMESTAMP '${lit.literal}', '${tz}'), 'UTC') AS TIMESTAMP)`;
555
+ return `CAST(at_timezone(with_timezone(TIMESTAMP '${literal}', '${tz}'), 'UTC') AS TIMESTAMP)`;
513
556
  }
514
- return `TIMESTAMP '${lit.literal}'`;
557
+ return `TIMESTAMP '${literal}'`;
558
+ }
559
+ sqlTimestamptzLiteral(_qi, literal, timezone) {
560
+ // Use with_timezone to create a TIMESTAMP WITH TIME ZONE
561
+ return `with_timezone(TIMESTAMP '${literal}', '${timezone}')`;
515
562
  }
516
563
  sqlTimeExtractExpr(qi, from) {
517
564
  const pgUnits = pg_impl_1.timeExtractMap[from.units] || from.units;
518
565
  let extractFrom = from.e.sql || '';
519
- if (malloy_types_1.TD.isTimestamp(from.e.typeDef)) {
566
+ if (malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
520
567
  const tz = qtz(qi);
521
568
  if (tz) {
522
- extractFrom = `at_timezone(${extractFrom},'${tz}')`;
569
+ // Convert both TIMESTAMP and TIMESTAMPTZ to query timezone for extraction
570
+ if (from.e.typeDef.type === 'timestamp') {
571
+ // TIMESTAMP: interpret as UTC, convert to query timezone
572
+ extractFrom = `at_timezone(with_timezone(${extractFrom}, 'UTC'), '${tz}')`;
573
+ }
574
+ else {
575
+ // TIMESTAMPTZ: convert to query timezone
576
+ extractFrom = `at_timezone(${extractFrom}, '${tz}')`;
577
+ }
523
578
  }
524
579
  }
525
580
  const extracted = `EXTRACT(${pgUnits} FROM ${extractFrom})`;
@@ -552,19 +607,96 @@ class PrestoDialect extends TrinoDialect {
552
607
  sqlGenerateUUID() {
553
608
  return 'CAST(UUID() AS VARCHAR)';
554
609
  }
555
- sqlLiteralTime(qi, lit) {
556
- if (malloy_types_1.TD.isDate(lit.typeDef)) {
557
- return `DATE '${lit.literal}'`;
558
- }
559
- const tz = lit.timezone || qtz(qi);
610
+ sqlDateLiteral(_qi, literal) {
611
+ return `DATE '${literal}'`;
612
+ }
613
+ sqlTimestampLiteral(qi, literal, timezone) {
614
+ const tz = timezone || qtz(qi);
560
615
  if (tz) {
561
- return `CAST(TIMESTAMP '${lit.literal} ${tz}' AT TIME ZONE 'UTC' AS TIMESTAMP)`;
616
+ return `CAST(TIMESTAMP '${literal} ${tz}' AT TIME ZONE 'UTC' AS TIMESTAMP)`;
562
617
  }
563
- return `TIMESTAMP '${lit.literal}'`;
618
+ return `TIMESTAMP '${literal}'`;
619
+ }
620
+ sqlTimestamptzLiteral(_qi, literal, timezone) {
621
+ return `TIMESTAMP '${literal} ${timezone}'`;
564
622
  }
565
- sqlConvertFromCivilTime(expr, _timezone) {
623
+ sqlConvertToCivilTime(expr, timezone, _typeDef) {
624
+ // Presto's AT TIME ZONE operator (not function) produces TIMESTAMPTZ
625
+ // Reinterprets the instant in the target timezone
626
+ return {
627
+ sql: `${expr} AT TIME ZONE '${timezone}'`,
628
+ typeDef: { type: 'timestamptz' },
629
+ };
630
+ }
631
+ sqlConvertFromCivilTime(expr, _timezone, destTypeDef) {
632
+ if (destTypeDef.type === 'timestamptz') {
633
+ return expr;
634
+ }
566
635
  return `CAST(${expr} AT TIME ZONE 'UTC' AS TIMESTAMP)`;
567
636
  }
637
+ sqlCast(qi, cast) {
638
+ const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
639
+ const tz = qtz(qi);
640
+ const expr = cast.e.sql || '';
641
+ // Timezone-aware casts when query timezone is set
642
+ // Presto uses AT TIME ZONE operator instead of Trino's with_timezone/at_timezone functions
643
+ if (tz && srcTypeDef && dstTypeDef) {
644
+ // TIMESTAMP → DATE: interpret as UTC, convert to query timezone
645
+ if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
646
+ return `CAST((${expr} AT TIME ZONE 'UTC') AT TIME ZONE '${tz}' AS DATE)`;
647
+ }
648
+ // TIMESTAMPTZ → DATE: convert to query timezone
649
+ if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
650
+ return `CAST(${expr} AT TIME ZONE '${tz}' AS DATE)`;
651
+ }
652
+ // DATE → TIMESTAMP: interpret date in query timezone, return UTC wall clock
653
+ // Presto doesn't have a way to interpret TIMESTAMP in a non-UTC timezone,
654
+ // so we build a TIMESTAMPTZ literal string and cast it
655
+ if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
656
+ const tstzLiteral = `CAST(CAST(${expr} AS VARCHAR) || ' 00:00:00 ${tz}' AS TIMESTAMP WITH TIME ZONE)`;
657
+ return `CAST(${tstzLiteral} AS TIMESTAMP)`;
658
+ }
659
+ // DATE → TIMESTAMPTZ: interpret date in query timezone
660
+ if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
661
+ return `CAST(CAST(${expr} AS VARCHAR) || ' 00:00:00 ${tz}' AS TIMESTAMP WITH TIME ZONE)`;
662
+ }
663
+ // TIMESTAMPTZ → TIMESTAMP: convert to query timezone wall clock
664
+ if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
665
+ return `CAST(${expr} AT TIME ZONE '${tz}' AS TIMESTAMP)`;
666
+ }
667
+ // TIMESTAMP → TIMESTAMPTZ: interpret TIMESTAMP as UTC
668
+ if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
669
+ return `${expr} AT TIME ZONE 'UTC'`;
670
+ }
671
+ }
672
+ // No special handling needed, or no query timezone
673
+ if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
674
+ const castFunc = cast.safe ? 'TRY_CAST' : 'CAST';
675
+ return `${castFunc}(${expr} AS ${dstSQLType})`;
676
+ }
677
+ return expr;
678
+ }
679
+ sqlTimeExtractExpr(qi, from) {
680
+ const pgUnits = pg_impl_1.timeExtractMap[from.units] || from.units;
681
+ let extractFrom = from.e.sql || '';
682
+ if (malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
683
+ const tz = qtz(qi);
684
+ if (tz) {
685
+ // Convert both TIMESTAMP and TIMESTAMPTZ to query timezone for extraction
686
+ if (from.e.typeDef.type === 'timestamp') {
687
+ // TIMESTAMP: interpret as UTC, convert to query timezone
688
+ // Presto uses AT TIME ZONE operator
689
+ extractFrom = `(${extractFrom} AT TIME ZONE 'UTC') AT TIME ZONE '${tz}'`;
690
+ }
691
+ else {
692
+ // TIMESTAMPTZ: convert to query timezone
693
+ extractFrom = `${extractFrom} AT TIME ZONE '${tz}'`;
694
+ }
695
+ }
696
+ }
697
+ const extracted = `EXTRACT(${pgUnits} FROM ${extractFrom})`;
698
+ return from.units === 'day_of_week' ? `mod(${extracted}+1,7)` : extracted;
699
+ }
568
700
  sqlUnnestAlias(source, alias, _fieldList, needDistinctKey, isArray, _isInNestedPipeline) {
569
701
  if (isArray) {
570
702
  if (needDistinctKey) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { DuckDBDialect, StandardSQLDialect, TrinoDialect, PostgresDialect, SnowflakeDialect, MySQLDialect, registerDialect, arg, qtz, overload, minScalar, anyExprType, minAggregate, maxScalar, sql, makeParam, param, variadicParam, literal, spread, Dialect, TinyParser, } from './dialect';
2
2
  export type { DialectFieldList, DialectFunctionOverloadDef, QueryInfo, MalloyStandardFunctionImplementations, DefinitionBlueprint, DefinitionBlueprintMap, OverloadedDefinitionBlueprint, TinyToken, } from './dialect';
3
- export type { QueryDataRow, 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, NamedQuery, NamedModelObject, ExpressionType, FunctionDef, FunctionOverloadDef, FunctionParameterDef, ExpressionValueType, TypeDesc, FunctionParamTypeDesc, DocumentLocation, DocumentRange, DocumentPosition, Sampling, Annotation, BasicAtomicTypeDef, BasicAtomicDef, AtomicTypeDef, AtomicFieldDef, ArrayDef, ArrayTypeDef, RecordTypeDef, RepeatedRecordTypeDef, RecordDef, RepeatedRecordDef, RecordLiteralNode, StringLiteralNode, ArrayLiteralNode, SourceComponentInfo, TimeLiteralNode, TypecastExpr, } from './model';
3
+ export type { QueryDataRow, 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, NamedQuery, NamedModelObject, ExpressionType, FunctionDef, FunctionOverloadDef, FunctionParameterDef, ExpressionValueType, TypeDesc, FunctionParamTypeDesc, DocumentLocation, DocumentRange, DocumentPosition, Sampling, Annotation, BasicAtomicTypeDef, BasicAtomicDef, AtomicTypeDef, AtomicFieldDef, ArrayDef, ArrayTypeDef, RecordTypeDef, RepeatedRecordTypeDef, RecordDef, RepeatedRecordDef, RecordLiteralNode, StringLiteralNode, ArrayLiteralNode, SourceComponentInfo, DateLiteralNode, TimestampLiteralNode, TimestamptzLiteralNode, TimeLiteralExpr, TypecastExpr, } from './model';
4
4
  export { isSourceDef, isBasicAtomic, isJoined, isJoinedSource, isSamplingEnable, isSamplingPercent, isSamplingRows, isRepeatedRecord, isBasicArray, mkArrayDef, mkFieldDef, expressionIsAggregate, expressionIsAnalytic, expressionIsCalculation, expressionIsScalar, expressionIsUngroupedAggregate, indent, composeSQLExpr, isTimestampUnit, isDateUnit, constantExprToSQL, } from './model';
5
5
  export { malloyToQuery, MalloyTranslator, } from './lang';
6
6
  export type { LogMessage, TranslateResponse } from './lang';
@@ -98,18 +98,36 @@ class ExprGranularTime extends expression_def_1.ExpressionDef {
98
98
  getExpression(fs) {
99
99
  const timeframe = this.units;
100
100
  const exprVal = this.expr.getExpression(fs);
101
+ let timeType;
102
+ let tsVal;
101
103
  if (malloy_types_1.TD.isTemporal(exprVal)) {
102
- const tsVal = {
103
- ...exprVal,
104
- timeframe: timeframe,
105
- };
104
+ if (exprVal.type === 'date') {
105
+ if (!(0, malloy_types_1.isDateUnit)(timeframe)) {
106
+ return this.loggedErrorExpr('unsupported-type-for-time-truncation', `Cannot truncate date to timestamp unit '${timeframe}'`);
107
+ }
108
+ tsVal = { ...exprVal, timeframe: timeframe };
109
+ timeType = { type: 'date', timeframe };
110
+ }
111
+ else {
112
+ // timestamp or timestamptz
113
+ tsVal = { ...exprVal, timeframe: timeframe };
114
+ if (exprVal.type === 'timestamptz') {
115
+ timeType = { type: 'timestamptz', timeframe };
116
+ }
117
+ else {
118
+ timeType = { type: 'timestamp', timeframe };
119
+ }
120
+ }
106
121
  if (this.truncate) {
107
122
  tsVal.value = {
108
123
  node: 'trunc',
109
- e: (0, malloy_types_1.mkTemporal)(exprVal.value, exprVal.type),
124
+ e: (0, malloy_types_1.mkTemporal)(exprVal.value, timeType),
110
125
  units: timeframe,
111
126
  };
112
127
  }
128
+ else {
129
+ tsVal.value = exprVal.value;
130
+ }
113
131
  return tsVal;
114
132
  }
115
133
  if (exprVal.type !== 'error') {
@@ -145,9 +163,9 @@ class ExprGranularTime extends expression_def_1.ExpressionDef {
145
163
  toRange(fs) {
146
164
  const begin = this.getExpression(fs);
147
165
  const one = { node: 'numberLiteral', literal: '1' };
148
- if (begin.type === 'timestamp') {
149
- const beginTS = expr_time_1.ExprTime.fromValue('timestamp', begin);
150
- const endTS = new expr_time_1.ExprTime('timestamp', (0, time_utils_1.timeOffset)('timestamp', begin.value, '+', one, this.units), [begin]);
166
+ if (malloy_types_1.TD.isAnyTimestamp(begin)) {
167
+ const beginTS = expr_time_1.ExprTime.fromValue(begin.type, begin);
168
+ const endTS = new expr_time_1.ExprTime(begin.type, (0, time_utils_1.timeOffset)(begin.type, begin.value, '+', one, this.units), [begin]);
151
169
  return new range_1.Range(beginTS, endTS);
152
170
  }
153
171
  const beginDate = new expr_time_1.ExprTime('date', begin.value, [begin]);
@@ -48,6 +48,18 @@ export declare class ExprProps extends ExpressionDef {
48
48
  morphic?: {
49
49
  [x: string]: import("../../../model/malloy_types").Expr;
50
50
  };
51
+ } | {
52
+ requiresGroupBy: import("../../../model/malloy_types").RequiredGroupBy[] | undefined;
53
+ type: "timestamptz";
54
+ timeframe?: import("../../../model/malloy_types").TimestampUnit;
55
+ expressionType: import("../../../model/malloy_types").ExpressionType;
56
+ evalSpace: import("../../../model/malloy_types").EvalSpace;
57
+ fieldUsage: import("../../../model/malloy_types").FieldUsage[];
58
+ ungroupings?: import("../../../model/malloy_types").AggregateUngrouping[];
59
+ value: import("../../../model/malloy_types").Expr;
60
+ morphic?: {
61
+ [x: string]: import("../../../model/malloy_types").Expr;
62
+ };
51
63
  } | {
52
64
  requiresGroupBy: import("../../../model/malloy_types").RequiredGroupBy[] | undefined;
53
65
  type: "number";
@@ -189,5 +201,17 @@ export declare class ExprProps extends ExpressionDef {
189
201
  morphic?: {
190
202
  [x: string]: import("../../../model/malloy_types").Expr;
191
203
  };
204
+ } | {
205
+ requiresGroupBy: import("../../../model/malloy_types").RequiredGroupBy[] | undefined;
206
+ type: "timestamptz";
207
+ timeframe?: import("../../../model/malloy_types").TimestampUnit;
208
+ expressionType: import("../../../model/malloy_types").ExpressionType;
209
+ evalSpace: import("../../../model/malloy_types").EvalSpace;
210
+ fieldUsage: import("../../../model/malloy_types").FieldUsage[];
211
+ ungroupings?: import("../../../model/malloy_types").AggregateUngrouping[];
212
+ value: import("../../../model/malloy_types").Expr;
213
+ morphic?: {
214
+ [x: string]: import("../../../model/malloy_types").Expr;
215
+ };
192
216
  };
193
217
  }
@@ -8,7 +8,7 @@ export declare class ForRange extends ExpressionDef {
8
8
  readonly duration: ExpressionDef;
9
9
  readonly timeframe: Timeframe;
10
10
  elementType: string;
11
- legalChildTypes: import("../../..").TypeDesc[];
11
+ legalChildTypes: import("../../../model/malloy_types").TypeDesc[];
12
12
  constructor(from: ExpressionDef, duration: ExpressionDef, timeframe: Timeframe);
13
13
  apply(fs: FieldSpace, op: BinaryMalloyOperator, expr: ExpressionDef): ExprValue;
14
14
  requestExpression(_fs: FieldSpace): undefined;
@@ -59,6 +59,7 @@ exports.ForRange = void 0;
59
59
  const ast_utils_1 = require("../ast-utils");
60
60
  const TDU = __importStar(require("../typedesc-utils"));
61
61
  const time_utils_1 = require("../time-utils");
62
+ const malloy_types_1 = require("../../../model/malloy_types");
62
63
  const expr_value_1 = require("../types/expr-value");
63
64
  const expression_def_1 = require("../types/expression-def");
64
65
  const expr_time_1 = require("./expr-time");
@@ -93,10 +94,10 @@ class ForRange extends expression_def_1.ExpressionDef {
93
94
  // If the duration resolution is smaller than date, we have
94
95
  // to do the computaion with timestamps.
95
96
  let rangeType = (0, time_utils_1.resolution)(units);
96
- // Next, if the beginning of the range is a timestamp, then we
97
- // also have to do the computation as a timestamp
98
- if (startV.type === 'timestamp') {
99
- rangeType = 'timestamp';
97
+ // Next, if the beginning of the range is a timestamp or timestamptz, then we
98
+ // also have to do the computation as a timestamp (or timestamptz)
99
+ if (malloy_types_1.TD.isAnyTimestamp(startV)) {
100
+ rangeType = startV.type;
100
101
  }
101
102
  // everything is dates, do date math
102
103
  if (checkV.type === 'date' && rangeType === 'date') {
@@ -1,5 +1,5 @@
1
1
  import type * as Malloy from '@malloydata/malloy-interfaces';
2
- import type { TemporalFieldType, TimestampUnit, TimeLiteralNode } from '../../../model/malloy_types';
2
+ import type { TemporalFieldType, TimestampUnit, TimeLiteralExpr } from '../../../model/malloy_types';
3
3
  import type { ExprValue } from '../types/expr-value';
4
4
  import type { FieldSpace } from '../types/field-space';
5
5
  import { ExpressionDef } from '../types/expression-def';
@@ -21,16 +21,18 @@ export declare abstract class TimeLiteral extends ExpressionDef {
21
21
  nextLit?: string;
22
22
  timeZone?: string;
23
23
  constructor(tm: TimeText, units: TimestampUnit | undefined, timeType: TemporalFieldType);
24
- protected makeLiteral(val: string, typ: TemporalFieldType, units: TimestampUnit | undefined): TimeLiteralNode;
25
- protected makeValue(val: string, dataType: TemporalFieldType): TimeResult;
24
+ protected makeLiteral(fs: FieldSpace, val: string, typ: TemporalFieldType, units: TimestampUnit | undefined): TimeLiteralExpr;
25
+ protected makeValue(fs: FieldSpace, val: string, dataType: TemporalFieldType): TimeResult;
26
26
  getStableLiteral(): Malloy.LiteralValue;
27
27
  getValue(): (import("../../../model/malloy_types").DateTypeDef & import("../../../model/malloy_types").TypeInfo & import("../types/expr-result").WithValue & {
28
28
  timeframe?: TimestampUnit;
29
29
  }) | (import("../../../model/malloy_types").TimestampTypeDef & import("../../../model/malloy_types").TypeInfo & import("../types/expr-result").WithValue & {
30
30
  timeframe?: TimestampUnit;
31
+ }) | (import("../../../model/malloy_types").TimestamptzTypeDef & import("../../../model/malloy_types").TypeInfo & import("../types/expr-result").WithValue & {
32
+ timeframe?: TimestampUnit;
31
33
  });
32
- getExpression(_fs: FieldSpace): ExprValue;
33
- getNext(): ExprValue | undefined;
34
+ getExpression(fs: FieldSpace): ExprValue;
35
+ getNext(fs: FieldSpace): ExprValue | undefined;
34
36
  granular(): boolean;
35
37
  }
36
38
  export declare class LiteralTimestamp extends TimeLiteral {
@@ -60,8 +62,8 @@ export declare class LiteralHour extends GranularLiteral {
60
62
  */
61
63
  declare abstract class DateBasedLiteral extends GranularLiteral {
62
64
  constructor(tm: TimeText, units: TimestampUnit, nextLit: string);
63
- getExpression(_fs: FieldSpace): ExprValue;
64
- getNext(): ExprValue | undefined;
65
+ getExpression(fs: FieldSpace): ExprValue;
66
+ getNext(fs: FieldSpace): ExprValue | undefined;
65
67
  }
66
68
  export declare class LiteralDay extends DateBasedLiteral {
67
69
  elementType: string;
@@ -29,6 +29,7 @@ const expr_value_1 = require("../types/expr-value");
29
29
  const range_1 = require("./range");
30
30
  const expr_time_1 = require("./expr-time");
31
31
  const expression_def_1 = require("../types/expression-def");
32
+ const dialect_1 = require("../../../dialect/dialect");
32
33
  class TimeFormatError extends Error {
33
34
  }
34
35
  exports.TimeFormatError = TimeFormatError;
@@ -69,24 +70,11 @@ class TimeLiteral extends expression_def_1.ExpressionDef {
69
70
  this.timeZone = tm.tzSpec;
70
71
  }
71
72
  }
72
- makeLiteral(val, typ, units) {
73
- const timeFrag = {
74
- node: 'timeLiteral',
75
- literal: val,
76
- typeDef: typ === 'timestamp'
77
- ? { type: typ, timeframe: units }
78
- : {
79
- type: typ,
80
- timeframe: units !== undefined && (0, malloy_types_1.isDateUnit)(units) ? units : undefined,
81
- },
82
- };
83
- if (this.timeZone) {
84
- timeFrag.timezone = this.timeZone;
85
- }
86
- return timeFrag;
73
+ makeLiteral(fs, val, typ, units) {
74
+ return dialect_1.Dialect.makeTimeLiteralNode(fs.dialectObj(), val, this.timeZone, units, typ);
87
75
  }
88
- makeValue(val, dataType) {
89
- const value = this.makeLiteral(val, dataType, this.units);
76
+ makeValue(fs, val, dataType) {
77
+ const value = this.makeLiteral(fs, val, dataType, this.units);
90
78
  return (0, expr_value_1.literalTimeResult)({
91
79
  value,
92
80
  dataType: value.typeDef,
@@ -96,48 +84,53 @@ class TimeLiteral extends expression_def_1.ExpressionDef {
96
84
  getStableLiteral() {
97
85
  const value = this.getValue();
98
86
  let granularity = value.timeframe;
99
- if (value.value.node !== 'timeLiteral') {
100
- // TODO should probably just throw...
87
+ const node = value.value;
88
+ if (node.node === 'dateLiteral') {
89
+ if (granularity === 'hour' ||
90
+ granularity === 'minute' ||
91
+ granularity === 'second') {
92
+ // TODO should probably just throw...
93
+ granularity = 'day';
94
+ }
101
95
  return {
102
- kind: 'timestamp_literal',
103
- timestamp_value: '1970-01-01 00:00:00',
96
+ kind: 'date_literal',
97
+ date_value: node.literal,
104
98
  granularity,
105
99
  };
106
100
  }
107
- const timeValue = value.value.literal;
108
- const timezone = value.value.timezone;
109
- if (value.type === 'timestamp') {
101
+ else if (node.node === 'timestampLiteral') {
110
102
  return {
111
103
  kind: 'timestamp_literal',
112
- timestamp_value: timeValue,
104
+ timestamp_value: node.literal,
113
105
  granularity,
114
- timezone,
106
+ timezone: node.timezone,
115
107
  };
116
108
  }
117
- else {
118
- if (granularity === 'hour' ||
119
- granularity === 'minute' ||
120
- granularity === 'second') {
121
- // TODO should probably just throw...
122
- granularity = 'day';
123
- }
109
+ else if (node.node === 'timestamptzLiteral') {
124
110
  return {
125
- kind: 'date_literal',
126
- date_value: timeValue,
111
+ kind: 'timestamp_literal',
112
+ timestamp_value: node.literal,
127
113
  granularity,
128
- timezone,
114
+ timezone: node.timezone,
129
115
  };
130
116
  }
117
+ throw new Error(`Unexpected time literal node type: ${node.node}`);
131
118
  }
132
119
  getValue() {
133
- return this.makeValue(this.literalPart, this.timeType);
120
+ // Used for stable literals - no dialect context, so pass undefined
121
+ const value = dialect_1.Dialect.makeTimeLiteralNode(undefined, this.literalPart, this.timeZone, this.units, this.timeType);
122
+ return (0, expr_value_1.literalTimeResult)({
123
+ value,
124
+ dataType: value.typeDef,
125
+ timeframe: this.units,
126
+ });
134
127
  }
135
- getExpression(_fs) {
136
- return this.makeValue(this.literalPart, this.timeType);
128
+ getExpression(fs) {
129
+ return this.makeValue(fs, this.literalPart, this.timeType);
137
130
  }
138
- getNext() {
131
+ getNext(fs) {
139
132
  if (this.nextLit) {
140
- return this.makeValue(this.nextLit, this.timeType);
133
+ return this.makeValue(fs, this.nextLit, this.timeType);
141
134
  }
142
135
  }
143
136
  granular() {
@@ -197,7 +190,7 @@ class GranularLiteral extends TimeLiteral {
197
190
  apply(fs, op, left) {
198
191
  // We have a chance to write our own range comparison will all constants.
199
192
  let rangeStart = this.getExpression(fs);
200
- let rangeEnd = this.getNext();
193
+ let rangeEnd = this.getNext(fs);
201
194
  if (rangeEnd) {
202
195
  const testValue = left.getExpression(fs);
203
196
  if (testValue.type === 'date' && op === '=' && this.units === 'day') {
@@ -210,7 +203,7 @@ class GranularLiteral extends TimeLiteral {
210
203
  // }
211
204
  return super.apply(fs, op, left);
212
205
  }
213
- if (testValue.type === 'timestamp' &&
206
+ if (malloy_types_1.TD.isAnyTimestamp(testValue) &&
214
207
  op === '=' &&
215
208
  this.units === undefined) {
216
209
  // TODO remove the === 'second' check above and warn
@@ -222,7 +215,7 @@ class GranularLiteral extends TimeLiteral {
222
215
  // }
223
216
  return super.apply(fs, op, left);
224
217
  }
225
- if (testValue.type === 'timestamp') {
218
+ if (malloy_types_1.TD.isAnyTimestamp(testValue)) {
226
219
  const newStart = (0, expression_def_1.getMorphicValue)(rangeStart, 'timestamp');
227
220
  const newEnd = (0, expression_def_1.getMorphicValue)(rangeEnd, 'timestamp');
228
221
  if (newStart && newEnd) {
@@ -274,14 +267,14 @@ class DateBasedLiteral extends GranularLiteral {
274
267
  constructor(tm, units, nextLit) {
275
268
  super(tm, units, 'date', nextLit);
276
269
  }
277
- getExpression(_fs) {
278
- const dateValue = this.makeValue(this.literalPart, 'date');
279
- const timestamp = this.makeLiteral(`${this.literalPart} 00:00:00`, 'timestamp', this.units);
270
+ getExpression(fs) {
271
+ const dateValue = this.makeValue(fs, this.literalPart, 'date');
272
+ const timestamp = this.makeLiteral(fs, `${this.literalPart} 00:00:00`, 'timestamp', this.units);
280
273
  return { ...dateValue, morphic: { timestamp }, evalSpace: 'literal' };
281
274
  }
282
- getNext() {
283
- const dateValue = this.makeValue(this.nextLit, 'date');
284
- const timestamp = this.makeLiteral(`${this.nextLit} 00:00:00`, 'timestamp', this.units);
275
+ getNext(fs) {
276
+ const dateValue = this.makeValue(fs, this.nextLit, 'date');
277
+ const timestamp = this.makeLiteral(fs, `${this.nextLit} 00:00:00`, 'timestamp', this.units);
285
278
  return { ...dateValue, morphic: { timestamp } };
286
279
  }
287
280
  }
@@ -126,8 +126,7 @@ class AtomicFieldDeclaration extends malloy_element_1.MalloyElement {
126
126
  if ((0, malloy_types_1.isAtomicFieldType)(exprValue.type) && exprValue.type !== 'error') {
127
127
  this.typecheckExprValue(exprValue);
128
128
  const ret = (0, malloy_types_1.mkFieldDef)(TDU.atomicDef(exprValue), exprName);
129
- if ((ret.type === 'date' || ret.type === 'timestamp') &&
130
- (0, granular_result_1.isGranularResult)(exprValue)) {
129
+ if (malloy_types_1.TD.isTemporal(ret) && (0, granular_result_1.isGranularResult)(exprValue)) {
131
130
  ret.timeframe = exprValue.timeframe;
132
131
  }
133
132
  ret.location = this.location;
@@ -75,7 +75,7 @@ function resolution(timeframe) {
75
75
  }
76
76
  function mkTimeResult(t, tt) {
77
77
  if (tt) {
78
- if (t.type === 'timestamp') {
78
+ if (malloy_types_1.TD.isAnyTimestamp(t)) {
79
79
  return { ...t, timeframe: tt };
80
80
  }
81
81
  if ((0, malloy_types_1.isDateUnit)(tt)) {
@@ -4,6 +4,7 @@ export declare const numberT: TypeDesc;
4
4
  export declare const stringT: TypeDesc;
5
5
  export declare const dateT: TypeDesc;
6
6
  export declare const timestampT: TypeDesc;
7
+ export declare const timestamptzT: TypeDesc;
7
8
  export declare const boolT: TypeDesc;
8
9
  export declare const errorT: TypeDesc;
9
10
  export declare const aggregateBoolT: TypeDesc;