@malloydata/malloy 0.0.331 → 0.0.333

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.
@@ -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') {
package/dist/malloy.d.ts CHANGED
@@ -484,6 +484,11 @@ export declare class PreparedResult implements Taggable {
484
484
  get _sourceExploreName(): string;
485
485
  get _sourceArguments(): Record<string, Argument> | undefined;
486
486
  get _sourceFilters(): FilterCondition[];
487
+ /**
488
+ * @return Whether this result has a schema. DDL statements (INSTALL, LOAD,
489
+ * CREATE SECRET, etc.) do not return a schema.
490
+ */
491
+ get hasSchema(): boolean;
487
492
  }
488
493
  /**
489
494
  * A URL reader which always throws an error when a URL's contents is requested.
package/dist/malloy.js CHANGED
@@ -889,6 +889,13 @@ class PreparedResult {
889
889
  get _sourceFilters() {
890
890
  return this.inner.sourceFilters || [];
891
891
  }
892
+ /**
893
+ * @return Whether this result has a schema. DDL statements (INSTALL, LOAD,
894
+ * CREATE SECRET, etc.) do not return a schema.
895
+ */
896
+ get hasSchema() {
897
+ return this.inner.structs.length > 0;
898
+ }
892
899
  }
893
900
  exports.PreparedResult = PreparedResult;
894
901
  /**
@@ -2432,6 +2439,14 @@ class Result extends PreparedResult {
2432
2439
  return this.inner.profilingUrl;
2433
2440
  }
2434
2441
  toJSON() {
2442
+ // DDL statements (INSTALL, LOAD, CREATE SECRET, etc.) don't have a schema,
2443
+ // so we can't call this.data.toJSON() which requires resultExplore.
2444
+ if (!this.hasSchema) {
2445
+ return {
2446
+ queryResult: this.inner,
2447
+ modelDef: this._modelDef,
2448
+ };
2449
+ }
2435
2450
  // The result rows are converted to JSON separately because they
2436
2451
  // may contain un-serializable data types.
2437
2452
  return {
@@ -280,6 +280,7 @@ function generateAppliedFilter(context, filterMatchExpr, qi) {
280
280
  break;
281
281
  case 'date':
282
282
  case 'timestamp':
283
+ case 'timestamptz':
283
284
  fParse = malloy_filter_1.TemporalFilterExpression.parse(filterSrc);
284
285
  break;
285
286
  default:
@@ -6,7 +6,7 @@ export declare const FilterCompilers: {
6
6
  numberCompile(nc: NumberFilter, x: string, d: Dialect): string;
7
7
  booleanCompile(bc: BooleanFilter, x: string, d: Dialect): string;
8
8
  stringCompile(sc: StringFilter, x: string, d: Dialect): string;
9
- temporalCompile(tc: TemporalFilter, x: string, d: Dialect, t: "date" | "timestamp", qi?: QueryInfo): string;
9
+ temporalCompile(tc: TemporalFilter, x: string, d: Dialect, t: "date" | "timestamp" | "timestamptz", qi?: QueryInfo): string;
10
10
  };
11
11
  /**
12
12
  * I felt like there was enough "helpful functions needed to make everything
@@ -16,10 +16,10 @@ export declare const FilterCompilers: {
16
16
  */
17
17
  export declare class TemporalFilterCompiler {
18
18
  readonly expr: string;
19
- readonly timetype: 'timestamp' | 'date';
19
+ readonly timetype: 'timestamp' | 'timestamptz' | 'date';
20
20
  readonly d: Dialect;
21
21
  readonly qi: QueryInfo;
22
- constructor(expr: string, dialect: Dialect, timetype?: 'timestamp' | 'date', queryInfo?: QueryInfo);
22
+ constructor(expr: string, dialect: Dialect, timetype?: 'timestamp' | 'timestamptz' | 'date', queryInfo?: QueryInfo);
23
23
  time(timeSQL: string): string;
24
24
  compile(tc: TemporalFilter): string;
25
25
  private expandLiteral;
@@ -47,7 +47,7 @@ exports.FilterCompilers = {
47
47
  else if (t === 'boolean' && (0, malloy_filter_1.isBooleanFilter)(c)) {
48
48
  return exports.FilterCompilers.booleanCompile(c, x, d);
49
49
  }
50
- else if ((t === 'date' || t === 'timestamp') && (0, malloy_filter_1.isTemporalFilter)(c)) {
50
+ else if ((0, malloy_types_1.isTemporalType)(t) && (0, malloy_filter_1.isTemporalFilter)(c)) {
51
51
  return exports.FilterCompilers.temporalCompile(c, x, d, t, qi);
52
52
  }
53
53
  throw new Error('INTERNAL ERROR: No filter compiler for ' + t);
@@ -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
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.331";
1
+ export declare const MALLOY_VERSION = "0.0.333";
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.331';
5
+ exports.MALLOY_VERSION = '0.0.333';
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.331",
3
+ "version": "0.0.333",
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.331",
49
- "@malloydata/malloy-interfaces": "0.0.331",
50
- "@malloydata/malloy-tag": "0.0.331",
48
+ "@malloydata/malloy-filter": "0.0.333",
49
+ "@malloydata/malloy-interfaces": "0.0.333",
50
+ "@malloydata/malloy-tag": "0.0.333",
51
51
  "antlr4ts": "^0.5.0-alpha.4",
52
52
  "assert": "^2.0.0",
53
53
  "jaro-winkler": "^0.2.8",