@malloydata/malloy 0.0.119-dev240116200529 → 0.0.119-dev240118211537

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.
@@ -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 = `${alias}->>'${fieldName}'`;
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 = `(${ret})::double precision`;
202
+ ret = `${ret}::double precision`;
203
203
  break;
204
204
  case 'struct':
205
- ret = `(${ret})::jsonb`;
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: string;
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: string, expr?: ExpressionDef, explicitSource?: boolean);
13
+ constructor(func: AggregateFunctionType, expr?: ExpressionDef, explicitSource?: boolean);
14
14
  returns(_forExpression: ExprValue): FieldValueType;
15
15
  getExpression(fs: FieldSpace): ExprValue;
16
16
  isSymmetricFunction(): boolean;
@@ -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, 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';
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
- mayNeedUniqueKey(): boolean;
65
+ uniqueKeyPossibleUse(): UniqueKeyPossibleUse | undefined;
61
66
  getJoinableParent(): QueryStruct;
62
67
  caseGroup(groupSets: number[], s: string): string;
63
68
  getFullOutputName(): string;
@@ -166,7 +171,7 @@ declare class FieldInstanceResult implements FieldInstance {
166
171
  structs(): FieldInstanceResult[];
167
172
  selectStructs(result: FieldInstanceResult[], fn: (result: FieldInstanceResult) => boolean): FieldInstanceResult[];
168
173
  calculateDefaultOrderBy(): OrderBy[];
169
- addStructToJoin(qs: QueryStruct, query: QueryQuery, mayNeedUniqueKey: boolean, joinStack: string[]): void;
174
+ addStructToJoin(qs: QueryStruct, query: QueryQuery, uniqueKeyPossibleUse: UniqueKeyPossibleUse | undefined, joinStack: string[]): void;
170
175
  findJoins(query: QueryQuery): void;
171
176
  root(): FieldInstanceResultRoot;
172
177
  getUngroupPartitions(ungroupSet: UngroupSet | undefined): FieldInstanceField[];
@@ -188,7 +193,7 @@ declare class JoinInstance {
188
193
  queryStruct: QueryStruct;
189
194
  alias: string;
190
195
  parent: JoinInstance | undefined;
191
- mayNeedUniqueKey: boolean;
196
+ uniqueKeyPossibleUses: UniqueKeyUse;
192
197
  makeUniqueKey: boolean;
193
198
  leafiest: boolean;
194
199
  joinFilterConditions?: QueryFieldBoolean[];
@@ -243,7 +248,7 @@ declare class QueryQuery extends QueryField {
243
248
  expandDependantField(resultStruct: FieldInstanceResult, fieldRef: FieldRef): void;
244
249
  expandWildCardStruct(struct: QueryStruct, expandChildren: boolean, filter?: ((qf: QueryNode) => boolean) | undefined): string[];
245
250
  expandWildCards(fields: QueryFieldDef[], filter?: ((qf: QueryNode) => boolean) | undefined): QueryFieldDef[];
246
- addDependantPath(resultStruct: FieldInstanceResult, context: QueryStruct, path: string, mayNeedUniqueKey: boolean, joinStack: string[]): void;
251
+ addDependantPath(resultStruct: FieldInstanceResult, context: QueryStruct, path: string, uniqueKeyPossibleUse: UniqueKeyPossibleUse | undefined, joinStack: string[]): void;
247
252
  addDependantExpr(resultStruct: FieldInstanceResult, context: QueryStruct, e: Expr, joinStack: string[]): void;
248
253
  addDependancies(resultStruct: FieldInstanceResult, field: QueryField): void;
249
254
  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
- mayNeedUniqueKey() {
177
- return false;
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
- const distinctKeySQL = this.generateDistinctKeyIfNecessary(resultSet, context, expr.structPath);
440
- if (distinctKeySQL) {
441
- func = 'COUNT(DISTINCT';
442
- thing = distinctKeySQL;
449
+ let struct = context;
450
+ if (expr.structPath) {
451
+ struct = this.parent.root().getStructByName(expr.structPath);
443
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`);
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)`;
@@ -746,7 +773,8 @@ class QueryFieldDistinctKey extends QueryAtomicField {
746
773
  }
747
774
  else if (this.parent.fieldDef.structSource.type === 'nested') {
748
775
  const parentKey = (_a = this.parent.parent) === null || _a === void 0 ? void 0 : _a.getDistinctKey().generateExpression(resultSet);
749
- return `CONCAT(${parentKey}, 'x', ${this.parent.dialect.sqlFieldReference(this.parent.getIdentifier(), '__row_id', 'string', true, false)})`;
776
+ return this.parent.dialect.concat(parentKey || '', // shouldn't have to do this...
777
+ "'x'", this.parent.dialect.sqlFieldReference(this.parent.getIdentifier(), '__row_id', 'string', true, false));
750
778
  }
751
779
  else {
752
780
  // return this.parent.getIdentifier() + "." + "__distinct_key";
@@ -1003,7 +1031,7 @@ class FieldInstanceResult {
1003
1031
  }
1004
1032
  return [];
1005
1033
  }
1006
- addStructToJoin(qs, query, mayNeedUniqueKey, joinStack) {
1034
+ addStructToJoin(qs, query, uniqueKeyPossibleUse, joinStack) {
1007
1035
  var _a;
1008
1036
  const name = qs.getIdentifier();
1009
1037
  // we're already chasing the dependency for this join.
@@ -1012,7 +1040,7 @@ class FieldInstanceResult {
1012
1040
  }
1013
1041
  let join;
1014
1042
  if ((join = this.root().joins.get(name))) {
1015
- join.mayNeedUniqueKey || (join.mayNeedUniqueKey = mayNeedUniqueKey);
1043
+ join.uniqueKeyPossibleUses.add_use(uniqueKeyPossibleUse);
1016
1044
  return;
1017
1045
  }
1018
1046
  // if we have a parent, join it first.
@@ -1020,7 +1048,7 @@ class FieldInstanceResult {
1020
1048
  const parentStruct = (_a = qs.parent) === null || _a === void 0 ? void 0 : _a.getJoinableParent();
1021
1049
  if (parentStruct) {
1022
1050
  // add dependant expressions first...
1023
- this.addStructToJoin(parentStruct, query, false, joinStack);
1051
+ this.addStructToJoin(parentStruct, query, undefined, joinStack);
1024
1052
  parent = this.root().joins.get(parentStruct.getIdentifier());
1025
1053
  }
1026
1054
  // add any dependant joins based on the ON
@@ -1038,11 +1066,11 @@ class FieldInstanceResult {
1038
1066
  join = new JoinInstance(qs, name, parent);
1039
1067
  this.root().joins.set(name, join);
1040
1068
  }
1041
- join.mayNeedUniqueKey || (join.mayNeedUniqueKey = mayNeedUniqueKey);
1069
+ join.uniqueKeyPossibleUses.add_use(uniqueKeyPossibleUse);
1042
1070
  }
1043
1071
  findJoins(query) {
1044
1072
  for (const dim of this.fields()) {
1045
- this.addStructToJoin(dim.f.getJoinableParent(), query, dim.f.mayNeedUniqueKey(), []);
1073
+ this.addStructToJoin(dim.f.getJoinableParent(), query, dim.f.uniqueKeyPossibleUse(), []);
1046
1074
  }
1047
1075
  for (const s of this.structs()) {
1048
1076
  s.findJoins(query);
@@ -1167,8 +1195,20 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
1167
1195
  // Nested Unique keys are dependant on the primary key of the parent
1168
1196
  // and the table.
1169
1197
  for (const [_name, join] of this.joins) {
1170
- // don't need keys on leafiest
1171
- if (!join.leafiest && join.mayNeedUniqueKey) {
1198
+ // in a one_to_many join we need a key to count there may be a failed
1199
+ // match in a left join.
1200
+ // users -> {
1201
+ // group_by: user_id
1202
+ // aggregate: order_count is orders.count()
1203
+ if (join.leafiest) {
1204
+ if (join.parent !== null &&
1205
+ join.uniqueKeyPossibleUses.has('count') &&
1206
+ !join.queryStruct.primaryKey()) {
1207
+ join.makeUniqueKey = true;
1208
+ }
1209
+ }
1210
+ else if (!join.leafiest &&
1211
+ join.uniqueKeyPossibleUses.hasAsymetricFunctions()) {
1172
1212
  let j = join;
1173
1213
  while (j) {
1174
1214
  if (!j.queryStruct.primaryKey()) {
@@ -1190,7 +1230,7 @@ class JoinInstance {
1190
1230
  this.queryStruct = queryStruct;
1191
1231
  this.alias = alias;
1192
1232
  this.parent = parent;
1193
- this.mayNeedUniqueKey = false;
1233
+ this.uniqueKeyPossibleUses = new UniqueKeyUse();
1194
1234
  this.makeUniqueKey = false;
1195
1235
  this.leafiest = false;
1196
1236
  this.children = [];
@@ -1474,7 +1514,7 @@ class QueryQuery extends QueryField {
1474
1514
  }
1475
1515
  return ret;
1476
1516
  }
1477
- addDependantPath(resultStruct, context, path, mayNeedUniqueKey, joinStack) {
1517
+ addDependantPath(resultStruct, context, path, uniqueKeyPossibleUse, joinStack) {
1478
1518
  const node = context.getFieldByName(path);
1479
1519
  let struct;
1480
1520
  if (node instanceof QueryField) {
@@ -1488,7 +1528,7 @@ class QueryQuery extends QueryField {
1488
1528
  }
1489
1529
  resultStruct
1490
1530
  .root()
1491
- .addStructToJoin(struct.getJoinableParent(), this, mayNeedUniqueKey, joinStack);
1531
+ .addStructToJoin(struct.getJoinableParent(), this, uniqueKeyPossibleUse, joinStack);
1492
1532
  }
1493
1533
  addDependantExpr(resultStruct, context, e, joinStack) {
1494
1534
  for (const expr of e) {
@@ -1523,7 +1563,7 @@ class QueryQuery extends QueryField {
1523
1563
  else {
1524
1564
  resultStruct
1525
1565
  .root()
1526
- .addStructToJoin(field.parent.getJoinableParent(), this, false, joinStack);
1566
+ .addStructToJoin(field.parent.getJoinableParent(), this, undefined, joinStack);
1527
1567
  // this.addDependantPath(resultStruct, field.parent, expr.path, false);
1528
1568
  }
1529
1569
  }
@@ -1572,18 +1612,18 @@ class QueryQuery extends QueryField {
1572
1612
  else if ((0, malloy_types_1.isAggregateFragment)(expr)) {
1573
1613
  if ((0, malloy_types_1.isAsymmetricFragment)(expr)) {
1574
1614
  if (expr.structPath) {
1575
- this.addDependantPath(resultStruct, context, expr.structPath, true, joinStack);
1615
+ this.addDependantPath(resultStruct, context, expr.structPath, expr.function, joinStack);
1576
1616
  }
1577
1617
  else {
1578
1618
  // we are doing a sum in the root. It may need symetric aggregates
1579
- resultStruct.addStructToJoin(context, this, true, joinStack);
1619
+ resultStruct.addStructToJoin(context, this, expr.function, joinStack);
1580
1620
  }
1581
1621
  }
1582
1622
  this.addDependantExpr(resultStruct, context, expr.e, joinStack);
1583
1623
  }
1584
1624
  else if ((0, malloy_types_1.isFunctionCallFragment)(expr)) {
1585
1625
  if (expr.structPath) {
1586
- this.addDependantPath(resultStruct, context, expr.structPath, true, joinStack);
1626
+ this.addDependantPath(resultStruct, context, expr.structPath, 'generic_aggregate', joinStack);
1587
1627
  }
1588
1628
  // TODO Do we need to call `addStructToJoin` here in the case when there is no `structPath`
1589
1629
  // and the function is an aggregate function?
@@ -1688,7 +1728,7 @@ class QueryQuery extends QueryField {
1688
1728
  prepare(_stageWriter) {
1689
1729
  if (!this.prepared) {
1690
1730
  this.expandFields(this.rootResult);
1691
- this.rootResult.addStructToJoin(this.parent, this, false, []);
1731
+ this.rootResult.addStructToJoin(this.parent, this, undefined, []);
1692
1732
  this.rootResult.findJoins(this);
1693
1733
  this.rootResult.calculateSymmetricAggregates();
1694
1734
  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: string;
114
+ function: AggregateFunctionType;
114
115
  e: Expr;
115
116
  structPath?: string;
116
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.119-dev240116200529",
3
+ "version": "0.0.119-dev240118211537",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",