@malloydata/malloy 0.0.34-dev230523182411 → 0.0.34

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 (49) hide show
  1. package/dist/dialect/dialect.d.ts +20 -5
  2. package/dist/dialect/dialect.js +40 -7
  3. package/dist/dialect/duckdb.d.ts +5 -5
  4. package/dist/dialect/duckdb.js +54 -120
  5. package/dist/dialect/postgres.d.ts +5 -5
  6. package/dist/dialect/postgres.js +50 -38
  7. package/dist/dialect/standardsql.d.ts +5 -5
  8. package/dist/dialect/standardsql.js +78 -42
  9. package/dist/lang/ast/elements/refined-source.js +8 -0
  10. package/dist/lang/ast/executors/reduce-executor.js +4 -0
  11. package/dist/lang/ast/expressions/expr-cast.js +1 -1
  12. package/dist/lang/ast/expressions/expr-granular-time.d.ts +2 -2
  13. package/dist/lang/ast/expressions/expr-granular-time.js +10 -19
  14. package/dist/lang/ast/expressions/expr-time-extract.js +31 -4
  15. package/dist/lang/ast/expressions/time-literal.d.ts +12 -6
  16. package/dist/lang/ast/expressions/time-literal.js +74 -67
  17. package/dist/lang/ast/field-space/dynamic-space.d.ts +2 -0
  18. package/dist/lang/ast/field-space/dynamic-space.js +6 -0
  19. package/dist/lang/ast/field-space/query-spaces.js +3 -0
  20. package/dist/lang/ast/index.d.ts +1 -0
  21. package/dist/lang/ast/index.js +1 -0
  22. package/dist/lang/ast/source-properties/timezone-statement.d.ts +6 -0
  23. package/dist/lang/ast/source-properties/timezone-statement.js +35 -0
  24. package/dist/lang/ast/sources/named-source.js +1 -1
  25. package/dist/lang/ast/sources/sql-source.js +4 -0
  26. package/dist/lang/ast/time-utils.d.ts +2 -1
  27. package/dist/lang/ast/time-utils.js +5 -1
  28. package/dist/lang/ast/types/expression-def.d.ts +2 -0
  29. package/dist/lang/ast/types/expression-def.js +48 -18
  30. package/dist/lang/ast/types/query-property.d.ts +2 -1
  31. package/dist/lang/ast/types/query-property.js +3 -1
  32. package/dist/lang/ast/types/source-property.d.ts +2 -1
  33. package/dist/lang/ast/types/source-property.js +3 -1
  34. package/dist/lang/lib/Malloy/MalloyLexer.d.ts +3 -3
  35. package/dist/lang/lib/Malloy/MalloyLexer.js +958 -972
  36. package/dist/lang/lib/Malloy/MalloyParser.d.ts +52 -33
  37. package/dist/lang/lib/Malloy/MalloyParser.js +1243 -1128
  38. package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +24 -0
  39. package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +15 -0
  40. package/dist/lang/malloy-to-ast.d.ts +2 -0
  41. package/dist/lang/malloy-to-ast.js +6 -0
  42. package/dist/lang/test/parse-expects.d.ts +1 -0
  43. package/dist/lang/test/parse-expects.js +11 -1
  44. package/dist/lang/test/parse.spec.js +29 -37
  45. package/dist/malloy.js +31 -23
  46. package/dist/model/malloy_query.d.ts +2 -0
  47. package/dist/model/malloy_query.js +42 -41
  48. package/dist/model/malloy_types.d.ts +3 -2
  49. package/package.json +1 -1
@@ -4,6 +4,20 @@ interface DialectField {
4
4
  sqlExpression: string;
5
5
  sqlOutputName: string;
6
6
  }
