@malloydata/malloy 0.0.412 → 0.0.414
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/foundation/core.d.ts +21 -0
- package/dist/api/foundation/core.js +30 -0
- package/dist/dialect/databricks/databricks.d.ts +2 -1
- package/dist/dialect/databricks/databricks.js +12 -6
- package/dist/dialect/dialect.d.ts +12 -1
- package/dist/dialect/dialect.js +25 -0
- package/dist/dialect/duckdb/duckdb.d.ts +2 -1
- package/dist/dialect/duckdb/duckdb.js +9 -2
- package/dist/dialect/mysql/mysql.d.ts +2 -1
- package/dist/dialect/mysql/mysql.js +14 -5
- package/dist/dialect/postgres/postgres.d.ts +2 -1
- package/dist/dialect/postgres/postgres.js +9 -2
- package/dist/dialect/snowflake/snowflake.d.ts +2 -1
- package/dist/dialect/snowflake/snowflake.js +11 -3
- package/dist/dialect/standardsql/standardsql.d.ts +2 -1
- package/dist/dialect/standardsql/standardsql.js +8 -2
- package/dist/dialect/trino/trino.d.ts +2 -1
- package/dist/dialect/trino/trino.js +8 -2
- package/dist/lang/ast/field-space/dynamic-space.js +14 -1
- package/dist/lang/ast/field-space/query-spaces.d.ts +1 -0
- package/dist/lang/ast/field-space/query-spaces.js +5 -0
- package/dist/lang/ast/query-items/field-references.d.ts +10 -1
- package/dist/lang/ast/query-items/field-references.js +67 -4
- package/dist/lang/ast/query-properties/nest.js +15 -0
- package/dist/lang/ast/source-elements/named-source.js +4 -1
- package/dist/lang/ast/statements/define-source.js +9 -2
- package/dist/lang/ast/types/malloy-element.js +3 -4
- package/dist/lang/parse-log.d.ts +10 -0
- package/dist/lang/parse-log.js +3 -0
- package/dist/model/expression_compiler.js +2 -2
- package/dist/model/field_instance.d.ts +1 -0
- package/dist/model/field_instance.js +14 -0
- package/dist/model/index.d.ts +2 -1
- package/dist/model/index.js +3 -1
- package/dist/model/malloy_types.d.ts +25 -6
- package/dist/model/nest-capability.d.ts +37 -0
- package/dist/model/nest-capability.js +31 -0
- package/dist/model/persist_utils.js +1 -1
- package/dist/model/query_query.d.ts +26 -5
- package/dist/model/query_query.js +256 -88
- package/dist/model/source_def_utils.d.ts +31 -7
- package/dist/model/source_def_utils.js +39 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -119,6 +119,27 @@ export declare class Explore extends Entity implements Taggable {
|
|
|
119
119
|
* model annotations. */
|
|
120
120
|
get _modelDef(): ModelDef;
|
|
121
121
|
get source(): Explore | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* THIS IS A HIGHLY EXPERIMENTAL API AND MAY VANISH OR CHANGE WITHOUT NOTICE
|
|
124
|
+
*
|
|
125
|
+
* If this source was created as an unmodified reference to another source, a
|
|
126
|
+
* stable identifier of the source it refers to; undefined when this source
|
|
127
|
+
* defines its own shape. Two sources that refer to the same thing share this
|
|
128
|
+
* id, so it can be compared to tell whether two otherwise un-nameable sources
|
|
129
|
+
* are the same — even when the referenced source can't be named here.
|
|
130
|
+
*/
|
|
131
|
+
get referenceSourceID(): string | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* THIS IS A HIGHLY EXPERIMENTAL API AND MAY VANISH OR CHANGE WITHOUT NOTICE
|
|
134
|
+
*
|
|
135
|
+
* If this source was created as an unmodified reference to a source that is in
|
|
136
|
+
* this model's namespace (`source: a is b`, or a plain join), return that
|
|
137
|
+
* source as it appears in the namespace — read `.name` for the name it goes by
|
|
138
|
+
* here. Returns undefined when this source defines its own shape (a table,
|
|
139
|
+
* SQL, query, or modified/extended source), or when the referenced source is
|
|
140
|
+
* not in this model's namespace.
|
|
141
|
+
*/
|
|
142
|
+
referencedSource(): Explore | undefined;
|
|
122
143
|
isIntrinsic(): boolean;
|
|
123
144
|
isExploreField(): this is ExploreField;
|
|
124
145
|
/** @deprecated Use `.annotations.parseAsTag(route)`. */
|
|
@@ -172,6 +172,36 @@ class Explore extends Entity {
|
|
|
172
172
|
get source() {
|
|
173
173
|
return this.sourceExplore;
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* THIS IS A HIGHLY EXPERIMENTAL API AND MAY VANISH OR CHANGE WITHOUT NOTICE
|
|
177
|
+
*
|
|
178
|
+
* If this source was created as an unmodified reference to another source, a
|
|
179
|
+
* stable identifier of the source it refers to; undefined when this source
|
|
180
|
+
* defines its own shape. Two sources that refer to the same thing share this
|
|
181
|
+
* id, so it can be compared to tell whether two otherwise un-nameable sources
|
|
182
|
+
* are the same — even when the referenced source can't be named here.
|
|
183
|
+
*/
|
|
184
|
+
get referenceSourceID() {
|
|
185
|
+
return (0, model_1.isSourceDef)(this._structDef)
|
|
186
|
+
? this._structDef.referenceID
|
|
187
|
+
: undefined;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* THIS IS A HIGHLY EXPERIMENTAL API AND MAY VANISH OR CHANGE WITHOUT NOTICE
|
|
191
|
+
*
|
|
192
|
+
* If this source was created as an unmodified reference to a source that is in
|
|
193
|
+
* this model's namespace (`source: a is b`, or a plain join), return that
|
|
194
|
+
* source as it appears in the namespace — read `.name` for the name it goes by
|
|
195
|
+
* here. Returns undefined when this source defines its own shape (a table,
|
|
196
|
+
* SQL, query, or modified/extended source), or when the referenced source is
|
|
197
|
+
* not in this model's namespace.
|
|
198
|
+
*/
|
|
199
|
+
referencedSource() {
|
|
200
|
+
if (!(0, model_1.isSourceDef)(this._structDef))
|
|
201
|
+
return undefined;
|
|
202
|
+
const ref = (0, source_def_utils_1.sourceNamespaceReference)(this._ownerModelDef, this._structDef);
|
|
203
|
+
return ref ? new Explore(this._ownerModelDef, ref.source) : undefined;
|
|
204
|
+
}
|
|
175
205
|
isIntrinsic() {
|
|
176
206
|
if ((0, model_1.isAtomicFieldType)(this._structDef.type)) {
|
|
177
207
|
return !('e' in this._structDef);
|
|
@@ -25,6 +25,7 @@ export declare class DatabricksDialect extends Dialect {
|
|
|
25
25
|
dontUnionIndex: boolean;
|
|
26
26
|
supportsQualify: boolean;
|
|
27
27
|
supportsNesting: boolean;
|
|
28
|
+
supportsNestedProjectionLimit: boolean;
|
|
28
29
|
hasLateralColumnAliasInSelect: boolean;
|
|
29
30
|
cantPartitionWindowFunctionsOnExpressions: boolean;
|
|
30
31
|
experimental: boolean;
|
|
@@ -48,7 +49,7 @@ export declare class DatabricksDialect extends Dialect {
|
|
|
48
49
|
sqlOrderBy(orderTerms: string[], obr?: OrderByRequest): string;
|
|
49
50
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
50
51
|
private buildNamedStructExpression;
|
|
51
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined): string;
|
|
52
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
52
53
|
private buildArraySortComparator;
|
|
53
54
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
54
55
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
@@ -62,6 +62,7 @@ class DatabricksDialect extends dialect_1.Dialect {
|
|
|
62
62
|
this.dontUnionIndex = false;
|
|
63
63
|
this.supportsQualify = false;
|
|
64
64
|
this.supportsNesting = true;
|
|
65
|
+
this.supportsNestedProjectionLimit = true;
|
|
65
66
|
this.hasLateralColumnAliasInSelect = true;
|
|
66
67
|
this.cantPartitionWindowFunctionsOnExpressions = true;
|
|
67
68
|
this.experimental = false;
|
|
@@ -169,13 +170,18 @@ class DatabricksDialect extends dialect_1.Dialect {
|
|
|
169
170
|
.join(', ') +
|
|
170
171
|
')');
|
|
171
172
|
}
|
|
172
|
-
sqlAggregateTurtle(groupSet, fieldList, orderBy) {
|
|
173
|
+
sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
|
|
173
174
|
const namedStruct = this.buildNamedStructExpression(fieldList);
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
176
|
+
const filterClause = cond ? ` FILTER (WHERE ${cond})` : '';
|
|
177
|
+
const collectExpr = `COLLECT_LIST(${namedStruct})${filterClause}`;
|
|
178
|
+
// COLLECT_LIST is unordered, so ordering is done post-aggregation via
|
|
179
|
+
// ARRAY_SORT — the limit must slice the *ordered* array.
|
|
180
|
+
const ordered = !orderBy || orderBy.length === 0
|
|
181
|
+
? collectExpr
|
|
182
|
+
: `ARRAY_SORT(${collectExpr}, (l, r) -> ${this.buildArraySortComparator(orderBy)})`;
|
|
183
|
+
// SLICE(array, start, length) is 1-based; length n keeps the first n.
|
|
184
|
+
return limit !== undefined ? `SLICE(${ordered}, 1, ${limit})` : ordered;
|
|
179
185
|
}
|
|
180
186
|
// Build a lambda comparator for ARRAY_SORT that handles multi-field
|
|
181
187
|
// mixed-direction ordering. Each field comparison returns -1/0/1;
|
|
@@ -63,6 +63,16 @@ export type FieldReferenceType = 'table' | 'nest source' | 'array[scalar]' | 'ar
|
|
|
63
63
|
export declare const dayIndex: number;
|
|
64
64
|
export declare function inDays(units: string): boolean;
|
|
65
65
|
export declare function qtz(qi: QueryInfo): string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* The per-element condition for an aggregate turtle (sqlAggregateTurtle). On
|
|
68
|
+
* the normal path it gates each row by its group_set (and a projection's own
|
|
69
|
+
* `where:`). On the single-group-set fast path `groupSet` is `undefined` --
|
|
70
|
+
* there is no group_set column, every row is in the one group -- so the
|
|
71
|
+
* condition is just the `where:`, or `undefined` to include unconditionally.
|
|
72
|
+
* Callers wrap the result in whatever form the dialect uses (`FILTER (WHERE
|
|
73
|
+
* ...)`, `CASE WHEN ... THEN`, `IF(...)`).
|
|
74
|
+
*/
|
|
75
|
+
export declare function turtleGroupSetCondition(groupSet: number | undefined, filterSQL: string | undefined): string | undefined;
|
|
66
76
|
export type OrderByClauseType = 'output_name' | 'ordinal' | 'expression';
|
|
67
77
|
export type OrderByRequest = 'query' | 'turtle' | 'analytical';
|
|
68
78
|
export type BooleanTypeSupport = 'supported' | 'simulated' | 'none';
|
|
@@ -99,6 +109,7 @@ export declare abstract class Dialect {
|
|
|
99
109
|
cantPartitionWindowFunctionsOnExpressions: boolean;
|
|
100
110
|
sqlLateralJoinBag(_expressions: LateralJoinExpression[]): string;
|
|
101
111
|
supportsPipelinesInViews: boolean;
|
|
112
|
+
supportsNestedProjectionLimit: boolean;
|
|
102
113
|
supportsArraysInData: boolean;
|
|
103
114
|
hasTimestamptz: boolean;
|
|
104
115
|
readsNestedData: boolean;
|
|
@@ -175,7 +186,7 @@ export declare abstract class Dialect {
|
|
|
175
186
|
sqlValidateTableName(input: string): ValidateTablePathResult;
|
|
176
187
|
abstract sqlGroupSetTable(groupSetCount: number): string;
|
|
177
188
|
abstract sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
178
|
-
abstract sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined): string;
|
|
189
|
+
abstract sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
179
190
|
sqlTurtleOrderByClause(orderBy: CompiledOrderBy[]): string;
|
|
180
191
|
abstract sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
181
192
|
abstract sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
package/dist/dialect/dialect.js
CHANGED
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.Dialect = exports.dayIndex = exports.EscapeStyle = exports.MAX_DECIMAL38 = exports.MIN_DECIMAL38 = exports.MAX_INT128 = exports.MIN_INT128 = exports.MAX_INT64 = exports.MIN_INT64 = exports.MAX_INT32 = exports.MIN_INT32 = void 0;
|
|
8
8
|
exports.inDays = inDays;
|
|
9
9
|
exports.qtz = qtz;
|
|
10
|
+
exports.turtleGroupSetCondition = turtleGroupSetCondition;
|
|
10
11
|
const malloy_types_1 = require("../model/malloy_types");
|
|
11
12
|
const table_path_1 = require("./table-path");
|
|
12
13
|
/*
|
|
@@ -61,6 +62,23 @@ function qtz(qi) {
|
|
|
61
62
|
}
|
|
62
63
|
return tz;
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The per-element condition for an aggregate turtle (sqlAggregateTurtle). On
|
|
67
|
+
* the normal path it gates each row by its group_set (and a projection's own
|
|
68
|
+
* `where:`). On the single-group-set fast path `groupSet` is `undefined` --
|
|
69
|
+
* there is no group_set column, every row is in the one group -- so the
|
|
70
|
+
* condition is just the `where:`, or `undefined` to include unconditionally.
|
|
71
|
+
* Callers wrap the result in whatever form the dialect uses (`FILTER (WHERE
|
|
72
|
+
* ...)`, `CASE WHEN ... THEN`, `IF(...)`).
|
|
73
|
+
*/
|
|
74
|
+
function turtleGroupSetCondition(groupSet, filterSQL) {
|
|
75
|
+
if (groupSet === undefined) {
|
|
76
|
+
return filterSQL;
|
|
77
|
+
}
|
|
78
|
+
return filterSQL
|
|
79
|
+
? `group_set=${groupSet} AND ${filterSQL}`
|
|
80
|
+
: `group_set=${groupSet}`;
|
|
81
|
+
}
|
|
64
82
|
class Dialect {
|
|
65
83
|
constructor() {
|
|
66
84
|
// -- we should add flags with default values from now on so as to not break
|
|
@@ -73,6 +91,13 @@ class Dialect {
|
|
|
73
91
|
this.cantPartitionWindowFunctionsOnExpressions = false;
|
|
74
92
|
// Snowflake can't yet support pipelines in nested views.
|
|
75
93
|
this.supportsPipelinesInViews = true;
|
|
94
|
+
// Can the dialect apply a `limit:` to a nested `select:` (a projection
|
|
95
|
+
// nest)? A projection has no group-by keys to ROW_NUMBER-shave on, so the
|
|
96
|
+
// limit is applied by limiting/slicing the aggregated array inside
|
|
97
|
+
// `sqlAggregateTurtle`. Defaults false so a new dialect must consciously
|
|
98
|
+
// opt in (and implement the slice) rather than silently dropping the limit;
|
|
99
|
+
// the compiler rejects projection `limit:` when this is false.
|
|
100
|
+
this.supportsNestedProjectionLimit = false;
|
|
76
101
|
// Some dialects don't supporrt arrays (mysql)
|
|
77
102
|
this.supportsArraysInData = true;
|
|
78
103
|
// Does the dialect support timestamptz (TIMESTAMP WITH TIME ZONE)?
|
|
@@ -22,6 +22,7 @@ export declare class DuckDBDialect extends PostgresBase {
|
|
|
22
22
|
supportsQualify: boolean;
|
|
23
23
|
supportsSafeCast: boolean;
|
|
24
24
|
supportsNesting: boolean;
|
|
25
|
+
supportsNestedProjectionLimit: boolean;
|
|
25
26
|
supportsCountApprox: boolean;
|
|
26
27
|
requiresExplicitUnnestOrdering: boolean;
|
|
27
28
|
integerTypeMappings: IntegerTypeMapping[];
|
|
@@ -31,7 +32,7 @@ export declare class DuckDBDialect extends PostgresBase {
|
|
|
31
32
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
32
33
|
sqlLiteralNumber(literal: string): string;
|
|
33
34
|
mapFields(fieldList: DialectFieldList): string;
|
|
34
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined): string;
|
|
35
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
35
36
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
36
37
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
37
38
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -55,6 +55,7 @@ class DuckDBDialect extends pg_impl_1.PostgresBase {
|
|
|
55
55
|
this.supportsQualify = true;
|
|
56
56
|
this.supportsSafeCast = true;
|
|
57
57
|
this.supportsNesting = true;
|
|
58
|
+
this.supportsNestedProjectionLimit = true;
|
|
58
59
|
this.supportsCountApprox = true;
|
|
59
60
|
// DuckDB UNNEST in LATERAL JOINs doesn't preserve array element order
|
|
60
61
|
this.requiresExplicitUnnestOrdering = true;
|
|
@@ -91,12 +92,18 @@ class DuckDBDialect extends pg_impl_1.PostgresBase {
|
|
|
91
92
|
mapFields(fieldList) {
|
|
92
93
|
return fieldList.join(', ');
|
|
93
94
|
}
|
|
94
|
-
sqlAggregateTurtle(groupSet, fieldList, orderBy) {
|
|
95
|
+
sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
|
|
95
96
|
const fields = fieldList
|
|
96
97
|
.map(f => `\n ${f.sqlOutputName}: ${f.sqlExpression}`)
|
|
97
98
|
.join(', ');
|
|
98
99
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
99
|
-
|
|
100
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
101
|
+
const list = `LIST({${fields}} ${orderByClause})` +
|
|
102
|
+
(cond ? ` FILTER (WHERE ${cond})` : '');
|
|
103
|
+
// A projection nest's limit is applied by slicing the aggregated array
|
|
104
|
+
// (duckdb lists are 1-based, inclusive).
|
|
105
|
+
const limited = limit !== undefined ? `(${list})[1:${limit}]` : list;
|
|
106
|
+
return `COALESCE(${limited},[])`;
|
|
100
107
|
}
|
|
101
108
|
sqlAnyValueTurtle(groupSet, fieldList) {
|
|
102
109
|
const fields = fieldList
|
|
@@ -31,6 +31,7 @@ export declare class MySQLDialect extends Dialect {
|
|
|
31
31
|
readsNestedData: boolean;
|
|
32
32
|
supportsComplexFilteredSources: boolean;
|
|
33
33
|
supportsArraysInData: boolean;
|
|
34
|
+
supportsNestedProjectionLimit: boolean;
|
|
34
35
|
compoundObjectInSchema: boolean;
|
|
35
36
|
booleanType: BooleanTypeSupport;
|
|
36
37
|
orderByClause: OrderByClauseType;
|
|
@@ -41,7 +42,7 @@ export declare class MySQLDialect extends Dialect {
|
|
|
41
42
|
sqlGroupSetTable(groupSetCount: number): string;
|
|
42
43
|
sqlAnyValue(_groupSet: number, fieldName: string): string;
|
|
43
44
|
private mapFields;
|
|
44
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined): string;
|
|
45
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, _limit?: number, filterSQL?: string): string;
|
|
45
46
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
46
47
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
47
48
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -102,6 +102,10 @@ class MySQLDialect extends dialect_1.Dialect {
|
|
|
102
102
|
this.readsNestedData = false;
|
|
103
103
|
this.supportsComplexFilteredSources = false;
|
|
104
104
|
this.supportsArraysInData = false;
|
|
105
|
+
// MySQL builds nested arrays with GROUP_CONCAT (not an array type), which has
|
|
106
|
+
// no in-expression slice; limiting would need query-level ROW_NUMBER plumbing
|
|
107
|
+
// the sqlAggregateTurtle seam doesn't expose. Left false (default) explicitly.
|
|
108
|
+
this.supportsNestedProjectionLimit = false;
|
|
105
109
|
this.compoundObjectInSchema = false;
|
|
106
110
|
this.booleanType = 'simulated';
|
|
107
111
|
this.orderByClause = 'ordinal';
|
|
@@ -157,14 +161,19 @@ class MySQLDialect extends dialect_1.Dialect {
|
|
|
157
161
|
.map(f => `${this.sqlLiteralString(f.rawName)}, ${f.sqlExpression}`)
|
|
158
162
|
.join(', ');
|
|
159
163
|
}
|
|
160
|
-
sqlAggregateTurtle(groupSet, fieldList, orderBy) {
|
|
164
|
+
sqlAggregateTurtle(groupSet, fieldList, orderBy, _limit, filterSQL) {
|
|
161
165
|
const separator = ',';
|
|
162
166
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
168
|
+
const json = `JSON_OBJECT(${this.mapFields(fieldList)})`;
|
|
169
|
+
const element = cond
|
|
170
|
+
? `IF(${cond},
|
|
171
|
+
${json}
|
|
166
172
|
, null
|
|
167
|
-
)
|
|
173
|
+
)`
|
|
174
|
+
: json;
|
|
175
|
+
let gc = `GROUP_CONCAT(
|
|
176
|
+
${element}
|
|
168
177
|
${orderByClause}
|
|
169
178
|
SEPARATOR '${separator}'
|
|
170
179
|
)`;
|
|
@@ -21,6 +21,7 @@ export declare class PostgresDialect extends PostgresBase {
|
|
|
21
21
|
dontUnionIndex: boolean;
|
|
22
22
|
supportsQualify: boolean;
|
|
23
23
|
supportsNesting: boolean;
|
|
24
|
+
supportsNestedProjectionLimit: boolean;
|
|
24
25
|
experimental: boolean;
|
|
25
26
|
readsNestedData: boolean;
|
|
26
27
|
supportsComplexFilteredSources: boolean;
|
|
@@ -31,7 +32,7 @@ export declare class PostgresDialect extends PostgresBase {
|
|
|
31
32
|
sqlGroupSetTable(groupSetCount: number): string;
|
|
32
33
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
33
34
|
mapFields(fieldList: DialectFieldList): string;
|
|
34
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined): string;
|
|
35
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
35
36
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
36
37
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
37
38
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -75,6 +75,7 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
|
|
|
75
75
|
this.dontUnionIndex = false;
|
|
76
76
|
this.supportsQualify = false;
|
|
77
77
|
this.supportsNesting = true;
|
|
78
|
+
this.supportsNestedProjectionLimit = true;
|
|
78
79
|
this.experimental = false;
|
|
79
80
|
this.readsNestedData = false;
|
|
80
81
|
this.supportsComplexFilteredSources = false;
|
|
@@ -96,10 +97,16 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
|
|
|
96
97
|
.map(f => `\n ${f.sqlExpression}${f.typeDef.type === 'number' ? `::${this.defaultNumberType}` : ''} as ${f.sqlOutputName}`)
|
|
97
98
|
.join(', ');
|
|
98
99
|
}
|
|
99
|
-
sqlAggregateTurtle(groupSet, fieldList, orderBy) {
|
|
100
|
+
sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
|
|
100
101
|
const fields = this.mapFields(fieldList);
|
|
101
102
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
102
|
-
|
|
103
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
104
|
+
const filterClause = cond ? ` FILTER (WHERE ${cond})` : '';
|
|
105
|
+
const arrayAgg = `(ARRAY_AGG((SELECT TO_JSONB(__x) FROM (SELECT ${fields}\n ) as __x) ${orderByClause} )${filterClause})`;
|
|
106
|
+
// Slice the native ARRAY (1-based, inclusive) before TO_JSONB turns it into
|
|
107
|
+
// a single jsonb value — a jsonb scalar can't be range-sliced.
|
|
108
|
+
const limited = limit !== undefined ? `${arrayAgg}[1:${limit}]` : arrayAgg;
|
|
109
|
+
return `COALESCE(TO_JSONB(${limited}),'[]'::JSONB)`;
|
|
103
110
|
}
|
|
104
111
|
sqlAnyValueTurtle(groupSet, fieldList) {
|
|
105
112
|
const fields = fieldList
|
|
@@ -17,6 +17,7 @@ export declare class SnowflakeDialect extends Dialect {
|
|
|
17
17
|
supportsSumDistinctFunction: boolean;
|
|
18
18
|
supportsSafeCast: boolean;
|
|
19
19
|
supportsNesting: boolean;
|
|
20
|
+
supportsNestedProjectionLimit: boolean;
|
|
20
21
|
defaultSampling: {
|
|
21
22
|
rows: number;
|
|
22
23
|
};
|
|
@@ -35,7 +36,7 @@ export declare class SnowflakeDialect extends Dialect {
|
|
|
35
36
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
36
37
|
mapFields(fieldList: DialectFieldList): string;
|
|
37
38
|
mapFieldsForObjectConstruct(fieldList: DialectFieldList): string;
|
|
38
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined): string;
|
|
39
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
39
40
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
40
41
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
41
42
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -71,6 +71,7 @@ class SnowflakeDialect extends dialect_1.Dialect {
|
|
|
71
71
|
this.supportsSumDistinctFunction = true;
|
|
72
72
|
this.supportsSafeCast = true;
|
|
73
73
|
this.supportsNesting = true;
|
|
74
|
+
this.supportsNestedProjectionLimit = true;
|
|
74
75
|
this.defaultSampling = { rows: 50000 };
|
|
75
76
|
this.supportsHyperLogLog = true;
|
|
76
77
|
// NOTE: safely setting all these to false for now
|
|
@@ -107,13 +108,20 @@ class SnowflakeDialect extends dialect_1.Dialect {
|
|
|
107
108
|
.map(f => `${this.sqlLiteralString(f.rawName)}, (${f.sqlExpression})`)
|
|
108
109
|
.join(', ');
|
|
109
110
|
}
|
|
110
|
-
sqlAggregateTurtle(groupSet, fieldList, orderBy) {
|
|
111
|
+
sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
|
|
111
112
|
const fields = this.mapFieldsForObjectConstruct(fieldList);
|
|
112
113
|
const orderByClause = orderBy
|
|
113
114
|
? ` WITHIN GROUP (${this.sqlTurtleOrderByClause(orderBy)})`
|
|
114
115
|
: '';
|
|
115
|
-
const
|
|
116
|
-
|
|
116
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
117
|
+
const struct = `OBJECT_CONSTRUCT_KEEP_NULL(${fields})`;
|
|
118
|
+
const element = cond ? `CASE WHEN ${cond} THEN ${struct} END` : struct;
|
|
119
|
+
const aggClause = `ARRAY_AGG(${element})${orderByClause}`;
|
|
120
|
+
// ARRAY_SLICE is 0-based, end-exclusive, so (0, n) keeps the first n.
|
|
121
|
+
const limited = limit !== undefined
|
|
122
|
+
? `ARRAY_SLICE(${aggClause}, 0, ${limit})`
|
|
123
|
+
: aggClause;
|
|
124
|
+
return `COALESCE(${limited}, [])`;
|
|
117
125
|
}
|
|
118
126
|
sqlAnyValueTurtle(groupSet, fieldList) {
|
|
119
127
|
const fields = this.mapFieldsForObjectConstruct(fieldList);
|
|
@@ -25,6 +25,7 @@ export declare class StandardSQLDialect extends Dialect {
|
|
|
25
25
|
supportsQualify: boolean;
|
|
26
26
|
supportsSafeCast: boolean;
|
|
27
27
|
supportsNesting: boolean;
|
|
28
|
+
supportsNestedProjectionLimit: boolean;
|
|
28
29
|
cantPartitionWindowFunctionsOnExpressions: boolean;
|
|
29
30
|
hasModOperator: boolean;
|
|
30
31
|
sqlLateralJoinBag(expressions: LateralJoinExpression[]): string;
|
|
@@ -42,7 +43,7 @@ export declare class StandardSQLDialect extends Dialect {
|
|
|
42
43
|
sqlGroupSetTable(groupSetCount: number): string;
|
|
43
44
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
44
45
|
sqlOrderBy(orderTerms: string[], obr?: OrderByRequest): string;
|
|
45
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined): string;
|
|
46
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
46
47
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
47
48
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
48
49
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -77,6 +77,7 @@ class StandardSQLDialect extends dialect_1.Dialect {
|
|
|
77
77
|
this.supportsQualify = true;
|
|
78
78
|
this.supportsSafeCast = true;
|
|
79
79
|
this.supportsNesting = true;
|
|
80
|
+
this.supportsNestedProjectionLimit = true;
|
|
80
81
|
this.cantPartitionWindowFunctionsOnExpressions = true;
|
|
81
82
|
this.hasModOperator = false;
|
|
82
83
|
this.nestedArrays = false; // Can't have an array of arrays for some reason
|
|
@@ -147,12 +148,17 @@ class StandardSQLDialect extends dialect_1.Dialect {
|
|
|
147
148
|
return `ORDER BY ${orderTerms.map(t => `${t} NULLS LAST`).join(',')}`;
|
|
148
149
|
}
|
|
149
150
|
// can array agg or any_value a struct...
|
|
150
|
-
sqlAggregateTurtle(groupSet, fieldList, orderBy) {
|
|
151
|
+
sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
|
|
151
152
|
const fields = fieldList
|
|
152
153
|
.map(f => `\n ${f.sqlExpression} as ${f.sqlOutputName}`)
|
|
153
154
|
.join(', ');
|
|
154
155
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
155
|
-
|
|
156
|
+
// BigQuery ARRAY_AGG takes LIMIT as its final sub-clause, after ORDER BY.
|
|
157
|
+
const limitClause = limit !== undefined ? ` LIMIT ${limit}` : '';
|
|
158
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
159
|
+
const struct = `STRUCT(${fields}\n )`;
|
|
160
|
+
const element = cond ? `CASE WHEN ${cond} THEN ${struct} END` : struct;
|
|
161
|
+
return `ARRAY_AGG(${element} IGNORE NULLS ${orderByClause}${limitClause})`;
|
|
156
162
|
}
|
|
157
163
|
sqlAnyValueTurtle(groupSet, fieldList) {
|
|
158
164
|
const fields = fieldList
|
|
@@ -22,6 +22,7 @@ export declare class TrinoDialect extends PostgresBase {
|
|
|
22
22
|
supportsQualify: boolean;
|
|
23
23
|
supportsSafeCast: boolean;
|
|
24
24
|
supportsNesting: boolean;
|
|
25
|
+
supportsNestedProjectionLimit: boolean;
|
|
25
26
|
cantPartitionWindowFunctionsOnExpressions: boolean;
|
|
26
27
|
orderByClause: OrderByClauseType;
|
|
27
28
|
nullMatchesFunctionSignature: boolean;
|
|
@@ -35,7 +36,7 @@ export declare class TrinoDialect extends PostgresBase {
|
|
|
35
36
|
exprToSQL(qi: QueryInfo, df: Expr): string | undefined;
|
|
36
37
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
37
38
|
buildTypeExpression(fieldList: DialectFieldList): string;
|
|
38
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined): string;
|
|
39
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
39
40
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
40
41
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
41
42
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -8,6 +8,7 @@ exports.PrestoDialect = exports.TrinoDialect = void 0;
|
|
|
8
8
|
const utils_1 = require("../../model/utils");
|
|
9
9
|
const malloy_types_1 = require("../../model/malloy_types");
|
|
10
10
|
const functions_1 = require("../functions");
|
|
11
|
+
const dialect_1 = require("../dialect");
|
|
11
12
|
const pg_impl_1 = require("../pg_impl");
|
|
12
13
|
const dialect_functions_1 = require("./dialect_functions");
|
|
13
14
|
const function_overrides_1 = require("./function_overrides");
|
|
@@ -76,6 +77,7 @@ class TrinoDialect extends pg_impl_1.PostgresBase {
|
|
|
76
77
|
this.supportsQualify = true;
|
|
77
78
|
this.supportsSafeCast = true;
|
|
78
79
|
this.supportsNesting = true;
|
|
80
|
+
this.supportsNestedProjectionLimit = true;
|
|
79
81
|
this.cantPartitionWindowFunctionsOnExpressions = false;
|
|
80
82
|
this.orderByClause = 'output_name';
|
|
81
83
|
this.nullMatchesFunctionSignature = false;
|
|
@@ -225,11 +227,15 @@ class TrinoDialect extends pg_impl_1.PostgresBase {
|
|
|
225
227
|
.join(', \n');
|
|
226
228
|
}
|
|
227
229
|
// can array agg or any_value a struct...
|
|
228
|
-
sqlAggregateTurtle(groupSet, fieldList, orderBy) {
|
|
230
|
+
sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
|
|
229
231
|
const expressions = fieldList.map(f => f.sqlExpression).join(',\n ');
|
|
230
232
|
const definitions = this.buildTypeExpression(fieldList);
|
|
231
233
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
232
|
-
|
|
234
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
235
|
+
const filterClause = cond ? ` FILTER (WHERE ${cond})` : '';
|
|
236
|
+
const arrayAgg = `ARRAY_AGG(CAST(ROW(${expressions}) AS ROW(${definitions})) ${orderByClause})${filterClause}`;
|
|
237
|
+
// SLICE(array, start, length) is 1-based; length n keeps the first n.
|
|
238
|
+
return limit !== undefined ? `SLICE(${arrayAgg}, 1, ${limit})` : arrayAgg;
|
|
233
239
|
}
|
|
234
240
|
sqlAnyValueTurtle(groupSet, fieldList) {
|
|
235
241
|
const expressions = fieldList.map(f => f.sqlExpression).join(',\n ');
|
|
@@ -79,7 +79,15 @@ class DynamicSpace extends static_space_1.StaticSpace {
|
|
|
79
79
|
}
|
|
80
80
|
newEntry(name, logTo, entry) {
|
|
81
81
|
if (this.entry(name)) {
|
|
82
|
-
|
|
82
|
+
if (this.isQueryFieldSpace() &&
|
|
83
|
+
this.outputSpace().synthesizedNames.has(name)) {
|
|
84
|
+
logTo.logError('definition-name-conflict', `Cannot redefine '${name}', which was auto-generated by renaming an ` +
|
|
85
|
+
'ambiguous reference; name one of the conflicting references ' +
|
|
86
|
+
"explicitly with 'is' to resolve this");
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
logTo.logError('definition-name-conflict', `Cannot redefine '${name}'`);
|
|
90
|
+
}
|
|
83
91
|
return;
|
|
84
92
|
}
|
|
85
93
|
this.setEntry(name, entry);
|
|
@@ -106,6 +114,10 @@ class DynamicSpace extends static_space_1.StaticSpace {
|
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
this.sourceDef = { ...this.fromSource, fields: [] };
|
|
117
|
+
// This is a freshly built (modified) source: it presents a new exported
|
|
118
|
+
// shape, so it is no longer a reference to the source it was built from.
|
|
119
|
+
// DefineSource resets referenceID to this source's own sourceID.
|
|
120
|
+
delete this.sourceDef.referenceID;
|
|
109
121
|
this.sourceDef.parameters = parameters;
|
|
110
122
|
const fieldIndices = new Map();
|
|
111
123
|
// Need to process the entities in specific order
|
|
@@ -188,6 +200,7 @@ class DynamicSpace extends static_space_1.StaticSpace {
|
|
|
188
200
|
emptyStructDef() {
|
|
189
201
|
const ret = { ...this.fromSource };
|
|
190
202
|
ret.fields = [];
|
|
203
|
+
delete ret.referenceID;
|
|
191
204
|
return ret;
|
|
192
205
|
}
|
|
193
206
|
}
|
|
@@ -43,6 +43,7 @@ export declare abstract class QueryOperationSpace extends RefinedSpace implement
|
|
|
43
43
|
name: string;
|
|
44
44
|
field: SpaceField;
|
|
45
45
|
})[];
|
|
46
|
+
synthesizedNames: Set<string>;
|
|
46
47
|
private _refSummary;
|
|
47
48
|
private _refSummaryComputed;
|
|
48
49
|
get refSummary(): model.RefSummary | undefined;
|
|
@@ -80,6 +80,11 @@ class QueryOperationSpace extends refined_space_1.RefinedSpace {
|
|
|
80
80
|
this.expandedWild = new Map();
|
|
81
81
|
this.drillDimensions = [];
|
|
82
82
|
this.compositeFieldUsers = [];
|
|
83
|
+
// Output names this space generated by auto-renaming an ambiguous reference
|
|
84
|
+
// (e.g. `user.name` -> `user_name`). Tracked here, on the namespace that
|
|
85
|
+
// minted them, rather than on the entries: a later explicit definition that
|
|
86
|
+
// lands on a synthesized name is a hard error, not a silent shadow.
|
|
87
|
+
this.synthesizedNames = new Set();
|
|
83
88
|
// Reference summary is not computed until `queryFieldDefs` is called
|
|
84
89
|
// (or `getPipeSegment` for index segments). Reading it before that throws;
|
|
85
90
|
// assigning to it (via the setter) flips the computed flag.
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import type { AnnotationsDef, FieldDef, RefToField, TypeDesc } from '../../../model/malloy_types';
|
|
2
2
|
import type { DynamicSpace } from '../field-space/dynamic-space';
|
|
3
3
|
import { DefinitionList } from '../types/definition-list';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FieldSpace } from '../types/field-space';
|
|
5
|
+
import { FieldName } from '../types/field-space';
|
|
5
6
|
import type { LookupResult } from '../types/lookup-result';
|
|
6
7
|
import { ListOf, MalloyElement } from '../types/malloy-element';
|
|
7
8
|
import type { Noteable } from '../types/noteable';
|
|
8
9
|
import { extendNoteMethod } from '../types/noteable';
|
|
9
10
|
import type { MakeEntry } from '../types/space-entry';
|
|
11
|
+
import type { FieldDeclarationConstructor } from './field-declaration';
|
|
12
|
+
import { AggregateFieldDeclaration, CalculateFieldDeclaration, GroupByFieldDeclaration, ProjectFieldDeclaration } from './field-declaration';
|
|
10
13
|
export type FieldReferenceConstructor = new (names: FieldName[]) => FieldReference;
|
|
11
14
|
export declare abstract class FieldReference extends ListOf<FieldName> implements Noteable, MakeEntry {
|
|
12
15
|
readonly isNoteableObj = true;
|
|
13
16
|
note?: AnnotationsDef;
|
|
14
17
|
extendNote: typeof extendNoteMethod;
|
|
15
18
|
constructor(names: FieldName[]);
|
|
19
|
+
protected autoRenameDeclarationCtor(): FieldDeclarationConstructor | undefined;
|
|
16
20
|
makeEntry(fs: DynamicSpace): void;
|
|
21
|
+
private autoRename;
|
|
17
22
|
getName(): string;
|
|
18
23
|
get refToField(): RefToField;
|
|
19
24
|
get refString(): string;
|
|
@@ -58,6 +63,7 @@ export declare class DrillFieldReference extends FieldReference {
|
|
|
58
63
|
}
|
|
59
64
|
export declare class CalculateFieldReference extends FieldReference {
|
|
60
65
|
elementType: string;
|
|
66
|
+
protected autoRenameDeclarationCtor(): typeof CalculateFieldDeclaration;
|
|
61
67
|
typecheck(type: TypeDesc): void;
|
|
62
68
|
}
|
|
63
69
|
export declare class IndexFieldReference extends FieldReference {
|
|
@@ -68,14 +74,17 @@ export declare class IndexFieldReference extends FieldReference {
|
|
|
68
74
|
}
|
|
69
75
|
export declare class AggregateFieldReference extends FieldReference {
|
|
70
76
|
elementType: string;
|
|
77
|
+
protected autoRenameDeclarationCtor(): typeof AggregateFieldDeclaration;
|
|
71
78
|
typecheck(type: TypeDesc): void;
|
|
72
79
|
}
|
|
73
80
|
export declare class GroupByFieldReference extends FieldReference {
|
|
74
81
|
elementType: string;
|
|
82
|
+
protected autoRenameDeclarationCtor(): typeof GroupByFieldDeclaration;
|
|
75
83
|
typecheck(type: TypeDesc): void;
|
|
76
84
|
}
|
|
77
85
|
export declare class ProjectFieldReference extends FieldReference {
|
|
78
86
|
elementType: string;
|
|
87
|
+
protected autoRenameDeclarationCtor(): typeof ProjectFieldDeclaration;
|
|
79
88
|
typecheck(type: TypeDesc): void;
|
|
80
89
|
}
|
|
81
90
|
export declare class DeclareFieldReference extends FieldReference {
|