@malloydata/malloy 0.0.321 → 0.0.323

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.
@@ -1,4 +1,4 @@
1
- import type { Sampling, AtomicTypeDef, TimeTruncExpr, TimeExtractExpr, TimeDeltaExpr, TypecastExpr, RegexMatchExpr, TimeLiteralNode, MeasureTimeExpr, BasicAtomicTypeDef, RecordLiteralNode, ArrayLiteralNode } from '../../model/malloy_types';
1
+ import type { Sampling, AtomicTypeDef, TimeExtractExpr, TypecastExpr, RegexMatchExpr, TimeLiteralNode, MeasureTimeExpr, BasicAtomicTypeDef, RecordLiteralNode, ArrayLiteralNode, TimestampUnit } from '../../model/malloy_types';
2
2
  import type { DialectFunctionOverloadDef } from '../functions';
3
3
  import type { DialectFieldList, OrderByRequest, QueryInfo } from '../dialect';
4
4
  import { Dialect } from '../dialect';
@@ -28,6 +28,10 @@ export declare class StandardSQLDialect extends Dialect {
28
28
  supportsHyperLogLog: boolean;
29
29
  likeEscape: boolean;
30
30
  quoteTablePath(tablePath: string): string;
31
+ needsCivilTimeComputation(typeDef: AtomicTypeDef, truncateTo: TimestampUnit | undefined, offsetUnit: TimestampUnit | undefined, qi: QueryInfo): {
32
+ needed: boolean;
33
+ tz: string | undefined;
34
+ };
31
35
  sqlGroupSetTable(groupSetCount: number): string;
32
36
  sqlAnyValue(groupSet: number, fieldName: string): string;
33
37
  sqlOrderBy(orderTerms: string[], obr?: OrderByRequest): string;
@@ -46,9 +50,11 @@ export declare class StandardSQLDialect extends Dialect {
46
50
  sqlSelectAliasAsStruct(alias: string): string;
47
51
  sqlMaybeQuoteIdentifier(identifier: string): string;
48
52
  sqlNowExpr(): string;
49
- sqlTruncExpr(qi: QueryInfo, trunc: TimeTruncExpr): string;
50
53
  sqlTimeExtractExpr(qi: QueryInfo, te: TimeExtractExpr): string;
51
- sqlAlterTimeExpr(df: TimeDeltaExpr): string;
54
+ sqlConvertToCivilTime(expr: string, timezone: string): string;
55
+ sqlConvertFromCivilTime(expr: string, timezone: string): string;
56
+ sqlTruncate(expr: string, unit: TimestampUnit, typeDef: AtomicTypeDef, inCivilTime: boolean, timezone?: string): string;
57
+ sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, typeDef: AtomicTypeDef, inCivilTime: boolean, _timezone?: string): string;
52
58
  ignoreInProject(fieldName: string): boolean;
53
59
  sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
54
60
  sqlRegexpMatch(match: RegexMatchExpr): string;
@@ -40,9 +40,6 @@ function timestampMeasureable(units) {
40
40
  'day',
41
41
  ].includes(units);
42
42
  }
