@malloydata/malloy 0.0.34-dev230523182411 → 0.0.34

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.
Files changed (49) hide show
  1. package/dist/dialect/dialect.d.ts +20 -5
  2. package/dist/dialect/dialect.js +40 -7
  3. package/dist/dialect/duckdb.d.ts +5 -5
  4. package/dist/dialect/duckdb.js +54 -120
  5. package/dist/dialect/postgres.d.ts +5 -5
  6. package/dist/dialect/postgres.js +50 -38
  7. package/dist/dialect/standardsql.d.ts +5 -5
  8. package/dist/dialect/standardsql.js +78 -42
  9. package/dist/lang/ast/elements/refined-source.js +8 -0
  10. package/dist/lang/ast/executors/reduce-executor.js +4 -0
  11. package/dist/lang/ast/expressions/expr-cast.js +1 -1
  12. package/dist/lang/ast/expressions/expr-granular-time.d.ts +2 -2
  13. package/dist/lang/ast/expressions/expr-granular-time.js +10 -19
  14. package/dist/lang/ast/expressions/expr-time-extract.js +31 -4
  15. package/dist/lang/ast/expressions/time-literal.d.ts +12 -6
  16. package/dist/lang/ast/expressions/time-literal.js +74 -67
  17. package/dist/lang/ast/field-space/dynamic-space.d.ts +2 -0
  18. package/dist/lang/ast/field-space/dynamic-space.js +6 -0
  19. package/dist/lang/ast/field-space/query-spaces.js +3 -0
  20. package/dist/lang/ast/index.d.ts +1 -0
  21. package/dist/lang/ast/index.js +1 -0
  22. package/dist/lang/ast/source-properties/timezone-statement.d.ts +6 -0
  23. package/dist/lang/ast/source-properties/timezone-statement.js +35 -0
  24. package/dist/lang/ast/sources/named-source.js +1 -1
  25. package/dist/lang/ast/sources/sql-source.js +4 -0
  26. package/dist/lang/ast/time-utils.d.ts +2 -1
  27. package/dist/lang/ast/time-utils.js +5 -1
  28. package/dist/lang/ast/types/expression-def.d.ts +2 -0
  29. package/dist/lang/ast/types/expression-def.js +48 -18
  30. package/dist/lang/ast/types/query-property.d.ts +2 -1
  31. package/dist/lang/ast/types/query-property.js +3 -1
  32. package/dist/lang/ast/types/source-property.d.ts +2 -1
  33. package/dist/lang/ast/types/source-property.js +3 -1
  34. package/dist/lang/lib/Malloy/MalloyLexer.d.ts +3 -3
  35. package/dist/lang/lib/Malloy/MalloyLexer.js +958 -972
  36. package/dist/lang/lib/Malloy/MalloyParser.d.ts +52 -33
  37. package/dist/lang/lib/Malloy/MalloyParser.js +1243 -1128
  38. package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +24 -0
  39. package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +15 -0
  40. package/dist/lang/malloy-to-ast.d.ts +2 -0
  41. package/dist/lang/malloy-to-ast.js +6 -0
  42. package/dist/lang/test/parse-expects.d.ts +1 -0
  43. package/dist/lang/test/parse-expects.js +11 -1
  44. package/dist/lang/test/parse.spec.js +29 -37
  45. package/dist/malloy.js +31 -23
  46. package/dist/model/malloy_query.d.ts +2 -0
  47. package/dist/model/malloy_query.js +42 -41
  48. package/dist/model/malloy_types.d.ts +3 -2
  49. package/package.json +1 -1
@@ -63,6 +63,7 @@ import { DefExplorePrimaryKeyContext } from "./MalloyParser";
63
63
  import { DefExploreRenameContext } from "./MalloyParser";
64
64
  import { DefExploreEditFieldContext } from "./MalloyParser";
65
65
  import { DefExploreQueryContext } from "./MalloyParser";
66
+ import { DefExploreTimezoneContext } from "./MalloyParser";
66
67
  import { FilterByShortcutContext } from "./MalloyParser";
67
68
  import { FilterByWhereContext } from "./MalloyParser";
