@malloydata/malloy 0.0.413 → 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 +1 -1
- package/dist/dialect/databricks/databricks.js +3 -2
- package/dist/dialect/dialect.d.ts +11 -1
- package/dist/dialect/dialect.js +18 -0
- package/dist/dialect/duckdb/duckdb.d.ts +1 -1
- package/dist/dialect/duckdb/duckdb.js +3 -2
- package/dist/dialect/mysql/mysql.d.ts +1 -1
- package/dist/dialect/mysql/mysql.js +9 -5
- package/dist/dialect/postgres/postgres.d.ts +1 -1
- package/dist/dialect/postgres/postgres.js +3 -2
- package/dist/dialect/snowflake/snowflake.d.ts +1 -1
- package/dist/dialect/snowflake/snowflake.js +4 -2
- package/dist/dialect/standardsql/standardsql.d.ts +1 -1
- package/dist/dialect/standardsql/standardsql.js +4 -2
- package/dist/dialect/trino/trino.d.ts +1 -1
- package/dist/dialect/trino/trino.js +4 -2
- package/dist/lang/ast/field-space/dynamic-space.js +5 -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/model/expression_compiler.js +2 -2
- package/dist/model/field_instance.d.ts +1 -0
- package/dist/model/field_instance.js +5 -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/persist_utils.js +1 -1
- package/dist/model/query_query.d.ts +19 -1
- package/dist/model/query_query.js +81 -10
- 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);
|
|
@@ -49,7 +49,7 @@ export declare class DatabricksDialect extends Dialect {
|
|
|
49
49
|
sqlOrderBy(orderTerms: string[], obr?: OrderByRequest): string;
|
|
50
50
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
51
51
|
private buildNamedStructExpression;
|
|
52
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
52
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
53
53
|
private buildArraySortComparator;
|
|
54
54
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
55
55
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
@@ -172,8 +172,9 @@ class DatabricksDialect extends dialect_1.Dialect {
|
|
|
172
172
|
}
|
|
173
173
|
sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
|
|
174
174
|
const namedStruct = this.buildNamedStructExpression(fieldList);
|
|
175
|
-
const
|
|
176
|
-
const
|
|
175
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
176
|
+
const filterClause = cond ? ` FILTER (WHERE ${cond})` : '';
|
|
177
|
+
const collectExpr = `COLLECT_LIST(${namedStruct})${filterClause}`;
|
|
177
178
|
// COLLECT_LIST is unordered, so ordering is done post-aggregation via
|
|
178
179
|
// ARRAY_SORT — the limit must slice the *ordered* array.
|
|
179
180
|
const ordered = !orderBy || orderBy.length === 0
|
|
@@ -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';
|
|
@@ -176,7 +186,7 @@ export declare abstract class Dialect {
|
|
|
176
186
|
sqlValidateTableName(input: string): ValidateTablePathResult;
|
|
177
187
|
abstract sqlGroupSetTable(groupSetCount: number): string;
|
|
178
188
|
abstract sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
179
|
-
abstract sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
189
|
+
abstract sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
180
190
|
sqlTurtleOrderByClause(orderBy: CompiledOrderBy[]): string;
|
|
181
191
|
abstract sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
182
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
|
|
@@ -32,7 +32,7 @@ export declare class DuckDBDialect extends PostgresBase {
|
|
|
32
32
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
33
33
|
sqlLiteralNumber(literal: string): string;
|
|
34
34
|
mapFields(fieldList: DialectFieldList): string;
|
|
35
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
35
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
36
36
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
37
37
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
38
38
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -97,8 +97,9 @@ class DuckDBDialect extends pg_impl_1.PostgresBase {
|
|
|
97
97
|
.map(f => `\n ${f.sqlOutputName}: ${f.sqlExpression}`)
|
|
98
98
|
.join(', ');
|
|
99
99
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
100
|
-
const
|
|
101
|
-
const list = `LIST({${fields}} ${orderByClause})
|
|
100
|
+
const cond = (0, dialect_1.turtleGroupSetCondition)(groupSet, filterSQL);
|
|
101
|
+
const list = `LIST({${fields}} ${orderByClause})` +
|
|
102
|
+
(cond ? ` FILTER (WHERE ${cond})` : '');
|
|
102
103
|
// A projection nest's limit is applied by slicing the aggregated array
|
|
103
104
|
// (duckdb lists are 1-based, inclusive).
|
|
104
105
|
const limited = limit !== undefined ? `(${list})[1:${limit}]` : list;
|
|
@@ -42,7 +42,7 @@ export declare class MySQLDialect extends Dialect {
|
|
|
42
42
|
sqlGroupSetTable(groupSetCount: number): string;
|
|
43
43
|
sqlAnyValue(_groupSet: number, fieldName: string): string;
|
|
44
44
|
private mapFields;
|
|
45
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, _limit?: number, filterSQL?: string): string;
|
|
45
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, _limit?: number, filterSQL?: string): string;
|
|
46
46
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
47
47
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
48
48
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -164,12 +164,16 @@ class MySQLDialect extends dialect_1.Dialect {
|
|
|
164
164
|
sqlAggregateTurtle(groupSet, fieldList, orderBy, _limit, filterSQL) {
|
|
165
165
|
const separator = ',';
|
|
166
166
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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}
|
|
171
172
|
, null
|
|
172
|
-
)
|
|
173
|
+
)`
|
|
174
|
+
: json;
|
|
175
|
+
let gc = `GROUP_CONCAT(
|
|
176
|
+
${element}
|
|
173
177
|
${orderByClause}
|
|
174
178
|
SEPARATOR '${separator}'
|
|
175
179
|
)`;
|
|
@@ -32,7 +32,7 @@ export declare class PostgresDialect extends PostgresBase {
|
|
|
32
32
|
sqlGroupSetTable(groupSetCount: number): string;
|
|
33
33
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
34
34
|
mapFields(fieldList: DialectFieldList): string;
|
|
35
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
35
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
36
36
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
37
37
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
38
38
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -100,8 +100,9 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
|
|
|
100
100
|
sqlAggregateTurtle(groupSet, fieldList, orderBy, limit, filterSQL) {
|
|
101
101
|
const fields = this.mapFields(fieldList);
|
|
102
102
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
103
|
-
const
|
|
104
|
-
const
|
|
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})`;
|
|
105
106
|
// Slice the native ARRAY (1-based, inclusive) before TO_JSONB turns it into
|
|
106
107
|
// a single jsonb value — a jsonb scalar can't be range-sliced.
|
|
107
108
|
const limited = limit !== undefined ? `${arrayAgg}[1:${limit}]` : arrayAgg;
|
|
@@ -36,7 +36,7 @@ export declare class SnowflakeDialect extends Dialect {
|
|
|
36
36
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
37
37
|
mapFields(fieldList: DialectFieldList): string;
|
|
38
38
|
mapFieldsForObjectConstruct(fieldList: DialectFieldList): string;
|
|
39
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
39
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
40
40
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
41
41
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
42
42
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -113,8 +113,10 @@ class SnowflakeDialect extends dialect_1.Dialect {
|
|
|
113
113
|
const orderByClause = orderBy
|
|
114
114
|
? ` WITHIN GROUP (${this.sqlTurtleOrderByClause(orderBy)})`
|
|
115
115
|
: '';
|
|
116
|
-
const
|
|
117
|
-
const
|
|
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}`;
|
|
118
120
|
// ARRAY_SLICE is 0-based, end-exclusive, so (0, n) keeps the first n.
|
|
119
121
|
const limited = limit !== undefined
|
|
120
122
|
? `ARRAY_SLICE(${aggClause}, 0, ${limit})`
|
|
@@ -43,7 +43,7 @@ export declare class StandardSQLDialect extends Dialect {
|
|
|
43
43
|
sqlGroupSetTable(groupSetCount: number): string;
|
|
44
44
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
45
45
|
sqlOrderBy(orderTerms: string[], obr?: OrderByRequest): string;
|
|
46
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
46
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
47
47
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
48
48
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
49
49
|
sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
|
|
@@ -155,8 +155,10 @@ class StandardSQLDialect extends dialect_1.Dialect {
|
|
|
155
155
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
156
156
|
// BigQuery ARRAY_AGG takes LIMIT as its final sub-clause, after ORDER BY.
|
|
157
157
|
const limitClause = limit !== undefined ? ` LIMIT ${limit}` : '';
|
|
158
|
-
const
|
|
159
|
-
|
|
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})`;
|
|
160
162
|
}
|
|
161
163
|
sqlAnyValueTurtle(groupSet, fieldList) {
|
|
162
164
|
const fields = fieldList
|
|
@@ -36,7 +36,7 @@ export declare class TrinoDialect extends PostgresBase {
|
|
|
36
36
|
exprToSQL(qi: QueryInfo, df: Expr): string | undefined;
|
|
37
37
|
sqlAnyValue(groupSet: number, fieldName: string): string;
|
|
38
38
|
buildTypeExpression(fieldList: DialectFieldList): string;
|
|
39
|
-
sqlAggregateTurtle(groupSet: number, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
39
|
+
sqlAggregateTurtle(groupSet: number | undefined, fieldList: DialectFieldList, orderBy: CompiledOrderBy[] | undefined, limit?: number, filterSQL?: string): string;
|
|
40
40
|
sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
|
|
41
41
|
sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
|
|
42
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");
|
|
@@ -230,8 +231,9 @@ class TrinoDialect extends pg_impl_1.PostgresBase {
|
|
|
230
231
|
const expressions = fieldList.map(f => f.sqlExpression).join(',\n ');
|
|
231
232
|
const definitions = this.buildTypeExpression(fieldList);
|
|
232
233
|
const orderByClause = orderBy ? this.sqlTurtleOrderByClause(orderBy) : '';
|
|
233
|
-
const
|
|
234
|
-
const
|
|
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}`;
|
|
235
237
|
// SLICE(array, start, length) is 1-based; length n keeps the first n.
|
|
236
238
|
return limit !== undefined ? `SLICE(${arrayAgg}, 1, ${limit})` : arrayAgg;
|
|
237
239
|
}
|
|
@@ -114,6 +114,10 @@ class DynamicSpace extends static_space_1.StaticSpace {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
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;
|
|
117
121
|
this.sourceDef.parameters = parameters;
|
|
118
122
|
const fieldIndices = new Map();
|
|
119
123
|
// Need to process the entities in specific order
|
|
@@ -196,6 +200,7 @@ class DynamicSpace extends static_space_1.StaticSpace {
|
|
|
196
200
|
emptyStructDef() {
|
|
197
201
|
const ret = { ...this.fromSource };
|
|
198
202
|
ret.fields = [];
|
|
203
|
+
delete ret.referenceID;
|
|
199
204
|
return ret;
|
|
200
205
|
}
|
|
201
206
|
}
|
|
@@ -91,7 +91,10 @@ class NamedSource extends source_1.Source {
|
|
|
91
91
|
else {
|
|
92
92
|
(_a = this.document()) === null || _a === void 0 ? void 0 : _a.checkExperimentalDialect(this, entry.dialect);
|
|
93
93
|
if ((0, malloy_types_1.isSourceDef)(entry)) {
|
|
94
|
-
|
|
94
|
+
// This struct is created as an unmodified reference to `entry`. Mark it
|
|
95
|
+
// with entry's own identity; the modification path (DynamicSpace) clears
|
|
96
|
+
// this so only true references carry a referenceID.
|
|
97
|
+
return { ...entry, referenceID: entry.sourceID };
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
100
|
// I think this is now a never
|
|
@@ -58,9 +58,16 @@ class DefineSource extends malloy_element_1.MalloyElement {
|
|
|
58
58
|
}
|
|
59
59
|
: { ...this.note };
|
|
60
60
|
}
|
|
61
|
-
if ((0, malloy_types_1.
|
|
61
|
+
if ((0, malloy_types_1.isSourceDef)(entry)) {
|
|
62
|
+
// Every source gets a stable identity for its own definition. referenceID
|
|
63
|
+
// is left as it arrived: set to the referenced source's sourceID for an
|
|
64
|
+
// unmodified rename (`source: a is b`), and absent for a source that
|
|
65
|
+
// defines its own shape (table/sql/query, or a modified/extended source,
|
|
66
|
+
// which DynamicSpace cleared).
|
|
62
67
|
entry.sourceID = (0, source_def_utils_1.mkSourceID)(this.name, (_b = this.location) === null || _b === void 0 ? void 0 : _b.url);
|
|
63
|
-
|
|
68
|
+
if ((0, malloy_types_1.isPersistableSourceDef)(entry)) {
|
|
69
|
+
entry.persistent = (0, persist_utils_1.checkPersistAnnotation)(entry).persist;
|
|
70
|
+
}
|
|
64
71
|
}
|
|
65
72
|
entry.partitionComposite =
|
|
66
73
|
(_d = (0, composite_source_utils_1.getPartitionCompositeDesc)(this.note, structDef, (_c = this.sourceExpr) !== null && _c !== void 0 ? _c : this)) !== null && _d !== void 0 ? _d : structDef.partitionComposite;
|
|
@@ -665,10 +665,9 @@ class Document extends MalloyElement {
|
|
|
665
665
|
this.modelWasModified = true;
|
|
666
666
|
}
|
|
667
667
|
this.documentModel.set(str, ent);
|
|
668
|
-
// Maintain
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
ent.entry.sourceID) {
|
|
668
|
+
// Maintain the source-id table so sourceID/referenceID values resolve to
|
|
669
|
+
// their SourceDef. Every named source is registered by its sourceID.
|
|
670
|
+
if ((0, malloy_types_1.isSourceDef)(ent.entry) && ent.entry.sourceID) {
|
|
672
671
|
this.documentSrcRegistry[ent.entry.sourceID] = {
|
|
673
672
|
entry: {
|
|
674
673
|
type: 'source_registry_reference',
|
|
@@ -143,7 +143,7 @@ function compileExpr(resultSet, context, expr, state = new utils_1.GenerateState
|
|
|
143
143
|
else {
|
|
144
144
|
throw new Error(`Internal Error: Unknown aggregate function ${expr.function}`);
|
|
145
145
|
}
|
|
146
|
-
if (resultSet.root().isComplexQuery) {
|
|
146
|
+
if (resultSet.root().isComplexQuery && resultSet.root().emitsGroupSet) {
|
|
147
147
|
let groupSet = resultSet.groupSet;
|
|
148
148
|
if (state.totalGroupSet !== -1) {
|
|
149
149
|
groupSet = state.totalGroupSet;
|
|
@@ -411,7 +411,7 @@ function generateAsymmetricStringAggExpression(resultSet, context, value, separa
|
|
|
411
411
|
return context.dialect.sqlStringAggDistinct(distinctKey, valueSQL, separatorSQL);
|
|
412
412
|
}
|
|
413
413
|
function generateAnalyticFragment(dialect, resultStruct, context, expr, overload, state, args, partitionByFields, funcOrdering) {
|
|
414
|
-
const isComplex = resultStruct.root().isComplexQuery;
|
|
414
|
+
const isComplex = resultStruct.root().isComplexQuery && resultStruct.root().emitsGroupSet;
|
|
415
415
|
const partitionFields = getAnalyticPartitions(resultStruct, partitionByFields);
|
|
416
416
|
const allPartitions = [
|
|
417
417
|
...(isComplex ? ['group_set'] : []),
|
|
@@ -96,6 +96,7 @@ export declare class FieldInstanceResultRoot extends FieldInstanceResult {
|
|
|
96
96
|
joins: Map<string, JoinInstance>;
|
|
97
97
|
havings: AndChain;
|
|
98
98
|
isComplexQuery: boolean;
|
|
99
|
+
emitsGroupSet: boolean;
|
|
99
100
|
queryUsesPartitioning: boolean;
|
|
100
101
|
computeOnlyGroups: number[];
|
|
101
102
|
elimatedComputeGroups: boolean;
|
|
@@ -410,6 +410,11 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
|
|
|
410
410
|
this.joins = new Map();
|
|
411
411
|
this.havings = new utils_1.AndChain();
|
|
412
412
|
this.isComplexQuery = false;
|
|
413
|
+
// True when the query emits a `group_set` column to demux fanned-out rows
|
|
414
|
+
// (the general path). The single-group-set fast path (generateSingleGroupSetSQL)
|
|
415
|
+
// sets this false: there is no group_set column, so aggregate and window
|
|
416
|
+
// expressions must not wrap themselves in `CASE WHEN group_set=N`.
|
|
417
|
+
this.emitsGroupSet = true;
|
|
413
418
|
this.queryUsesPartitioning = false;
|
|
414
419
|
this.computeOnlyGroups = [];
|
|
415
420
|
this.elimatedComputeGroups = false;
|
package/dist/model/index.d.ts
CHANGED
|
@@ -9,4 +9,5 @@ export { getModelAnnotations } from './annotation_utils';
|
|
|
9
9
|
export { constantExprToSQL } from './constant_expression_compiler';
|
|
10
10
|
export { getCompiledSQL } from './sql_compiled';
|
|
11
11
|
export { MalloyCompileError } from './malloy_compile_error';
|
|
12
|
-
export { mkSourceID, mkBuildID, mkQuerySourceDef, mkSQLSourceDef, mkTableSourceDef, resolveSourceID, registerSource, hasSourceRegistryEntry, } from './source_def_utils';
|
|
12
|
+
export { mkSourceID, mkBuildID, mkQuerySourceDef, mkSQLSourceDef, mkTableSourceDef, resolveSourceID, resolveSourceRef, sourceNamespaceReference, registerSource, hasSourceRegistryEntry, } from './source_def_utils';
|
|
13
|
+
export type { NamespaceReference } from './source_def_utils';
|
package/dist/model/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
18
18
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.hasSourceRegistryEntry = exports.registerSource = exports.resolveSourceID = exports.mkTableSourceDef = exports.mkSQLSourceDef = exports.mkQuerySourceDef = exports.mkBuildID = exports.mkSourceID = exports.MalloyCompileError = exports.getCompiledSQL = exports.constantExprToSQL = exports.getModelAnnotations = exports.typeDefToString = exports.pathToKey = exports.mkModelID = exports.mkModelDef = exports.makeDigest = exports.composeSQLExpr = exports.indent = exports.getResultStructDefForView = exports.getResultStructDefForQuery = exports.QueryModel = exports.QueryQuery = exports.QueryStruct = exports.QueryField = void 0;
|
|
21
|
+
exports.hasSourceRegistryEntry = exports.registerSource = exports.sourceNamespaceReference = exports.resolveSourceRef = exports.resolveSourceID = exports.mkTableSourceDef = exports.mkSQLSourceDef = exports.mkQuerySourceDef = exports.mkBuildID = exports.mkSourceID = exports.MalloyCompileError = exports.getCompiledSQL = exports.constantExprToSQL = exports.getModelAnnotations = exports.typeDefToString = exports.pathToKey = exports.mkModelID = exports.mkModelDef = exports.makeDigest = exports.composeSQLExpr = exports.indent = exports.getResultStructDefForView = exports.getResultStructDefForQuery = exports.QueryModel = exports.QueryQuery = exports.QueryStruct = exports.QueryField = void 0;
|
|
22
22
|
__exportStar(require("./malloy_types"), exports);
|
|
23
23
|
const query_node_1 = require("./query_node");
|
|
24
24
|
Object.defineProperty(exports, "QueryField", { enumerable: true, get: function () { return query_node_1.QueryField; } });
|
|
@@ -60,6 +60,8 @@ Object.defineProperty(exports, "mkQuerySourceDef", { enumerable: true, get: func
|
|
|
60
60
|
Object.defineProperty(exports, "mkSQLSourceDef", { enumerable: true, get: function () { return source_def_utils_1.mkSQLSourceDef; } });
|
|
61
61
|
Object.defineProperty(exports, "mkTableSourceDef", { enumerable: true, get: function () { return source_def_utils_1.mkTableSourceDef; } });
|
|
62
62
|
Object.defineProperty(exports, "resolveSourceID", { enumerable: true, get: function () { return source_def_utils_1.resolveSourceID; } });
|
|
63
|
+
Object.defineProperty(exports, "resolveSourceRef", { enumerable: true, get: function () { return source_def_utils_1.resolveSourceRef; } });
|
|
64
|
+
Object.defineProperty(exports, "sourceNamespaceReference", { enumerable: true, get: function () { return source_def_utils_1.sourceNamespaceReference; } });
|
|
63
65
|
Object.defineProperty(exports, "registerSource", { enumerable: true, get: function () { return source_def_utils_1.registerSource; } });
|
|
64
66
|
Object.defineProperty(exports, "hasSourceRegistryEntry", { enumerable: true, get: function () { return source_def_utils_1.hasSourceRegistryEntry; } });
|
|
65
67
|
//# sourceMappingURL=index.js.map
|
|
@@ -858,6 +858,22 @@ interface SourceDefBase extends StructDefBase, Filtered, ResultStructMetadata {
|
|
|
858
858
|
primaryKey?: PrimaryKeyRef;
|
|
859
859
|
dialect: string;
|
|
860
860
|
partitionComposite?: PartitionCompositeDesc;
|
|
861
|
+
/**
|
|
862
|
+
* Identity of this source's own definition (`name@modelURL`). Set for every
|
|
863
|
+
* source when it is defined. The persistence machinery reads this (gated by
|
|
864
|
+
* `isPersistableSourceDef`).
|
|
865
|
+
*/
|
|
866
|
+
sourceID?: SourceID;
|
|
867
|
+
/**
|
|
868
|
+
* Set only when this source was created as an unmodified reference to another
|
|
869
|
+
* source (`source: a is b`, or a plain join) — then it holds the `sourceID`
|
|
870
|
+
* of the *immediately* referenced source. Absent when the source defines its
|
|
871
|
+
* own shape (table/sql/query, or a modified/extended source). So
|
|
872
|
+
* `referenceID !== undefined` means "created as a reference", and the value
|
|
873
|
+
* resolves through `ModelDef.sourceRegistry` to the referenced source and its
|
|
874
|
+
* namespace name (see `sourceNamespaceReference`).
|
|
875
|
+
*/
|
|
876
|
+
referenceID?: SourceID;
|
|
861
877
|
}
|
|
862
878
|
/** which field is the primary key in this struct */
|
|
863
879
|
export type PrimaryKeyRef = string;
|
|
@@ -878,7 +894,7 @@ export interface SQLStringSegment {
|
|
|
878
894
|
export type SQLPhraseSegment = Query | PersistableSourceDef | SQLStringSegment;
|
|
879
895
|
export declare function isSegmentSQL(f: SQLPhraseSegment): f is SQLStringSegment;
|
|
880
896
|
export declare function isSegmentSource(f: SQLPhraseSegment): f is PersistableSourceDef;
|
|
881
|
-
/** Format: "name@modelUrl" - uniquely identifies a source
|
|
897
|
+
/** Format: "name@modelUrl" - uniquely identifies a defined source */
|
|
882
898
|
export type SourceID = string;
|
|
883
899
|
/** Created with `mkGivenID`. */
|
|
884
900
|
export type GivenID = string;
|
|
@@ -915,7 +931,6 @@ export interface SourceRegistryValue {
|
|
|
915
931
|
}
|
|
916
932
|
export declare function isSourceRegistryReference(entry: SourceRegistryEntry): entry is SourceRegistryReference;
|
|
917
933
|
export interface PersistableSourceProperties {
|
|
918
|
-
sourceID?: SourceID;
|
|
919
934
|
extends?: SourceID;
|
|
920
935
|
persistent?: boolean;
|
|
921
936
|
}
|
|
@@ -943,13 +958,17 @@ export declare function isSourceDef(sd: NamedModelObject | FieldDef): sd is Sour
|
|
|
943
958
|
/**
|
|
944
959
|
* Union of all source definition types.
|
|
945
960
|
*
|
|
946
|
-
*
|
|
947
|
-
* methods in source_def_utils.ts
|
|
961
|
+
* When building a *persisted/derived* source in the compiler, use the factory
|
|
962
|
+
* methods in source_def_utils.ts rather than object spread:
|
|
948
963
|
* - mkSQLSourceDef(base, ...) - create SQLSourceDef from base
|
|
949
964
|
* - mkQuerySourceDef(base, ...) - create QuerySourceDef from base
|
|
950
965
|
*
|
|
951
|
-
* These factories explicitly copy only safe fields,
|
|
952
|
-
*
|
|
966
|
+
* These factories explicitly copy only safe fields, dropping the identity
|
|
967
|
+
* fields (sourceID/referenceID/extends/persistent) so they are not propagated
|
|
968
|
+
* onto a freshly built source. In the translator, by contrast, object spread is
|
|
969
|
+
* used deliberately to *carry* sourceID/referenceID through an unmodified
|
|
970
|
+
* reference; DefineSource then sets them, and DynamicSpace clears referenceID
|
|
971
|
+
* on the modification path.
|
|
953
972
|
*/
|
|
954
973
|
export type SourceDef = TableSourceDef | SQLSourceDef | QuerySourceDef | QueryResultDef | FinalizeSourceDef | NestSourceDef | VirtualSourceDef | CompositeSourceDef;
|
|
955
974
|
/** Sources that can be persisted (materialized to tables) */
|
|
@@ -145,7 +145,7 @@ function findPersistentDependencies(root, modelDef, tagParseLog = []) {
|
|
|
145
145
|
return results;
|
|
146
146
|
}
|
|
147
147
|
function processJoinedSource(source) {
|
|
148
|
-
// If it has
|
|
148
|
+
// If it has an sourceID, go through the registry
|
|
149
149
|
if ((0, malloy_types_1.isPersistableSourceDef)(source) && source.sourceID) {
|
|
150
150
|
return processSourceID(source.sourceID);
|
|
151
151
|
}
|
|
@@ -91,7 +91,25 @@ export declare class QueryQuery extends QueryField {
|
|
|
91
91
|
*/
|
|
92
92
|
collectArrayJoins(ji: JoinInstance): JoinInstance[];
|
|
93
93
|
genereateSQLOrderBy(queryDef: QuerySegment, resultStruct: FieldInstanceResult): string;
|
|
94
|
+
/**
|
|
95
|
+
* The single-scan form, with no `group_set` fan-out: one `SELECT` over the
|
|
96
|
+
* scan, grouped by the dimensions. Used both for a query with no nests and --
|
|
97
|
+
* via canUseSingleGroupSetSQL -- for one whose only nests are single-stage
|
|
98
|
+
* grain-preserving projections, which become an array-agg in the same SELECT.
|
|
99
|
+
* That array-agg needs no `FILTER (WHERE group_set=N)` (every row is in the
|
|
100
|
+
* one group), and aggregate/window expressions must not gate themselves by
|
|
101
|
+
* group_set either, so emitsGroupSet is cleared (see FieldInstanceResultRoot).
|
|
102
|
+
*/
|
|
94
103
|
generateSimpleSQL(stageWriter: StageWriter): string;
|
|
104
|
+
/**
|
|
105
|
+
* Can this query take the single-group-set fast path? True when the query
|
|
106
|
+
* needs no group-set fan-out: it has nests (so it's "complex") but every one
|
|
107
|
+
* is a single-stage grain-preserving projection riding group_set 0, and there
|
|
108
|
+
* are no reduce nests, ungroupings, or totals (all of which would mint a
|
|
109
|
+
* second group set and push maxGroupSet above 0). Anything outside that shape
|
|
110
|
+
* falls back to the general demux path, which is correct for every case.
|
|
111
|
+
*/
|
|
112
|
+
canUseSingleGroupSetSQL(): boolean;
|
|
95
113
|
generatePipelinedStages(outputPipelinedSQL: OutputPipelinedSQL[], lastStageName: string, stageWriter: StageWriter, priorStageColumns: StageOutputColumn[]): string;
|
|
96
114
|
generateStage0Fields(resultSet: FieldInstanceResult, output: StageOutputContext, stageWriter: StageWriter): void;
|
|
97
115
|
generateSQLWhereChildren(resultStruct: FieldInstanceResult): AndChain;
|
|
@@ -102,7 +120,7 @@ export declare class QueryQuery extends QueryField {
|
|
|
102
120
|
generateSQLDepthN(depth: number, stageWriter: StageWriter, stageName: string): string;
|
|
103
121
|
genereateSQLCombineTurtles(stageWriter: StageWriter, stage0Name: string): string;
|
|
104
122
|
buildDialectFieldList(resultStruct: FieldInstanceResult): DialectFieldList;
|
|
105
|
-
generateTurtleSQL(resultStruct: FieldInstanceResult, stageWriter: StageWriter, sqlFieldName: string, outputPipelinedSQL: OutputPipelinedSQL[]): string;
|
|
123
|
+
generateTurtleSQL(resultStruct: FieldInstanceResult, stageWriter: StageWriter, sqlFieldName: string, outputPipelinedSQL: OutputPipelinedSQL[], omitGroupSetFilter?: boolean): string;
|
|
106
124
|
generateTurtlePipelineSQL(fi: FieldInstanceResult, stageWriter: StageWriter, sourceSQLExpression: string): {
|
|
107
125
|
structDef: QueryResultDef;
|
|
108
126
|
pipeOut: any;
|
|
@@ -30,6 +30,11 @@ function usesShaveLimit(result) {
|
|
|
30
30
|
const isNestedProjection = result.parent !== undefined && result.firstSegment.type === 'project';
|
|
31
31
|
return !isNestedProjection;
|
|
32
32
|
}
|
|
33
|
+
/** Is this an analytic (window) field -- a `calculate:`? */
|
|
34
|
+
function fieldIsAnalytic(fi) {
|
|
35
|
+
return ((0, malloy_types_1.hasExpression)(fi.f.fieldDef) &&
|
|
36
|
+
(0, malloy_types_1.expressionIsAnalytic)(fi.f.fieldDef.expressionType));
|
|
37
|
+
}
|
|
33
38
|
// 1-based SELECT positions of the dimension columns, for `GROUP BY 1,2,...`.
|
|
34
39
|
function groupByPositions(columns) {
|
|
35
40
|
return columns.flatMap((c, i) => (c.isDimension ? [i + 1] : []));
|
|
@@ -903,15 +908,31 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
903
908
|
}
|
|
904
909
|
return s;
|
|
905
910
|
}
|
|
911
|
+
/**
|
|
912
|
+
* The single-scan form, with no `group_set` fan-out: one `SELECT` over the
|
|
913
|
+
* scan, grouped by the dimensions. Used both for a query with no nests and --
|
|
914
|
+
* via canUseSingleGroupSetSQL -- for one whose only nests are single-stage
|
|
915
|
+
* grain-preserving projections, which become an array-agg in the same SELECT.
|
|
916
|
+
* That array-agg needs no `FILTER (WHERE group_set=N)` (every row is in the
|
|
917
|
+
* one group), and aggregate/window expressions must not gate themselves by
|
|
918
|
+
* group_set either, so emitsGroupSet is cleared (see FieldInstanceResultRoot).
|
|
919
|
+
*/
|
|
906
920
|
generateSimpleSQL(stageWriter) {
|
|
921
|
+
this.rootResult.emitsGroupSet = false;
|
|
907
922
|
let s = '';
|
|
908
923
|
s += 'SELECT \n';
|
|
909
924
|
const fields = [];
|
|
910
|
-
|
|
911
|
-
|
|
925
|
+
const outputPipelinedSQL = [];
|
|
926
|
+
for (const [name, fi] of this.rootResult.allFields) {
|
|
912
927
|
const sqlName = this.parent.dialect.sqlQuoteIdentifier(name);
|
|
913
|
-
if (fi
|
|
914
|
-
|
|
928
|
+
if (fi instanceof field_instance_1.FieldInstanceField) {
|
|
929
|
+
if (fi.fieldUsage.type === 'result') {
|
|
930
|
+
fields.push(` ${fi.generateExpression()} as ${sqlName}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
else if (fi instanceof field_instance_1.FieldInstanceResult) {
|
|
934
|
+
const turtle = this.generateTurtleSQL(fi, stageWriter, sqlName, outputPipelinedSQL, true);
|
|
935
|
+
fields.push(` ${turtle} as ${sqlName}`);
|
|
915
936
|
}
|
|
916
937
|
}
|
|
917
938
|
s += (0, utils_1.indent)(fields.join(',\n')) + '\n';
|
|
@@ -938,8 +959,52 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
938
959
|
s += `LIMIT ${this.firstSegment.limit}\n`;
|
|
939
960
|
}
|
|
940
961
|
this.resultStage = stageWriter.addStage(s);
|
|
962
|
+
// Consumes any pipelined turtle output. The nests that reach here are
|
|
963
|
+
// single-stage (canUseSingleGroupSetSQL), so outputPipelinedSQL is empty and
|
|
964
|
+
// this is a no-op; kept so the function stays correct if that guard widens.
|
|
965
|
+
this.resultStage = this.generatePipelinedStages(outputPipelinedSQL, this.resultStage, stageWriter, []);
|
|
941
966
|
return this.resultStage;
|
|
942
967
|
}
|
|
968
|
+
/**
|
|
969
|
+
* Can this query take the single-group-set fast path? True when the query
|
|
970
|
+
* needs no group-set fan-out: it has nests (so it's "complex") but every one
|
|
971
|
+
* is a single-stage grain-preserving projection riding group_set 0, and there
|
|
972
|
+
* are no reduce nests, ungroupings, or totals (all of which would mint a
|
|
973
|
+
* second group set and push maxGroupSet above 0). Anything outside that shape
|
|
974
|
+
* falls back to the general demux path, which is correct for every case.
|
|
975
|
+
*/
|
|
976
|
+
canUseSingleGroupSetSQL() {
|
|
977
|
+
if (this.maxGroupSet !== 0)
|
|
978
|
+
return false;
|
|
979
|
+
if (this.firstSegment.type !== 'reduce')
|
|
980
|
+
return false;
|
|
981
|
+
for (const [, fi] of this.rootResult.allFields) {
|
|
982
|
+
if (fi instanceof field_instance_1.FieldInstanceField) {
|
|
983
|
+
// A root-level `calculate:` (window) needs the general path's analytic
|
|
984
|
+
// handling: on dialects that can't partition a window on an expression
|
|
985
|
+
// (BigQuery) it rides a lateral-join bag this path doesn't build.
|
|
986
|
+
if (fieldIsAnalytic(fi))
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
else if (fi instanceof field_instance_1.FieldInstanceResult) {
|
|
990
|
+
if (fi.firstSegment.type !== 'project')
|
|
991
|
+
return false;
|
|
992
|
+
if (fi.turtleDef.pipeline.length !== 1)
|
|
993
|
+
return false;
|
|
994
|
+
if (fi.getRepeatedResultType() !== 'nested')
|
|
995
|
+
return false;
|
|
996
|
+
// A `calculate:` inside a `select:` would put a window function inside
|
|
997
|
+
// the array-agg, which no dialect allows; leave it on the general path.
|
|
998
|
+
for (const [, nestField] of fi.allFields) {
|
|
999
|
+
if (nestField instanceof field_instance_1.FieldInstanceField &&
|
|
1000
|
+
fieldIsAnalytic(nestField)) {
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return true;
|
|
1007
|
+
}
|
|
943
1008
|
// This probably should be generated in a dialect independat way.
|
|
944
1009
|
// but for now, it is just googleSQL.
|
|
945
1010
|
generatePipelinedStages(outputPipelinedSQL, lastStageName, stageWriter,
|
|
@@ -1509,7 +1574,12 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1509
1574
|
}
|
|
1510
1575
|
return dialectFieldList;
|
|
1511
1576
|
}
|
|
1512
|
-
generateTurtleSQL(resultStruct, stageWriter, sqlFieldName, outputPipelinedSQL
|
|
1577
|
+
generateTurtleSQL(resultStruct, stageWriter, sqlFieldName, outputPipelinedSQL,
|
|
1578
|
+
// The single-group-set fast path (generateSingleGroupSetSQL) has no
|
|
1579
|
+
// group_set column to filter on: every row is in the one group. The
|
|
1580
|
+
// array-agg drops its `FILTER (WHERE group_set=N)` and keeps only a
|
|
1581
|
+
// projection's own `where:`.
|
|
1582
|
+
omitGroupSetFilter = false) {
|
|
1513
1583
|
// How to emit this nest's first stage — and the last line of defense before
|
|
1514
1584
|
// broken SQL for IR that didn't pass through the translator (cached,
|
|
1515
1585
|
// deserialized, or query-builder IR). Same `nestStrategy` the translator
|
|
@@ -1580,7 +1650,7 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1580
1650
|
}
|
|
1581
1651
|
}
|
|
1582
1652
|
// (capability is guarded by nestStrategy at the top of this method)
|
|
1583
|
-
ret = this.parent.dialect.sqlAggregateTurtle(resultStruct.groupSet, dialectFieldList, orderBy, limit, filterSQL);
|
|
1653
|
+
ret = this.parent.dialect.sqlAggregateTurtle(omitGroupSetFilter ? undefined : resultStruct.groupSet, dialectFieldList, orderBy, limit, filterSQL);
|
|
1584
1654
|
}
|
|
1585
1655
|
// If the turtle is a pipeline, generate a UDF to compute it.
|
|
1586
1656
|
const newStageWriter = new stage_writer_1.StageWriter(this.parent.dialect.supportsCTEinCoorelatedSubQueries, stageWriter);
|
|
@@ -1658,12 +1728,13 @@ class QueryQuery extends query_node_1.QueryField {
|
|
|
1658
1728
|
this.maxGroupSet = r.nextGroupSetNumber - 1;
|
|
1659
1729
|
this.rootResult.assignFieldsToGroups();
|
|
1660
1730
|
(_a = this.rootResult).isComplexQuery || (_a.isComplexQuery = this.maxDepth > 0 || r.isComplex);
|
|
1661
|
-
|
|
1731
|
+
// A complex query needs the group-set fan-out unless it has just one group
|
|
1732
|
+
// set (only single-stage projection nests), in which case generateSimpleSQL
|
|
1733
|
+
// emits the same no-fan-out form a non-nested query gets.
|
|
1734
|
+
if (this.rootResult.isComplexQuery && !this.canUseSingleGroupSetSQL()) {
|
|
1662
1735
|
return this.generateComplexSQL(stageWriter);
|
|
1663
1736
|
}
|
|
1664
|
-
|
|
1665
|
-
return this.generateSimpleSQL(stageWriter);
|
|
1666
|
-
}
|
|
1737
|
+
return this.generateSimpleSQL(stageWriter);
|
|
1667
1738
|
}
|
|
1668
1739
|
generateSQLFromPipeline(stageWriter) {
|
|
1669
1740
|
this.parent.maybeEmitParameterizedSourceUsage();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SourceDef Utilities for Persistence
|
|
3
3
|
*
|
|
4
|
-
* Key invariant:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Key invariant: a source's identity fields (sourceID, referenceID, extends)
|
|
5
|
+
* are assigned in the translator (DefineSource / the reference path), never by
|
|
6
|
+
* these compiler factories. The factory functions explicitly copy only the
|
|
7
|
+
* fields they need, never using spread, so identity is not propagated onto a
|
|
8
|
+
* freshly built source.
|
|
9
9
|
*/
|
|
10
10
|
import type { BuildID, FieldDef, GivenID, ModelDef, PersistableSourceDef, Query, QuerySourceDef, SourceDef, SourceID, SourceRegistryEntry, SourceRegistryValue, SQLPhraseSegment, SQLSourceDef, TableSourceDef } from './malloy_types';
|
|
11
11
|
export declare function mkSourceID(name: string, url: string | undefined): SourceID;
|
|
@@ -30,13 +30,37 @@ export declare function mkSQLSourceDef(base: SourceDef, selectStr: string, selec
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function mkTableSourceDef(name: string, connection: string, tablePath: string, dialect: string, fields: FieldDef[]): TableSourceDef;
|
|
32
32
|
/**
|
|
33
|
-
* Resolve a sourceID
|
|
33
|
+
* Resolve a SourceID (a source's own `sourceID`, or a `referenceID` pointing at
|
|
34
|
+
* one) to its SourceDef using the sourceRegistry, returning any kind of source.
|
|
34
35
|
*
|
|
35
36
|
* @param modelDef The model definition containing the registry
|
|
36
|
-
* @param sourceID The
|
|
37
|
+
* @param sourceID The SourceID to resolve
|
|
37
38
|
* @returns The SourceDef if found, undefined otherwise
|
|
38
39
|
*/
|
|
40
|
+
export declare function resolveSourceRef(modelDef: ModelDef, sourceID: SourceID): SourceDef | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a sourceID to a persistable SourceDef using the sourceRegistry.
|
|
43
|
+
*
|
|
44
|
+
* @param modelDef The model definition containing the registry
|
|
45
|
+
* @param sourceID The sourceID to resolve
|
|
46
|
+
* @returns The PersistableSourceDef if found, undefined otherwise
|
|
47
|
+
*/
|
|
39
48
|
export declare function resolveSourceID(modelDef: ModelDef, sourceID: SourceID): PersistableSourceDef | undefined;
|
|
49
|
+
/** The namespace entry a source refers to (see `sourceNamespaceReference`). */
|
|
50
|
+
export interface NamespaceReference {
|
|
51
|
+
/** The name this source is bound to in `modelDef.contents`. */
|
|
52
|
+
name: string;
|
|
53
|
+
/** The referenced source. */
|
|
54
|
+
source: SourceDef;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* If `sd` was created as an unmodified reference to another source
|
|
58
|
+
* (`sd.referenceID` is set) and that source is present in this model's
|
|
59
|
+
* namespace, return it together with the name it goes by. Returns undefined
|
|
60
|
+
* when `sd` defines its own shape, or when the referenced source is not in this
|
|
61
|
+
* model's namespace (e.g. an imported source whose own target wasn't imported).
|
|
62
|
+
*/
|
|
63
|
+
export declare function sourceNamespaceReference(modelDef: ModelDef, sd: SourceDef): NamespaceReference | undefined;
|
|
40
64
|
/**
|
|
41
65
|
* Add an entry to the sourceRegistry.
|
|
42
66
|
*
|
|
@@ -10,7 +10,9 @@ exports.mkBuildID = mkBuildID;
|
|
|
10
10
|
exports.mkQuerySourceDef = mkQuerySourceDef;
|
|
11
11
|
exports.mkSQLSourceDef = mkSQLSourceDef;
|
|
12
12
|
exports.mkTableSourceDef = mkTableSourceDef;
|
|
13
|
+
exports.resolveSourceRef = resolveSourceRef;
|
|
13
14
|
exports.resolveSourceID = resolveSourceID;
|
|
15
|
+
exports.sourceNamespaceReference = sourceNamespaceReference;
|
|
14
16
|
exports.registerSource = registerSource;
|
|
15
17
|
exports.hasSourceRegistryEntry = hasSourceRegistryEntry;
|
|
16
18
|
const utils_1 = require("./utils");
|
|
@@ -59,8 +61,9 @@ function mkQuerySourceDef(base, query, name) {
|
|
|
59
61
|
dialect: base.dialect,
|
|
60
62
|
partitionComposite: base.partitionComposite,
|
|
61
63
|
errorFactory: base.errorFactory,
|
|
62
|
-
//
|
|
64
|
+
// Identity fields - explicitly NOT copied
|
|
63
65
|
// sourceID: undefined,
|
|
66
|
+
// referenceID: undefined,
|
|
64
67
|
// extends: undefined,
|
|
65
68
|
// persistent: undefined,
|
|
66
69
|
};
|
|
@@ -97,8 +100,9 @@ function mkSQLSourceDef(base, selectStr, selectSegments) {
|
|
|
97
100
|
dialect: base.dialect,
|
|
98
101
|
partitionComposite: base.partitionComposite,
|
|
99
102
|
errorFactory: base.errorFactory,
|
|
100
|
-
//
|
|
103
|
+
// Identity fields - explicitly NOT copied
|
|
101
104
|
// sourceID: undefined,
|
|
105
|
+
// referenceID: undefined,
|
|
102
106
|
// extends: undefined,
|
|
103
107
|
// persistent: undefined,
|
|
104
108
|
};
|
|
@@ -120,25 +124,51 @@ function mkTableSourceDef(name, connection, tablePath, dialect, fields) {
|
|
|
120
124
|
// Source Registry Utilities
|
|
121
125
|
// =============================================================================
|
|
122
126
|
/**
|
|
123
|
-
* Resolve a sourceID
|
|
127
|
+
* Resolve a SourceID (a source's own `sourceID`, or a `referenceID` pointing at
|
|
128
|
+
* one) to its SourceDef using the sourceRegistry, returning any kind of source.
|
|
124
129
|
*
|
|
125
130
|
* @param modelDef The model definition containing the registry
|
|
126
|
-
* @param sourceID The
|
|
131
|
+
* @param sourceID The SourceID to resolve
|
|
127
132
|
* @returns The SourceDef if found, undefined otherwise
|
|
128
133
|
*/
|
|
129
|
-
function
|
|
134
|
+
function resolveSourceRef(modelDef, sourceID) {
|
|
130
135
|
const value = modelDef.sourceRegistry[sourceID];
|
|
131
136
|
if (!value)
|
|
132
137
|
return undefined;
|
|
133
138
|
if ((0, malloy_types_1.isSourceRegistryReference)(value.entry)) {
|
|
134
139
|
const obj = (0, malloy_types_1.safeRecordGet)(modelDef.contents, value.entry.name);
|
|
135
|
-
return obj && (0, malloy_types_1.isSourceDef)(obj)
|
|
136
|
-
? obj
|
|
137
|
-
: undefined;
|
|
140
|
+
return obj && (0, malloy_types_1.isSourceDef)(obj) ? obj : undefined;
|
|
138
141
|
}
|
|
139
|
-
// It's a PersistableSourceDef
|
|
140
142
|
return value.entry;
|
|
141
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Resolve a sourceID to a persistable SourceDef using the sourceRegistry.
|
|
146
|
+
*
|
|
147
|
+
* @param modelDef The model definition containing the registry
|
|
148
|
+
* @param sourceID The sourceID to resolve
|
|
149
|
+
* @returns The PersistableSourceDef if found, undefined otherwise
|
|
150
|
+
*/
|
|
151
|
+
function resolveSourceID(modelDef, sourceID) {
|
|
152
|
+
const sd = resolveSourceRef(modelDef, sourceID);
|
|
153
|
+
return sd && (0, malloy_types_1.isPersistableSourceDef)(sd) ? sd : undefined;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* If `sd` was created as an unmodified reference to another source
|
|
157
|
+
* (`sd.referenceID` is set) and that source is present in this model's
|
|
158
|
+
* namespace, return it together with the name it goes by. Returns undefined
|
|
159
|
+
* when `sd` defines its own shape, or when the referenced source is not in this
|
|
160
|
+
* model's namespace (e.g. an imported source whose own target wasn't imported).
|
|
161
|
+
*/
|
|
162
|
+
function sourceNamespaceReference(modelDef, sd) {
|
|
163
|
+
if (sd.referenceID === undefined)
|
|
164
|
+
return undefined;
|
|
165
|
+
const value = modelDef.sourceRegistry[sd.referenceID];
|
|
166
|
+
if (!value || !(0, malloy_types_1.isSourceRegistryReference)(value.entry))
|
|
167
|
+
return undefined;
|
|
168
|
+
const name = value.entry.name;
|
|
169
|
+
const source = (0, malloy_types_1.safeRecordGet)(modelDef.contents, name);
|
|
170
|
+
return source && (0, malloy_types_1.isSourceDef)(source) ? { name, source } : undefined;
|
|
171
|
+
}
|
|
142
172
|
/**
|
|
143
173
|
* Add an entry to the sourceRegistry.
|
|
144
174
|
*
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MALLOY_VERSION = "0.0.
|
|
1
|
+
export declare const MALLOY_VERSION = "0.0.414";
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MALLOY_VERSION = void 0;
|
|
4
4
|
// generated with 'generate-version-file' script; do not edit manually
|
|
5
|
-
exports.MALLOY_VERSION = '0.0.
|
|
5
|
+
exports.MALLOY_VERSION = '0.0.414';
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/malloy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.414",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@malloydata/malloy-filter": "0.0.
|
|
55
|
-
"@malloydata/malloy-interfaces": "0.0.
|
|
56
|
-
"@malloydata/malloy-tag": "0.0.
|
|
54
|
+
"@malloydata/malloy-filter": "0.0.414",
|
|
55
|
+
"@malloydata/malloy-interfaces": "0.0.414",
|
|
56
|
+
"@malloydata/malloy-tag": "0.0.414",
|
|
57
57
|
"@noble/hashes": "^1.8.0",
|
|
58
58
|
"antlr4ts": "^0.5.0-alpha.4",
|
|
59
59
|
"assert": "^2.0.0",
|