@malloydata/malloy-tests 0.0.434 → 0.0.436

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.
@@ -697,6 +697,13 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
697
697
  run: range + { where: t ~ f'2001' }
698
698
  `).toMatchRows(range, inRange);
699
699
  });
700
+ test('date in month, in a query time zone east of UTC', async () => {
701
+ // Midnight starting a month in Tokyo is still the previous day in UTC
702
+ const range = mkDateRange('2001-01-01', '2001-02-01');
703
+ await expect(`
704
+ run: range + { timezone: 'Asia/Tokyo'; where: t ~ f'2001-01' }
705
+ `).toMatchRows(range, inRange);
706
+ });
700
707
  test('2 days ago', async () => {
701
708
  nowIs('2001-01-15 12:00:00');
702
709
  const rangeQuery = mkRangeQuery(
@@ -79,11 +79,13 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
79
79
  `).toMatchResult(testModel, {fieldName: 'one', weight: 51});
80
80
  });
81
81
 
82
- // bigquery doesn't support row count based sampling.
82
+ // bigquery doesn't support row count based sampling; sqlserver's TABLESAMPLE
83
+ // returns whole pages, not the count asked for.
83
84
  test.when(
84
85
  databaseName !== 'bigquery' &&
85
86
  databaseName !== 'trino' &&
86
- databaseName !== 'presto'
87
+ databaseName !== 'presto' &&
88
+ databaseName !== 'sqlserver'
87
89
  )(`index rows count - ${databaseName}`, async () => {
88
90
  await expect(`
89
91
  run: ${databaseName}.table('malloytest.state_facts') extend {
@@ -527,12 +527,13 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
527
527
  source: a is ${databaseName}.table('malloytest.aircraft_models') extend { where: aircraft_model_code ? '0270202' }
528
528
 
529
529
  run: a -> {
530
- group_by: boolean_1 is sql_boolean("\${seats} > 20")
531
- group_by: boolean_2 is sql_boolean("\${engines} = 2")
530
+ group_by:
531
+ boolean_1 is pick 'yes' when sql_boolean("\${seats} > 20") else 'no'
532
+ boolean_2 is pick 'yes' when sql_boolean("\${engines} = 2") else 'no'
532
533
  }
533
534
  `).toMatchResult(testModel, {
534
- boolean_1: true,
535
- boolean_2: false,
535
+ boolean_1: 'yes',
536
+ boolean_2: 'no',
536
537
  });
537
538
  });
538
539
 
@@ -1067,12 +1068,12 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
1067
1068
  dimension: fot is (false) or (true)
1068
1069
  } -> {
1069
1070
  select:
1070
- no_paren is false and fot
1071
- paren is false and (fot)
1071
+ no_paren is pick 'yes' when false and fot else 'no'
1072
+ paren is pick 'yes' when false and (fot) else 'no'
1072
1073
  }`
1073
1074
  ).toMatchResult(testModel, {
1074
- paren: false,
1075
- no_paren: false,
1075
+ paren: 'no',
1076
+ no_paren: 'no',
1076
1077
  });
1077
1078
  });
1078
1079
  });
@@ -51,8 +51,6 @@ expressionModels.forEach((x, databaseName) => {
51
51
  const expressionModel = x.expressionModel;
52
52
  const runtime = x.runtime;
53
53
  const testModel = wrapTestModel(runtime, modelText(databaseName));
54
- const dbTrue = runtime.dialect.resultBoolean(true);
55
- const dbFalse = runtime.dialect.resultBoolean(false);
56
54
  const funcTestGeneral = async (
57
55
  expr: string,
58
56
  type: 'group_by' | 'aggregate',
@@ -124,14 +122,6 @@ expressionModels.forEach((x, databaseName) => {
124
122
  await funcTestMultiple(
125
123
  ["concat('foo', 'bar')", 'foobar'],
126
124
  ["concat(1, 'bar')", '1bar'],
127
- [
128
- "concat('cons', true)",
129
- databaseName === 'postgres'
130
- ? 'const'
131
- : databaseName === 'mysql'
132
- ? 'cons1'
133
- : 'construe',
134
- ],
135
125
  ["concat('foo', @2003)", 'foo2003-01-01'],
136
126
  [
137
127
  "concat('foo', @2003-01-01 12:00:00)",
@@ -142,6 +132,20 @@ expressionModels.forEach((x, databaseName) => {
142
132
  ['concat()', '']
143
133
  );
144
134
  });
135
+ it.when(
136
+ runtime.dialect.booleanType !== 'none' &&
137
+ !brokenIn('trino', databaseName) &&
138
+ !brokenIn('presto', databaseName)
139
+ )(`works with a boolean - ${databaseName}`, async () => {
140
+ await funcTest(
141
+ "concat('cons', true)",
142
+ databaseName === 'postgres'
143
+ ? 'const'
144
+ : databaseName === 'mysql'
145
+ ? 'cons1'
146
+ : 'construe'
147
+ );
148
+ });
145
149
  });
146
150
 
147
151
  describe('round', () => {
@@ -624,16 +628,13 @@ expressionModels.forEach((x, databaseName) => {
624
628
  # test.debug
625
629
  run: state_facts -> {
626
630
  group_by: state
627
- calculate: lag_val is lag(@2011-11-11 11:11:11, 1, now).year = now.year
631
+ calculate: lag_val is
632
+ pick 'now' when lag(@2011-11-11 11:11:11, 1, now).year = now.year else 'then'
628
633
  }`
