@malloydata/malloy 0.0.110 → 0.0.111-dev231204165238

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.
@@ -137,6 +137,7 @@ import { QueryExtendStatementContext } from "./MalloyParser";
137
137
  import { QueryExtendStatementListContext } from "./MalloyParser";
138
138
  import { JoinListContext } from "./MalloyParser";
139
139
  import { IsExploreContext } from "./MalloyParser";
140
+ import { MatrixOperationContext } from "./MalloyParser";
140
141
  import { JoinDefContext } from "./MalloyParser";
141
142
  import { JoinExpressionContext } from "./MalloyParser";
142
143
  import { FilterStatementContext } from "./MalloyParser";
@@ -1742,6 +1743,16 @@ export interface MalloyParserListener extends ParseTreeListener {
1742
1743
  * @param ctx the parse tree
1743
1744
  */
1744
1745
  exitIsExplore?: (ctx: IsExploreContext) => void;
1746
+ /**
1747
+ * Enter a parse tree produced by `MalloyParser.matrixOperation`.
1748
+ * @param ctx the parse tree
1749
+ */
1750
+ enterMatrixOperation?: (ctx: MatrixOperationContext) => void;
1751
+ /**
1752
+ * Exit a parse tree produced by `MalloyParser.matrixOperation`.
1753
+ * @param ctx the parse tree
1754
+ */
1755
+ exitMatrixOperation?: (ctx: MatrixOperationContext) => void;
1745
1756
  /**
1746
1757
  * Enter a parse tree produced by `MalloyParser.joinDef`.
1747
1758
  * @param ctx the parse tree
@@ -137,6 +137,7 @@ import { QueryExtendStatementContext } from "./MalloyParser";
137
137
  import { QueryExtendStatementListContext } from "./MalloyParser";
138
138
  import { JoinListContext } from "./MalloyParser";
139
139
  import { IsExploreContext } from "./MalloyParser";
140
+ import { MatrixOperationContext } from "./MalloyParser";
140
141
  import { JoinDefContext } from "./MalloyParser";
141
142
  import { JoinExpressionContext } from "./MalloyParser";
142
143
  import { FilterStatementContext } from "./MalloyParser";
@@ -1117,6 +1118,12 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
1117
1118
  * @return the visitor result
1118
1119
  */
1119
1120
  visitIsExplore?: (ctx: IsExploreContext) => Result;
1121
+ /**
1122
+ * Visit a parse tree produced by `MalloyParser.matrixOperation`.
1123
+ * @param ctx the parse tree
1124
+ * @return the visitor result
1125
+ */
1126
+ visitMatrixOperation?: (ctx: MatrixOperationContext) => Result;
1120
1127
  /**
1121
1128
  * Visit a parse tree produced by `MalloyParser.joinDef`.
1122
1129
  * @param ctx the parse tree
@@ -432,10 +432,18 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
432
432
  return result;
433
433
  }
434
434
  visitJoinOn(pcx) {
435
+ var _a;
435
436
  const joinAs = this.getModelEntryName(pcx.joinNameDef());
436
437
  const { joinFrom, notes } = this.getJoinSource(joinAs, pcx.isExplore());
437
438
  const join = new ast.ExpressionJoin(joinAs, joinFrom);
438
439
  const onCx = pcx.joinExpression();
440
+ const mop = ((_a = pcx.matrixOperation()) === null || _a === void 0 ? void 0 : _a.text.toLocaleLowerCase()) || 'left';
441
+ if ((0, malloy_types_1.isMatrixOperation)(mop)) {
442
+ join.matrixOperation = mop;
443
+ }
444
+ else {
445
+ this.contextError(pcx, 'Internal Error: Unknown matrixOperation');
446
+ }
439
447
  if (onCx) {
440
448
  join.joinOn = this.getFieldExpr(onCx);
441
449
  }
@@ -143,6 +143,7 @@ describe('structdef comprehension', () => {
143
143
  dialect: 'standardsql',
144
144
  structRelationship: {
145
145
  type: 'one',
146
+ matrixOperation: 'left',
146
147
  onExpression: [
147
148
  { type: 'field', path: 'aKey' },
148
149
  '=',
@@ -240,6 +240,7 @@ class TestTranslator extends parse_malloy_1.MalloyTranslator {
240
240
  as: 'b',
241
241
  structRelationship: {
242
242
  type: 'one',
243
+ matrixOperation: 'left',
243
244
  onExpression: [
244
245
  { type: 'field', path: 'astr' },
245
246
  '=',
@@ -195,6 +195,7 @@ declare class JoinInstance {
195
195
  children: JoinInstance[];
196
196
  constructor(queryStruct: QueryStruct, alias: string, parent: JoinInstance | undefined);
197
197
  parentRelationship(): 'root' | JoinRelationship;
198
+ forceAllSymmetricCalculations(): boolean;
198
199
  getDialectFieldList(): DialectFieldList;
199
200
  }
200
201
  /** nested query */
@@ -696,6 +696,7 @@ class QueryFieldStruct extends QueryAtomicField {
696
696
  ...this.parent.fieldDef,
697
697
  structRelationship: {
698
698
  type: 'one',
699
+ matrixOperation: 'left',
699
700
  onExpression: [
700
701
  {
701
702
  type: 'field',
@@ -1138,7 +1139,8 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
1138
1139
  for (const [name, join] of this.joins) {
1139
1140
  // first join is by default the
1140
1141
  const relationship = join.parentRelationship();
1141
- if (relationship === 'many_to_many') {
1142
+ if (relationship === 'many_to_many' ||
1143
+ join.forceAllSymmetricCalculations()) {
1142
1144
  // everything must be calculated with symmetric aggregates
1143
1145
  leafiest = '0never';
1144
1146
  }
@@ -1231,6 +1233,20 @@ class JoinInstance {
1231
1233
  throw new Error(`Internal error unknown relationship type to parent for ${this.queryStruct.fieldDef.name}`);
1232
1234
  }
1233
1235
  }
1236
+ // For now, we force all symmetric calculations for full and right joins
1237
+ // because we need distinct keys for COUNT(xx) operations. Don't really need
1238
+ // this for sums. This will produce correct results and we can optimize this
1239
+ // at some point..
1240
+ forceAllSymmetricCalculations() {
1241
+ const sr = this.queryStruct.fieldDef.structRelationship;
1242
+ if (this.queryStruct.parent === undefined || !(0, malloy_types_1.isJoinOn)(sr)) {
1243
+ return false;
1244
+ }
1245
+ if (sr.matrixOperation === 'right' || sr.matrixOperation === 'full') {
1246
+ return true;
1247
+ }
1248
+ return false;
1249
+ }
1234
1250
  // postgres unnest needs to know the names of the physical fields.
1235
1251
  getDialectFieldList() {
1236
1252
  const dialectFieldList = [];
@@ -1869,6 +1885,7 @@ class QueryQuery extends QueryField {
1869
1885
  const structRelationship = qs.fieldDef.structRelationship;
1870
1886
  let structSQL = qs.structSourceSQL(stageWriter);
1871
1887
  if ((0, malloy_types_1.isJoinOn)(structRelationship)) {
1888
+ const matrixOperation = structRelationship.matrixOperation.toUpperCase();
1872
1889
  if (ji.makeUniqueKey) {
1873
1890
  const passKeys = this.generateSQLPassthroughKeys(qs);
1874
1891
  structSQL = `(SELECT ${qs.dialect.sqlGenerateUUID()} as __distinct_key, x.* ${passKeys} FROM ${structSQL} as x)`;
@@ -1896,7 +1913,7 @@ class QueryQuery extends QueryField {
1896
1913
  if (conditions !== undefined && conditions.length >= 1) {
1897
1914
  filters = ` AND (${conditions.join(' AND ')})`;
1898
1915
  }
1899
- s += `LEFT JOIN ${structSQL} AS ${ji.alias}\n ON ${onCondition}${filters}\n`;
1916
+ s += ` ${matrixOperation} JOIN ${structSQL} AS ${ji.alias}\n ON ${onCondition}${filters}\n`;
1900
1917
  }
1901
1918
  else {
1902
1919
  let select = `SELECT ${ji.alias}.*`;
@@ -1907,7 +1924,7 @@ class QueryQuery extends QueryField {
1907
1924
  select += `, ${this.parent.dialect.sqlSelectAliasAsStruct(childJoin.alias, physicalFields)} AS ${childJoin.alias}`;
1908
1925
  }
1909
1926
  select += `\nFROM ${structSQL} AS ${ji.alias}\n${joins}\nWHERE ${conditions === null || conditions === void 0 ? void 0 : conditions.join(' AND ')}\n`;
1910
- s += `LEFT JOIN (\n${(0, utils_1.indent)(select)}) AS ${ji.alias}\n ON ${onCondition}\n`;
1927
+ s += `${matrixOperation} JOIN (\n${(0, utils_1.indent)(select)}) AS ${ji.alias}\n ON ${onCondition}\n`;
1911
1928
  return s;
1912
1929
  }
1913
1930
  }
@@ -459,8 +459,11 @@ export interface TurtleDef extends NamedObject, Pipeline {
459
459
  annotation?: Annotation;
460
460
  }
461
461
  export type JoinRelationship = 'one_to_one' | 'one_to_many' | 'many_to_one' | 'many_to_many';
462
+ export type MatrixOperation = 'left' | 'right' | 'full' | 'inner';
463
+ export declare function isMatrixOperation(x: string): x is MatrixOperation;
462
464
  export interface JoinOn {
463
465
  type: 'one' | 'many' | 'cross';
466
+ matrixOperation: MatrixOperation;
464
467
  onExpression?: Expr;
465
468
  }
466
469
  export declare function isJoinOn(sr: StructRelationship): sr is JoinOn;
@@ -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.isSQLFragment = exports.isJoinOn = exports.isIndexSegment = 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.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 = void 0;
25
+ exports.isJoinOn = exports.isMatrixOperation = exports.isIndexSegment = 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.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 = void 0;
27
27
  function isValueParameter(p) {
28
28
  return p.value !== undefined;
29
29
  }
@@ -314,6 +314,10 @@ function isIndexSegment(pe) {
314
314
  return pe.type === 'index';
315
315
  }
316
316
  exports.isIndexSegment = isIndexSegment;
317
+ function isMatrixOperation(x) {
318
+ return ['left', 'right', 'full', 'inner'].includes(x);
319
+ }
320
+ exports.isMatrixOperation = isMatrixOperation;
317
321
  function isJoinOn(sr) {
318
322
  return ['one', 'many', 'cross'].includes(sr.type);
319
323
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.110",
3
+ "version": "0.0.111-dev231204165238",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",