@malloydata/malloy 0.0.323 → 0.0.324
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.
- package/dist/api/core.js +2 -0
- package/dist/api/util.js +16 -24
- package/dist/dialect/dialect.d.ts +113 -23
- package/dist/dialect/dialect.js +83 -13
- package/dist/dialect/duckdb/duckdb.d.ts +1 -4
- package/dist/dialect/duckdb/duckdb.js +13 -17
- package/dist/dialect/mysql/mysql.d.ts +9 -4
- package/dist/dialect/mysql/mysql.js +18 -11
- package/dist/dialect/pg_impl.d.ts +11 -2
- package/dist/dialect/pg_impl.js +79 -15
- package/dist/dialect/postgres/postgres.d.ts +1 -4
- package/dist/dialect/postgres/postgres.js +6 -18
- package/dist/dialect/snowflake/snowflake.d.ts +10 -4
- package/dist/dialect/snowflake/snowflake.js +80 -31
- package/dist/dialect/standardsql/standardsql.d.ts +9 -4
- package/dist/dialect/standardsql/standardsql.js +21 -19
- package/dist/dialect/tiny_parser.js +1 -1
- package/dist/dialect/trino/trino.d.ts +19 -6
- package/dist/dialect/trino/trino.js +163 -31
- package/dist/index.d.ts +1 -1
- package/dist/lang/ast/expressions/expr-granular-time.js +26 -8
- package/dist/lang/ast/expressions/expr-props.d.ts +24 -0
- package/dist/lang/ast/expressions/for-range.d.ts +1 -1
- package/dist/lang/ast/expressions/for-range.js +5 -4
- package/dist/lang/ast/expressions/time-literal.d.ts +9 -7
- package/dist/lang/ast/expressions/time-literal.js +43 -50
- package/dist/lang/ast/query-items/field-declaration.js +1 -2
- package/dist/lang/ast/time-utils.js +1 -1
- package/dist/lang/ast/typedesc-utils.d.ts +1 -0
- package/dist/lang/ast/typedesc-utils.js +14 -1
- package/dist/lang/ast/types/expression-def.js +1 -1
- package/dist/lang/ast/types/granular-result.js +2 -1
- package/dist/lang/composite-source-utils.js +1 -1
- package/dist/lang/lib/Malloy/MalloyLexer.d.ts +76 -75
- package/dist/lang/lib/Malloy/MalloyLexer.js +1252 -1243
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +77 -75
- package/dist/lang/lib/Malloy/MalloyParser.js +515 -510
- package/dist/lang/malloy-to-stable-query.js +13 -14
- package/dist/lang/test/expr-to-str.js +5 -1
- package/dist/malloy.d.ts +3 -2
- package/dist/malloy.js +6 -0
- package/dist/model/field_instance.js +1 -1
- package/dist/model/filter_compilers.d.ts +2 -1
- package/dist/model/filter_compilers.js +8 -5
- package/dist/model/malloy_types.d.ts +31 -9
- package/dist/model/malloy_types.js +49 -6
- package/dist/model/query_node.d.ts +2 -2
- package/dist/model/query_node.js +1 -0
- package/dist/model/query_query.js +15 -3
- package/dist/to_stable.js +13 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -6
package/dist/dialect/pg_impl.js
CHANGED
|
@@ -18,13 +18,17 @@ exports.timeExtractMap = {
|
|
|
18
18
|
* same implementations for the much of the SQL code generation
|
|
19
19
|
*/
|
|
20
20
|
class PostgresBase extends dialect_1.Dialect {
|
|
21
|
+
constructor() {
|
|
22
|
+
super(...arguments);
|
|
23
|
+
this.hasTimestamptz = true;
|
|
24
|
+
}
|
|
21
25
|
sqlNowExpr() {
|
|
22
26
|
return 'LOCALTIMESTAMP';
|
|
23
27
|
}
|
|
24
28
|
sqlTimeExtractExpr(qi, from) {
|
|
25
29
|
const units = exports.timeExtractMap[from.units] || from.units;
|
|
26
30
|
let extractFrom = from.e.sql;
|
|
27
|
-
if (malloy_types_1.TD.
|
|
31
|
+
if (malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
|
|
28
32
|
const tz = (0, dialect_1.qtz)(qi);
|
|
29
33
|
if (tz) {
|
|
30
34
|
extractFrom = `(${extractFrom}::TIMESTAMPTZ AT TIME ZONE '${tz}')`;
|
|
@@ -35,15 +39,32 @@ class PostgresBase extends dialect_1.Dialect {
|
|
|
35
39
|
}
|
|
36
40
|
sqlCast(qi, cast) {
|
|
37
41
|
const expr = cast.e.sql || '';
|
|
38
|
-
const {
|
|
42
|
+
const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
|
|
39
43
|
const tz = (0, dialect_1.qtz)(qi);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// Timezone-aware casts when query timezone is set
|
|
45
|
+
if (tz && srcTypeDef && dstTypeDef) {
|
|
46
|
+
// TIMESTAMP → DATE: convert via TIMESTAMPTZ to query timezone
|
|
47
|
+
if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
|
|
48
|
+
return `CAST((${expr}::TIMESTAMPTZ) AT TIME ZONE '${tz}' AS DATE)`;
|
|
49
|
+
}
|
|
50
|
+
// TIMESTAMPTZ → DATE: convert to query timezone
|
|
51
|
+
if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
|
|
52
|
+
return `CAST((${expr}) AT TIME ZONE '${tz}' AS DATE)`;
|
|
53
|
+
}
|
|
54
|
+
// DATE → TIMESTAMP: interpret date in query timezone, return UTC timestamp
|
|
55
|
+
if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
|
|
56
|
+
return `CAST((${expr})::TIMESTAMP AT TIME ZONE '${tz}' AS TIMESTAMP)`;
|
|
57
|
+
}
|
|
58
|
+
// DATE → TIMESTAMPTZ: interpret date in query timezone
|
|
59
|
+
if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
|
|
60
|
+
return `(${expr})::TIMESTAMP AT TIME ZONE '${tz}'`;
|
|
61
|
+
}
|
|
62
|
+
// TIMESTAMPTZ → TIMESTAMP: convert to query timezone (returns TIMESTAMP)
|
|
63
|
+
if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
|
|
64
|
+
return `(${expr}) AT TIME ZONE '${tz}'`;
|
|
65
|
+
}
|
|
46
66
|
}
|
|
67
|
+
// No special handling needed, or no query timezone
|
|
47
68
|
if (!malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
|
|
48
69
|
const castFunc = cast.safe ? 'TRY_CAST' : 'CAST';
|
|
49
70
|
return `${castFunc}(${expr} AS ${dstSQLType})`;
|
|
@@ -53,15 +74,18 @@ class PostgresBase extends dialect_1.Dialect {
|
|
|
53
74
|
sqlRegexpMatch(df) {
|
|
54
75
|
return `${df.kids.expr.sql} ~ ${df.kids.regex.sql}`;
|
|
55
76
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const tz =
|
|
77
|
+
sqlDateLiteral(_qi, literal) {
|
|
78
|
+
return `DATE '${literal}'`;
|
|
79
|
+
}
|
|
80
|
+
sqlTimestampLiteral(qi, literal, timezone) {
|
|
81
|
+
const tz = timezone || (0, dialect_1.qtz)(qi);
|
|
61
82
|
if (tz) {
|
|
62
|
-
return `TIMESTAMPTZ '${
|
|
83
|
+
return `TIMESTAMPTZ '${literal} ${tz}'::TIMESTAMP`;
|
|
63
84
|
}
|
|
64
|
-
return `TIMESTAMP '${
|
|
85
|
+
return `TIMESTAMP '${literal}'`;
|
|
86
|
+
}
|
|
87
|
+
sqlTimestamptzLiteral(_qi, literal, timezone) {
|
|
88
|
+
return `TIMESTAMPTZ '${literal} ${timezone}'`;
|
|
65
89
|
}
|
|
66
90
|
sqlLiteralRecord(_lit) {
|
|
67
91
|
throw new Error('Cannot create a record literal for postgres base dialect');
|
|
@@ -73,6 +97,46 @@ class PostgresBase extends dialect_1.Dialect {
|
|
|
73
97
|
sqlMaybeQuoteIdentifier(identifier) {
|
|
74
98
|
return '"' + identifier.replace(/"/g, '""') + '"';
|
|
75
99
|
}
|
|
100
|
+
sqlConvertToCivilTime(expr, timezone, typeDef) {
|
|
101
|
+
// PostgreSQL/DuckDB: AT TIME ZONE is polymorphic
|
|
102
|
+
// For timestamptz (TIMESTAMPTZ): AT TIME ZONE converts to plain TIMESTAMP (civil in timezone)
|
|
103
|
+
if (typeDef.type === 'timestamptz') {
|
|
104
|
+
return {
|
|
105
|
+
sql: `(${expr}) AT TIME ZONE '${timezone}'`,
|
|
106
|
+
typeDef: { type: 'timestamp' },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// For plain timestamps: cast to TIMESTAMPTZ (interprets as UTC)
|
|
110
|
+
// Then AT TIME ZONE converts to plain TIMESTAMP (civil in timezone)
|
|
111
|
+
return {
|
|
112
|
+
sql: `(${expr})::TIMESTAMPTZ AT TIME ZONE '${timezone}'`,
|
|
113
|
+
typeDef: { type: 'timestamp' },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
sqlConvertFromCivilTime(expr, timezone, destTypeDef) {
|
|
117
|
+
if (destTypeDef.type === 'timestamptz') {
|
|
118
|
+
return `(${expr}) AT TIME ZONE '${timezone}'`;
|
|
119
|
+
}
|
|
120
|
+
return `((${expr}) AT TIME ZONE '${timezone}')::TIMESTAMP`;
|
|
121
|
+
}
|
|
122
|
+
sqlTruncate(expr, unit, _typeDef, inCivilTime, _timezone) {
|
|
123
|
+
// PostgreSQL/DuckDB starts weeks on Monday, Malloy wants Sunday
|
|
124
|
+
// Add 1 day before truncating, subtract 1 day after
|
|
125
|
+
let truncated;
|
|
126
|
+
if (unit === 'week') {
|
|
127
|
+
truncated = `(DATE_TRUNC('${unit}', (${expr} + INTERVAL '1' DAY)) - INTERVAL '1' DAY)`;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
truncated = `DATE_TRUNC('${unit}', ${expr})`;
|
|
131
|
+
}
|
|
132
|
+
// DATE_TRUNC returns DATE for calendar units (day/week/month/quarter/year)
|
|
133
|
+
// When in civil time (plain TIMESTAMP), cast DATE back to TIMESTAMP to continue operations
|
|
134
|
+
const calendarUnits = ['day', 'week', 'month', 'quarter', 'year'];
|
|
135
|
+
if (inCivilTime && calendarUnits.includes(unit)) {
|
|
136
|
+
return `(${truncated})::TIMESTAMP`;
|
|
137
|
+
}
|
|
138
|
+
return truncated;
|
|
139
|
+
}
|
|
76
140
|
}
|
|
77
141
|
exports.PostgresBase = PostgresBase;
|
|
78
142
|
//# sourceMappingURL=pg_impl.js.map
|
|
@@ -44,9 +44,6 @@ export declare class PostgresDialect extends PostgresBase {
|
|
|
44
44
|
sqlFinalStage(lastStageName: string, _fields: string[]): string;
|
|
45
45
|
sqlSelectAliasAsStruct(alias: string): string;
|
|
46
46
|
sqlCreateTableAsSelect(_tableName: string, _sql: string): string;
|
|
47
|
-
sqlConvertToCivilTime(expr: string, timezone: string): string;
|
|
48
|
-
sqlConvertFromCivilTime(expr: string, timezone: string): string;
|
|
49
|
-
sqlTruncate(expr: string, unit: TimestampUnit, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
|
|
50
47
|
sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
|
|
51
48
|
sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
|
|
52
49
|
sqlMeasureTimeExpr(df: MeasureTimeExpr): string;
|
|
@@ -63,7 +60,7 @@ export declare class PostgresDialect extends PostgresBase {
|
|
|
63
60
|
[name: string]: DialectFunctionOverloadDef[];
|
|
64
61
|
};
|
|
65
62
|
malloyTypeToSQLType(malloyType: AtomicTypeDef): string;
|
|
66
|
-
sqlTypeToMalloyType(
|
|
63
|
+
sqlTypeToMalloyType(rawSqlType: string): BasicAtomicTypeDef;
|
|
67
64
|
castToString(expression: string): string;
|
|
68
65
|
concat(...values: string[]): string;
|
|
69
66
|
validateTypeName(sqlType: string): boolean;
|
|
@@ -46,6 +46,7 @@ const inSeconds = {
|
|
|
46
46
|
'day': 24 * 3600,
|
|
47
47
|
'week': 7 * 24 * 3600,
|
|
48
48
|
};
|
|
49
|
+
// TODO there needs be an audit of this data structure
|
|
49
50
|
const postgresToMalloyTypes = {
|
|
50
51
|
'character varying': { type: 'string' },
|
|
51
52
|
'name': { type: 'string' },
|
|
@@ -54,10 +55,10 @@ const postgresToMalloyTypes = {
|
|
|
54
55
|
'integer': { type: 'number', numberType: 'integer' },
|
|
55
56
|
'bigint': { type: 'number', numberType: 'integer' },
|
|
56
57
|
'double precision': { type: 'number', numberType: 'float' },
|
|
57
|
-
'timestamp without time zone': { type: 'timestamp' },
|
|
58
|
+
'timestamp without time zone': { type: 'timestamp' },
|
|
59
|
+
'timestamp with time zone': { type: 'timestamptz' },
|
|
58
60
|
'oid': { type: 'string' },
|
|
59
61
|
'boolean': { type: 'boolean' },
|
|
60
|
-
// ARRAY: "string",
|
|
61
62
|
'timestamp': { type: 'timestamp' },
|
|
62
63
|
'"char"': { type: 'string' },
|
|
63
64
|
'character': { type: 'string' },
|
|
@@ -209,20 +210,6 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
|
|
|
209
210
|
sqlCreateTableAsSelect(_tableName, _sql) {
|
|
210
211
|
throw new Error('Not implemented Yet');
|
|
211
212
|
}
|
|
212
|
-
sqlConvertToCivilTime(expr, timezone) {
|
|
213
|
-
return `(${expr})::TIMESTAMPTZ AT TIME ZONE '${timezone}'`;
|
|
214
|
-
}
|
|
215
|
-
sqlConvertFromCivilTime(expr, timezone) {
|
|
216
|
-
return `((${expr}) AT TIME ZONE '${timezone}')::TIMESTAMP`;
|
|
217
|
-
}
|
|
218
|
-
sqlTruncate(expr, unit, _typeDef, _inCivilTime, _timezone) {
|
|
219
|
-
// PostgreSQL starts weeks on Monday, Malloy wants Sunday
|
|
220
|
-
// Add 1 day before truncating, subtract 1 day after
|
|
221
|
-
if (unit === 'week') {
|
|
222
|
-
return `(DATE_TRUNC('${unit}', (${expr} + INTERVAL '1' DAY)) - INTERVAL '1' DAY)`;
|
|
223
|
-
}
|
|
224
|
-
return `DATE_TRUNC('${unit}', ${expr})`;
|
|
225
|
-
}
|
|
226
213
|
sqlOffsetTime(expr, op, magnitude, unit, _typeDef, _inCivilTime, _timezone) {
|
|
227
214
|
// Convert quarter/week to supported units
|
|
228
215
|
let offsetUnit = unit;
|
|
@@ -343,8 +330,9 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
|
|
|
343
330
|
}
|
|
344
331
|
return malloyType.type;
|
|
345
332
|
}
|
|
346
|
-
sqlTypeToMalloyType(
|
|
333
|
+
sqlTypeToMalloyType(rawSqlType) {
|
|
347
334
|
var _a, _b, _c;
|
|
335
|
+
const sqlType = rawSqlType.toLowerCase();
|
|
348
336
|
// Remove trailing params
|
|
349
337
|
const baseSqlType = (_b = (_a = sqlType.match(/^([\w\s]+)/)) === null || _a === void 0 ? void 0 : _a.at(0)) !== null && _b !== void 0 ? _b : sqlType;
|
|
350
338
|
return ((_c = postgresToMalloyTypes[baseSqlType.trim().toLowerCase()]) !== null && _c !== void 0 ? _c : {
|
|
@@ -380,7 +368,7 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
|
|
|
380
368
|
sqlTimeExtractExpr(qi, from) {
|
|
381
369
|
const units = pg_impl_1.timeExtractMap[from.units] || from.units;
|
|
382
370
|
let extractFrom = from.e.sql;
|
|
383
|
-
if (malloy_types_1.TD.
|
|
371
|
+
if (malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
|
|
384
372
|
const tz = (0, dialect_1.qtz)(qi);
|
|
385
373
|
if (tz) {
|
|
386
374
|
extractFrom = `(${extractFrom}::TIMESTAMPTZ AT TIME ZONE '${tz}')`;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Sampling, AtomicTypeDef, TimeExtractExpr, TypecastExpr,
|
|
1
|
+
import type { Sampling, AtomicTypeDef, TimeExtractExpr, TypecastExpr, MeasureTimeExpr, RegexMatchExpr, BasicAtomicTypeDef, TimestampTypeDef, ArrayLiteralNode, RecordLiteralNode } from '../../model/malloy_types';
|
|
2
2
|
import type { DialectFunctionOverloadDef } from '../functions';
|
|
3
3
|
import type { DialectFieldList, FieldReferenceType, QueryInfo } from '../dialect';
|
|
4
4
|
import { Dialect } from '../dialect';
|
|
5
5
|
export declare class SnowflakeDialect extends Dialect {
|
|
6
6
|
name: string;
|
|
7
7
|
experimental: boolean;
|
|
8
|
+
hasTimestamptz: boolean;
|
|
8
9
|
defaultNumberType: string;
|
|
9
10
|
defaultDecimalType: string;
|
|
10
11
|
udfPrefix: string;
|
|
@@ -45,15 +46,20 @@ export declare class SnowflakeDialect extends Dialect {
|
|
|
45
46
|
sqlSelectAliasAsStruct(alias: string): string;
|
|
46
47
|
sqlMaybeQuoteIdentifier(identifier: string): string;
|
|
47
48
|
sqlCreateTableAsSelect(tableName: string, sql: string): string;
|
|
48
|
-
sqlConvertToCivilTime(expr: string, timezone: string):
|
|
49
|
-
|
|
49
|
+
sqlConvertToCivilTime(expr: string, timezone: string, typeDef: AtomicTypeDef): {
|
|
50
|
+
sql: string;
|
|
51
|
+
typeDef: AtomicTypeDef;
|
|
52
|
+
};
|
|
53
|
+
sqlConvertFromCivilTime(expr: string, timezone: string, _destTypeDef: TimestampTypeDef): string;
|
|
50
54
|
sqlTruncate(expr: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
|
|
51
55
|
sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: string, typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
|
|
52
56
|
sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
|
|
53
57
|
private atTz;
|
|
54
58
|
sqlNowExpr(): string;
|
|
55
59
|
sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
|
|
56
|
-
|
|
60
|
+
sqlDateLiteral(_qi: QueryInfo, literal: string): string;
|
|
61
|
+
sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
|
|
62
|
+
sqlTimestamptzLiteral(_qi: QueryInfo, literal: string, timezone: string): string;
|
|
57
63
|
sqlMeasureTimeExpr(df: MeasureTimeExpr): string;
|
|
58
64
|
sqlRegexpMatch(compare: RegexMatchExpr): string;
|
|
59
65
|
sqlSampleTable(tableSQL: string, sample: Sampling | undefined): string;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.SnowflakeDialect = void 0;
|
|
26
|
+
const luxon_1 = require("luxon");
|
|
26
27
|
const utils_1 = require("../../model/utils");
|
|
27
28
|
const malloy_types_1 = require("../../model/malloy_types");
|
|
28
29
|
const functions_1 = require("../functions");
|
|
@@ -68,9 +69,9 @@ const snowflakeToMalloyTypes = {
|
|
|
68
69
|
'timestampntz': { type: 'timestamp' },
|
|
69
70
|
'timestamp_ntz': { type: 'timestamp' },
|
|
70
71
|
'timestamp without time zone': { type: 'timestamp' },
|
|
71
|
-
'timestamptz': { type: '
|
|
72
|
-
'timestamp_tz': { type: '
|
|
73
|
-
'timestamp with time zone': { type: '
|
|
72
|
+
'timestamptz': { type: 'timestamptz' },
|
|
73
|
+
'timestamp_tz': { type: 'timestamptz' },
|
|
74
|
+
'timestamp with time zone': { type: 'timestamptz' },
|
|
74
75
|
/* timestamp_ltz is not supported in malloy snowflake dialect */
|
|
75
76
|
};
|
|
76
77
|
class SnowflakeDialect extends dialect_1.Dialect {
|
|
@@ -78,6 +79,7 @@ class SnowflakeDialect extends dialect_1.Dialect {
|
|
|
78
79
|
super(...arguments);
|
|
79
80
|
this.name = 'snowflake';
|
|
80
81
|
this.experimental = false;
|
|
82
|
+
this.hasTimestamptz = true;
|
|
81
83
|
this.defaultNumberType = 'NUMBER';
|
|
82
84
|
this.defaultDecimalType = 'NUMBER';
|
|
83
85
|
this.udfPrefix = '__udf';
|
|
@@ -249,11 +251,23 @@ ${(0, utils_1.indent)(sql)}
|
|
|
249
251
|
);
|
|
250
252
|
`;
|
|
251
253
|
}
|
|
252
|
-
sqlConvertToCivilTime(expr, timezone) {
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
sqlConvertToCivilTime(expr, timezone, typeDef) {
|
|
255
|
+
// For timestamptz (TIMESTAMP_TZ): use 2-arg form
|
|
256
|
+
// Returns TIMESTAMP_TZ with timezone preserved
|
|
257
|
+
if (typeDef.type === 'timestamptz') {
|
|
258
|
+
return {
|
|
259
|
+
sql: `CONVERT_TIMEZONE('${timezone}', ${expr})`,
|
|
260
|
+
typeDef: { type: 'timestamptz' },
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// For plain timestamps (TIMESTAMP_NTZ): use 3-arg form
|
|
264
|
+
// Must cast to TIMESTAMP_NTZ first, returns TIMESTAMP_NTZ
|
|
265
|
+
return {
|
|
266
|
+
sql: `CONVERT_TIMEZONE('UTC', '${timezone}', (${expr})::TIMESTAMP_NTZ)`,
|
|
267
|
+
typeDef: { type: 'timestamp' },
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
sqlConvertFromCivilTime(expr, timezone, _destTypeDef) {
|
|
257
271
|
// After civil time operations, we have a TIMESTAMP_NTZ in the target timezone
|
|
258
272
|
// Convert from timezone to UTC, returning TIMESTAMP_NTZ
|
|
259
273
|
return `CONVERT_TIMEZONE('${timezone}', 'UTC', (${expr})::TIMESTAMP_NTZ)`;
|
|
@@ -261,6 +275,8 @@ ${(0, utils_1.indent)(sql)}
|
|
|
261
275
|
sqlTruncate(expr, unit, _typeDef, _inCivilTime, _timezone) {
|
|
262
276
|
// Snowflake session is configured with WEEK_START=7 (Sunday)
|
|
263
277
|
// so DATE_TRUNC already truncates to Sunday - no adjustment needed
|
|
278
|
+
// Unlike PostgreSQL/DuckDB, Snowflake's DATE_TRUNC preserves the input type
|
|
279
|
+
// (TIMESTAMP_NTZ → TIMESTAMP_NTZ, TIMESTAMP_TZ → TIMESTAMP_TZ)
|
|
264
280
|
return `DATE_TRUNC('${unit}', ${expr})`;
|
|
265
281
|
}
|
|
266
282
|
sqlOffsetTime(expr, op, magnitude, unit, typeDef, _inCivilTime, _timezone) {
|
|
@@ -272,7 +288,7 @@ ${(0, utils_1.indent)(sql)}
|
|
|
272
288
|
const extractUnits = extractionMap[from.units] || from.units;
|
|
273
289
|
let extractFrom = from.e.sql;
|
|
274
290
|
const tz = (0, dialect_1.qtz)(qi);
|
|
275
|
-
if (tz && malloy_types_1.TD.
|
|
291
|
+
if (tz && malloy_types_1.TD.isAnyTimestamp(from.e.typeDef)) {
|
|
276
292
|
extractFrom = `CONVERT_TIMEZONE('${tz}', ${extractFrom})`;
|
|
277
293
|
}
|
|
278
294
|
return `EXTRACT(${extractUnits} FROM ${extractFrom})`;
|
|
@@ -291,7 +307,7 @@ ${(0, utils_1.indent)(sql)}
|
|
|
291
307
|
}
|
|
292
308
|
sqlCast(qi, cast) {
|
|
293
309
|
const src = cast.e.sql || '';
|
|
294
|
-
const {
|
|
310
|
+
const { srcTypeDef, dstTypeDef, dstSQLType } = this.sqlCastPrep(cast);
|
|
295
311
|
if (malloy_types_1.TD.eq(srcTypeDef, dstTypeDef)) {
|
|
296
312
|
return src;
|
|
297
313
|
}
|
|
@@ -303,35 +319,65 @@ ${(0, utils_1.indent)(sql)}
|
|
|
303
319
|
refer to: https://docs.snowflake.com/en/sql-reference/functions/try_cast`);
|
|
304
320
|
}
|
|
305
321
|
const tz = (0, dialect_1.qtz)(qi);
|
|
306
|
-
//
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
|
|
322
|
+
// Timezone-aware casts when query timezone is set
|
|
323
|
+
if (tz && srcTypeDef && dstTypeDef) {
|
|
324
|
+
// TIMESTAMP → DATE: convert to query timezone, then to date
|
|
325
|
+
if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
|
|
326
|
+
return `TO_DATE(CONVERT_TIMEZONE('${tz}', ${src}))`;
|
|
327
|
+
}
|
|
328
|
+
// TIMESTAMPTZ → DATE: convert to query timezone, then to date
|
|
329
|
+
if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isDate(dstTypeDef)) {
|
|
330
|
+
return `TO_DATE(CONVERT_TIMEZONE('${tz}', ${src}))`;
|
|
331
|
+
}
|
|
332
|
+
// DATE → TIMESTAMP: interpret date in query timezone, return UTC timestamp
|
|
333
|
+
if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
|
|
334
|
+
const retExpr = `TO_TIMESTAMP(${src})`;
|
|
335
|
+
return this.atTz(retExpr, tz);
|
|
336
|
+
}
|
|
337
|
+
// DATE → TIMESTAMPTZ: interpret date in query timezone
|
|
338
|
+
if (malloy_types_1.TD.isDate(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
|
|
339
|
+
const retExpr = `TO_TIMESTAMP(${src})`;
|
|
340
|
+
return this.atTz(retExpr, tz);
|
|
341
|
+
}
|
|
342
|
+
// TIMESTAMPTZ → TIMESTAMP: convert to query timezone, get UTC wall clock
|
|
343
|
+
if (malloy_types_1.TD.isTimestamptz(srcTypeDef) && malloy_types_1.TD.isTimestamp(dstTypeDef)) {
|
|
344
|
+
return `CONVERT_TIMEZONE('${tz}', ${src})::TIMESTAMP_NTZ`;
|
|
345
|
+
}
|
|
346
|
+
// TIMESTAMP → TIMESTAMPTZ: interpret as UTC, convert to TIMESTAMPTZ
|
|
347
|
+
if (malloy_types_1.TD.isTimestamp(srcTypeDef) && malloy_types_1.TD.isTimestamptz(dstTypeDef)) {
|
|
348
|
+
return this.atTz(src, tz);
|
|
311
349
|
}
|
|
312
|
-
return `TO_DATE(${castExpr})`;
|
|
313
|
-
}
|
|
314
|
-
else if (op === 'date::timestamp') {
|
|
315
|
-
const retExpr = `TO_TIMESTAMP(${src})`;
|
|
316
|
-
return this.atTz(retExpr, tz);
|
|
317
350
|
}
|
|
318
351
|
const castFunc = cast.safe ? 'TRY_CAST' : 'CAST';
|
|
319
352
|
return `${castFunc}(${src} AS ${dstSQLType})`;
|
|
320
353
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// Interpret the literal as being in targetTimeZone, convert to UTC
|
|
331
|
-
ret = `CONVERT_TIMEZONE('${targetTimeZone}', 'UTC', ${ret})`;
|
|
354
|
+
sqlDateLiteral(_qi, literal) {
|
|
355
|
+
return `TO_DATE('${literal}')`;
|
|
356
|
+
}
|
|
357
|
+
sqlTimestampLiteral(qi, literal, timezone) {
|
|
358
|
+
const tz = timezone || (0, dialect_1.qtz)(qi);
|
|
359
|
+
let ret = `'${literal}'::TIMESTAMP_NTZ`;
|
|
360
|
+
if (tz) {
|
|
361
|
+
// Interpret the literal as being in query timezone, convert to UTC
|
|
362
|
+
ret = `CONVERT_TIMEZONE('${tz}', 'UTC', ${ret})`;
|
|
332
363
|
}
|
|
333
364
|
return ret;
|
|
334
365
|
}
|
|
366
|
+
sqlTimestamptzLiteral(_qi, literal, timezone) {
|
|
367
|
+
// Use TIMESTAMP_TZ_FROM_PARTS to create timestamptz
|
|
368
|
+
const dt = luxon_1.DateTime.fromFormat(literal, 'yyyy-LL-dd HH:mm:ss');
|
|
369
|
+
if (!dt.isValid) {
|
|
370
|
+
throw new Error(`Invalid timestamp literal: ${literal}`);
|
|
371
|
+
}
|
|
372
|
+
const year = dt.year;
|
|
373
|
+
const month = dt.month;
|
|
374
|
+
const day = dt.day;
|
|
375
|
+
const hour = dt.hour;
|
|
376
|
+
const minute = dt.minute;
|
|
377
|
+
const second = dt.second;
|
|
378
|
+
const nanosecond = dt.millisecond * 1000000;
|
|
379
|
+
return `TIMESTAMP_TZ_FROM_PARTS(${year}, ${month}, ${day}, ${hour}, ${minute}, ${second}, ${nanosecond}, '${timezone}')`;
|
|
380
|
+
}
|
|
335
381
|
sqlMeasureTimeExpr(df) {
|
|
336
382
|
const from = df.kids.left;
|
|
337
383
|
const to = df.kids.right;
|
|
@@ -413,6 +459,9 @@ ${(0, utils_1.indent)(sql)}
|
|
|
413
459
|
else if ((0, malloy_types_1.isBasicArray)(malloyType)) {
|
|
414
460
|
return `ARRAY(${this.malloyTypeToSQLType(malloyType.elementTypeDef)})`;
|
|
415
461
|
}
|
|
462
|
+
else if (malloyType.type === 'timestamptz') {
|
|
463
|
+
return 'TIMESTAMP_TZ';
|
|
464
|
+
}
|
|
416
465
|
return malloyType.type;
|
|
417
466
|
}
|
|
418
467
|
sqlTypeToMalloyType(sqlType) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Sampling, AtomicTypeDef, TimeExtractExpr, TypecastExpr, RegexMatchExpr,
|
|
1
|
+
import type { Sampling, AtomicTypeDef, TimeExtractExpr, TypecastExpr, RegexMatchExpr, MeasureTimeExpr, BasicAtomicTypeDef, RecordLiteralNode, ArrayLiteralNode, TimestampUnit, TimestampTypeDef } 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';
|
|
@@ -51,14 +51,19 @@ export declare class StandardSQLDialect extends Dialect {
|
|
|
51
51
|
sqlMaybeQuoteIdentifier(identifier: string): string;
|
|
52
52
|
sqlNowExpr(): string;
|
|
53
53
|
sqlTimeExtractExpr(qi: QueryInfo, te: TimeExtractExpr): string;
|
|
54
|
-
sqlConvertToCivilTime(expr: string, timezone: string):
|
|
55
|
-
|
|
54
|
+
sqlConvertToCivilTime(expr: string, timezone: string, _typeDef: AtomicTypeDef): {
|
|
55
|
+
sql: string;
|
|
56
|
+
typeDef: AtomicTypeDef;
|
|
57
|
+
};
|
|
58
|
+
sqlConvertFromCivilTime(expr: string, timezone: string, _destTypeDef: TimestampTypeDef): string;
|
|
56
59
|
sqlTruncate(expr: string, unit: TimestampUnit, typeDef: AtomicTypeDef, inCivilTime: boolean, timezone?: string): string;
|
|
57
60
|
sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: TimestampUnit, typeDef: AtomicTypeDef, inCivilTime: boolean, _timezone?: string): string;
|
|
58
61
|
ignoreInProject(fieldName: string): boolean;
|
|
59
62
|
sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
|
|
60
63
|
sqlRegexpMatch(match: RegexMatchExpr): string;
|
|
61
|
-
|
|
64
|
+
sqlDateLiteral(_qi: QueryInfo, literal: string): string;
|
|
65
|
+
sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
|
|
66
|
+
sqlTimestamptzLiteral(_qi: QueryInfo, _literal: string, _timezone: string): string;
|
|
62
67
|
sqlMeasureTimeExpr(measure: MeasureTimeExpr): string;
|
|
63
68
|
sqlSampleTable(tableSQL: string, sample: Sampling | undefined): string;
|
|
64
69
|
sqlLiteralString(literal: string): string;
|
|
@@ -108,7 +108,7 @@ class StandardSQLDialect extends dialect_1.Dialect {
|
|
|
108
108
|
const calendarUnits = ['day', 'week', 'month', 'quarter', 'year'];
|
|
109
109
|
const isCalendarTruncate = truncateTo !== undefined && calendarUnits.includes(truncateTo);
|
|
110
110
|
const isCalendarOffset = offsetUnit !== undefined && calendarUnits.includes(offsetUnit);
|
|
111
|
-
const needed = malloy_types_1.TD.
|
|
111
|
+
const needed = malloy_types_1.TD.isAnyTimestamp(typeDef) && (isCalendarTruncate || isCalendarOffset);
|
|
112
112
|
const tz = needed ? qtz(qi) || 'UTC' : undefined;
|
|
113
113
|
return { needed, tz };
|
|
114
114
|
}
|
|
@@ -222,14 +222,18 @@ ${(0, utils_1.indent)(sql)}
|
|
|
222
222
|
}
|
|
223
223
|
sqlTimeExtractExpr(qi, te) {
|
|
224
224
|
const extractTo = extractMap[te.units] || te.units;
|
|
225
|
-
const tz = malloy_types_1.TD.
|
|
225
|
+
const tz = malloy_types_1.TD.isAnyTimestamp(te.e.typeDef) && qtz(qi);
|
|
226
226
|
const tzAdd = tz ? ` AT TIME ZONE '${tz}'` : '';
|
|
227
227
|
return `EXTRACT(${extractTo} FROM ${te.e.sql}${tzAdd})`;
|
|
228
228
|
}
|
|
229
|
-
sqlConvertToCivilTime(expr, timezone) {
|
|
230
|
-
|
|
229
|
+
sqlConvertToCivilTime(expr, timezone, _typeDef) {
|
|
230
|
+
// BigQuery has no timestamptz type, so typeDef.timestamptz will never be true
|
|
231
|
+
return {
|
|
232
|
+
sql: `DATETIME(${expr}, '${timezone}')`,
|
|
233
|
+
typeDef: { type: 'timestamp' },
|
|
234
|
+
};
|
|
231
235
|
}
|
|
232
|
-
sqlConvertFromCivilTime(expr, timezone) {
|
|
236
|
+
sqlConvertFromCivilTime(expr, timezone, _destTypeDef) {
|
|
233
237
|
return `TIMESTAMP(${expr}, '${timezone}')`;
|
|
234
238
|
}
|
|
235
239
|
sqlTruncate(expr, unit, typeDef, inCivilTime, timezone) {
|
|
@@ -283,21 +287,19 @@ ${(0, utils_1.indent)(sql)}
|
|
|
283
287
|
sqlRegexpMatch(match) {
|
|
284
288
|
return `REGEXP_CONTAINS(${match.kids.expr.sql},${match.kids.regex.sql})`;
|
|
285
289
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
timestampArgs += `,'${tz}'`;
|
|
295
|
-
}
|
|
296
|
-
return `TIMESTAMP(${timestampArgs})`;
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
throw new Error(`Unsupported Literal time format ${lit.typeDef}`);
|
|
290
|
+
sqlDateLiteral(_qi, literal) {
|
|
291
|
+
return `DATE('${literal}')`;
|
|
292
|
+
}
|
|
293
|
+
sqlTimestampLiteral(qi, literal, timezone) {
|
|
294
|
+
let timestampArgs = `'${literal}'`;
|
|
295
|
+
const tz = timezone || qtz(qi);
|
|
296
|
+
if (tz && tz !== 'UTC') {
|
|
297
|
+
timestampArgs += `,'${tz}'`;
|
|
300
298
|
}
|
|
299
|
+
return `TIMESTAMP(${timestampArgs})`;
|
|
300
|
+
}
|
|
301
|
+
sqlTimestamptzLiteral(_qi, _literal, _timezone) {
|
|
302
|
+
throw new Error('BigQuery does not support timestamptz');
|
|
301
303
|
}
|
|
302
304
|
sqlMeasureTimeExpr(measure) {
|
|
303
305
|
const measureMap = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Expr, Sampling, AtomicTypeDef, TypecastExpr, RegexMatchExpr, MeasureTimeExpr,
|
|
1
|
+
import type { Expr, Sampling, AtomicTypeDef, ATimestampTypeDef, TypecastExpr, RegexMatchExpr, MeasureTimeExpr, 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,8 +51,11 @@ 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
|
-
sqlConvertToCivilTime(expr: string, timezone: string):
|
|
55
|
-
|
|
54
|
+
sqlConvertToCivilTime(expr: string, timezone: string, typeDef: AtomicTypeDef): {
|
|
55
|
+
sql: string;
|
|
56
|
+
typeDef: AtomicTypeDef;
|
|
57
|
+
};
|
|
58
|
+
sqlConvertFromCivilTime(expr: string, _timezone: string, destTypeDef: ATimestampTypeDef): string;
|
|
56
59
|
sqlTruncate(expr: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
|
|
57
60
|
sqlOffsetTime(expr: string, op: '+' | '-', magnitude: string, unit: string, _typeDef: AtomicTypeDef, _inCivilTime: boolean, _timezone?: string): string;
|
|
58
61
|
sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
|
|
@@ -74,7 +77,9 @@ export declare class TrinoDialect extends PostgresBase {
|
|
|
74
77
|
sqlMakeUnnestKey(key: string, rowKey: string): string;
|
|
75
78
|
sqlStringAggDistinct(distinctKey: string, valueSQL: string, separatorSQL: string): string;
|
|
76
79
|
validateTypeName(sqlType: string): boolean;
|
|
77
|
-
|
|
80
|
+
sqlDateLiteral(_qi: QueryInfo, literal: string): string;
|
|
81
|
+
sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
|
|
82
|
+
sqlTimestamptzLiteral(_qi: QueryInfo, literal: string, timezone: string): string;
|
|
78
83
|
sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
|
|
79
84
|
sqlLiteralRecord(lit: RecordLiteralNode): string;
|
|
80
85
|
}
|
|
@@ -83,8 +88,16 @@ export declare class PrestoDialect extends TrinoDialect {
|
|
|
83
88
|
supportsPipelinesInViews: boolean;
|
|
84
89
|
supportsLeftJoinUnnest: boolean;
|
|
85
90
|
sqlGenerateUUID(): string;
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
sqlDateLiteral(_qi: QueryInfo, literal: string): string;
|
|
92
|
+
sqlTimestampLiteral(qi: QueryInfo, literal: string, timezone: string | undefined): string;
|
|
93
|
+
sqlTimestamptzLiteral(_qi: QueryInfo, literal: string, timezone: string): string;
|
|
94
|
+
sqlConvertToCivilTime(expr: string, timezone: string, _typeDef: AtomicTypeDef): {
|
|
95
|
+
sql: string;
|
|
96
|
+
typeDef: AtomicTypeDef;
|
|
97
|
+
};
|
|
98
|
+
sqlConvertFromCivilTime(expr: string, _timezone: string, destTypeDef: ATimestampTypeDef): string;
|
|
99
|
+
sqlCast(qi: QueryInfo, cast: TypecastExpr): string;
|
|
100
|
+
sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
|
|
88
101
|
sqlUnnestAlias(source: string, alias: string, _fieldList: DialectFieldList, needDistinctKey: boolean, isArray: boolean, _isInNestedPipeline: boolean): string;
|
|
89
102
|
getDialectFunctions(): {
|
|
90
103
|
[name: string]: DialectFunctionOverloadDef[];
|