629
634
  )
630
635
  .run();
631
- expect(result.data.path(0, 'lag_val').value).toBe(
632
- runtime.dialect.resultBoolean(true)
633
- );
634
- expect(result.data.path(1, 'lag_val').value).toBe(
635
- runtime.dialect.resultBoolean(false)
636
- );
636
+ expect(result.data.path(0, 'lag_val').value).toBe('now');
637
+ expect(result.data.path(1, 'lag_val').value).toBe('then');
637
638
  });
638
639
  });
639
640
 
@@ -855,32 +856,45 @@ expressionModels.forEach((x, databaseName) => {
855
856
  : "'+inf'::number";
856
857
  it.when(databaseName !== 'mysql')(`works - ${databaseName}`, async () => {
857
858
  await funcTestMultiple(
858
- [`is_inf(${inf})`, dbTrue],
859
- ['is_inf(100)', dbFalse],
860
- ['is_inf(null)', dbFalse]
859
+ [`pick 'yes' when is_inf(${inf}) else 'no'`, 'yes'],
860
+ ["pick 'yes' when is_inf(100) else 'no'", 'no'],
861
+ ["pick 'yes' when is_inf(null) else 'no'", 'no']
861
862
  );
862
863
  });
864
+ // A null input is false, not null
865
+ it.when(databaseName !== 'mysql' && runtime.dialect.booleanType !== 'none')(
866
+ `is false for null - ${databaseName}`,
867
+ async () => {
868
+ await funcTest('is_inf(null)', runtime.dialect.resultBoolean(false));
869
+ }
870
+ );
863
871
  });
864
872
  describe('is_nan', () => {
865
873
  it.when(databaseName !== 'mysql')(`works - ${databaseName}`, async () => {
866
874
  await funcTestMultiple(
867
- ["is_nan('NaN'::number)", dbTrue],
868
- ['is_nan(100)', dbFalse],
869
- ['is_nan(null)', dbFalse]
875
+ ["pick 'yes' when is_nan('NaN'::number) else 'no'", 'yes'],
876
+ ["pick 'yes' when is_nan(100) else 'no'", 'no'],
877
+ ["pick 'yes' when is_nan(null) else 'no'", 'no']
870
878
  );
871
879
  });
880
+ it.when(databaseName !== 'mysql' && runtime.dialect.booleanType !== 'none')(
881
+ `is false for null - ${databaseName}`,
882
+ async () => {
883
+ await funcTest('is_nan(null)', runtime.dialect.resultBoolean(false));
884
+ }
885
+ );
872
886
  });