7
+ /**
8
+ * Data which dialect methods need in order to correctly generate SQL.
9
+ * Initially this is just timezone related, but I made this an interface
10
+ * so it would be extensible with other useful information which might not be
11
+ * available until runtime (e.g. version number of db)
12
+ * mtoy TODO rename this interface to something other than "QueryInfo"
13
+ */
14
+ export interface QueryInfo {
15
+ queryTimezone?: string;
16
+ systemTimezone?: string;
17
+ }
18
+ export declare const dayIndex: number;
19
+ export declare function inDays(units: string): boolean;
20
+ export declare function qtz(qi: QueryInfo): string | undefined;
7
21
  /**
8
22
  * Someday this might be used to control how a function call in malloy is
9
23
  * translated into a function call in SQL. Today this is just so that
@@ -48,19 +62,20 @@ export declare abstract class Dialect {
48
62
  sqlDateToString(sqlDateExp: string): string;
49
63
  abstract sqlMaybeQuoteIdentifier(identifier: string): string;
50
64
  abstract sqlNow(): Expr;
51
- abstract sqlTrunc(sqlTime: TimeValue, units: TimestampUnit): Expr;
52
- abstract sqlExtract(sqlTime: TimeValue, units: ExtractUnit): Expr;
65
+ abstract sqlTrunc(qi: QueryInfo, sqlTime: TimeValue, units: TimestampUnit): Expr;
66
+ abstract sqlExtract(qi: QueryInfo, sqlTime: TimeValue, units: ExtractUnit): Expr;
53
67
  abstract sqlMeasureTime(from: TimeValue, to: TimeValue, units: TimestampUnit): Expr;
54
68
  abstract sqlAlterTime(op: '+' | '-', expr: TimeValue, n: Expr, timeframe: TimestampUnit): Expr;
55
69
  ignoreInProject(_fieldName: string): boolean;
56
- abstract sqlCast(cast: TypecastFragment): Expr;
57
- abstract sqlLiteralTime(timeString: string, type: TimeFieldType, timezone: string): string;
70
+ abstract sqlCast(qi: QueryInfo, cast: TypecastFragment): Expr;
71
+ abstract sqlLiteralTime(qi: QueryInfo, timeString: string, type: TimeFieldType, timezone?: string): string;
58
72
  abstract sqlLiteralString(literak: string): string;
59
73
  abstract sqlRegexpMatch(expr: Expr, regex: string): Expr;
60
74
  getFunctionInfo(functionName: string): FunctionInfo | undefined;
61
- dialectExpr(df: DialectFragment): Expr;
75
+ dialectExpr(qi: QueryInfo, df: DialectFragment): Expr;
62
76
  sqlSumDistinct(_key: string, _value: string, _funcName: string): string;
63
77
  sqlSampleTable(tableSQL: string, sample: Sampling | undefined): string;
64
78
  sqlOrderBy(orderTerms: string[]): string;
79
+ sqlTzStr(qi: QueryInfo): string;
65
80
  }
66
81
  export {};
@@ -22,8 +22,35 @@
22
22
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.Dialect = void 0;
25
+ exports.Dialect = exports.qtz = exports.inDays = exports.dayIndex = void 0;
26
26
  const malloy_types_1 = require("../model/malloy_types");
27
+ const allUnits = [
28
+ 'microsecond',
29
+ 'millisecond',
30
+ 'second',
31
+ 'minute',
32
+ 'hour',
33
+ 'day',
34
+ 'week',
35
+ 'month',
36
+ 'quarter',
37
+ 'year',
38
+ ];
39
+ exports.dayIndex = allUnits.indexOf('day');
40
+ function inDays(units) {
41
+ return allUnits.indexOf(units) >= exports.dayIndex;
42
+ }
43
+ exports.inDays = inDays;
44
+ // Return the active query timezone, if it different than the
45
+ // "native" timezone for timestamps.
46
+ function qtz(qi) {
47
+ const tz = qi.queryTimezone;
48
+ if (tz === undefined || tz === qi.systemTimezone) {
49
+ return undefined;
50
+ }
51
+ return tz;
52
+ }
53
+ exports.qtz = qtz;
27
54
  class Dialect {
28
55
  sqlFinalStage(_lastStageName, _fields) {
29
56
  throw new Error('Dialect has no final Stage but called Anyway');
@@ -40,7 +67,7 @@ class Dialect {
40
67
  getFunctionInfo(functionName) {
41
68
  return this.functionInfo[functionName.toLowerCase()];
42
69
  }
43
- dialectExpr(df) {
70
+ dialectExpr(qi, df) {
44
71
  switch (df.function) {
45
72
  case 'now':
46
73
  return this.sqlNow();
@@ -49,11 +76,11 @@ class Dialect {
49
76
  case 'delta':
50
77
  return this.sqlAlterTime(df.op, df.base, df.delta, df.units);
51
78
  case 'trunc':
52
- return this.sqlTrunc(df.expr, df.units);
79
+ return this.sqlTrunc(qi, df.expr, df.units);
53
80
  case 'extract':
54
- return this.sqlExtract(df.expr, df.units);
81
+ return this.sqlExtract(qi, df.expr, df.units);
55
82
  case 'cast':
56
- return this.sqlCast(df);
83
+ return this.sqlCast(qi, df);
57
84
  case 'regexpMatch':
58
85
  return this.sqlRegexpMatch(df.expr, df.regexp);
59
86
  case 'div': {
@@ -62,8 +89,11 @@ class Dialect {
62
89
  }
63
90
  return (0, malloy_types_1.mkExpr) `${df.numerator}/${df.denominator}`;
64
91
  }
65
- case 'timeLiteral':
66
- return [this.sqlLiteralTime(df.literal, df.literalType, df.timezone)];
92
+ case 'timeLiteral': {
93
+ return [
94
+ this.sqlLiteralTime(qi, df.literal, df.literalType, df.timezone),
95
+ ];
96
+ }
67
97
  case 'stringLiteral':
68
98
  return [this.sqlLiteralString(df.literal)];
69
99
  }
@@ -80,6 +110,9 @@ class Dialect {
80
110
  sqlOrderBy(orderTerms) {
81
111
  return `ORDER BY ${orderTerms.join(',')}`;
82
112
  }
113
+ sqlTzStr(qi) {
114
+ return `"${qi.queryTimezone}"`;
115
+ }
83
116
  }
84
117
  exports.Dialect = Dialect;
85
118
  //# sourceMappingURL=dialect.js.map
@@ -1,5 +1,5 @@
1
1
  import { DateUnit, Expr, ExtractUnit, Sampling, StructDef, TimeFieldType, TimeValue, TimestampUnit, TypecastFragment } from '../model/malloy_types';
2
- import { Dialect, DialectFieldList, FunctionInfo } from './dialect';
2
+ import { Dialect, DialectFieldList, FunctionInfo, QueryInfo } from './dialect';
3
3
  export declare class DuckDBDialect extends Dialect {
4
4
  name: string;
5
5
  defaultNumberType: string;
@@ -39,12 +39,12 @@ export declare class DuckDBDialect extends Dialect {
39
39
  getFunctionInfo(functionName: string): FunctionInfo | undefined;
40
40
  sqlMeasureTime(from: TimeValue, to: TimeValue, units: string): Expr;
41
41
  sqlNow(): Expr;
42
- sqlTrunc(sqlTime: TimeValue, units: TimestampUnit): Expr;
43
- sqlExtract(from: TimeValue, units: ExtractUnit): Expr;
42
+ sqlTrunc(qi: QueryInfo, sqlTime: TimeValue, units: TimestampUnit): Expr;
43
+ sqlExtract(qi: QueryInfo, from: TimeValue, units: ExtractUnit): Expr;
44
44
  sqlAlterTime(op: '+' | '-', expr: TimeValue, n: Expr, timeframe: DateUnit): Expr;
45
- sqlCast(cast: TypecastFragment): Expr;
45
+ sqlCast(qi: QueryInfo, cast: TypecastFragment): Expr;
46
46
  sqlRegexpMatch(expr: Expr, regexp: string): Expr;
47
- sqlLiteralTime(timeString: string, type: TimeFieldType, _timezone: string): string;
47
+ sqlLiteralTime(qi: QueryInfo, timeString: string, type: TimeFieldType, timezone: string | undefined): string;
48
48
  sqlSumDistinct(key: string, value: string, funcName: string): string;
49
49
  sqlSampleTable(tableSQL: string, sample: Sampling | undefined): string;
50
50
  sqlOrderBy(orderTerms: string[]): string;
@@ -28,85 +28,6 @@ const utils_1 = require("../model/utils");
28
28
  const dialect_1 = require("./dialect");
29
29
  // need to refactor runSQL to take a SQLBlock instead of just a sql string.
30
30
  const hackSplitComment = '-- hack: split on this';
31
- const _keywords = `
32
- ALL
33
- ANALYSE
34
- ANALYZE
35
- AND
36
- ANY
37
- ARRAY
38
- AS
39
- ASC_P
40
- ASYMMETRIC
41
- BOTH
42
- CASE
43
- CAST
44
- CHECK_P
45
- COLLATE
46
- COLUMN
47
- CONSTRAINT
48
- CREATE_P
49
- CURRENT_CATALOG
50
- CURRENT_DATE
51
- CURRENT_ROLE
52
- CURRENT_TIME
53
- CURRENT_TIMESTAMP
54
- CURRENT_USER
55
- DEFAULT
56
- DEFERRABLE
57
- DESC_P
58
- DISTINCT
59
- DO
60
- ELSE
61
- END_P
62
- EXCEPT
63
- FALSE_P
64
- FETCH
65
- FOR
66
- FOREIGN
67
- FROM
68
- GRANT
69
- GROUP_P
70
- HAVING
71
- IN_P
72
- INITIALLY
73
- INTERSECT
74
- INTO
75
- LATERAL_P
76
- LEADING
77
- LIMIT
78
- LOCALTIME
79
- LOCALTIMESTAMP
80
- NOT
81
- NULL_P
82
- OFFSET
83
- ON
84
- ONLY
85
- OR
86
- ORDER
87
- PLACING
88
- PRIMARY
89
- REFERENCES
90
- RETURNING
91
- SELECT
92
- SESSION_USER
93
- SOME
94
- SYMMETRIC
95
- TABLE
96
- THEN
97
- TO
98
- TRAILING
99
- TRUE_P
100
- UNION
101
- UNIQUE
102
- USER
103
- USING
104
- VARIADIC
105
- WHEN
106
- WHERE
107
- WINDOW
108
- WITH
109
- `.split(/\s/);
110
31
  const castMap = {
111
32
  number: 'double precision',
112
33
  string: 'varchar',
@@ -115,11 +36,6 @@ const pgExtractionMap = {
115
36
  day_of_week: 'dow',
116
37
  day_of_year: 'doy',
117
38
  };
118
- const inSeconds = {
119
- second: 1,
120
- minute: 60,
121
- hour: 3600,
122
- };
123
39
  class DuckDBDialect extends dialect_1.Dialect {
124
40
  constructor() {
125
41
  super(...arguments);
@@ -268,42 +184,52 @@ class DuckDBDialect extends dialect_1.Dialect {
268
184
  sqlMeasureTime(from, to, units) {
269
185
  let lVal = from.value;
270
186
  let rVal = to.value;
271
- if (inSeconds[units]) {
272
- lVal = (0, malloy_types_1.mkExpr) `EXTRACT(EPOCH FROM ${lVal})`;
273
- rVal = (0, malloy_types_1.mkExpr) `EXTRACT(EPOCH FROM ${rVal})`;
274
- const duration = (0, malloy_types_1.mkExpr) `(${rVal} - ${lVal})`;
275
- return units === 'second'
276
- ? duration
277
- : (0, malloy_types_1.mkExpr) `FLOOR(${duration}/${inSeconds[units].toString()})`;
278
- }
279
- if (from.valueType !== 'date') {
280
- lVal = (0, malloy_types_1.mkExpr) `CAST((${lVal}) AS DATE)`;
281
- }
282
- if (to.valueType !== 'date') {
283
- rVal = (0, malloy_types_1.mkExpr) `CAST((${rVal}) AS DATE)`;
284
- }
285
- if (units === 'week') {
286
- // DuckDB's weeks start on Monday, but Malloy's weeks start on Sunday
287
- lVal = (0, malloy_types_1.mkExpr) `(${lVal} + INTERVAL 1 DAY)`;
288
- rVal = (0, malloy_types_1.mkExpr) `(${rVal} + INTERVAL 1 DAY)`;
187
+ if (!(0, dialect_1.inDays)(units)) {
188
+ if (from.valueType === 'date') {
189
+ lVal = (0, malloy_types_1.mkExpr) `(${lVal})::TIMESTAMP`;
190
+ }
191
+ if (to.valueType === 'date') {
192
+ rVal = (0, malloy_types_1.mkExpr) `(${rVal})::TIMESTAMP`;
193
+ }
289
194
  }
290
- return (0, malloy_types_1.mkExpr) `DATE_DIFF('${units}', ${lVal}, ${rVal})`;
195
+ return (0, malloy_types_1.mkExpr) `DATE_SUB('${units}',${lVal},${rVal})`;
291
196
  }
292
197
  sqlNow() {
293
- return (0, malloy_types_1.mkExpr) `CURRENT_TIMESTAMP::TIMESTAMP`;
198
+ return (0, malloy_types_1.mkExpr) `LOCALTIMESTAMP`;
294
199
  }
295
- sqlTrunc(sqlTime, units) {
200
+ sqlTrunc(qi, sqlTime, units) {
296
201
  // adjusting for monday/sunday weeks
297
202
  const week = units === 'week';
298
203
  const truncThis = week
299
204
  ? (0, malloy_types_1.mkExpr) `${sqlTime.value} + INTERVAL 1 DAY`
300
205
  : sqlTime.value;
301
- const trunced = (0, malloy_types_1.mkExpr) `DATE_TRUNC('${units}', ${truncThis})`;
302
- return week ? (0, malloy_types_1.mkExpr) `(${trunced} - INTERVAL 1 DAY)` : trunced;
206
+ if (sqlTime.valueType === 'timestamp') {
207
+ const tz = (0, dialect_1.qtz)(qi);
208
+ if (tz) {
209
+ const civilSource = (0, malloy_types_1.mkExpr) `(${truncThis}::TIMESTAMPTZ AT TIME ZONE '${tz}')`;
210
+ let civilTrunc = (0, malloy_types_1.mkExpr) `DATE_TRUNC('${units}', ${civilSource})`;
211
+ // MTOY todo ... only need to do this if this is a date ...
212
+ civilTrunc = (0, malloy_types_1.mkExpr) `${civilTrunc}::TIMESTAMP`;
213
+ const truncTsTz = (0, malloy_types_1.mkExpr) `${civilTrunc} AT TIME ZONE '${tz}'`;
214
+ return (0, malloy_types_1.mkExpr) `(${truncTsTz})::TIMESTAMP`;
215
+ }
216
+ }
217
+ let result = (0, malloy_types_1.mkExpr) `DATE_TRUNC('${units}', ${truncThis})`;
218
+ if (week) {
219
+ result = (0, malloy_types_1.mkExpr) `(${result} - INTERVAL 1 DAY)`;
220
+ }
221
+ return result;
303
222
  }
304
- sqlExtract(from, units) {
223
+ sqlExtract(qi, from, units) {
305
224
  const pgUnits = pgExtractionMap[units] || units;
306
- const extracted = (0, malloy_types_1.mkExpr) `EXTRACT(${pgUnits} FROM ${from.value})`;
225
+ let extractFrom = from.value;
226
+ if (from.valueType === 'timestamp') {
227
+ const tz = (0, dialect_1.qtz)(qi);
228
+ if (tz) {
229
+ extractFrom = (0, malloy_types_1.mkExpr) `(${extractFrom}::TIMESTAMPTZ AT TIME ZONE '${tz}')`;
230
+ }
231
+ }
232
+ const extracted = (0, malloy_types_1.mkExpr) `EXTRACT(${pgUnits} FROM ${extractFrom})`;
307
233
  return units === 'day_of_week' ? (0, malloy_types_1.mkExpr) `(${extracted}+1)` : extracted;
308
234
  }
309
235
  sqlAlterTime(op, expr, n, timeframe) {
@@ -316,28 +242,36 @@ class DuckDBDialect extends dialect_1.Dialect {
316
242
  n = (0, malloy_types_1.mkExpr) `${n}*7`;
317
243
  }
318
244
  const interval = (0, malloy_types_1.mkExpr) `INTERVAL (${n}) ${timeframe}`;
319
- return (0, malloy_types_1.mkExpr) `((${expr.value})) ${op} ${interval}`;
320
- }
321
- sqlCast(cast) {
245
+ return (0, malloy_types_1.mkExpr) `${expr.value} ${op} ${interval}`;
246
+ }
247
+ sqlCast(qi, cast) {
248
+ const op = `${cast.srcType}::${cast.dstType}`;
249
+ const castTo = castMap[cast.dstType] || cast.dstType;
250
+ const tz = (0, dialect_1.qtz)(qi);
251
+ if (op === 'timestamp::date' && tz) {
252
+ const tstz = (0, malloy_types_1.mkExpr) `${cast.expr}::TIMESTAMPTZ`;
253
+ return (0, malloy_types_1.mkExpr) `CAST((${tstz}) AT TIME ZONE '${tz}' AS DATE)`;
254
+ }
255
+ else if (op === 'date::timestamp' && tz) {
256
+ return (0, malloy_types_1.mkExpr) `CAST((${cast.expr})::TIMESTAMP AT TIME ZONE '${tz}' AS TIMESTAMP)`;
257
+ }
322
258
  if (cast.dstType !== cast.srcType) {
323
- const castTo = castMap[cast.dstType] || cast.dstType;
324
- return (0, malloy_types_1.mkExpr) `cast(${cast.expr} as ${castTo})`;
259
+ return (0, malloy_types_1.mkExpr) `CAST(${cast.expr} AS ${castTo})`;
325
260
  }
326
261
  return cast.expr;
327
262
  }
328
263
  sqlRegexpMatch(expr, regexp) {
329
264
  return (0, malloy_types_1.mkExpr) `REGEXP_MATCHES(${expr}, ${regexp})`;
330
265
  }
331
- sqlLiteralTime(timeString, type, _timezone) {
266
+ sqlLiteralTime(qi, timeString, type, timezone) {
332
267
  if (type === 'date') {
333
268
  return `DATE '${timeString}'`;
334
269
  }
335
- else if (type === 'timestamp') {
336
- return `TIMESTAMP '${timeString}'`;
337
- }
338
- else {
339
- throw new Error(`Unknown Literal time format ${type}`);
270
+ const tz = timezone || (0, dialect_1.qtz)(qi);
271
+ if (tz) {
272
+ return `TIMESTAMPTZ '${timeString} ${tz}'::TIMESTAMP`;
340
273
  }
274
+ return `TIMESTAMP '${timeString}'`;
341
275
  }
342
276
  sqlSumDistinct(key, value, funcName) {
343
277
  // return `sum_distinct(list({key:${key}, val: ${value}}))`;
@@ -1,5 +1,5 @@
1
1
  import { DateUnit, Expr, ExtractUnit, Sampling, TimeFieldType, TimeValue, TimestampUnit, TypecastFragment } from '../model/malloy_types';
2
- import { Dialect, DialectFieldList, FunctionInfo } from './dialect';
2
+ import { Dialect, DialectFieldList, FunctionInfo, QueryInfo } from './dialect';
3
3
  export declare class PostgresDialect extends Dialect {
4
4
  name: string;
5
5
  defaultNumberType: string;
@@ -37,12 +37,12 @@ export declare class PostgresDialect extends Dialect {
37
37
  sqlMaybeQuoteIdentifier(identifier: string): string;
38
38
  sqlCreateTableAsSelect(_tableName: string, _sql: string): string;
39
39
  sqlNow(): Expr;
40
- sqlTrunc(sqlTime: TimeValue, units: TimestampUnit): Expr;
41
- sqlExtract(from: TimeValue, units: ExtractUnit): Expr;
40
+ sqlTrunc(qi: QueryInfo, sqlTime: TimeValue, units: TimestampUnit): Expr;
41
+ sqlExtract(qi: QueryInfo, from: TimeValue, units: ExtractUnit): Expr;
42
42
  sqlAlterTime(op: '+' | '-', expr: TimeValue, n: Expr, timeframe: DateUnit): Expr;
43
- sqlCast(cast: TypecastFragment): Expr;
43
+ sqlCast(qi: QueryInfo, cast: TypecastFragment): Expr;
44
44
  sqlRegexpMatch(expr: Expr, regexp: string): Expr;
45
- sqlLiteralTime(timeString: string, type: TimeFieldType, _timezone: string): string;
45
+ sqlLiteralTime(qi: QueryInfo, timeString: string, type: TimeFieldType, timezone: string | undefined): string;
46
46
  getFunctionInfo(functionName: string): FunctionInfo | undefined;
47
47
  sqlMeasureTime(from: TimeValue, to: TimeValue, units: string): Expr;
48
48
  sqlSumDistinct(key: string, value: string, funcName: string): string;
@@ -47,6 +47,8 @@ const inSeconds = {
47
47
  second: 1,
48
48
  minute: 60,
49
49
  hour: 3600,
50
+ day: 24 * 3600,
51
+ week: 7 * 24 * 3600,
50
52
  };
51
53
  class PostgresDialect extends dialect_1.Dialect {
52
54
  constructor() {
@@ -218,18 +220,39 @@ class PostgresDialect extends dialect_1.Dialect {
218
220
  sqlNow() {
219
221
  return (0, malloy_types_1.mkExpr) `CURRENT_TIMESTAMP`;
220
222
  }
221
- sqlTrunc(sqlTime, units) {
223
+ sqlTrunc(qi, sqlTime, units) {
222
224
  // adjusting for monday/sunday weeks
223
225
  const week = units === 'week';
224
226
  const truncThis = week
225
- ? (0, malloy_types_1.mkExpr) `${sqlTime.value}+interval'1'day`
227
+ ? (0, malloy_types_1.mkExpr) `${sqlTime.value} + INTERVAL '1' DAY`
226
228
  : sqlTime.value;
227
- const trunced = (0, malloy_types_1.mkExpr) `DATE_TRUNC('${units}', ${truncThis})`;
228
- return week ? (0, malloy_types_1.mkExpr) `(${trunced}-interval'1'day)` : trunced;
229
+ if (sqlTime.valueType === 'timestamp') {
230
+ const tz = (0, dialect_1.qtz)(qi);
231
+ if (tz) {
232
+ const civilSource = (0, malloy_types_1.mkExpr) `(${truncThis}::TIMESTAMPTZ AT TIME ZONE '${tz}')`;
233
+ let civilTrunc = (0, malloy_types_1.mkExpr) `DATE_TRUNC('${units}', ${civilSource})`;
234
+ // MTOY todo ... only need to do this if this is a date ...
235
+ civilTrunc = (0, malloy_types_1.mkExpr) `${civilTrunc}::TIMESTAMP`;
236
+ const truncTsTz = (0, malloy_types_1.mkExpr) `${civilTrunc} AT TIME ZONE '${tz}'`;
237
+ return (0, malloy_types_1.mkExpr) `(${truncTsTz})::TIMESTAMP`;
238
+ }
239
+ }
240
+ let result = (0, malloy_types_1.mkExpr) `DATE_TRUNC('${units}', ${truncThis})`;
241
+ if (week) {
242
+ result = (0, malloy_types_1.mkExpr) `(${result} - INTERVAL '1' DAY)`;
243
+ }
244
+ return result;
229
245
  }
230
- sqlExtract(from, units) {
246
+ sqlExtract(qi, from, units) {
231
247
  const pgUnits = pgExtractionMap[units] || units;
232
- const extracted = (0, malloy_types_1.mkExpr) `EXTRACT(${pgUnits} FROM ${from.value})`;
248
+ let extractFrom = from.value;
249
+ if (from.valueType === 'timestamp') {
250
+ const tz = (0, dialect_1.qtz)(qi);
251
+ if (tz) {
252
+ extractFrom = (0, malloy_types_1.mkExpr) `(${extractFrom}::TIMESTAMPTZ AT TIME ZONE '${tz}')`;
253
+ }
254
+ }
255
+ const extracted = (0, malloy_types_1.mkExpr) `EXTRACT(${pgUnits} FROM ${extractFrom})`;
233
256
  return units === 'day_of_week' ? (0, malloy_types_1.mkExpr) `(${extracted}+1)` : extracted;
234
257
  }
235
258
  sqlAlterTime(op, expr, n, timeframe) {
@@ -240,26 +263,34 @@ class PostgresDialect extends dialect_1.Dialect {
240
263
  const interval = (0, malloy_types_1.mkExpr) `make_interval(${pgMakeIntervalMap[timeframe]}=>${n})`;
241
264
  return (0, malloy_types_1.mkExpr) `((${expr.value})${op}${interval})`;
242
265
  }
243
- sqlCast(cast) {
266
+ sqlCast(qi, cast) {
267
+ const op = `${cast.srcType}::${cast.dstType}`;
268
+ const castTo = castMap[cast.dstType] || cast.dstType;
269
+ const tz = (0, dialect_1.qtz)(qi);
270
+ if (op === 'timestamp::date' && tz) {
271
+ const tstz = (0, malloy_types_1.mkExpr) `${cast.expr}::TIMESTAMPTZ`;
272
+ return (0, malloy_types_1.mkExpr) `CAST((${tstz}) AT TIME ZONE '${tz}' AS DATE)`;
273
+ }
274
+ else if (op === 'date::timestamp' && tz) {
275
+ return (0, malloy_types_1.mkExpr) `CAST((${cast.expr})::TIMESTAMP AT TIME ZONE '${tz}' AS TIMESTAMP)`;
276
+ }
244
277
  if (cast.dstType !== cast.srcType) {
245
- const castTo = castMap[cast.dstType] || cast.dstType;
246
- return (0, malloy_types_1.mkExpr) `cast(${cast.expr} as ${castTo})`;
278
+ return (0, malloy_types_1.mkExpr) `CAST(${cast.expr} AS ${castTo})`;
247
279
  }
248
280
  return cast.expr;
249
281
  }
250
282
  sqlRegexpMatch(expr, regexp) {
251
283
  return (0, malloy_types_1.mkExpr) `(${expr} ~ ${regexp})`;
252
284
  }
253
- sqlLiteralTime(timeString, type, _timezone) {
285
+ sqlLiteralTime(qi, timeString, type, timezone) {
254
286
  if (type === 'date') {
255
- return `DATE('${timeString}')`;
287
+ return `DATE '${timeString}'`;
256
288
  }
257
- else if (type === 'timestamp') {
258
- return `TIMESTAMP '${timeString}'`;
259
- }
260
- else {
261
- throw new Error(`Unknown Literal time format ${type}`);
289
+ const tz = timezone || (0, dialect_1.qtz)(qi);
290
+ if (tz) {
291
+ return `TIMESTAMPTZ '${timeString} ${tz}'::TIMESTAMP`;
262
292
  }
293
+ return `TIMESTAMP '${timeString}'`;
263
294
  }
264
295
  getFunctionInfo(functionName) {
265
296
  return this.functionInfo[functionName];
@@ -270,29 +301,10 @@ class PostgresDialect extends dialect_1.Dialect {
270
301
  if (inSeconds[units]) {
271
302
  lVal = (0, malloy_types_1.mkExpr) `EXTRACT(EPOCH FROM ${lVal})`;
272
303
  rVal = (0, malloy_types_1.mkExpr) `EXTRACT(EPOCH FROM ${rVal})`;
273
- const duration = (0, malloy_types_1.mkExpr) `(${rVal} - ${lVal})`;
304
+ const duration = (0, malloy_types_1.mkExpr) `${rVal}-${lVal}`;
274
305
  return units === 'second'
275
- ? duration
276
- : (0, malloy_types_1.mkExpr) `TRUNC(${duration}/${inSeconds[units].toString()})`;
277
- }
278
- if (units === 'day') {
279
- return (0, malloy_types_1.mkExpr) `${rVal}::date - ${lVal}::date`;
280
- }
281
- const yearDiff = (0, malloy_types_1.mkExpr) `(DATE_PART('year', ${rVal}) - DATE_PART('year', ${lVal}))`;
282
- if (units === 'year') {
283
- return yearDiff;
284
- }
285
- if (units === 'week') {
286
- const dayDiffForWeekStart = (0, malloy_types_1.mkExpr) `(DATE_TRUNC('week', ${rVal} + '1 day'::interval)::date - DATE_TRUNC('week', ${lVal} + '1 day'::interval)::date)`;
287
- return (0, malloy_types_1.mkExpr) `${dayDiffForWeekStart} / 7`;
288
- }
289
- if (units === 'month') {
290
- const monthDiff = (0, malloy_types_1.mkExpr) `DATE_PART('month', ${rVal}) - DATE_PART('month', ${lVal})`;
291
- return (0, malloy_types_1.mkExpr) `${yearDiff} * 12 + ${monthDiff}`;
292
- }
293
- if (units === 'quarter') {
294
- const qDiff = (0, malloy_types_1.mkExpr) `DATE_PART('quarter', ${rVal}) - DATE_PART('quarter', ${lVal})`;
295
- return (0, malloy_types_1.mkExpr) `${yearDiff} * 4 + ${qDiff}`;
306
+ ? (0, malloy_types_1.mkExpr) `FLOOR(${duration})`
307
+ : (0, malloy_types_1.mkExpr) `FLOOR((${duration})/${inSeconds[units].toString()}.0)`;
296
308
  }
297
309
  throw new Error(`Unknown or unhandled postgres time unit: ${units}`);
298
310
  }
@@ -1,5 +1,5 @@
1
1
  import { Expr, ExtractUnit, Sampling, TimeValue, TimestampUnit, TypecastFragment } from '../model/malloy_types';
2
- import { Dialect, DialectFieldList, FunctionInfo } from './dialect';
2
+ import { Dialect, DialectFieldList, FunctionInfo, QueryInfo } from './dialect';
3
3
  export declare class StandardSQLDialect extends Dialect {
4
4
  name: string;
5
5
  defaultNumberType: string;
@@ -36,13 +36,13 @@ export declare class StandardSQLDialect extends Dialect {
36
36
  keywords: string[];
37
37
  sqlMaybeQuoteIdentifier(identifier: string): string;
38
38
  sqlNow(): Expr;
39
- sqlTrunc(sqlTime: TimeValue, units: TimestampUnit): Expr;
40
- sqlExtract(expr: TimeValue, units: ExtractUnit): Expr;
39
+ sqlTrunc(qi: QueryInfo, sqlTime: TimeValue, units: TimestampUnit): Expr;
40
+ sqlExtract(qi: QueryInfo, expr: TimeValue, units: ExtractUnit): Expr;
41
41
  sqlAlterTime(op: '+' | '-', expr: TimeValue, n: Expr, timeframe: TimestampUnit): Expr;
42
42
  ignoreInProject(fieldName: string): boolean;
43
- sqlCast(cast: TypecastFragment): Expr;
43
+ sqlCast(qi: QueryInfo, cast: TypecastFragment): Expr;
44
44
  sqlRegexpMatch(expr: Expr, regexp: string): Expr;
45
- sqlLiteralTime(timeString: string, type: 'date' | 'timestamp', timezone: string): string;
45
+ sqlLiteralTime(qi: QueryInfo, timeString: string, type: 'date' | 'timestamp', timezone: string | undefined): string;
46
46
  sqlMeasureTime(from: TimeValue, to: TimeValue, units: string): Expr;
47
47
  sqlSampleTable(tableSQL: string, sample: Sampling | undefined): string;
48
48
  sqlLiteralString(literal: string): string;