@malloydata/malloy 0.0.330 → 0.0.332

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/util.js CHANGED
@@ -88,14 +88,12 @@ function mapData(data, schema) {
88
88
  }
89
89
  else if (field.type.kind === 'number_type') {
90
90
  const subtype = field.type.subtype;
91
- if (subtype === 'bigint') {
92
- const stringValue = (0, row_data_utils_1.rowDataToSerializedBigint)(value);
93
- return { kind: 'big_number_cell', number_value: stringValue, subtype };
94
- }
91
+ const stringValue = subtype === 'bigint' ? (0, row_data_utils_1.rowDataToSerializedBigint)(value) : undefined;
95
92
  return {
96
93
  kind: 'number_cell',
97
94
  number_value: (0, row_data_utils_1.rowDataToNumber)(value),
98
95
  subtype,
96
+ string_value: stringValue,
99
97
  };
100
98
  }
101
99
  else if (field.type.kind === 'string_type') {
@@ -76,6 +76,7 @@ export declare abstract class Dialect {
76
76
  supportsTempTables: boolean;
77
77
  hasModOperator: boolean;
78
78
  supportsLeftJoinUnnest: boolean;
79
+ requiresExplicitUnnestOrdering: boolean;
79
80
  supportsCountApprox: boolean;
80
81
  supportsHyperLogLog: boolean;
81
82
  supportsFullJoin: boolean;
@@ -98,6 +98,9 @@ class Dialect {
98
98
  this.hasModOperator = true;
99
99
  // can LEFT JOIN UNNEST
100
100
  this.supportsLeftJoinUnnest = true;
101
+ // UNNEST in LATERAL JOINs doesn't guarantee array element order.
102
+ // When true, compiler adds ORDER BY on array ordinality columns (__row_id)
103
+ this.requiresExplicitUnnestOrdering = false;
101
104
  this.supportsCountApprox = false;
102
105
  this.supportsHyperLogLog = false;
103
106
  // MYSQL doesn't have full join, maybe others.
@@ -22,6 +22,7 @@ export declare class DuckDBDialect extends PostgresBase {
22
22
  supportsSafeCast: boolean;
23
23
  supportsNesting: boolean;
24
24
  supportsCountApprox: boolean;
25
+ requiresExplicitUnnestOrdering: boolean;
25
26
  integerTypeMappings: IntegerTypeMapping[];
26
27
  get udfPrefix(): string;
27
28
  quoteTablePath(tableName: string): string;
@@ -33,7 +34,7 @@ export declare class DuckDBDialect extends PostgresBase {
33
34
  sqlAnyValueTurtle(groupSet: number, fieldList: DialectFieldList): string;
34
35
  sqlAnyValueLastTurtle(name: string, groupSet: number, sqlName: string): string;
35
36
  sqlCoaleseMeasuresInline(groupSet: number, fieldList: DialectFieldList): string;
36
- sqlUnnestAlias(source: string, alias: string, _fieldList: DialectFieldList, needDistinctKey: boolean, _isArray: boolean, isInNestedPipeline: boolean): string;
37
+ sqlUnnestAlias(source: string, alias: string, _fieldList: DialectFieldList, _needDistinctKey: boolean, _isArray: boolean, isInNestedPipeline: boolean): string;
37
38
  sqlSumDistinctHashedKey(_sqlDistinctKey: string): string;
38
39
  sqlGenerateUUID(): string;
39
40
  sqlDateToString(sqlDateExp: string): string;
@@ -73,6 +73,8 @@ class DuckDBDialect extends pg_impl_1.PostgresBase {
73
73
  this.supportsSafeCast = true;
74
74
  this.supportsNesting = true;
75
75
  this.supportsCountApprox = true;
76
+ // DuckDB UNNEST in LATERAL JOINs doesn't preserve array element order
77
+ this.requiresExplicitUnnestOrdering = true;
76
78
  // DuckDB: 32-bit INTEGER is safe, larger integers need bigint
77
79
  this.integerTypeMappings = [
78
80
  { min: BigInt(dialect_1.MIN_INT32), max: BigInt(dialect_1.MAX_INT32), numberType: 'integer' },
@@ -139,7 +141,7 @@ class DuckDBDialect extends pg_impl_1.PostgresBase {
139
141
  // // When DuckDB supports lateral joins...
140
142
  // //return `,(select UNNEST(generate_series(1, length(${source}),1))) as ${alias}(__row_id)`;
141
143
  // }
142
- sqlUnnestAlias(source, alias, _fieldList, needDistinctKey, _isArray, isInNestedPipeline) {
144
+ sqlUnnestAlias(source, alias, _fieldList, _needDistinctKey, _isArray, isInNestedPipeline) {
143
145
  if (this.unnestWithNumbers) {
144
146
  // Duckdb can't unnest in a coorelated subquery at the moment so we hack it.
145
147
  const arrayLen = isInNestedPipeline
@@ -149,13 +151,8 @@ class DuckDBDialect extends pg_impl_1.PostgresBase {
149
151
  ${arrayLen},
150
152
  1)) as __row_id) as ${alias} ON ${alias}.__row_id <= array_length(${source})`;
151
153
  }
152
- //Simulate left joins by guarenteeing there is at least one row.
153
- if (!needDistinctKey) {
154
- return `LEFT JOIN LATERAL (SELECT UNNEST(${source}), 1 as ignoreme) as ${alias}_outer(${alias},ignoreme) ON ${alias}_outer.ignoreme=1`;
155
- }
156
- else {
157
- return `LEFT JOIN LATERAL (SELECT UNNEST(GENERATE_SERIES(1, length(${source}),1)) as __row_id, UNNEST(${source}), 1 as ignoreme) as ${alias}_outer(__row_id, ${alias},ignoreme) ON ${alias}_outer.ignoreme=1`;
158
- }
154
+ // Use WITH ORDINALITY to preserve array element order via __row_id
155
+ return `LEFT JOIN LATERAL UNNEST(${source}) WITH ORDINALITY as ${alias}_outer(${alias}, __row_id) ON true`;
159
156
  }
160
157
  sqlSumDistinctHashedKey(_sqlDistinctKey) {
161
158
  return 'uses sumDistinctFunction, should not be called';
@@ -296,6 +296,9 @@ class QuerySpace extends QueryOperationSpace {
296
296
  throw new Error('Invalid type for fieldref');
297
297
  }
298
298
  ret.location = (_c = ret.location) !== null && _c !== void 0 ? _c : this.astEl.location;
299
+ if (queryFieldDef.annotation) {
300
+ ret.annotation = queryFieldDef.annotation;
301
+ }
299
302
  return ret;
300
303
  }
301
304
  // Gets the primary key field for the output struct of this query;
@@ -95,7 +95,7 @@ function composeSources(sources, compositeCodeSource) {
95
95
  fieldUsage: [
96
96
  { path: [fieldName], at: compositeCodeSource.codeLocation },
97
97
  ],
98
- code: compositeCodeSource.code,
98
+ code: undefined,
99
99
  location: compositeCodeSource.codeLocation,
100
100
  // A composite field's grouping may differ from slice to slice
101
101
  requiresGroupBy: undefined,
@@ -1,5 +1,5 @@
1
1
  import type * as Malloy from '@malloydata/malloy-interfaces';
2
- import type { Expr, TimestampUnit } from '../../../model/malloy_types';
2
+ import type { Expr, TimestampUnit, FilterExprType } from '../../../model/malloy_types';
3
3
  import type { ExprValue } from './expr-value';
4
4
  import type { FieldSpace } from './field-space';
5
5
  import { MalloyElement } from './malloy-element';
@@ -85,4 +85,4 @@ export declare function getMorphicValue(mv: ExprValue, mt: MorphicType): ExprVal
85
85
  * @return ExprValue of the expression
86
86
  */
87
87
  export declare function applyBinary(fs: FieldSpace, left: ExpressionDef, op: BinaryMalloyOperator, right: ExpressionDef): ExprValue;
88
- export declare function checkFilterExpression(logTo: MalloyElement, ft: string, fexpr: Expr): void;
88
+ export declare function checkFilterExpression(logTo: MalloyElement, ft: FilterExprType, fexpr: Expr): void;
@@ -207,7 +207,7 @@ class ExprDuration extends ExpressionDef {
207
207
  }
208
208
  exports.ExprDuration = ExprDuration;
209
209
  function willMorphTo(ev, t) {
210
- if (ev.type === t) {
210
+ if (ev.type === t || (t === 'timestamp' && ev.type === 'timestamptz')) {
211
211
  return ev.value;
212
212
  }
213
213
  return ev.morphic && ev.morphic[t];
@@ -542,7 +542,7 @@ function checkFilterExpression(logTo, ft, fexpr) {
542
542
  }
543
543
  const fsrc = fexpr.filterSrc;
544
544
  let err;
545
- if (ft === 'date' || ft === 'timestamp') {
545
+ if ((0, malloy_types_1.isTemporalType)(ft)) {
546
546
  err = (_a = malloy_filter_1.TemporalFilterExpression.parse(fsrc).log[0]) === null || _a === void 0 ? void 0 : _a.message;
547
547
  }
548
548
  else if (ft === 'string') {
@@ -66,6 +66,11 @@ export declare class QueryQuery extends QueryField {
66
66
  generateSQLJoinBlock(stageWriter: StageWriter, ji: JoinInstance, depth: number): string;
67
67
  generateSQLPassthroughKeys(qs: QueryStruct): string;
68
68
  generateSQLJoins(stageWriter: StageWriter): string;
69
+ /**
70
+ * Collect all array joins from the join tree in depth-first order.
71
+ * This ordering ensures parent arrays are ordered before child arrays.
72
+ */
73
+ collectArrayJoins(ji: JoinInstance): JoinInstance[];
69
74
  genereateSQLOrderBy(queryDef: QuerySegment, resultStruct: FieldInstanceResult): string;
70
75
  generateSimpleSQL(stageWriter: StageWriter): string;
71
76
  generatePipelinedStages(outputPipelinedSQL: OutputPipelinedSQL[], lastStageName: string, stageWriter: StageWriter): string;
@@ -737,10 +737,37 @@ class QueryQuery extends query_node_1.QueryField {
737
737
  }
738
738
  return s;
739
739
  }
740
+ /**
741
+ * Collect all array joins from the join tree in depth-first order.
742
+ * This ordering ensures parent arrays are ordered before child arrays.
743
+ */
744
+ collectArrayJoins(ji) {
745
+ const result = [];
746
+ if (ji.queryStruct.structDef.type === 'array') {
747
+ result.push(ji);
748
+ }
749
+ for (const child of ji.children) {
750
+ result.push(...this.collectArrayJoins(child));
751
+ }
752
+ return result;
753
+ }
740
754
  genereateSQLOrderBy(queryDef, resultStruct) {
741
755
  let s = '';
756
+ // Collect array joins for dialects that need explicit ordering.
757
+ // Only for project queries - reduce queries aggregate rows so individual
758
+ // row order doesn't matter, and we can't ORDER BY columns not in GROUP BY.
759
+ let arrayJoins = [];
760
+ if (this.parent.dialect.requiresExplicitUnnestOrdering &&
761
+ this.firstSegment.type === 'project') {
762
+ const [[, rootJoin]] = this.rootResult.joins;
763
+ arrayJoins = this.collectArrayJoins(rootJoin);
764
+ }
742
765
  if (this.firstSegment.type === 'project' && !queryDef.orderBy) {
743
- return ''; // No default ordering for project.
766
+ // For project without explicit ordering, we still need array ordinality
767
+ // ordering if the dialect requires it
768
+ if (arrayJoins.length === 0) {
769
+ return ''; // No default ordering for project.
770
+ }
744
771
  }
745
772
  // Intermediate results (in a pipeline or join) that have no limit, don't need an orderby
746
773
  // Some database don't have this optimization.
@@ -795,6 +822,10 @@ class QueryQuery extends query_node_1.QueryField {
795
822
  }
796
823
  }
797
824
  }
825
+ // Add array ordinality ordering for dialects that require it
826
+ for (const aj of arrayJoins) {
827
+ o.push(`${aj.alias}_outer.__row_id ASC`);
828
+ }
798
829
  if (o.length > 0) {
799
830
  s = this.parent.dialect.sqlOrderBy(o, 'query') + '\n';
800
831
  }
@@ -27,9 +27,11 @@ function cellToValue(cell, fieldInfo) {
27
27
  case 'string_cell':
28
28
  return cell.string_value;
29
29
  case 'number_cell':
30
+ // Return BigInt for bigint values (when string_value is present for precision)
31
+ if (cell.string_value !== undefined) {
32
+ return BigInt(cell.string_value);
33
+ }
30
34
  return cell.number_value;
31
- case 'big_number_cell':
32
- return BigInt(cell.number_value);
33
35
  case 'boolean_cell':
34
36
  return cell.boolean_value;
35
37
  case 'date_cell':
@@ -159,9 +159,11 @@ function cellToValue(cell) {
159
159
  case 'string_cell':
160
160
  return cell.string_value;
161
161
  case 'number_cell':
162
+ // Return BigInt for bigint values (when string_value is present)
163
+ if (cell.string_value !== undefined) {
164
+ return BigInt(cell.string_value);
165
+ }
162
166
  return cell.number_value;
163
- case 'big_number_cell':
164
- return BigInt(cell.number_value);
165
167
  case 'boolean_cell':
166
168
  return cell.boolean_value;
167
169
  case 'date_cell':
@@ -276,6 +278,18 @@ function matchCell(cell, fieldInfo, expected, path, dialect, mode) {
276
278
  }
277
279
  // Handle number cells
278
280
  if (cell.kind === 'number_cell') {
281
+ // For bigint values, use string_value for precision
282
+ if (cell.string_value !== undefined) {
283
+ const actual = BigInt(cell.string_value);
284
+ if (looseEqual(actual, expected)) {
285
+ return { pass: true };
286
+ }
287
+ // Also allow matching against string representation of the number
288
+ if (typeof expected === 'string' && expected === cell.string_value) {
289
+ return { pass: true };
290
+ }
291
+ return matchFail(path, expected, actual);
292
+ }
279
293
  let actual = cell.number_value;
280
294
  // Handle simulated booleans (MySQL returns 1/0 for booleans)
281
295
  if (typeof expected === 'boolean' &&
@@ -288,18 +302,6 @@ function matchCell(cell, fieldInfo, expected, path, dialect, mode) {
288
302
  }
289
303
  return matchFail(path, expected, actual);
290
304
  }
291
- // Handle big number cells
292
- if (cell.kind === 'big_number_cell') {
293
- const actual = BigInt(cell.number_value);
294
- if (looseEqual(actual, expected)) {
295
- return { pass: true };
296
- }
297
- // Also allow matching against string representation of the number
298
- if (typeof expected === 'string' && expected === cell.number_value) {
299
- return { pass: true };
300
- }
301
- return matchFail(path, expected, actual);
302
- }
303
305
  // Handle JSON cells
304
306
  if (cell.kind === 'json_cell') {
305
307
  const actual = JSON.parse(cell.json_value);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.330";
1
+ export declare const MALLOY_VERSION = "0.0.332";
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.330';
5
+ exports.MALLOY_VERSION = '0.0.332';
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.330",
3
+ "version": "0.0.332",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -45,9 +45,9 @@
45
45
  "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"
46
46
  },
47
47
  "dependencies": {
48
- "@malloydata/malloy-filter": "0.0.330",
49
- "@malloydata/malloy-interfaces": "0.0.330",
50
- "@malloydata/malloy-tag": "0.0.330",
48
+ "@malloydata/malloy-filter": "0.0.332",
49
+ "@malloydata/malloy-interfaces": "0.0.332",
50
+ "@malloydata/malloy-tag": "0.0.332",
51
51
  "antlr4ts": "^0.5.0-alpha.4",
52
52
  "assert": "^2.0.0",
53
53
  "jaro-winkler": "^0.2.8",