43
- function dateMeasureable(units) {
44
- return ['day', 'week', 'month', 'quarter', 'year'].includes(units);
45
- }
46
43
  const extractMap = {
47
44
  'day_of_week': 'dayofweek',
48
45
  'day_of_year': 'dayofyear',
@@ -104,6 +101,17 @@ class StandardSQLDialect extends dialect_1.Dialect {
104
101
  quoteTablePath(tablePath) {
105
102
  return `\`${tablePath}\``;
106
103
  }
104
+ needsCivilTimeComputation(typeDef, truncateTo, offsetUnit, qi) {
105
+ // In addition to using "civil" space for units where a query time zone is
106
+ // set, BigQuery also uses civil space for unit operations not supported
107
+ // by the TIMESTAMP functions.
108
+ const calendarUnits = ['day', 'week', 'month', 'quarter', 'year'];
109
+ const isCalendarTruncate = truncateTo !== undefined && calendarUnits.includes(truncateTo);
110
+ const isCalendarOffset = offsetUnit !== undefined && calendarUnits.includes(offsetUnit);
111
+ const needed = malloy_types_1.TD.isTimestamp(typeDef) && (isCalendarTruncate || isCalendarOffset);
112
+ const tz = needed ? qtz(qi) || 'UTC' : undefined;
113
+ return { needed, tz };
114
+ }
107
115
  sqlGroupSetTable(groupSetCount) {
108
116
  return `CROSS JOIN (SELECT row_number() OVER() -1 group_set FROM UNNEST(GENERATE_ARRAY(0,${groupSetCount},1)))`;
109
117
  }
@@ -212,45 +220,46 @@ ${(0, utils_1.indent)(sql)}
212
220
  sqlNowExpr() {
213
221
  return 'CURRENT_TIMESTAMP()';
214
222
  }
215
- sqlTruncExpr(qi, trunc) {
216
- const tz = qtz(qi);
217
- const tzAdd = tz ? `, "${tz}"` : '';
218
- if (malloy_types_1.TD.isDate(trunc.e.typeDef)) {
219
- if (dateMeasureable(trunc.units)) {
220
- return `DATE_TRUNC(${trunc.e.sql},${trunc.units})`;
221
- }
222
- return `TIMESTAMP(${trunc.e.sql}${tzAdd})`;
223
- }
224
- return `TIMESTAMP_TRUNC(${trunc.e.sql},${trunc.units}${tzAdd})`;
225
- }
226
223
  sqlTimeExtractExpr(qi, te) {
227
224
  const extractTo = extractMap[te.units] || te.units;
228
225
  const tz = malloy_types_1.TD.isTimestamp(te.e.typeDef) && qtz(qi);
229
226
  const tzAdd = tz ? ` AT TIME ZONE '${tz}'` : '';
230
227
  return `EXTRACT(${extractTo} FROM ${te.e.sql}${tzAdd})`;
231
228
  }
232
- sqlAlterTimeExpr(df) {
233
- const from = df.kids.base;
234
- let dataType = from === null || from === void 0 ? void 0 : from.typeDef.type;
235
- let sql = from.sql;
236
- if (df.units !== 'day' && timestampMeasureable(df.units)) {
237
- // The units must be done in timestamp, no matter the input type
238
- if (dataType !== 'timestamp') {
239
- sql = `TIMESTAMP(${sql})`;
240
- dataType = 'timestamp';
241
- }
229
+ sqlConvertToCivilTime(expr, timezone) {
230
+ return `DATETIME(${expr}, '${timezone}')`;
231
+ }
232
+ sqlConvertFromCivilTime(expr, timezone) {
233
+ return `TIMESTAMP(${expr}, '${timezone}')`;
234
+ }
235
+ sqlTruncate(expr, unit, typeDef, inCivilTime, timezone) {
236
+ if (inCivilTime) {
237
+ // Operating on DATETIME (civil time)
238
+ return `DATETIME_TRUNC(${expr}, ${unit})`;
239
+ }
240
+ // Operating on DATE or TIMESTAMP
241
+ if (malloy_types_1.TD.isDate(typeDef)) {
242
+ return `DATE_TRUNC(${expr}, ${unit})`;
242
243
  }
243
- else if (dataType === 'timestamp') {
244
- sql = `DATETIME(${sql})`;
245
- dataType = 'datetime';
244
+ // TIMESTAMP truncation with optional timezone
245
+ const tzParam = timezone ? `, '${timezone}'` : '';
246
+ return `TIMESTAMP_TRUNC(${expr}, ${unit}${tzParam})`;
247
+ }
248
+ sqlOffsetTime(expr, op, magnitude, unit, typeDef, inCivilTime, _timezone) {
249
+ if (inCivilTime) {
250
+ // Operating on DATETIME (civil time)
251
+ const funcName = op === '+' ? 'DATETIME_ADD' : 'DATETIME_SUB';
252
+ return `${funcName}(${expr}, INTERVAL ${magnitude} ${unit})`;
246
253
  }
247
- const funcTail = df.op === '+' ? '_ADD' : '_SUB';
248
- const funcName = `${dataType.toUpperCase()}${funcTail}`;
249
- const newTime = `${funcName}(${sql}, INTERVAL ${df.kids.delta.sql} ${df.units})`;
250
- if (dataType === from.typeDef.type) {
251
- return newTime;
254
+ // Operating on DATE or TIMESTAMP
255
+ const baseType = typeDef.type;
256
+ if (baseType === 'date') {
257
+ const funcName = op === '+' ? 'DATE_ADD' : 'DATE_SUB';
258
+ return `${funcName}(${expr}, INTERVAL ${magnitude} ${unit})`;
252
259
  }
253
- return `${from.typeDef.type.toUpperCase()}(${newTime})`;
260
+ // TIMESTAMP with sub-day units only (calendar units go through civil time)
261
+ const funcName = op === '+' ? 'TIMESTAMP_ADD' : 'TIMESTAMP_SUB';
262
+ return `${funcName}(${expr}, INTERVAL ${magnitude} ${unit})`;
254
263
  }
255
264
  ignoreInProject(fieldName) {
256
265
  return fieldName === '_PARTITIONTIME';
@@ -1,4 +1,4 @@
1
- import type { Expr, Sampling, AtomicTypeDef, TimeDeltaExpr, TypecastExpr, RegexMatchExpr, MeasureTimeExpr, TimeLiteralNode, TimeExtractExpr, TimeTruncExpr, BasicAtomicTypeDef, RecordLiteralNode } from '../../model/malloy_types';
1
+ import type { Expr, Sampling, AtomicTypeDef, TypecastExpr, RegexMatchExpr, MeasureTimeExpr, TimeLiteralNode, TimeExtractExpr, BasicAtomicTypeDef, RecordLiteralNode } from '../../model/malloy_types';
2
2
  import type { DialectFunctionOverloadDef } from '../functions';
3
3
  import type { DialectFieldList, OrderByClauseType, QueryInfo } from '../dialect';
4
4
  import { PostgresBase } from '../pg_impl';
@@ -51,7 +51,10 @@ export declare class TrinoDialect extends PostgresBase {
51
51
  sqlCreateFunctionCombineLastStage(lastStageName: string, fieldList: DialectFieldList): string;
52
52
  sqlSelectAliasAsStruct(alias: string, fieldList: any): string;
53
53
  keywords: string[];
54
- sqlAlterTimeExpr(df: TimeDeltaExpr): string;
54
+ sqlConvertToCivilTime(expr: string, timezone: string): string;
55
+ sqlConvertFromCivilTime(expr: string, _timezone: string): string;
56
+ sqlTruncate(expr: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
57
+ sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
55
58
  sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
56
59
  sqlRegexpMatch(reCmp: RegexMatchExpr): string;
57
60
  sqlMeasureTimeExpr(mf: MeasureTimeExpr): string;
@@ -72,7 +75,6 @@ export declare class TrinoDialect extends PostgresBase {
72
75
  sqlStringAggDistinct(distinctKey: string, valueSQL: string, separatorSQL: string): string;
73
76
  validateTypeName(sqlType: string): boolean;
74
77
  sqlLiteralTime(qi: QueryInfo, lit: TimeLiteralNode): string;
75
- sqlTruncExpr(qi: QueryInfo, df: TimeTruncExpr): string;
76
78
  sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
77
79
  sqlLiteralRecord(lit: RecordLiteralNode): string;
78
80
  }
@@ -81,6 +83,8 @@ export declare class PrestoDialect extends TrinoDialect {
81
83
  supportsPipelinesInViews: boolean;
82
84
  supportsLeftJoinUnnest: boolean;
83
85
  sqlGenerateUUID(): string;
86
+ sqlLiteralTime(qi: QueryInfo, lit: TimeLiteralNode): string;
87
+ sqlConvertFromCivilTime(expr: string, _timezone: string): string;
84
88
  sqlUnnestAlias(source: string, alias: string, _fieldList: DialectFieldList, needDistinctKey: boolean, isArray: boolean, _isInNestedPipeline: boolean): string;
85
89
  getDialectFunctions(): {
86
90
  [name: string]: DialectFunctionOverloadDef[];
@@ -330,21 +330,35 @@ ${(0, utils_1.indent)(sql)}
330
330
  const definitions = this.buildTypeExpression(fieldList);
331
331
  return `CAST(ROW(${fields}) as ROW(${definitions})`;
332
332
  }
333
- sqlAlterTimeExpr(df) {
334
- let timeframe = df.units;
335
- let n = df.kids.delta.sql;
336
- if (timeframe === 'quarter') {
337
- timeframe = 'month';
338
- n = `${n}*3`;
333
+ sqlConvertToCivilTime(expr, timezone) {
334
+ return `${expr} AT TIME ZONE '${timezone}'`;
335
+ }
336
+ sqlConvertFromCivilTime(expr, _timezone) {
337
+ return `CAST(at_timezone(${expr}, 'UTC') AS TIMESTAMP)`;
338
+ }
339
+ sqlTruncate(expr, unit, _typeDef, _inCivilTime, _timezone) {
340
+ // Trino starts weeks on Monday, Malloy wants Sunday
341
+ // Add 1 day before truncating, subtract 1 day after
342
+ if (unit === 'week') {
343
+ return `(DATE_TRUNC('${unit}', (${expr} + INTERVAL '1' DAY)) - INTERVAL '1' DAY)`;
339
344
  }
340
- if (timeframe === 'week') {
341
- timeframe = 'day';
342
- n = `${n}*7`;
345
+ return `DATE_TRUNC('${unit}', ${expr})`;
346
+ }
347
+ sqlOffsetTime(expr, op, magnitude, unit, _typeDef, _inCivilTime, _timezone) {
348
+ // Convert quarter/week to supported units
349
+ let offsetUnit = unit;
350
+ let offsetMag = magnitude;
351
+ if (unit === 'quarter') {
352
+ offsetUnit = 'month';
353
+ offsetMag = `${magnitude}*3`;
343
354
  }
344
- if (df.op === '-') {
345
- n = `(${n})*-1`;
355
+ else if (unit === 'week') {
356
+ offsetUnit = 'day';
357
+ offsetMag = `${magnitude}*7`;
346
358
  }
347
- return `DATE_ADD('${timeframe}', ${n}, ${df.kids.base.sql})`;
359
+ // Handle subtraction by negating
360
+ const n = op === '-' ? `(${offsetMag})*-1` : offsetMag;
361
+ return `DATE_ADD('${offsetUnit}', ${n}, ${expr})`;
348
362
  }
349
363
  sqlCast(qi, cast) {
350
364
  const { op, srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
@@ -494,40 +508,11 @@ ${(0, utils_1.indent)(sql)}
494
508
  }
495
509
  const tz = lit.timezone || qtz(qi);
496
510
  if (tz) {
497
- return `TIMESTAMP '${lit.literal} ${tz}'`;
511
+ // 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)`;
498
513
  }
499
514
  return `TIMESTAMP '${lit.literal}'`;
500
515
  }
501
- sqlTruncExpr(qi, df) {
502
- // adjusting for monday/sunday weeks
503
- const week = df.units === 'week';
504
- const truncThis = week ? `${df.e.sql} + INTERVAL '1' DAY` : df.e.sql;
505
- // Only do timezone conversion for timestamps, not dates
506
- if (malloy_types_1.TD.isTimestamp(df.e.typeDef)) {
507
- const tz = qtz(qi);
508
- if (tz) {
509
- // get a civil version of the time in the query time zone
510
- const civilSource = `(CAST(${truncThis} AS TIMESTAMP WITH TIME ZONE) AT TIME ZONE '${tz}')`;
511
- // do truncation in that time space
512
- let civilTrunc = `DATE_TRUNC('${df.units}', ${civilSource})`;
513
- if (week) {
514
- civilTrunc = `(${civilTrunc} - INTERVAL '1' DAY)`;
515
- }
516
- // make a tstz from the civil time ... "AT TIME ZONE" of
517
- // a TIMESTAMP will produce a TIMESTAMP WITH TIME ZONE in that zone
518
- // where the civil appearance is the same as the TIMESTAMP
519
- const truncTsTz = `${civilTrunc} AT TIME ZONE '${tz}'`;
520
- // Now just make a system TIMESTAMP from that
521
- return `CAST(${truncTsTz} AS TIMESTAMP)`;
522
- }
523
- }
524
- // For dates (civil time) or timestamps without query timezone
525
- let result = `DATE_TRUNC('${df.units}', ${truncThis})`;
526
- if (week) {
527
- result = `(${result} - INTERVAL '1' DAY)`;
528
- }
529
- return result;
530
- }
531
516
  sqlTimeExtractExpr(qi, from) {
532
517
  const pgUnits = pg_impl_1.timeExtractMap[from.units] || from.units;
533
518
  let extractFrom = from.e.sql || '';
@@ -567,6 +552,19 @@ class PrestoDialect extends TrinoDialect {
567
552
  sqlGenerateUUID() {
568
553
  return 'CAST(UUID() AS VARCHAR)';
569
554
  }
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);
560
+ if (tz) {
561
+ return `CAST(TIMESTAMP '${lit.literal} ${tz}' AT TIME ZONE 'UTC' AS TIMESTAMP)`;
562
+ }
563
+ return `TIMESTAMP '${lit.literal}'`;
564
+ }
565
+ sqlConvertFromCivilTime(expr, _timezone) {
566
+ return `CAST(${expr} AT TIME ZONE 'UTC' AS TIMESTAMP)`;
567
+ }
570
568
  sqlUnnestAlias(source, alias, _fieldList, needDistinctKey, isArray, _isInNestedPipeline) {
571
569
  if (isArray) {
572
570
  if (needDistinctKey) {
@@ -52,7 +52,7 @@ class ErrorFactory {
52
52
  return factoryJoin;
53
53
  }
54
54
  static didCreate(s) {
55
- return s.type === 'table' && 'errorFactory' in s;
55
+ return 'errorFactory' in s;
56
56
  }
57
57
  static get query() {
58
58
  return {
@@ -141,7 +141,11 @@ class StaticSpace {
141
141
  type: found instanceof struct_space_field_base_1.StructSpaceFieldBase
142
142
  ? 'joinReference'
143
143
  : 'fieldReference',
144
- definition,
144
+ definition: {
145
+ type: definition.type,
146
+ annotation: definition.annotation,
147
+ location: definition.location,
148
+ },
145
149
  location: head.location,
146
150
  text: head.refString,
147
151
  });
@@ -118,7 +118,11 @@ class MalloyElement {
118
118
  this.addReference({
119
119
  type: 'queryReference',
120
120
  text: key,
121
- definition: result.entry,
121
+ definition: {
122
+ type: result.entry.type,
123
+ annotation: result.entry.annotation,
124
+ location: result.entry.location,
125
+ },
122
126
  location: reference.location,
123
127
  });
124
128
  }
@@ -126,7 +130,11 @@ class MalloyElement {
126
130
  this.addReference({
127
131
  type: 'exploreReference',
128
132
  text: key,
129
- definition: result.entry,
133
+ definition: {
134
+ type: result.entry.type,
135
+ annotation: result.entry.annotation,
136
+ location: result.entry.location,
137
+ },
130
138
  location: reference.location,
131
139
  });
132
140
  }