@malloydata/malloy 0.0.119-dev240116200529 → 0.0.119-dev240118215411
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/dialect/functions/all_functions.js +6 -0
- package/dist/dialect/functions/sql.d.ts +6 -0
- package/dist/dialect/functions/sql.js +62 -0
- package/dist/dialect/postgres/postgres.js +3 -3
- package/dist/lang/ast/expressions/expr-aggregate-function.d.ts +3 -3
- package/dist/lang/ast/expressions/expr-aggregate-function.js +1 -0
- package/dist/lang/ast/expressions/expr-func.js +72 -1
- package/dist/model/malloy_query.d.ts +13 -5
- package/dist/model/malloy_query.js +89 -22
- package/dist/model/malloy_types.d.ts +18 -2
- package/dist/model/malloy_types.js +14 -2
- package/dist/model/utils.js +10 -0
- package/package.json +1 -1
|
@@ -60,6 +60,7 @@ const simple_numeric_functions_1 = require("./simple_numeric_functions");
|
|
|
60
60
|
const avg_moving_1 = require("./avg_moving");
|
|
61
61
|
const function_map_1 = require("./function_map");
|
|
62
62
|
const coalesce_1 = require("./coalesce");
|
|
63
|
+
const sql_1 = require("./sql");
|
|
63
64
|
/**
|
|
64
65
|
* This is a function map containing default implementations of all Malloy
|
|
65
66
|
* built-in functions. These don't work in all dialects, but are a good starting
|
|
@@ -131,5 +132,10 @@ exports.FUNCTIONS.add('min_window', sum_min_max_window_1.fnMinWindow);
|
|
|
131
132
|
exports.FUNCTIONS.add('max_window', sum_min_max_window_1.fnMaxWindow);
|
|
132
133
|
exports.FUNCTIONS.add('sum_window', sum_min_max_window_1.fnSumWindow);
|
|
133
134
|
exports.FUNCTIONS.add('avg_moving', avg_moving_1.fnAvgRolling);
|
|
135
|
+
exports.FUNCTIONS.add('sql_number', sql_1.fnSqlNumber);
|
|
136
|
+
exports.FUNCTIONS.add('sql_string', sql_1.fnSqlString);
|
|
137
|
+
exports.FUNCTIONS.add('sql_date', sql_1.fnSqlDate);
|
|
138
|
+
exports.FUNCTIONS.add('sql_timestamp', sql_1.fnSqlTimestamp);
|
|
139
|
+
exports.FUNCTIONS.add('sql_boolean', sql_1.fnSqlBoolean);
|
|
134
140
|
exports.FUNCTIONS.seal();
|
|
135
141
|
//# sourceMappingURL=all_functions.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DialectFunctionOverloadDef } from './util';
|
|
2
|
+
export declare function fnSqlNumber(): DialectFunctionOverloadDef[];
|
|
3
|
+
export declare function fnSqlString(): DialectFunctionOverloadDef[];
|
|
4
|
+
export declare function fnSqlDate(): DialectFunctionOverloadDef[];
|
|
5
|
+
export declare function fnSqlTimestamp(): DialectFunctionOverloadDef[];
|
|
6
|
+
export declare function fnSqlBoolean(): DialectFunctionOverloadDef[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2023 Google LLC
|
|
4
|
+
*
|
|
5
|
+
* Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
* a copy of this software and associated documentation files
|
|
7
|
+
* (the "Software"), to deal in the Software without restriction,
|
|
8
|
+
* including without limitation the rights to use, copy, modify, merge,
|
|
9
|
+
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
10
|
+
* and to permit persons to whom the Software is furnished to do so,
|
|
11
|
+
* subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be
|
|
14
|
+
* included in all copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.fnSqlBoolean = exports.fnSqlTimestamp = exports.fnSqlDate = exports.fnSqlString = exports.fnSqlNumber = void 0;
|
|
26
|
+
const util_1 = require("./util");
|
|
27
|
+
function fnSqlNumber() {
|
|
28
|
+
const value = (0, util_1.makeParam)('value', (0, util_1.literal)((0, util_1.maxScalar)('string')));
|
|
29
|
+
return [
|
|
30
|
+
(0, util_1.overload)((0, util_1.minScalar)('number'), [value.param], [{ type: 'sql-string', e: [value.arg] }]),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
exports.fnSqlNumber = fnSqlNumber;
|
|
34
|
+
function fnSqlString() {
|
|
35
|
+
const value = (0, util_1.makeParam)('value', (0, util_1.literal)((0, util_1.maxScalar)('string')));
|
|
36
|
+
return [
|
|
37
|
+
(0, util_1.overload)((0, util_1.minScalar)('string'), [value.param], [{ type: 'sql-string', e: [value.arg] }]),
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
exports.fnSqlString = fnSqlString;
|
|
41
|
+
function fnSqlDate() {
|
|
42
|
+
const value = (0, util_1.makeParam)('value', (0, util_1.literal)((0, util_1.maxScalar)('string')));
|
|
43
|
+
return [
|
|
44
|
+
(0, util_1.overload)((0, util_1.minScalar)('date'), [value.param], [{ type: 'sql-string', e: [value.arg] }]),
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
exports.fnSqlDate = fnSqlDate;
|
|
48
|
+
function fnSqlTimestamp() {
|
|
49
|
+
const value = (0, util_1.makeParam)('value', (0, util_1.literal)((0, util_1.maxScalar)('string')));
|
|
50
|
+
return [
|
|
51
|
+
(0, util_1.overload)((0, util_1.minScalar)('timestamp'), [value.param], [{ type: 'sql-string', e: [value.arg] }]),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
exports.fnSqlTimestamp = fnSqlTimestamp;
|
|
55
|
+
function fnSqlBoolean() {
|
|
56
|
+
const value = (0, util_1.makeParam)('value', (0, util_1.literal)((0, util_1.maxScalar)('string')));
|
|
57
|
+
return [
|
|
58
|
+
(0, util_1.overload)((0, util_1.minScalar)('boolean'), [value.param], [{ type: 'sql-string', e: [value.arg] }]),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
exports.fnSqlBoolean = fnSqlBoolean;
|
|
62
|
+
//# sourceMappingURL=sql.js.map
|
|
@@ -193,16 +193,16 @@ class PostgresDialect extends dialect_1.Dialect {
|
|
|
193
193
|
return 'GEN_RANDOM_UUID()';
|
|
194
194
|
}
|
|
195
195
|
sqlFieldReference(alias, fieldName, fieldType, isNested, _isArray) {
|
|
196
|
-
let ret =
|
|
196
|
+
let ret = `(${alias}->>'${fieldName}')`;
|
|
197
197
|
if (isNested) {
|
|
198
198
|
switch (fieldType) {
|
|
199
199
|
case 'string':
|
|
200
200
|
break;
|
|
201
201
|
case 'number':
|
|
202
|
-
ret =
|
|
202
|
+
ret = `${ret}::double precision`;
|
|
203
203
|
break;
|
|
204
204
|
case 'struct':
|
|
205
|
-
ret =
|
|
205
|
+
ret = `${ret}::jsonb`;
|
|
206
206
|
break;
|
|
207
207
|
}
|
|
208
208
|
return ret;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { FieldValueType, StructRelationship } from '../../../model/malloy_types';
|
|
1
|
+
import { AggregateFunctionType, FieldValueType, StructRelationship } from '../../../model/malloy_types';
|
|
2
2
|
import { FieldReference } from '../query-items/field-references';
|
|
3
3
|
import { ExprValue } from '../types/expr-value';
|
|
4
4
|
import { ExpressionDef } from '../types/expression-def';
|
|
5
5
|
import { FieldSpace } from '../types/field-space';
|
|
6
6
|
export declare abstract class ExprAggregateFunction extends ExpressionDef {
|
|
7
|
-
readonly func:
|
|
7
|
+
readonly func: AggregateFunctionType;
|
|
8
8
|
elementType: string;
|
|
9
9
|
source?: FieldReference;
|
|
10
10
|
expr?: ExpressionDef;
|
|
11
11
|
explicitSource?: boolean;
|
|
12
12
|
legalChildTypes: import("../../../model/malloy_types").TypeDesc[];
|
|
13
|
-
constructor(func:
|
|
13
|
+
constructor(func: AggregateFunctionType, expr?: ExpressionDef, explicitSource?: boolean);
|
|
14
14
|
returns(_forExpression: ExprValue): FieldValueType;
|
|
15
15
|
getExpression(fs: FieldSpace): ExprValue;
|
|
16
16
|
isSymmetricFunction(): boolean;
|
|
@@ -216,6 +216,7 @@ function getJoinUsage(fs, expr) {
|
|
|
216
216
|
};
|
|
217
217
|
(0, utils_1.exprWalk)(expr, frag => {
|
|
218
218
|
if (typeof frag !== 'string') {
|
|
219
|
+
// TODO make this work for field references inside sql_* functions
|
|
219
220
|
if (frag.type === 'field') {
|
|
220
221
|
const def = lookup(fs, frag.path);
|
|
221
222
|
if (def.def.type !== 'struct' && def.def.type !== 'turtle') {
|
|
@@ -161,7 +161,7 @@ class ExprFunc extends expression_def_1.ExpressionDef {
|
|
|
161
161
|
.join(', ')}) with source`);
|
|
162
162
|
return (0, ast_utils_1.errorFor)('cannot call with source');
|
|
163
163
|
}
|
|
164
|
-
|
|
164
|
+
let funcCall = [
|
|
165
165
|
{
|
|
166
166
|
type: 'function_call',
|
|
167
167
|
overload,
|
|
@@ -170,6 +170,50 @@ class ExprFunc extends expression_def_1.ExpressionDef {
|
|
|
170
170
|
structPath,
|
|
171
171
|
},
|
|
172
172
|
];
|
|
173
|
+
if ([
|
|
174
|
+
'sql_number',
|
|
175
|
+
'sql_string',
|
|
176
|
+
'sql_date',
|
|
177
|
+
'sql_timestamp',
|
|
178
|
+
'sql_boolean',
|
|
179
|
+
].includes(func.name)) {
|
|
180
|
+
if (!this.inExperiment('sql_functions', true)) {
|
|
181
|
+
return (0, ast_utils_1.errorFor)(`Cannot use sql_function \`${func.name}\`; use \`sql_functions\` experiment to enable this behavior`);
|
|
182
|
+
}
|
|
183
|
+
const str = argExprs[0].value;
|
|
184
|
+
if (str.length !== 1 ||
|
|
185
|
+
typeof str[0] === 'string' ||
|
|
186
|
+
str[0].type !== 'dialect' ||
|
|
187
|
+
str[0].function !== 'stringLiteral') {
|
|
188
|
+
this.log(`Invalid string literal for \`${func.name}\``);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const literal = str[0].literal;
|
|
192
|
+
const parts = parseSQLInterpolation(literal);
|
|
193
|
+
const unsupportedInterpolations = parts
|
|
194
|
+
.filter(part => part.type === 'interpolation' && part.name.includes('.'))
|
|
195
|
+
.map(unsupportedPart => unsupportedPart.type === 'interpolation'
|
|
196
|
+
? `\${${unsupportedPart.name}}`
|
|
197
|
+
: `\${${unsupportedPart.value}}`);
|
|
198
|
+
if (unsupportedInterpolations.length > 0) {
|
|
199
|
+
const unsupportedInterpolationMsg = unsupportedInterpolations.length === 1
|
|
200
|
+
? `'.' paths are not yet supported in sql interpolations, found ${unsupportedInterpolations.at(0)}`
|
|
201
|
+
: `'.' paths are not yet supported in sql interpolations, found [${unsupportedInterpolations.join(', ')}]`;
|
|
202
|
+
this.log(unsupportedInterpolationMsg);
|
|
203
|
+
return (0, ast_utils_1.errorFor)(`${unsupportedInterpolationMsg}. See LookML \${...} documentation at https://cloud.google.com/looker/docs/reference/param-field-sql#sql_for_dimensions`);
|
|
204
|
+
}
|
|
205
|
+
funcCall = [
|
|
206
|
+
{
|
|
207
|
+
type: 'sql-string',
|
|
208
|
+
e: parts.map(part => part.type === 'string'
|
|
209
|
+
? part.value
|
|
210
|
+
: part.name === 'TABLE'
|
|
211
|
+
? { type: 'source-reference' }
|
|
212
|
+
: { type: 'field-reference', path: part.name }),
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
173
217
|
if (type.dataType === 'any') {
|
|
174
218
|
this.log(`Invalid return type ${type.dataType} for function '${this.name}'`);
|
|
175
219
|
return (0, ast_utils_1.errorFor)('invalid return type');
|
|
@@ -285,4 +329,31 @@ function findOverload(func, args) {
|
|
|
285
329
|
}
|
|
286
330
|
}
|
|
287
331
|
}
|
|
332
|
+
function parseSQLInterpolation(template) {
|
|
333
|
+
const parts = [];
|
|
334
|
+
let remaining = template;
|
|
335
|
+
while (remaining.length) {
|
|
336
|
+
const nextInterp = remaining.indexOf('${');
|
|
337
|
+
if (nextInterp === -1) {
|
|
338
|
+
parts.push({ type: 'string', value: remaining });
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const interpEnd = remaining.slice(nextInterp).indexOf('}');
|
|
343
|
+
if (interpEnd === -1) {
|
|
344
|
+
parts.push({ type: 'string', value: remaining });
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
if (nextInterp > 0) {
|
|
348
|
+
parts.push({ type: 'string', value: remaining.slice(0, nextInterp) });
|
|
349
|
+
}
|
|
350
|
+
parts.push({
|
|
351
|
+
type: 'interpolation',
|
|
352
|
+
name: remaining.slice(nextInterp + 2, interpEnd + nextInterp),
|
|
353
|
+
});
|
|
354
|
+
remaining = remaining.slice(interpEnd + nextInterp + 1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return parts;
|
|
358
|
+
}
|
|
288
359
|
//# sourceMappingURL=expr-func.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Dialect, DialectFieldList } from '../dialect';
|
|
2
|
-
import { AggregateFragment, CompiledQuery, DialectFragment, Expr, FieldDef, FieldFragment, FieldRef, Filtered, FilterExpression, FilterFragment, FunctionCallFragment, FunctionOverloadDef, JoinRelationship, ModelDef, OrderBy, OutputFieldFragment, Parameter, ParameterFragment, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, SpreadFragment, SQLExpressionFragment, StructDef, StructRef, TurtleDef, UngroupFragment } from './malloy_types';
|
|
2
|
+
import { AggregateFragment, AggregateFunctionType, CompiledQuery, DialectFragment, Expr, FieldDef, FieldFragment, FieldRef, FieldReferenceFragment, Filtered, FilterExpression, FilterFragment, FunctionCallFragment, FunctionOverloadDef, JoinRelationship, ModelDef, OrderBy, OutputFieldFragment, Parameter, ParameterFragment, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, SourceReferenceFragment, SpreadFragment, SQLExpressionFragment, SqlStringFragment, StructDef, StructRef, TurtleDef, UngroupFragment } from './malloy_types';
|
|
3
3
|
import { Connection } from '../runtime_types';
|
|
4
4
|
import { AndChain } from './utils';
|
|
5
5
|
import { QueryInfo } from '../dialect/dialect';
|
|
@@ -17,6 +17,11 @@ interface OutputPipelinedSQL {
|
|
|
17
17
|
sqlFieldName: string;
|
|
18
18
|
pipelineSQL: string;
|
|
19
19
|
}
|
|
20
|
+
type UniqueKeyPossibleUse = AggregateFunctionType | 'generic_aggregate';
|
|
21
|
+
declare class UniqueKeyUse extends Set<UniqueKeyPossibleUse> {
|
|
22
|
+
add_use(k: UniqueKeyPossibleUse | undefined): this | undefined;
|
|
23
|
+
hasAsymetricFunctions(): boolean;
|
|
24
|
+
}
|
|
20
25
|
declare class StageWriter {
|
|
21
26
|
parent: StageWriter | undefined;
|
|
22
27
|
withs: string[];
|
|
@@ -57,7 +62,7 @@ declare class QueryField extends QueryNode {
|
|
|
57
62
|
fieldDef: FieldDef;
|
|
58
63
|
parent: QueryStruct;
|
|
59
64
|
constructor(fieldDef: FieldDef, parent: QueryStruct);
|
|
60
|
-
|
|
65
|
+
uniqueKeyPossibleUse(): UniqueKeyPossibleUse | undefined;
|
|
61
66
|
getJoinableParent(): QueryStruct;
|
|
62
67
|
caseGroup(groupSets: number[], s: string): string;
|
|
63
68
|
getFullOutputName(): string;
|
|
@@ -78,6 +83,9 @@ declare class QueryField extends QueryNode {
|
|
|
78
83
|
generateAvgFragment(resultSet: FieldInstanceResult, context: QueryStruct, expr: AggregateFragment, state: GenerateState): string;
|
|
79
84
|
generateCountFragment(resultSet: FieldInstanceResult, context: QueryStruct, expr: AggregateFragment, state: GenerateState): string;
|
|
80
85
|
generateDialect(resultSet: FieldInstanceResult, context: QueryStruct, expr: DialectFragment, state: GenerateState): string;
|
|
86
|
+
generateFieldReference(resultSet: FieldInstanceResult, context: QueryStruct, expr: FieldReferenceFragment, state: GenerateState): string;
|
|
87
|
+
generateSqlString(resultSet: FieldInstanceResult, context: QueryStruct, expr: SqlStringFragment, state: GenerateState): string;
|
|
88
|
+
generateSourceReference(resultSet: FieldInstanceResult, context: QueryStruct, expr: SourceReferenceFragment): string;
|
|
81
89
|
getAnalyticPartitions(resultStruct: FieldInstanceResult): string;
|
|
82
90
|
generateAnalyticFragment(resultStruct: FieldInstanceResult, context: QueryStruct, expr: Expr, overload: FunctionOverloadDef, state: GenerateState, args: Expr[]): string;
|
|
83
91
|
generateExpressionFromExpr(resultSet: FieldInstanceResult, context: QueryStruct, e: Expr, state?: GenerateState): string;
|
|
@@ -166,7 +174,7 @@ declare class FieldInstanceResult implements FieldInstance {
|
|
|
166
174
|
structs(): FieldInstanceResult[];
|
|
167
175
|
selectStructs(result: FieldInstanceResult[], fn: (result: FieldInstanceResult) => boolean): FieldInstanceResult[];
|
|
168
176
|
calculateDefaultOrderBy(): OrderBy[];
|
|
169
|
-
addStructToJoin(qs: QueryStruct, query: QueryQuery,
|
|
177
|
+
addStructToJoin(qs: QueryStruct, query: QueryQuery, uniqueKeyPossibleUse: UniqueKeyPossibleUse | undefined, joinStack: string[]): void;
|
|
170
178
|
findJoins(query: QueryQuery): void;
|
|
171
179
|
root(): FieldInstanceResultRoot;
|
|
172
180
|
getUngroupPartitions(ungroupSet: UngroupSet | undefined): FieldInstanceField[];
|
|
@@ -188,7 +196,7 @@ declare class JoinInstance {
|
|
|
188
196
|
queryStruct: QueryStruct;
|
|
189
197
|
alias: string;
|
|
190
198
|
parent: JoinInstance | undefined;
|
|
191
|
-
|
|
199
|
+
uniqueKeyPossibleUses: UniqueKeyUse;
|
|
192
200
|
makeUniqueKey: boolean;
|
|
193
201
|
leafiest: boolean;
|
|
194
202
|
joinFilterConditions?: QueryFieldBoolean[];
|
|
@@ -243,7 +251,7 @@ declare class QueryQuery extends QueryField {
|
|
|
243
251
|
expandDependantField(resultStruct: FieldInstanceResult, fieldRef: FieldRef): void;
|
|
244
252
|
expandWildCardStruct(struct: QueryStruct, expandChildren: boolean, filter?: ((qf: QueryNode) => boolean) | undefined): string[];
|
|
245
253
|
expandWildCards(fields: QueryFieldDef[], filter?: ((qf: QueryNode) => boolean) | undefined): QueryFieldDef[];
|
|
246
|
-
addDependantPath(resultStruct: FieldInstanceResult, context: QueryStruct, path: string,
|
|
254
|
+
addDependantPath(resultStruct: FieldInstanceResult, context: QueryStruct, path: string, uniqueKeyPossibleUse: UniqueKeyPossibleUse | undefined, joinStack: string[]): void;
|
|
247
255
|
addDependantExpr(resultStruct: FieldInstanceResult, context: QueryStruct, e: Expr, joinStack: string[]): void;
|
|
248
256
|
addDependancies(resultStruct: FieldInstanceResult, field: QueryField): void;
|
|
249
257
|
expandFields(resultStruct: FieldInstanceResult): void;
|
|
@@ -34,6 +34,16 @@ function generateSQLStringLiteral(sourceString) {
|
|
|
34
34
|
function identifierNormalize(s) {
|
|
35
35
|
return s.replace(/[^a-zA-Z0-9_]/g, '_o_');
|
|
36
36
|
}
|
|
37
|
+
class UniqueKeyUse extends Set {
|
|
38
|
+
add_use(k) {
|
|
39
|
+
if (k !== undefined) {
|
|
40
|
+
return this.add(k);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
hasAsymetricFunctions() {
|
|
44
|
+
return this.has('sum') || this.has('avg') || this.has('count');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
37
47
|
class StageWriter {
|
|
38
48
|
constructor(useCTE = true, parent) {
|
|
39
49
|
this.parent = parent;
|
|
@@ -173,8 +183,8 @@ class QueryField extends QueryNode {
|
|
|
173
183
|
this.parent = parent;
|
|
174
184
|
this.fieldDef = fieldDef;
|
|
175
185
|
}
|
|
176
|
-
|
|
177
|
-
return
|
|
186
|
+
uniqueKeyPossibleUse() {
|
|
187
|
+
return undefined;
|
|
178
188
|
}
|
|
179
189
|
getJoinableParent() {
|
|
180
190
|
// if it is inline it should always have a parent
|
|
@@ -436,11 +446,28 @@ class QueryField extends QueryNode {
|
|
|
436
446
|
generateCountFragment(resultSet, context, expr, state) {
|
|
437
447
|
let func = 'COUNT(';
|
|
438
448
|
let thing = '1';
|
|
439
|
-
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
|
|
449
|
+
let struct = context;
|
|
450
|
+
if (expr.structPath) {
|
|
451
|
+
struct = this.parent.root().getStructByName(expr.structPath);
|
|
452
|
+
}
|
|
453
|
+
const joinName = struct.getJoinableParent().getIdentifier();
|
|
454
|
+
const join = resultSet.root().joins.get(joinName);
|
|
455
|
+
if (!join) {
|
|
456
|
+
throw new Error(`Join ${joinName} not found in result set`);
|
|
443
457
|
}
|
|
458
|
+
if (!join.leafiest || join.makeUniqueKey) {
|
|
459
|
+
func = 'COUNT(DISTINCT';
|
|
460
|
+
thing = struct.getDistinctKey().generateExpression(resultSet);
|
|
461
|
+
}
|
|
462
|
+
// const distinctKeySQL = this.generateDistinctKeyIfNecessary(
|
|
463
|
+
// resultSet,
|
|
464
|
+
// context,
|
|
465
|
+
// expr.structPath
|
|
466
|
+
// );
|
|
467
|
+
// if (distinctKeySQL) {
|
|
468
|
+
// func = 'COUNT(DISTINCT';
|
|
469
|
+
// thing = distinctKeySQL;
|
|
470
|
+
// }
|
|
444
471
|
// find the structDef and return the path to the field...
|
|
445
472
|
if (state.whereSQL) {
|
|
446
473
|
return `${func} CASE WHEN ${state.whereSQL} THEN ${thing} END)`;
|
|
@@ -452,6 +479,24 @@ class QueryField extends QueryNode {
|
|
|
452
479
|
generateDialect(resultSet, context, expr, state) {
|
|
453
480
|
return this.generateExpressionFromExpr(resultSet, context, context.dialect.dialectExpr(resultSet.getQueryInfo(), expr), state);
|
|
454
481
|
}
|
|
482
|
+
generateFieldReference(resultSet, context, expr, state) {
|
|
483
|
+
return this.generateFieldFragment(resultSet, context, { type: 'field', path: expr.path }, state);
|
|
484
|
+
}
|
|
485
|
+
generateSqlString(resultSet, context, expr, state) {
|
|
486
|
+
return expr.e
|
|
487
|
+
.map(part => typeof part === 'string'
|
|
488
|
+
? part
|
|
489
|
+
: this.generateExpressionFromExpr(resultSet, context, [part], state))
|
|
490
|
+
.join('');
|
|
491
|
+
}
|
|
492
|
+
generateSourceReference(resultSet, context, expr) {
|
|
493
|
+
if (expr.path === undefined) {
|
|
494
|
+
return context.getSQLIdentifier();
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
return context.getFieldByName(expr.path).getIdentifier();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
455
500
|
getAnalyticPartitions(resultStruct) {
|
|
456
501
|
const ret = [];
|
|
457
502
|
let p = resultStruct.parent;
|
|
@@ -616,6 +661,15 @@ class QueryField extends QueryNode {
|
|
|
616
661
|
else if (expr.type === 'dialect') {
|
|
617
662
|
s += this.generateDialect(resultSet, context, expr, state);
|
|
618
663
|
}
|
|
664
|
+
else if (expr.type === 'sql-string') {
|
|
665
|
+
s += this.generateSqlString(resultSet, context, expr, state);
|
|
666
|
+
}
|
|
667
|
+
else if (expr.type === 'source-reference') {
|
|
668
|
+
s += this.generateSourceReference(resultSet, context, expr);
|
|
669
|
+
}
|
|
670
|
+
else if (expr.type === 'field-reference') {
|
|
671
|
+
s += this.generateFieldReference(resultSet, context, expr, state);
|
|
672
|
+
}
|
|
619
673
|
else {
|
|
620
674
|
throw new Error(`Internal Error: Unknown expression fragment ${JSON.stringify(expr, undefined, 2)}`);
|
|
621
675
|
}
|
|
@@ -746,7 +800,8 @@ class QueryFieldDistinctKey extends QueryAtomicField {
|
|
|
746
800
|
}
|
|
747
801
|
else if (this.parent.fieldDef.structSource.type === 'nested') {
|
|
748
802
|
const parentKey = (_a = this.parent.parent) === null || _a === void 0 ? void 0 : _a.getDistinctKey().generateExpression(resultSet);
|
|
749
|
-
return
|
|
803
|
+
return this.parent.dialect.concat(parentKey || '', // shouldn't have to do this...
|
|
804
|
+
"'x'", this.parent.dialect.sqlFieldReference(this.parent.getIdentifier(), '__row_id', 'string', true, false));
|
|
750
805
|
}
|
|
751
806
|
else {
|
|
752
807
|
// return this.parent.getIdentifier() + "." + "__distinct_key";
|
|
@@ -1003,7 +1058,7 @@ class FieldInstanceResult {
|
|
|
1003
1058
|
}
|
|
1004
1059
|
return [];
|
|
1005
1060
|
}
|
|
1006
|
-
addStructToJoin(qs, query,
|
|
1061
|
+
addStructToJoin(qs, query, uniqueKeyPossibleUse, joinStack) {
|
|
1007
1062
|
var _a;
|
|
1008
1063
|
const name = qs.getIdentifier();
|
|
1009
1064
|
// we're already chasing the dependency for this join.
|
|
@@ -1012,7 +1067,7 @@ class FieldInstanceResult {
|
|
|
1012
1067
|
}
|
|
1013
1068
|
let join;
|
|
1014
1069
|
if ((join = this.root().joins.get(name))) {
|
|
1015
|
-
join.
|
|
1070
|
+
join.uniqueKeyPossibleUses.add_use(uniqueKeyPossibleUse);
|
|
1016
1071
|
return;
|
|
1017
1072
|
}
|
|
1018
1073
|
// if we have a parent, join it first.
|
|
@@ -1020,7 +1075,7 @@ class FieldInstanceResult {
|
|
|
1020
1075
|
const parentStruct = (_a = qs.parent) === null || _a === void 0 ? void 0 : _a.getJoinableParent();
|
|
1021
1076
|
if (parentStruct) {
|
|
1022
1077
|
// add dependant expressions first...
|
|
1023
|
-
this.addStructToJoin(parentStruct, query,
|
|
1078
|
+
this.addStructToJoin(parentStruct, query, undefined, joinStack);
|
|
1024
1079
|
parent = this.root().joins.get(parentStruct.getIdentifier());
|
|
1025
1080
|
}
|
|
1026
1081
|
// add any dependant joins based on the ON
|
|
@@ -1038,11 +1093,11 @@ class FieldInstanceResult {
|
|
|
1038
1093
|
join = new JoinInstance(qs, name, parent);
|
|
1039
1094
|
this.root().joins.set(name, join);
|
|
1040
1095
|
}
|
|
1041
|
-
join.
|
|
1096
|
+
join.uniqueKeyPossibleUses.add_use(uniqueKeyPossibleUse);
|
|
1042
1097
|
}
|
|
1043
1098
|
findJoins(query) {
|
|
1044
1099
|
for (const dim of this.fields()) {
|
|
1045
|
-
this.addStructToJoin(dim.f.getJoinableParent(), query, dim.f.
|
|
1100
|
+
this.addStructToJoin(dim.f.getJoinableParent(), query, dim.f.uniqueKeyPossibleUse(), []);
|
|
1046
1101
|
}
|
|
1047
1102
|
for (const s of this.structs()) {
|
|
1048
1103
|
s.findJoins(query);
|
|
@@ -1167,8 +1222,20 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
|
|
|
1167
1222
|
// Nested Unique keys are dependant on the primary key of the parent
|
|
1168
1223
|
// and the table.
|
|
1169
1224
|
for (const [_name, join] of this.joins) {
|
|
1170
|
-
//
|
|
1171
|
-
|
|
1225
|
+
// in a one_to_many join we need a key to count there may be a failed
|
|
1226
|
+
// match in a left join.
|
|
1227
|
+
// users -> {
|
|
1228
|
+
// group_by: user_id
|
|
1229
|
+
// aggregate: order_count is orders.count()
|
|
1230
|
+
if (join.leafiest) {
|
|
1231
|
+
if (join.parent !== null &&
|
|
1232
|
+
join.uniqueKeyPossibleUses.has('count') &&
|
|
1233
|
+
!join.queryStruct.primaryKey()) {
|
|
1234
|
+
join.makeUniqueKey = true;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
else if (!join.leafiest &&
|
|
1238
|
+
join.uniqueKeyPossibleUses.hasAsymetricFunctions()) {
|
|
1172
1239
|
let j = join;
|
|
1173
1240
|
while (j) {
|
|
1174
1241
|
if (!j.queryStruct.primaryKey()) {
|
|
@@ -1190,7 +1257,7 @@ class JoinInstance {
|
|
|
1190
1257
|
this.queryStruct = queryStruct;
|
|
1191
1258
|
this.alias = alias;
|
|
1192
1259
|
this.parent = parent;
|
|
1193
|
-
this.
|
|
1260
|
+
this.uniqueKeyPossibleUses = new UniqueKeyUse();
|
|
1194
1261
|
this.makeUniqueKey = false;
|
|
1195
1262
|
this.leafiest = false;
|
|
1196
1263
|
this.children = [];
|
|
@@ -1474,7 +1541,7 @@ class QueryQuery extends QueryField {
|
|
|
1474
1541
|
}
|
|
1475
1542
|
return ret;
|
|
1476
1543
|
}
|
|
1477
|
-
addDependantPath(resultStruct, context, path,
|
|
1544
|
+
addDependantPath(resultStruct, context, path, uniqueKeyPossibleUse, joinStack) {
|
|
1478
1545
|
const node = context.getFieldByName(path);
|
|
1479
1546
|
let struct;
|
|
1480
1547
|
if (node instanceof QueryField) {
|
|
@@ -1488,7 +1555,7 @@ class QueryQuery extends QueryField {
|
|
|
1488
1555
|
}
|
|
1489
1556
|
resultStruct
|
|
1490
1557
|
.root()
|
|
1491
|
-
.addStructToJoin(struct.getJoinableParent(), this,
|
|
1558
|
+
.addStructToJoin(struct.getJoinableParent(), this, uniqueKeyPossibleUse, joinStack);
|
|
1492
1559
|
}
|
|
1493
1560
|
addDependantExpr(resultStruct, context, e, joinStack) {
|
|
1494
1561
|
for (const expr of e) {
|
|
@@ -1523,7 +1590,7 @@ class QueryQuery extends QueryField {
|
|
|
1523
1590
|
else {
|
|
1524
1591
|
resultStruct
|
|
1525
1592
|
.root()
|
|
1526
|
-
.addStructToJoin(field.parent.getJoinableParent(), this,
|
|
1593
|
+
.addStructToJoin(field.parent.getJoinableParent(), this, undefined, joinStack);
|
|
1527
1594
|
// this.addDependantPath(resultStruct, field.parent, expr.path, false);
|
|
1528
1595
|
}
|
|
1529
1596
|
}
|
|
@@ -1572,18 +1639,18 @@ class QueryQuery extends QueryField {
|
|
|
1572
1639
|
else if ((0, malloy_types_1.isAggregateFragment)(expr)) {
|
|
1573
1640
|
if ((0, malloy_types_1.isAsymmetricFragment)(expr)) {
|
|
1574
1641
|
if (expr.structPath) {
|
|
1575
|
-
this.addDependantPath(resultStruct, context, expr.structPath,
|
|
1642
|
+
this.addDependantPath(resultStruct, context, expr.structPath, expr.function, joinStack);
|
|
1576
1643
|
}
|
|
1577
1644
|
else {
|
|
1578
1645
|
// we are doing a sum in the root. It may need symetric aggregates
|
|
1579
|
-
resultStruct.addStructToJoin(context, this,
|
|
1646
|
+
resultStruct.addStructToJoin(context, this, expr.function, joinStack);
|
|
1580
1647
|
}
|
|
1581
1648
|
}
|
|
1582
1649
|
this.addDependantExpr(resultStruct, context, expr.e, joinStack);
|
|
1583
1650
|
}
|
|
1584
1651
|
else if ((0, malloy_types_1.isFunctionCallFragment)(expr)) {
|
|
1585
1652
|
if (expr.structPath) {
|
|
1586
|
-
this.addDependantPath(resultStruct, context, expr.structPath,
|
|
1653
|
+
this.addDependantPath(resultStruct, context, expr.structPath, 'generic_aggregate', joinStack);
|
|
1587
1654
|
}
|
|
1588
1655
|
// TODO Do we need to call `addStructToJoin` here in the case when there is no `structPath`
|
|
1589
1656
|
// and the function is an aggregate function?
|
|
@@ -1688,7 +1755,7 @@ class QueryQuery extends QueryField {
|
|
|
1688
1755
|
prepare(_stageWriter) {
|
|
1689
1756
|
if (!this.prepared) {
|
|
1690
1757
|
this.expandFields(this.rootResult);
|
|
1691
|
-
this.rootResult.addStructToJoin(this.parent, this,
|
|
1758
|
+
this.rootResult.addStructToJoin(this.parent, this, undefined, []);
|
|
1692
1759
|
this.rootResult.findJoins(this);
|
|
1693
1760
|
this.rootResult.calculateSymmetricAggregates();
|
|
1694
1761
|
this.prepared = true;
|
|
@@ -108,9 +108,10 @@ export interface FilterFragment {
|
|
|
108
108
|
}
|
|
109
109
|
export declare function isFilterFragment(f: Fragment): f is FilterFragment;
|
|
110
110
|
export declare function isDialectFragment(f: Fragment): f is DialectFragment;
|
|
111
|
+
export type AggregateFunctionType = 'sum' | 'avg' | 'count' | 'count_distinct' | 'max' | 'min';
|
|
111
112
|
export interface AggregateFragment {
|
|
112
113
|
type: 'aggregate';
|
|
113
|
-
function:
|
|
114
|
+
function: AggregateFunctionType;
|
|
114
115
|
e: Expr;
|
|
115
116
|
structPath?: string;
|
|
116
117
|
}
|
|
@@ -150,6 +151,21 @@ export interface FieldFragment {
|
|
|
150
151
|
path: string;
|
|
151
152
|
}
|
|
152
153
|
export declare function isFieldFragment(f: Fragment): f is FieldFragment;
|
|
154
|
+
export interface FieldReferenceFragment {
|
|
155
|
+
type: 'field-reference';
|
|
156
|
+
path: string;
|
|
157
|
+
}
|
|
158
|
+
export declare function isFieldReferenceFragment(f: Fragment): f is FieldReferenceFragment;
|
|
159
|
+
export interface SqlStringFragment {
|
|
160
|
+
type: 'sql-string';
|
|
161
|
+
e: Expr;
|
|
162
|
+
}
|
|
163
|
+
export declare function isSqlStringFragment(f: Fragment): f is SqlStringFragment;
|
|
164
|
+
export interface SourceReferenceFragment {
|
|
165
|
+
type: 'source-reference';
|
|
166
|
+
path?: string;
|
|
167
|
+
}
|
|
168
|
+
export declare function isSourceReferenceFragment(f: Fragment): f is SourceReferenceFragment;
|
|
153
169
|
export interface ParameterFragment {
|
|
154
170
|
type: 'parameter';
|
|
155
171
|
path: string;
|
|
@@ -233,7 +249,7 @@ export interface NumberLiteralFragment extends DialectFragmentBase {
|
|
|
233
249
|
literal: string;
|
|
234
250
|
}
|
|
235
251
|
export type DialectFragment = DivFragment | TimeLiteralFragment | NowFragment | TimeDeltaFragment | TimeDiffFragment | TimeTruncFragment | TypecastFragment | TimeExtractFragment | StringLiteralFragment | RegexpLiteralFragment | NumberLiteralFragment | RegexpMatchFragment;
|
|
236
|
-
export type Fragment = string | ApplyFragment | ApplyValueFragment | FieldFragment | ParameterFragment | FilterFragment | OutputFieldFragment | AggregateFragment | UngroupFragment | DialectFragment | FunctionParameterFragment | FunctionCallFragment | SQLExpressionFragment | SpreadFragment;
|
|
252
|
+
export type Fragment = string | ApplyFragment | ApplyValueFragment | FieldFragment | FieldReferenceFragment | SourceReferenceFragment | SqlStringFragment | ParameterFragment | FilterFragment | OutputFieldFragment | AggregateFragment | UngroupFragment | DialectFragment | FunctionParameterFragment | FunctionCallFragment | SQLExpressionFragment | SpreadFragment;
|
|
237
253
|
export type Expr = Fragment[];
|
|
238
254
|
export interface TypedValue {
|
|
239
255
|
value: Expr;
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.
|
|
26
|
-
exports.isValueDate = exports.isValueTimestamp = exports.isValueBoolean = exports.isValueNumber = exports.isValueString = exports.isMeasureLike = exports.getPhysicalFields = exports.getDimensions = exports.isPhysical = exports.isDimensional = exports.isAtomicField = exports.isTurtleDef = exports.getIdentifier = exports.isFieldStructDef = exports.isFieldTimeBased = exports.isFieldTypeDef = exports.isSQLBlockStruct = exports.mergeEvalSpaces = exports.isSQLFragment = exports.isJoinOn = void 0;
|
|
25
|
+
exports.isSamplingEnable = exports.isSamplingPercent = exports.isSamplingRows = exports.isQuerySegment = exports.isProjectSegment = exports.isPartialSegment = exports.isReduceSegment = exports.refIsStructDef = exports.isByExpression = exports.isByName = exports.ValueType = exports.isExtractUnit = exports.isTimestampUnit = exports.isDateUnit = exports.FieldIsIntrinsic = exports.isCastType = exports.isAtomicFieldType = exports.isTimeFieldType = exports.hasExpression = exports.maxOfExpressionTypes = exports.maxExpressionType = exports.isExpressionTypeLEQ = exports.expressionIsAnalytic = exports.expressionIsCalculation = exports.expressionInvolvesAggregate = exports.expressionIsUngroupedAggregate = exports.expressionIsAggregate = exports.expressionIsScalar = exports.mkExpr = exports.isApplyFragment = exports.isApplyValue = exports.isParameterFragment = exports.isSourceReferenceFragment = exports.isSqlStringFragment = exports.isFieldReferenceFragment = exports.isFieldFragment = exports.isSpreadFragment = exports.isSQLExpressionFragment = exports.isFunctionCallFragment = exports.isFunctionParameterFragment = exports.isUngroupFragment = exports.isAsymmetricFragment = exports.isAggregateFragment = exports.isDialectFragment = exports.isFilterFragment = exports.isOutputFieldFragment = exports.isFilteredAliasedName = exports.paramHasValue = exports.isConditionParameter = exports.isValueParameter = void 0;
|
|
26
|
+
exports.isValueDate = exports.isValueTimestamp = exports.isValueBoolean = exports.isValueNumber = exports.isValueString = exports.isMeasureLike = exports.getPhysicalFields = exports.getDimensions = exports.isPhysical = exports.isDimensional = exports.isAtomicField = exports.isTurtleDef = exports.getIdentifier = exports.isFieldStructDef = exports.isFieldTimeBased = exports.isFieldTypeDef = exports.isSQLBlockStruct = exports.mergeEvalSpaces = exports.isSQLFragment = exports.isJoinOn = exports.isMatrixOperation = exports.isIndexSegment = exports.isRawSegment = void 0;
|
|
27
27
|
function isValueParameter(p) {
|
|
28
28
|
return p.value !== undefined;
|
|
29
29
|
}
|
|
@@ -90,6 +90,18 @@ function isFieldFragment(f) {
|
|
|
90
90
|
return (f === null || f === void 0 ? void 0 : f.type) === 'field';
|
|
91
91
|
}
|
|
92
92
|
exports.isFieldFragment = isFieldFragment;
|
|
93
|
+
function isFieldReferenceFragment(f) {
|
|
94
|
+
return (f === null || f === void 0 ? void 0 : f.type) === 'field-reference';
|
|
95
|
+
}
|
|
96
|
+
exports.isFieldReferenceFragment = isFieldReferenceFragment;
|
|
97
|
+
function isSqlStringFragment(f) {
|
|
98
|
+
return (f === null || f === void 0 ? void 0 : f.type) === 'sql-string';
|
|
99
|
+
}
|
|
100
|
+
exports.isSqlStringFragment = isSqlStringFragment;
|
|
101
|
+
function isSourceReferenceFragment(f) {
|
|
102
|
+
return (f === null || f === void 0 ? void 0 : f.type) === 'source-reference';
|
|
103
|
+
}
|
|
104
|
+
exports.isSourceReferenceFragment = isSourceReferenceFragment;
|
|
93
105
|
function isParameterFragment(f) {
|
|
94
106
|
return (f === null || f === void 0 ? void 0 : f.type) === 'parameter';
|
|
95
107
|
}
|
package/dist/model/utils.js
CHANGED
|
@@ -126,6 +126,11 @@ function exprMap(expr, func) {
|
|
|
126
126
|
case 'parameter':
|
|
127
127
|
case 'outputField':
|
|
128
128
|
return fragment;
|
|
129
|
+
case 'sql-string':
|
|
130
|
+
return {
|
|
131
|
+
...fragment,
|
|
132
|
+
e: exprMap(fragment.e, func),
|
|
133
|
+
};
|
|
129
134
|
case 'function_call':
|
|
130
135
|
return {
|
|
131
136
|
...fragment,
|
|
@@ -232,6 +237,11 @@ function exprWalk(expr, func) {
|
|
|
232
237
|
...fragment,
|
|
233
238
|
args: fragment.args.map(arg => exprWalk(arg, func)),
|
|
234
239
|
};
|
|
240
|
+
case 'sql-string':
|
|
241
|
+
return {
|
|
242
|
+
...fragment,
|
|
243
|
+
e: exprWalk(fragment.e, func),
|
|
244
|
+
};
|
|
235
245
|
case 'filterExpression':
|
|
236
246
|
return {
|
|
237
247
|
...fragment,
|