873
887
  describe('greatest', () => {
874
888
  it(`works - ${databaseName}`, async () => {
875
889
  await funcTestMultiple(
876
890
  ['greatest(1, 10, -100)', 10],
877
891
  [
878
- 'greatest(@2003, @2004, @1994) = @2004',
879
- runtime.dialect.resultBoolean(true),
892
+ "pick 'yes' when greatest(@2003, @2004, @1994) = @2004 else 'no'",
893
+ 'yes',
880
894
  ],
881
895
  [
882
- 'greatest(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:59:00',
883
- runtime.dialect.resultBoolean(true),
896
+ "pick 'yes' when greatest(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:59:00 else 'no'",
897
+ 'yes',
884
898
  ],
885
899
  ["greatest('a', 'b')", 'b'],
886
900
  ['greatest(1, null, 0)', null],
@@ -892,13 +906,10 @@ expressionModels.forEach((x, databaseName) => {
892
906
  it(`works - ${databaseName}`, async () => {
893
907
  await funcTestMultiple(
894
908
  ['least(1, 10, -100)', -100],
909
+ ["pick 'yes' when least(@2003, @2004, @1994) = @1994 else 'no'", 'yes'],
895
910
  [
896
- 'least(@2003, @2004, @1994) = @1994',
897
- runtime.dialect.resultBoolean(true),
898
- ],
899
- [
900
- 'least(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:58:00',
901
- runtime.dialect.resultBoolean(true),
911
+ "pick 'yes' when least(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:58:00 else 'no'",
912
+ 'yes',
902
913
  ],
903
914
  ["least('a', 'b')", 'a'],
904
915
  ['least(1, null, 0)', null],
@@ -929,36 +940,48 @@ expressionModels.forEach((x, databaseName) => {
929
940
  it(`works - ${databaseName}`, async () => {
930
941
  await funcTestMultiple(
931
942
  [
932
- "starts_with('hello world', 'hello')",
933
- runtime.dialect.resultBoolean(true),
943
+ "pick 'yes' when starts_with('hello world', 'hello') else 'no'",
944
+ 'yes',
934
945
  ],
935
- [
936
- "starts_with('hello world', 'world')",
937
- runtime.dialect.resultBoolean(false),
938
- ],
939
- ["starts_with(null, 'world')", runtime.dialect.resultBoolean(false)],
940
- [
941
- "starts_with('hello world', null)",
942
- runtime.dialect.resultBoolean(false),
943
- ]
946
+ ["pick 'yes' when starts_with('hello world', 'world') else 'no'", 'no'],
947
+ ["pick 'yes' when starts_with(null, 'world') else 'no'", 'no'],
948
+ ["pick 'yes' when starts_with('hello world', null) else 'no'", 'no']
944
949
  );
945
950
  });
951
+ it.when(runtime.dialect.booleanType !== 'none')(
952
+ `is false for null - ${databaseName}`,
953
+ async () => {
954
+ await funcTestMultiple(
955
+ ["starts_with(null, 'world')", runtime.dialect.resultBoolean(false)],
956
+ [
957
+ "starts_with('hello world', null)",
958
+ runtime.dialect.resultBoolean(false),
959
+ ]
960
+ );
961
+ }
962
+ );
946
963
  });
947
964
  describe('ends_with', () => {
948
965
  it(`works - ${databaseName}`, async () => {
949
966
  await funcTestMultiple(
950
- [
951
- "ends_with('hello world', 'world')",
952
- runtime.dialect.resultBoolean(true),
953
- ],
954
- [
955
- "ends_with('hello world', 'hello')",
956
- runtime.dialect.resultBoolean(false),
957
- ],
958
- ["ends_with(null, 'world')", runtime.dialect.resultBoolean(false)],
959
- ["ends_with('hello world', null)", runtime.dialect.resultBoolean(false)]
967
+ ["pick 'yes' when ends_with('hello world', 'world') else 'no'", 'yes'],
968
+ ["pick 'yes' when ends_with('hello world', 'hello') else 'no'", 'no'],
969
+ ["pick 'yes' when ends_with(null, 'world') else 'no'", 'no'],
970
+ ["pick 'yes' when ends_with('hello world', null) else 'no'", 'no']
960
971
  );
961
972
  });
973
+ it.when(runtime.dialect.booleanType !== 'none')(
974
+ `is false for null - ${databaseName}`,
975
+ async () => {
976
+ await funcTestMultiple(
977
+ ["ends_with(null, 'world')", runtime.dialect.resultBoolean(false)],
978
+ [
979
+ "ends_with('hello world', null)",
980
+ runtime.dialect.resultBoolean(false),
981
+ ]
982
+ );
983
+ }
984
+ );
962
985
  });
963
986
  describe('trim', () => {
964
987
  it(`trim works - ${databaseName}`, async () => {
@@ -1001,14 +1024,17 @@ expressionModels.forEach((x, databaseName) => {
1001
1024
  // There are around a billion values that rand() can be, so if this
1002
1025
  // test fails, most likely something is broken. Otherwise, you're the lucky
1003
1026
  // one in a billion!
1004
- await funcTest('rand() = rand()', runtime.dialect.resultBoolean(false));
1027
+ await funcTest(
1028
+ "pick 'same' when rand() = rand() else 'differ'",
1029
+ 'differ'
1030
+ );
1005
1031
  });
1006
1032
  });
1007
1033
  describe('pi', () => {
1008
1034
  it(`is pi - ${databaseName}`, async () => {
1009
1035
  await funcTest(
1010
- 'abs(pi() - 3.141592653589793) < 0.0000000000001',
1011
- runtime.dialect.resultBoolean(true)
1036
+ "pick 'yes' when abs(pi() - 3.141592653589793) < 0.0000000000001 else 'no'",
1037
+ 'yes'
1012
1038
  );
1013
1039
  });
1014
1040
  });
@@ -11,8 +11,11 @@ import '@malloydata/malloy/test/matchers';
11
11
  const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
12
12
 
13
13
  describe.each(runtimes.runtimeList)('New matchers for %s', (db, runtime) => {
14
+ // A dialect without boolean values has no boolean column
15
+ const booleans = runtime.dialect.booleanType !== 'none';
16
+
14
17
  describe('mkTestModel and TV', () => {
15
- test('basic types with type inference', async () => {
18
+ test.when(booleans)('basic types with type inference', async () => {
16
19
  const tm = mkTestModel(runtime, {
17
20
  data: [
18
21
  {t_int: 1, t_string: 'a', t_bool: true},
@@ -155,16 +158,19 @@ describe.each(runtimes.runtimeList)('New matchers for %s', (db, runtime) => {
155
158
  });
156
159
  });
157
160
 
158
- test('plain booleans work with dialect boolean handling', async () => {
159
- const tm = mkTestModel(runtime, {
160
- data: [{a: true, b: false}],
161
- });
162
- // Booleans are handled by dialect.resultBoolean() in the matcher
163
- await expect('run: data -> { select: * }').toMatchResult(tm, {
164
- a: true,
165
- b: false,
166
- });
167
- });
161
+ test.when(booleans)(
162
+ 'plain booleans work with dialect boolean handling',
163
+ async () => {
164
+ const tm = mkTestModel(runtime, {
165
+ data: [{a: true, b: false}],
166
+ });
167
+ // Booleans are handled by dialect.resultBoolean() in the matcher
168
+ await expect('run: data -> { select: * }').toMatchResult(tm, {
169
+ a: true,
170
+ b: false,
171
+ });
172
+ }
173
+ );
168
174
  });
169
175
 
170
176
  describe('test.debug tag', () => {
@@ -24,20 +24,26 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
24
24
  }`
25
25
  );
26
26
 
27
- it(`boolean type - ${databaseName}`, async () => {
28
- await expect(`
27
+ // A dialect without boolean values cannot group by one
28
+ it.when(runtime.dialect.booleanType !== 'none')(
29
+ `boolean type - ${databaseName}`,
30
+ async () => {
31
+ await expect(`
29
32
  run: models-> {
30
33
  group_by: big is seats >=20
31
34
  aggregate: model_count is count()
32
35
  }
33
36
  `).toMatchResult(orderByModel, {
34
- big: false,
35
- model_count: 58451,
36
- });
37
- });
37
+ big: false,
38
+ model_count: 58451,
39
+ });
40
+ }
41
+ );
38
42
 
39
- it(`boolean in pipeline - ${databaseName}`, async () => {
40
- await expect(`
43
+ it.when(runtime.dialect.booleanType !== 'none')(
44
+ `boolean in pipeline - ${databaseName}`,
45
+ async () => {
46
+ await expect(`
41
47
  run: models->{
42
48
  group_by:
43
49
  manufacturer,
@@ -48,10 +54,11 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
48
54
  aggregate: model_count is model_count.sum()
49
55
  }
50
56
  `).toMatchResult(orderByModel, {
51
- big: false,
52
- model_count: 58500,
53
- });
54
- });
57
+ big: false,
58
+ model_count: 58500,
59
+ });
60
+ }
61
+ );
55
62
 
56
63
  it(`filtered measures in model are aggregates #352 - ${databaseName}`, async () => {
57
64
  await expect(`
@@ -15,8 +15,11 @@ describe.each(runtimes.runtimeList)('TestSelect for %s', (db, runtime) => {
15
15
  const ts = new TestSelect(runtime.dialect);
16
16
  const testModel = wrapTestModel(runtime, '');
17
17
 
18
+ // A dialect without boolean values has no boolean column
19
+ const booleans = runtime.dialect.booleanType !== 'none';
20
+
18
21
  // Basic Type Tests
19
- test(`${db} inferred basic types`, async () => {
22
+ test.when(booleans)(`${db} inferred basic types`, async () => {
20
23
  const sql = ts.generate(
21
24
  {t_int: 1, t_string: 'a', t_bool: true, t_float: 1.5},
22
25
  {t_int: 2, t_string: 'b', t_bool: false, t_float: 2.5}
@@ -27,7 +30,7 @@ describe.each(runtimes.runtimeList)('TestSelect for %s', (db, runtime) => {
27
30
  ]);
28
31
  });
29
32
 
30
- test(`${db} explicit type hints`, async () => {
33
+ test.when(booleans)(`${db} explicit type hints`, async () => {
31
34
  const sql = ts.generate({
32
35
  t_int: ts.mk_int(1),
33
36
  t_float: ts.mk_float(1.5),