68
69
  import { JoinWithContext } from "./MalloyParser";
@@ -141,6 +142,7 @@ import { IndexElementContext } from "./MalloyParser";
141
142
  import { IndexFieldsContext } from "./MalloyParser";
142
143
  import { IndexStatementContext } from "./MalloyParser";
143
144
  import { SampleStatementContext } from "./MalloyParser";
145
+ import { TimezoneStatementContext } from "./MalloyParser";
144
146
  import { SampleSpecContext } from "./MalloyParser";
145
147
  import { AggregateContext } from "./MalloyParser";
146
148
  import { MalloyTypeContext } from "./MalloyParser";
@@ -944,6 +946,18 @@ export interface MalloyParserListener extends ParseTreeListener {
944
946
  * @param ctx the parse tree
945
947
  */
946
948
  exitDefExploreQuery?: (ctx: DefExploreQueryContext) => void;
949
+ /**
950
+ * Enter a parse tree produced by the `defExploreTimezone`
951
+ * labeled alternative in `MalloyParser.exploreStatement`.
952
+ * @param ctx the parse tree
953
+ */
954
+ enterDefExploreTimezone?: (ctx: DefExploreTimezoneContext) => void;
955
+ /**
956
+ * Exit a parse tree produced by the `defExploreTimezone`
957
+ * labeled alternative in `MalloyParser.exploreStatement`.
958
+ * @param ctx the parse tree
959
+ */
960
+ exitDefExploreTimezone?: (ctx: DefExploreTimezoneContext) => void;
947
961
  /**
948
962
  * Enter a parse tree produced by the `filterByShortcut`
949
963
  * labeled alternative in `MalloyParser.filteredBy`.
@@ -1732,6 +1746,16 @@ export interface MalloyParserListener extends ParseTreeListener {
1732
1746
  * @param ctx the parse tree
1733
1747
  */
1734
1748
  exitSampleStatement?: (ctx: SampleStatementContext) => void;
1749
+ /**
1750
+ * Enter a parse tree produced by `MalloyParser.timezoneStatement`.
1751
+ * @param ctx the parse tree
1752
+ */
1753
+ enterTimezoneStatement?: (ctx: TimezoneStatementContext) => void;
1754
+ /**
1755
+ * Exit a parse tree produced by `MalloyParser.timezoneStatement`.
1756
+ * @param ctx the parse tree
1757
+ */
1758
+ exitTimezoneStatement?: (ctx: TimezoneStatementContext) => void;
1735
1759
  /**
1736
1760
  * Enter a parse tree produced by `MalloyParser.sampleSpec`.
1737
1761
  * @param ctx the parse tree
@@ -63,6 +63,7 @@ import { DefExplorePrimaryKeyContext } from "./MalloyParser";
63
63
  import { DefExploreRenameContext } from "./MalloyParser";
64
64
  import { DefExploreEditFieldContext } from "./MalloyParser";
65
65
  import { DefExploreQueryContext } from "./MalloyParser";
66
+ import { DefExploreTimezoneContext } from "./MalloyParser";
66
67
  import { FilterByShortcutContext } from "./MalloyParser";
67
68
  import { FilterByWhereContext } from "./MalloyParser";
68
69
  import { JoinWithContext } from "./MalloyParser";
@@ -141,6 +142,7 @@ import { IndexElementContext } from "./MalloyParser";
141
142
  import { IndexFieldsContext } from "./MalloyParser";
142
143
  import { IndexStatementContext } from "./MalloyParser";
143
144
  import { SampleStatementContext } from "./MalloyParser";
145
+ import { TimezoneStatementContext } from "./MalloyParser";
144
146
  import { SampleSpecContext } from "./MalloyParser";
145
147
  import { AggregateContext } from "./MalloyParser";
146
148
  import { MalloyTypeContext } from "./MalloyParser";
@@ -627,6 +629,13 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
627
629
  * @return the visitor result
628
630
  */
629
631
  visitDefExploreQuery?: (ctx: DefExploreQueryContext) => Result;
632
+ /**
633
+ * Visit a parse tree produced by the `defExploreTimezone`
634
+ * labeled alternative in `MalloyParser.exploreStatement`.
635
+ * @param ctx the parse tree
636
+ * @return the visitor result
637
+ */
638
+ visitDefExploreTimezone?: (ctx: DefExploreTimezoneContext) => Result;
630
639
  /**
631
640
  * Visit a parse tree produced by the `filterByShortcut`
632
641
  * labeled alternative in `MalloyParser.filteredBy`.
@@ -1099,6 +1108,12 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
1099
1108
  * @return the visitor result
1100
1109
  */
1101
1110
  visitSampleStatement?: (ctx: SampleStatementContext) => Result;
1111
+ /**
1112
+ * Visit a parse tree produced by `MalloyParser.timezoneStatement`.
1113
+ * @param ctx the parse tree
1114
+ * @return the visitor result
1115
+ */
1116
+ visitTimezoneStatement?: (ctx: TimezoneStatementContext) => Result;
1102
1117
  /**
1103
1118
  * Visit a parse tree produced by `MalloyParser.sampleSpec`.
1104
1119
  * @param ctx the parse tree
@@ -88,6 +88,8 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
88
88
  visitDefExplorePrimaryKey(pcx: parse.DefExplorePrimaryKeyContext): ast.PrimaryKey;
89
89
  visitFieldNameList(pcx: parse.FieldNameListContext): ast.FieldReferences;
90
90
  visitDefExploreEditField(pcx: parse.DefExploreEditFieldContext): ast.FieldListEdit;
91
+ visitDefExploreTimezone(cx: parse.DefExploreTimezoneContext): ast.TimezoneStatement;
92
+ visitTimezoneStatement(cx: parse.TimezoneStatementContext): ast.TimezoneStatement;
91
93
  visitQueryProperties(pcx: parse.QueryPropertiesContext): ast.QOPDesc;
92
94
  visitFieldPath(pcx: parse.FieldPathContext): ast.FieldReference;
93
95
  visitQueryFieldDef(pcx: parse.QueryFieldDefContext): ast.QueryItem;
@@ -468,6 +468,12 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
468
468
  const action = pcx.ACCEPT() ? 'accept' : 'except';
469
469
  return new ast.FieldListEdit(action, this.visitFieldNameList(pcx.fieldNameList()));
470
470
  }
471
+ visitDefExploreTimezone(cx) {
472
+ return this.visitTimezoneStatement(cx.timezoneStatement());
473
+ }
474
+ visitTimezoneStatement(cx) {
475
+ return this.astAt(new ast.TimezoneStatement(this.stripQuotes(cx.STRING_LITERAL().text)), cx);
476
+ }
471
477
  visitQueryProperties(pcx) {
472
478
  const qProps = pcx
473
479
  .queryStatement()
@@ -6,6 +6,7 @@ declare global {
6
6
  toBeErrorless(): R;
7
7
  toCompile(): R;
8
8
  modelCompiled(): R;
9
+ expressionCompiled(): R;
9
10
  toReturnType(tp: string): R;
10
11
  compileToFailWith(...expectedErrors: string[]): R;
11
12
  isLocationIn(at: DocumentLocation, txt: string): R;
@@ -116,6 +116,15 @@ expect.extend({
116
116
  x.translate();
117
117
  return checkForNeededs(x);
118
118
  },
119
+ expressionCompiled: function (src) {
120
+ const x = new test_translator_1.BetaExpression(src);
121
+ x.compile();
122
+ const errorCheck = checkForErrors(x);
123
+ if (!errorCheck.pass) {
124
+ return errorCheck;
125
+ }
126
+ return checkForNeededs(x);
127
+ },
119
128
  toBeErrorless: function (trans) {
120
129
  return checkForErrors(trans);
121
130
  },
@@ -135,7 +144,7 @@ expect.extend({
135
144
  },
136
145
  compileToFailWith: function (s, ...msgs) {
137
146
  var _a;
138
- let emsg = 'Compile Error expectation not met\nExpected error';
147
+ let emsg = 'Compile Error expectation not met\nExpected message';
139
148
  let mSrc;
140
149
  const qmsgs = msgs.map(s => `error '${s}'`);
141
150
  if (msgs.length === 1) {
@@ -161,6 +170,7 @@ expect.extend({
161
170
  m = new test_translator_1.TestTranslator(src);
162
171
  }
163
172
  emsg += `\nSource:\n${src}`;
173
+ m.compile();
164
174
  const t = m.translate();
165
175
  if (t.translated) {
166
176
  return { pass: false, message: () => emsg };
@@ -533,7 +533,6 @@ describe('literals', () => {
533
533
  ['@1960-Q2', 'date', 'quarter', { literal: '1960-04-01' }],
534
534
  ['@1960-06', 'date', 'month', { literal: '1960-06-01' }],
535
535
  ['@1960-06-26-WK', 'date', 'week', { literal: '1960-06-26' }],
536
- ['@1960-06-30-WK', 'date', 'week', { literal: '1960-06-26' }],
537
536
  ['@1960-06-30', 'date', 'day', { literal: '1960-06-30' }],
538
537
  ['@1960-06-30 10', 'timestamp', 'hour', { literal: '1960-06-30 10:00:00' }],
539
538
  [
@@ -560,24 +559,6 @@ describe('literals', () => {
560
559
  undefined,
561
560
  { literal: '1960-06-30 10:30:00' },
562
561
  ],
563
- [
564
- '@1960-06-30 10:30:00+0',
565
- 'timestamp',
566
- undefined,
567
- { literal: '1960-06-30 10:30:00', timezone: '+0' },
568
- ],
569
- [
570
- '@1960-06-30 10:30:00-7',
571
- 'timestamp',
572
- undefined,
573
- { literal: '1960-06-30 10:30:00', timezone: '-7' },
574
- ],
575
- [
576
- '@1960-06-30 10:30:00-00:15',
577
- 'timestamp',
578
- undefined,
579
- { literal: '1960-06-30 10:30:00', timezone: '-00:15' },
580
- ],
581
562
  [
582
563
  '@1960-06-30 10:30:00[America/Los_Angeles]',
583
564
  'timestamp',
@@ -585,7 +566,6 @@ describe('literals', () => {
585
566
  {
586
567
  literal: '1960-06-30 10:30:00',
587
568
  timezone: 'America/Los_Angeles',
588
- tzIsLocale: true,
589
569
  },
590
570
  ],
591
571
  ];
@@ -609,7 +589,7 @@ describe('literals', () => {
609
589
  ['@1960', '1960-01-01 00:00:00'],
610
590
  ['@1960-Q2', '1960-04-01 00:00:00'],
611
591
  ['@1960-06', '1960-06-01 00:00:00'],
612
- ['@1960-06-30-Wk', '1960-06-26 00:00:00'],
592
+ ['@1960-06-26-Wk', '1960-06-26 00:00:00'],
613
593
  ['@1960-06-30', '1960-06-30 00:00:00'],
614
594
  ['@1960-06-30 00:00', undefined],
615
595
  ];
@@ -648,16 +628,16 @@ describe('expressions', () => {
648
628
  'quarter',
649
629
  'year',
650
630
  ];
651
- describe('timestamp truncation', () => {
652
- for (const unit of timeframes) {
653
- test(`timestamp truncate ${unit}`, exprOK(`ats.${unit}`));
654
- }
631
+ test.each(timeframes.map(x => [x]))('truncate %s', unit => {
632
+ expect(new test_translator_1.BetaExpression(`ats.${unit}`)).modelParsed();
655
633
  });
656
- describe('timestamp difference', () => {
657
- for (const unit of timeframes) {
658
- // TODO expect these to error ...
659
- test(`timestamp extract ${unit}`, exprOK(`${unit}(@2021 to ats)`));
660
- }
634
+ // mtoy todo units missing: implement, or document
635
+ const diffable = ['second', 'minute', 'hour', 'day'];
636
+ test.each(diffable.map(x => [x]))('timestamp difference - %s', unit => {
637
+ expect(new test_translator_1.BetaExpression(`${unit}(@2021 to ats)`)).modelParsed();
638
+ });
639
+ test.each(diffable.map(x => [x]))('timestamp difference - %s', unit => {
640
+ expect(new test_translator_1.BetaExpression(`${unit}(ats to @2030)`)).modelParsed();
661
641
  });
662
642
  });
663
643
  test('field name', exprOK('astr'));
@@ -681,6 +661,25 @@ describe('expressions', () => {
681
661
  test('not', exprOK('not true'));
682
662
  test('and', exprOK('true and false'));
683
663
  test('or', exprOK('true or false'));
664
+ test('disallow date OP number', () => {
665
+ expect(new test_translator_1.BetaExpression('@2001 = 7')).compileToFailWith('Cannot compare a date to a number');
666
+ });
667
+ test('disallow date OP timestamp', () => {
668
+ expect(new test_translator_1.BetaExpression('ad = ats')).compileToFailWith('Cannot compare a date to a timestamp');
669
+ });
670
+ test('disallow interval from date to timestamp', () => {
671
+ expect(new test_translator_1.BetaExpression('days(ad to ats)')).compileToFailWith('Cannot measure from date to timestamp');
672
+ });
673
+ test('comparison promotes date literal to timestamp', () => {
674
+ expect('@2001 = ats').expressionCompiled();
675
+ });
676
+ test('can apply range to date', () => {
677
+ expect('ad ? @2001 for 1 day').expressionCompiled();
678
+ });
679
+ const noOffset = ['second', 'minute', 'hour'];
680
+ test.each(noOffset.map(x => [x]))('disallow date delta %s', unit => {
681
+ expect(new test_translator_1.BetaExpression(`ad + 10 ${unit}s`)).compileToFailWith(`Cannot offset date by ${unit}`);
682
+ });
684
683
  });
685
684
  test('filtered measure', exprOK("acount {? astr = 'why?' }"));
686
685
  describe('aggregate forms', () => {
@@ -877,13 +876,6 @@ describe('error handling', () => {
877
876
  test('query without fields', badModel('query: a -> { top: 5 }', "Can't determine query type (group_by/aggregate/nest,project,index)"));
878
877
  test("refine can't change query type", badModel('query: ab -> aturtle { project: astr }', 'project: not legal in grouping query'));
879
878
  test('undefined field ref in query', badModel('query: ab -> { aggregate: xyzzy }', "'xyzzy' is not defined"));
880
- // test("queries with anonymous expressions", () => {
881
- // const m = new TestTranslator("query: a->{\n group_by: a+1\n}");
882
- // expect(m).not.modelParsed();
883
- // const errList = m.errors().errors;
884
- // const firstError = errList[0];
885
- // expect(firstError.message).toBe("Expressions in queries must have names");
886
- // });
887
879
  test('query on source with errors', () => {
888
880
  expect((0, test_translator_1.markSource) `
889
881
  explore: na is a { join_one: ${'n'} on astr }
package/dist/malloy.js CHANGED
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.CSVWriter = exports.JSONWriter = exports.DataWriter = exports.DataRecord = exports.DataArray = exports.Result = exports.ExploreMaterializer = exports.SQLBlockMaterializer = exports.PreparedResultMaterializer = exports.QueryMaterializer = exports.ModelMaterializer = exports.SingleConnectionRuntime = exports.ConnectionRuntime = exports.Runtime = exports.ExploreField = exports.JoinRelationship = exports.QueryField = exports.Query = exports.StringField = exports.UnsupportedField = exports.JSONField = exports.BooleanField = exports.NumberField = exports.TimestampField = exports.DateField = exports.TimestampTimeframe = exports.DateTimeframe = exports.AtomicField = exports.AtomicFieldType = exports.Explore = exports.SourceRelationship = exports.FixedConnectionMap = exports.InMemoryURLReader = exports.EmptyURLReader = exports.PreparedResult = exports.DocumentCompletion = exports.DocumentSymbol = exports.DocumentPosition = exports.DocumentRange = exports.DocumentHighlight = exports.Parse = exports.parseTableURI = exports.PreparedQuery = exports.Model = exports.MalloyError = exports.Malloy = void 0;
26
26
  const lang_1 = require("./lang");
27
27
  const model_1 = require("./model");
28
+ const luxon_1 = require("luxon");
28
29
  class Malloy {
29
30
  // TODO load from file built during release
30
31
  static get version() {
@@ -2194,25 +2195,41 @@ class DataNumber extends ScalarData {
2194
2195
  return this._field;
2195
2196
  }
2196
2197
  }
2198
+ function valueToDate(value) {
2199
+ // TODO properly map the data from BQ/Postgres types
2200
+ if (value instanceof Date) {
2201
+ return value;
2202
+ }
2203
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2204
+ const valAsAny = value;
2205
+ if (valAsAny.constructor.name === 'Date') {
2206
+ // For some reason duckdb TSTZ values come back as objects which do not
2207
+ // pass "instance of" but do seem date like.
2208
+ return new Date(value);
2209
+ }
2210
+ else if (typeof value === 'number') {
2211
+ return new Date(value);
2212
+ }
2213
+ else if (typeof value !== 'string') {
2214
+ return new Date(value.value);
2215
+ }
2216
+ else {
2217
+ // Postgres timestamps end up here, and ideally we would know the system
2218
+ // timezone of the postgres instance to correctly create a Date() object
2219
+ // which represents the same instant in time, but we don't have the data
2220
+ // flow to implement that. This may be a problem at some future day,
2221
+ // so here is a comment, for that day.
2222
+ const parsed = luxon_1.DateTime.fromISO(value, { zone: 'UTC' });
2223
+ return parsed.toJSDate();
2224
+ }
2225
+ }
2197
2226
  class DataTimestamp extends ScalarData {
2198
2227
  constructor(value, field) {
2199
2228
  super(value, field);
2200
2229
  this._field = field;
2201
2230
  }
2202
2231
  get value() {
2203
- // TODO properly map the data from BQ/Postgres types
2204
- if (this._value instanceof Date) {
2205
- return this._value;
2206
- }
2207
- else if (typeof this._value === 'number') {
2208
- return new Date(this._value);
2209
- }
2210
- else if (typeof this._value !== 'string') {
2211
- return new Date(this._value.value);
2212
- }
2213
- else {
2214
- return new Date(this._value);
2215
- }
2232
+ return valueToDate(this._value);
2216
2233
  }
2217
2234
  get field() {
2218
2235
  return this._field;
@@ -2224,16 +2241,7 @@ class DataDate extends ScalarData {
2224
2241
  this._field = field;
2225
2242
  }
2226
2243
  get value() {
2227
- // TODO properly map the data from BQ/Postgres types
2228
- if (this._value instanceof Date) {
2229
- return this._value;
2230
- }
2231
- else if (typeof this._value !== 'string') {
2232
- return new Date(this._value.value);
2233
- }
2234
- else {
2235
- return new Date(this._value);
2236
- }
2244
+ return valueToDate(this._value);
2237
2245
  }
2238
2246
  get field() {
2239
2247
  return this._field;
@@ -2,6 +2,7 @@ import { Dialect, DialectFieldList } from '../dialect';
2
2
  import { AggregateFragment, AnalyticFragment, CompiledQuery, DialectFragment, Expr, FieldDef, FieldFragment, FieldRef, Filtered, FilterExpression, FilterFragment, JoinRelationship, ModelDef, NamedQuery, OrderBy, Parameter, ParameterFragment, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, StructDef, StructRef, TurtleDef, UngroupFragment } from './malloy_types';
3
3
  import { Connection } from '../runtime_types';
4
4
  import { AndChain } from './utils';
5
+ import { QueryInfo } from '../dialect/dialect';
5
6
  interface TurtleDefPlus extends TurtleDef, Filtered {
6
7
  }
7
8
  /** Parent from QueryStruct. */
@@ -134,6 +135,7 @@ declare class FieldInstanceResult implements FieldInstance {
134
135
  ungroupedSets: Map<string, UngroupSet>;
135
136
  resultUsesUngrouped: boolean;
136
137
  constructor(turtleDef: TurtleDef, parent: FieldInstanceResult | undefined);
138
+ getQueryInfo(): QueryInfo;
137
139
  addField(as: string, field: QueryField, usage: FieldUsage): void;
138
140
  parentGroupSet(): number;
139
141
  add(name: string, f: FieldInstance): void;
@@ -349,7 +349,7 @@ class QueryField extends QueryNode {
349
349
  }
350
350
  }
351
351
  generateDialect(resultSet, context, expr, state) {
352
- return this.generateExpressionFromExpr(resultSet, context, context.dialect.dialectExpr(expr), state);
352
+ return this.generateExpressionFromExpr(resultSet, context, context.dialect.dialectExpr(resultSet.getQueryInfo(), expr), state);
353
353
  }
354
354
  generateAnalyticFragment(resultStruct, context, expr, state) {
355
355
  const fields = resultStruct.getUngroupPartitions(undefined);
@@ -559,7 +559,7 @@ class QueryFieldDate extends QueryAtomicField {
559
559
  return super.generateExpression(resultSet);
560
560
  }
561
561
  else {
562
- const truncated = this.parent.dialect.sqlTrunc({ value: this.getExpr(), valueType: 'date' }, fd.timeframe);
562
+ const truncated = this.parent.dialect.sqlTrunc(resultSet.getQueryInfo(), { value: this.getExpr(), valueType: 'date' }, fd.timeframe);
563
563
  return this.generateExpressionFromExpr(resultSet, this.parent, truncated);
564
564
  }
565
565
  }
@@ -666,9 +666,18 @@ class FieldInstanceResult {
666
666
  this.turtleDef = turtleDef;
667
667
  this.firstSegment = turtleDef.pipeline[0];
668
668
  }
669
+ getQueryInfo() {
670
+ if (!(0, malloy_types_1.isIndexSegment)(this.firstSegment)) {
671
+ const { queryTimezone } = this.firstSegment;
672
+ if (queryTimezone) {
673
+ return { queryTimezone };
674
+ }
675
+ }
676
+ return {};
677
+ }
669
678
  addField(as, field, usage) {
670
- let fi;
671
- if ((fi = this.allFields.get(as))) {
679
+ const fi = this.allFields.get(as);
680
+ if (fi) {
672
681
  if (fi.type === 'query') {
673
682
  throw new Error(`Redefinition of field ${field.fieldDef.name} as struct`);
674
683
  }
@@ -1085,7 +1094,6 @@ class QueryTurtle extends QueryField {
1085
1094
  * half translated to the new world of types ..
1086
1095
  */
1087
1096
  class Segment {
1088
- // static nextStructDef(s: StructDef, q: AnonymousQueryDef): StructDef
1089
1097
  static nextStructDef(structDef, segment) {
1090
1098
  const qs = new QueryStruct(structDef, {
1091
1099
  model: new QueryModel(undefined),
@@ -1118,6 +1126,7 @@ class QueryQuery extends QueryField {
1118
1126
  let flatTurtleDef = parentStruct.flattenTurtleDef(fieldDef);
1119
1127
  let parent = parentStruct;
1120
1128
  const firstStage = flatTurtleDef.pipeline[0];
1129
+ const sourceDef = parentStruct.fieldDef;
1121
1130
  // if we are generating code
1122
1131
  // and have extended declaration, we need to make a new QueryStruct
1123
1132
  // copy the definitions into a new structdef
@@ -1126,8 +1135,8 @@ class QueryQuery extends QueryField {
1126
1135
  (0, malloy_types_1.isQuerySegment)(firstStage) &&
1127
1136
  firstStage.extendSource !== undefined) {
1128
1137
  parent = new QueryStruct({
1129
- ...parentStruct.fieldDef,
1130
- fields: [...parentStruct.fieldDef.fields, ...firstStage.extendSource],
1138
+ ...sourceDef,
1139
+ fields: [...sourceDef.fields, ...firstStage.extendSource],
1131
1140
  }, parent.parent ? { struct: parent } : { model: parent.model });
1132
1141
  flatTurtleDef = {
1133
1142
  ...flatTurtleDef,
@@ -1140,6 +1149,11 @@ class QueryQuery extends QueryField {
1140
1149
  ],
1141
1150
  };
1142
1151
  }
1152
+ if (sourceDef.queryTimezone &&
1153
+ (0, malloy_types_1.isQuerySegment)(firstStage) &&
1154
+ firstStage.queryTimezone === undefined) {
1155
+ firstStage.queryTimezone = sourceDef.queryTimezone;
1156
+ }
1143
1157
  switch (firstStage.type) {
1144
1158
  case 'reduce':
1145
1159
  return new QueryQueryReduce(flatTurtleDef, parent, stageWriter);
@@ -1585,39 +1599,22 @@ class QueryQuery extends QueryField {
1585
1599
  break;
1586
1600
  case 'timestamp': {
1587
1601
  const timeframe = fi.f.fieldDef.timeframe;
1588
- switch (timeframe) {
1589
- case 'year':
1590
- case 'month':
1591
- case 'week':
1592
- case 'quarter':
1593
- case 'day':
1594
- fields.push({
1595
- name,
1596
- type: 'date',
1597
- timeframe,
1598
- resultMetadata,
1599
- location,
1600
- });
1601
- break;
1602
- case 'second':
1603
- case 'minute':
1604
- case 'hour':
1605
- fields.push({
1606
- name,
1607
- type: 'timestamp',
1608
- timeframe,
1609
- resultMetadata,
1610
- location,
1611
- });
1612
- break;
1613
- default:
1614
- fields.push({
1615
- name,
1616
- type: 'timestamp',
1617
- resultMetadata,
1618
- location,
1619
- });
1620
- break;
1602
+ if (timeframe) {
1603
+ fields.push({
1604
+ name,
1605
+ type: 'timestamp',
1606
+ timeframe,
1607
+ resultMetadata,
1608
+ location,
1609
+ });
1610
+ }
1611
+ else {
1612
+ fields.push({
1613
+ name,
1614
+ type: 'timestamp',
1615
+ resultMetadata,
1616
+ location,
1617
+ });
1621
1618
  }
1622
1619
  break;
1623
1620
  }
@@ -1649,7 +1646,7 @@ class QueryQuery extends QueryField {
1649
1646
  }
1650
1647
  }
1651
1648
  }
1652
- return {
1649
+ const outputStruct = {
1653
1650
  fields,
1654
1651
  name: this.resultStage || 'result',
1655
1652
  dialect: this.parent.dialect.name,
@@ -1662,6 +1659,10 @@ class QueryQuery extends QueryField {
1662
1659
  resultMetadata: this.getResultMetadata(this.rootResult),
1663
1660
  type: 'struct',
1664
1661
  };
1662
+ if ((0, malloy_types_1.isQuerySegment)(this.firstSegment) && this.firstSegment.queryTimezone) {
1663
+ outputStruct.queryTimezone = this.firstSegment.queryTimezone;
1664
+ }
1665
+ return outputStruct;
1665
1666
  }
1666
1667
  generateSQLJoinBlock(stageWriter, ji) {
1667
1668
  let s = '';
@@ -196,8 +196,7 @@ export interface TimeLiteralFragment extends DialectFragmentBase {
196
196
  function: 'timeLiteral';
197
197
  literal: string;
198
198
  literalType: TimeFieldType;
199
- timezone: string;
200
- tzIsLocale?: boolean;
199
+ timezone?: string;
201
200
  }
202
201
  export interface StringLiteralFragment extends DialectFragmentBase {
203
202
  function: 'stringLiteral';
@@ -378,6 +377,7 @@ export interface QuerySegment extends Filtered {
378
377
  limit?: number;
379
378
  by?: By;
380
379
  orderBy?: OrderBy[];
380
+ queryTimezone?: string;
381
381
  }
382
382
  export interface TurtleDef extends NamedObject, Pipeline {
383
383
  type: 'turtle';
@@ -449,6 +449,7 @@ export interface StructDef extends NamedObject, ResultStructMetadata, Filtered {
449
449
  fields: FieldDef[];
450
450
  primaryKey?: PrimaryKeyRef;
451
451
  parameters?: Record<string, Parameter>;
452
+ queryTimezone?: string;
452
453
  dialect: string;
453
454
  }
454
455
  export interface SQLBlockStructDef extends StructDef {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.34-dev230523182411",
3
+ "version": "0.0.34",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",