@cubejs-backend/schema-compiler 0.23.8 → 0.24.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,60 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.24.1](https://github.com/cube-js/cube.js/compare/v0.24.0...v0.24.1) (2020-11-27)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Specifying `dateRange` in time dimension should produce same result as `inDateRange` in filter ([a7603d7](https://github.com/cube-js/cube.js/commit/a7603d724732a51301227f68c39ba699333c0e06)), closes [#962](https://github.com/cube-js/cube.js/issues/962)
12
+
13
+
14
+
15
+
16
+
17
+ # [0.24.0](https://github.com/cube-js/cube.js/compare/v0.23.15...v0.24.0) (2020-11-26)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * Error: Type must be provided for null values. -- `null` parameter values are passed to BigQuery when used for dimensions that contain `?` ([6417e7d](https://github.com/cube-js/cube.js/commit/6417e7d120a95c4792557a4c4a0d6abb7c483db9))
23
+
24
+
25
+ ### Features
26
+
27
+ * Make default refreshKey to be `every 10 seconds` and enable scheduled refresh in dev mode by default ([221003a](https://github.com/cube-js/cube.js/commit/221003aa73aa1ece3d649de9164a7379a4a690be))
28
+
29
+
30
+ ### BREAKING CHANGES
31
+
32
+ * `every 10 seconds` refreshKey becomes a default refreshKey for all cubes.
33
+
34
+
35
+
36
+
37
+
38
+ ## [0.23.15](https://github.com/cube-js/cube.js/compare/v0.23.14...v0.23.15) (2020-11-25)
39
+
40
+
41
+ ### Bug Fixes
42
+
43
+ * Error: Cannot find module 'antlr4/index' ([0d2e330](https://github.com/cube-js/cube.js/commit/0d2e33040dfea3fb80df2a1af2ccff46db0f8673))
44
+
45
+
46
+
47
+
48
+
49
+ ## [0.23.11](https://github.com/cube-js/cube.js/compare/v0.23.10...v0.23.11) (2020-11-13)
50
+
51
+
52
+ ### Features
53
+
54
+ * **@cubejs-backend/mysql-aurora-serverless-driver:** Add a new driver to support AWS Aurora Serverless MySql ([#1333](https://github.com/cube-js/cube.js/issues/1333)) Thanks to [@kcwinner](https://github.com/kcwinner)! ([154fab1](https://github.com/cube-js/cube.js/commit/154fab1a222685e1e83d5187a4f00f745c4613a3))
55
+
56
+
57
+
58
+
59
+
6
60
  ## [0.23.8](https://github.com/cube-js/cube.js/compare/v0.23.7...v0.23.8) (2020-11-06)
7
61
 
8
62
  **Note:** Version bump only for package @cubejs-backend/schema-compiler
@@ -16,8 +16,8 @@ const GRANULARITY_TO_INTERVAL = {
16
16
  };
17
17
 
18
18
  class AWSElasticSearchQueryFilter extends BaseFilter {
19
- likeIgnoreCase(column, not) {
20
- return `${column}${not ? ' NOT' : ''} LIKE CONCAT('%', ?, '%')`;
19
+ likeIgnoreCase(column, not, param) {
20
+ return `${column}${not ? ' NOT' : ''} LIKE CONCAT('%', ${this.allocateParam(param)}, '%')`;
21
21
  }
22
22
  }
23
23
 
@@ -48,8 +48,16 @@ class BaseFilter extends BaseDimension {
48
48
  /[A-Z]/,
49
49
  (c) => (c != null ? c : '').toLowerCase()
50
50
  )}Where`;
51
- const sql = this[operatorMethod](columnSql);
52
- return this.query.paramAllocator.allocateParamsForQuestionString(sql, this.filterParams());
51
+
52
+ let sql = this[operatorMethod](columnSql);
53
+ if (sql.match(this.query.paramAllocator.paramsMatchRegex)) {
54
+ return sql;
55
+ }
56
+ // TODO DEPRECATED: remove and replace with error
57
+ // columnSql can contain `?` so allocate params first and then substitute columnSql
58
+ // fallback implementation for drivers that still use `?` substitution
59
+ sql = this[operatorMethod]('$$$COLUMN$$$');
60
+ return this.query.paramAllocator.allocateParamsForQuestionString(sql, this.filterParams()).replace(/\$\$\$COLUMN\$\$\$/g, columnSql);
53
61
  }
54
62
 
55
63
  measureDefinition() {
@@ -107,6 +115,37 @@ class BaseFilter extends BaseDimension {
107
115
  return '?';
108
116
  }
109
117
 
118
+ firstParameter() {
119
+ const params = this.filterParams();
120
+ if (!params[0]) {
121
+ throw new Error('Expected one parameter but nothing found');
122
+ }
123
+ return this.allocateCastParam(params[0]);
124
+ }
125
+
126
+ allocateCastParam(param) {
127
+ return this.query.paramAllocator.allocateParamsForQuestionString(this.castParameter(), [param]);
128
+ }
129
+
130
+ allocateTimestampParam(param) {
131
+ return this.query.paramAllocator.allocateParamsForQuestionString(this.query.timeStampParam(this), [param]);
132
+ }
133
+
134
+ allocateTimestampParams() {
135
+ return this.filterParams().map((p, i) => {
136
+ if (i > 1) {
137
+ throw new Error(`Expected only 2 parameters for timestamp filter but got: ${this.filterParams()}`);
138
+ }
139
+ return this.allocateTimestampParam(
140
+ i === 0 ? this.inDbTimeZoneDateFrom(p) : this.inDbTimeZoneDateTo(p)
141
+ );
142
+ });
143
+ }
144
+
145
+ allParamsRepeat(basePart) {
146
+ return this.filterParams().map(p => this.query.paramAllocator.allocateParamsForQuestionString(basePart, [p]));
147
+ }
148
+
110
149
  isArrayValues() {
111
150
  return Array.isArray(this.values) && this.values.length > 1;
112
151
  }
@@ -120,8 +159,7 @@ class BaseFilter extends BaseDimension {
120
159
  }
121
160
 
122
161
  likeOr(column, not) {
123
- const basePart = this.likeIgnoreCase(column, not);
124
- return `${join(not ? ' AND ' : ' OR ', repeat(basePart, this.filterParams().length))}${this.orIsNullCheck(column, not)}`;
162
+ return `${join(not ? ' AND ' : ' OR ', this.filterParams().map(p => this.likeIgnoreCase(column, not, p)))}${this.orIsNullCheck(column, not)}`;
125
163
  }
126
164
 
127
165
  orIsNullCheck(column, not) {
@@ -132,8 +170,12 @@ class BaseFilter extends BaseDimension {
132
170
  return not ? !this.valuesContainNull() : this.valuesContainNull();
133
171
  }
134
172
 
135
- likeIgnoreCase(column, not) {
136
- return `${column}${not ? ' NOT' : ''} ILIKE '%' || ? || '%'`;
173
+ likeIgnoreCase(column, not, param) {
174
+ return `${column}${not ? ' NOT' : ''} ILIKE '%' || ${this.allocateParam(param)} || '%'`;
175
+ }
176
+
177
+ allocateParam(param) {
178
+ return this.query.paramAllocator.allocateParam(param);
137
179
  }
138
180
 
139
181
  equalsWhere(column) {
@@ -145,11 +187,11 @@ class BaseFilter extends BaseDimension {
145
187
  return this.notSetWhere(column);
146
188
  }
147
189
 
148
- return `${column} = ${this.castParameter()}${this.orIsNullCheck(column, false)}`;
190
+ return `${column} = ${this.firstParameter()}${this.orIsNullCheck(column, false)}`;
149
191
  }
150
192
 
151
193
  inPlaceholders() {
152
- return `(${join(', ', repeat(this.castParameter(), this.filterParams().length || 1))})`;
194
+ return `(${join(', ', this.filterParams().map(p => this.allocateCastParam(p)))})`;
153
195
  }
154
196
 
155
197
  inWhere(column) {
@@ -165,7 +207,7 @@ class BaseFilter extends BaseDimension {
165
207
  return this.setWhere(column);
166
208
  }
167
209
 
168
- return `${column} <> ${this.castParameter()}${this.orIsNullCheck(column, true)}`;
210
+ return `${column} <> ${this.firstParameter()}${this.orIsNullCheck(column, true)}`;
169
211
  }
170
212
 
171
213
  notInWhere(column) {
@@ -181,19 +223,19 @@ class BaseFilter extends BaseDimension {
181
223
  }
182
224
 
183
225
  gtWhere(column) {
184
- return `${column} > ${this.castParameter()}`;
226
+ return `${column} > ${this.firstParameter()}`;
185
227
  }
186
228
 
187
229
  gteWhere(column) {
188
- return `${column} >= ${this.castParameter()}`;
230
+ return `${column} >= ${this.firstParameter()}`;
189
231
  }
190
232
 
191
233
  ltWhere(column) {
192
- return `${column} < ${this.castParameter()}`;
234
+ return `${column} < ${this.firstParameter()}`;
193
235
  }
194
236
 
195
237
  lteWhere(column) {
196
- return `${column} <= ${this.castParameter()}`;
238
+ return `${column} <= ${this.firstParameter()}`;
197
239
  }
198
240
 
199
241
  expressionEqualsWhere(column) {
@@ -201,23 +243,28 @@ class BaseFilter extends BaseDimension {
201
243
  }
202
244
 
203
245
  inDateRangeWhere(column) {
204
- return this.query.timeRangeFilter(column, this.query.timeStampParam(this), this.query.timeStampParam(this));
246
+ const [from, to] = this.allocateTimestampParams();
247
+ return this.query.timeRangeFilter(column, from, to);
205
248
  }
206
249
 
207
250
  notInDateRangeWhere(column) {
208
- return this.query.timeNotInRangeFilter(column, this.query.timeStampParam(this), this.query.timeStampParam(this));
251
+ const [from, to] = this.allocateTimestampParams();
252
+ return this.query.timeNotInRangeFilter(column, from, to);
209
253
  }
210
254
 
211
255
  onTheDateWhere(column) {
212
- return this.query.timeRangeFilter(column, this.query.timeStampParam(this), this.query.timeStampParam(this));
256
+ const [from, to] = this.allocateTimestampParams();
257
+ return this.query.timeRangeFilter(column, from, to);
213
258
  }
214
259
 
215
260
  beforeDateWhere(column) {
216
- return this.query.beforeDateFilter(column, this.query.timeStampParam(this));
261
+ const [before] = this.allocateTimestampParams();
262
+ return this.query.beforeDateFilter(column, before);
217
263
  }
218
264
 
219
265
  afterDateWhere(column) {
220
- return this.query.afterDateFilter(column, this.query.timeStampParam(this));
266
+ const [after] = this.allocateTimestampParams();
267
+ return this.query.afterDateFilter(column, after);
221
268
  }
222
269
 
223
270
  formatFromDate(date) {
@@ -1666,25 +1666,7 @@ class BaseQuery {
1666
1666
  }
1667
1667
  }
1668
1668
  refreshKeyAllSetManually = false;
1669
- const timeDimensions =
1670
- !(cubeFromPath.refreshKey && cubeFromPath.refreshKey.immutable) ?
1671
- this.cubeEvaluator.timeDimensionPathsForCube(cube) :
1672
- [];
1673
- if (timeDimensions.length) {
1674
- const dimension = timeDimensions.filter(f => f.toLowerCase().indexOf('update') !== -1)[0] || timeDimensions[0];
1675
- const foundMainTimeDimension = this.newTimeDimension({ dimension });
1676
- const aggSelect = this.evaluateSymbolSqlWithContext(
1677
- () => this.aggSelectForDimension(cube, foundMainTimeDimension, 'max'),
1678
- { preAggregationQuery: true }
1679
- );
1680
- if (aggSelect) {
1681
- return aggSelect;
1682
- }
1683
- }
1684
- return this.evaluateSymbolSqlWithContext(
1685
- () => `select count(*) from ${this.cubeSql(cube)} ${this.asSyntaxTable} ${this.cubeAlias(cube)}`,
1686
- { preAggregationQuery: true }
1687
- );
1669
+ return `SELECT ${this.everyRefreshKeySql(this.defaultEveryRefreshKey())}`;
1688
1670
  };
1689
1671
  const queries = cubes
1690
1672
  .map(cube => [cube, refreshKeyQueryByCube(cube)])
@@ -1967,6 +1949,12 @@ class BaseQuery {
1967
1949
  return 10;
1968
1950
  }
1969
1951
 
1952
+ defaultEveryRefreshKey() {
1953
+ return {
1954
+ every: '10 seconds'
1955
+ };
1956
+ }
1957
+
1970
1958
  preAggregationInvalidateKeyQueries(cube, preAggregation) {
1971
1959
  return this.cacheValue(
1972
1960
  ['preAggregationInvalidateKeyQueries', cube, JSON.stringify(preAggregation)],
@@ -2027,10 +2015,9 @@ class BaseQuery {
2027
2015
  if (cubeFromPath.refreshKey && cubeFromPath.refreshKey.immutable) {
2028
2016
  return `SELECT ${this.incrementalRefreshKey(preAggregationQueryForSql, `(${originalRefreshKey})`)}`;
2029
2017
  } else if (!cubeFromPath.refreshKey) {
2030
- // TODO handle WHERE while generating originalRefreshKey
2031
- return refreshKeyCube === preAggregationQueryForSql.timeDimensions[0].path()[0] ?
2032
- `${originalRefreshKey} WHERE ${preAggregationQueryForSql.timeDimensions[0].filterToWhere()}` :
2033
- originalRefreshKey;
2018
+ return `SELECT ${this.everyRefreshKeySql({
2019
+ every: '1 hour'
2020
+ })}`;
2034
2021
  }
2035
2022
  return originalRefreshKey;
2036
2023
  }
@@ -12,8 +12,8 @@ const GRANULARITY_TO_INTERVAL = {
12
12
  };
13
13
 
14
14
  class BigqueryFilter extends BaseFilter {
15
- likeIgnoreCase(column, not) {
16
- return `LOWER(${column})${not ? ' NOT' : ''} LIKE CONCAT('%', LOWER(?) ,'%')`;
15
+ likeIgnoreCase(column, not, param) {
16
+ return `LOWER(${column})${not ? ' NOT' : ''} LIKE CONCAT('%', LOWER(${this.allocateParam(param)}) ,'%')`;
17
17
  }
18
18
 
19
19
  castParameter() {
@@ -133,6 +133,12 @@ class BigqueryQuery extends BaseQuery {
133
133
  defaultRefreshKeyRenewalThreshold() {
134
134
  return 120;
135
135
  }
136
+
137
+ defaultEveryRefreshKey() {
138
+ return {
139
+ every: '2 minutes'
140
+ };
141
+ }
136
142
  }
137
143
 
138
144
  module.exports = BigqueryQuery;
@@ -13,8 +13,8 @@ const GRANULARITY_TO_INTERVAL = {
13
13
  };
14
14
 
15
15
  class ClickHouseFilter extends BaseFilter {
16
- likeIgnoreCase(column, not) {
17
- return `lower(${column}) ${not ? 'NOT' : ''} LIKE CONCAT('%', lower(?), '%')`;
16
+ likeIgnoreCase(column, not, param) {
17
+ return `lower(${column}) ${not ? 'NOT' : ''} LIKE CONCAT('%', lower(${this.allocateParam(param)}), '%')`;
18
18
  }
19
19
 
20
20
  castParameter() {
@@ -16,8 +16,8 @@ const GRANULARITY_TO_INTERVAL = {
16
16
  };
17
17
 
18
18
  class ElasticSearchQueryFilter extends BaseFilter {
19
- likeIgnoreCase(column, not) {
20
- return `${not ? " NOT" : ""} MATCH(${column}, ?, 'fuzziness=AUTO:1,5')`;
19
+ likeIgnoreCase(column, not, param) {
20
+ return `${not ? " NOT" : ""} MATCH(${column}, ${this.allocateParam(param)}, 'fuzziness=AUTO:1,5')`;
21
21
  }
22
22
  }
23
23
 
@@ -14,8 +14,8 @@ const GRANULARITY_TO_INTERVAL = {
14
14
  };
15
15
 
16
16
  class HiveFilter extends BaseFilter {
17
- likeIgnoreCase(column, not) {
18
- return `${column}${not ? ' NOT' : ''} LIKE CONCAT('%', ?, '%')`;
17
+ likeIgnoreCase(column, not, param) {
18
+ return `${column}${not ? ' NOT' : ''} LIKE CONCAT('%', ${this.allocateParam(param)}, '%')`;
19
19
  }
20
20
  }
21
21
 
@@ -43,8 +43,8 @@ class MssqlFilter extends BaseFilter {
43
43
  return typeof param === 'string' ? param.replace(/([_%])/gi, '[$1]') : param;
44
44
  }
45
45
 
46
- likeIgnoreCase(column, not) {
47
- return `LOWER(${column})${not ? ' NOT' : ''} LIKE CONCAT('%', LOWER(?) ,'%')`;
46
+ likeIgnoreCase(column, not, param) {
47
+ return `LOWER(${column})${not ? ' NOT' : ''} LIKE CONCAT('%', LOWER(${this.allocateParam(param)}) ,'%')`;
48
48
  }
49
49
  }
50
50
 
@@ -15,8 +15,8 @@ const GRANULARITY_TO_INTERVAL = {
15
15
  };
16
16
 
17
17
  class MysqlFilter extends BaseFilter {
18
- likeIgnoreCase(column, not) {
19
- return `${column}${not ? ' NOT' : ''} LIKE CONCAT('%', ?, '%')`;
18
+ likeIgnoreCase(column, not, param) {
19
+ return `${column}${not ? ' NOT' : ''} LIKE CONCAT('%', ${this.allocateParam(param)}, '%')`;
20
20
  }
21
21
  }
22
22
 
@@ -20,8 +20,8 @@ class OracleFilter extends BaseFilter {
20
20
  /**
21
21
  * "ILIKE" does't support
22
22
  */
23
- likeIgnoreCase(column, not) {
24
- return `${column}${not ? ' NOT' : ''} LIKE '%' || ${this.castParameter()} || '%'`;
23
+ likeIgnoreCase(column, not, param) {
24
+ return `${column}${not ? ' NOT' : ''} LIKE '%' || ${this.allocateParam(param)} || '%'`;
25
25
  }
26
26
  }
27
27
 
@@ -1,6 +1,7 @@
1
1
  class ParamAllocator {
2
2
  constructor() {
3
3
  this.params = [];
4
+ this.paramsMatchRegex = /\$(\d+)\$/g;
4
5
  }
5
6
 
6
7
  allocateParam(param) {
@@ -16,7 +17,7 @@ class ParamAllocator {
16
17
 
17
18
  buildSqlAndParams(annotatedSql) {
18
19
  const paramsInSqlOrder = [];
19
- return [annotatedSql.replace(/\$(\d+)\$/g, (match, paramIndex) => {
20
+ return [annotatedSql.replace(this.paramsMatchRegex, (match, paramIndex) => {
20
21
  paramsInSqlOrder.push(this.params[paramIndex]);
21
22
  return this.paramPlaceHolder(paramsInSqlOrder.length - 1);
22
23
  }), paramsInSqlOrder];
@@ -12,8 +12,8 @@ const GRANULARITY_TO_INTERVAL = {
12
12
  };
13
13
 
14
14
  class PrestodbFilter extends BaseFilter {
15
- likeIgnoreCase(column, not) {
16
- return `LOWER(${column})${not ? ' NOT' : ''} LIKE CONCAT('%', LOWER(?) ,'%') ESCAPE '\\'`;
15
+ likeIgnoreCase(column, not, param) {
16
+ return `LOWER(${column})${not ? ' NOT' : ''} LIKE CONCAT('%', LOWER(${this.allocateParam(param)}) ,'%') ESCAPE '\\'`;
17
17
  }
18
18
 
19
19
  castParameter() {
@@ -81,6 +81,12 @@ class PrestodbQuery extends BaseQuery {
81
81
  return 120;
82
82
  }
83
83
 
84
+ defaultEveryRefreshKey() {
85
+ return {
86
+ every: '2 minutes'
87
+ };
88
+ }
89
+
84
90
  hllInit(sql) {
85
91
  return `cast(approx_set(${sql}) as varbinary)`;
86
92
  }
@@ -18,6 +18,7 @@ const ADAPTERS = {
18
18
  postgres,
19
19
  redshift,
20
20
  mysql,
21
+ mysqlauroraserverless: mysql,
21
22
  mongobi,
22
23
  mssql,
23
24
  bigquery,
@@ -27,6 +27,12 @@ class SnowflakeQuery extends BaseQuery {
27
27
  return 120;
28
28
  }
29
29
 
30
+ defaultEveryRefreshKey() {
31
+ return {
32
+ every: '2 minutes'
33
+ };
34
+ }
35
+
30
36
  nowTimestampSql() {
31
37
  return `CURRENT_TIMESTAMP`;
32
38
  }
@@ -14,8 +14,8 @@ const GRANULARITY_TO_INTERVAL = {
14
14
  };
15
15
 
16
16
  class SqliteFilter extends BaseFilter {
17
- likeIgnoreCase(column, not) {
18
- return `${column}${not ? ' NOT' : ''} LIKE '%' || ? || '%' COLLATE NOCASE`;
17
+ likeIgnoreCase(column, not, param) {
18
+ return `${column}${not ? ' NOT' : ''} LIKE '%' || ${this.allocateParam(param)} || '%' COLLATE NOCASE`;
19
19
  }
20
20
  }
21
21
 
@@ -60,8 +60,8 @@ class CubeEvaluator extends CubeSymbols {
60
60
  }).reduce((a, b) => a.concat(b), []);
61
61
  }
62
62
 
63
- cubeNamesWithRefreshKeys() {
64
- return Object.keys(this.evaluatedCubes).filter(c => !!this.evaluatedCubes[c].refreshKey);
63
+ cubeNames() {
64
+ return Object.keys(this.evaluatedCubes);
65
65
  }
66
66
 
67
67
  isMeasure(measurePath) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@cubejs-backend/schema-compiler",
3
3
  "description": "Cube.js schema compiler",
4
4
  "author": "Cube Dev, Inc.",
5
- "version": "0.23.8",
5
+ "version": "0.24.1",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/cube-js/cube.js.git",
@@ -30,7 +30,7 @@
30
30
  "@babel/traverse": "^7.4.0",
31
31
  "@babel/types": "^7.4.0",
32
32
  "@hapi/joi": "^15.1.1",
33
- "antlr4": "^4.8.0",
33
+ "antlr4": "4.8.0",
34
34
  "cron-parser": "^2.16.3",
35
35
  "humps": "^2.0.1",
36
36
  "inflection": "^1.12.0",
@@ -62,5 +62,5 @@
62
62
  "uuid": "^3.3.2"
63
63
  },
64
64
  "license": "Apache-2.0",
65
- "gitHead": "527077fabdf797bcdfb0d5619e21d9432b50f15b"
65
+ "gitHead": "0479050307f08ca69f4fdaee2d08d3e4f39453e6"
66
66
  }
@@ -138,6 +138,14 @@ describe('PreAggregations', function test() {
138
138
  granularity: 'hour',
139
139
  partitionGranularity: 'hour'
140
140
  },
141
+ partitionedHourlyForRange: {
142
+ type: 'rollup',
143
+ measureReferences: [checkinsTotal],
144
+ dimensionReferences: [source, createdAt],
145
+ timeDimensionReference: createdAt,
146
+ granularity: 'hour',
147
+ partitionGranularity: 'hour'
148
+ },
141
149
  ratio: {
142
150
  type: 'rollup',
143
151
  measureReferences: [checkinsTotal, uniqueSourceCount],
@@ -599,7 +607,7 @@ describe('PreAggregations', function test() {
599
607
  const preAggregationsDescription = query.preAggregations.preAggregationsDescription();
600
608
  console.log(JSON.stringify(preAggregationsDescription, null, 2));
601
609
 
602
- preAggregationsDescription[0].invalidateKeyQueries[0][0].should.match(/>=/);
610
+ preAggregationsDescription[0].invalidateKeyQueries[0][0].should.match(/FLOOR/);
603
611
 
604
612
  return dbRunner.testQueries(tempTablePreAggregations(preAggregationsDescription).concat([
605
613
  query.buildSqlAndParams()
@@ -795,6 +803,54 @@ describe('PreAggregations', function test() {
795
803
  });
796
804
  }));
797
805
 
806
+ it('partitioned inDateRange', () => compiler.compile().then(() => {
807
+ const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
808
+ measures: [
809
+ 'visitors.checkinsTotal'
810
+ ],
811
+ dimensions: [
812
+ 'visitors.source'
813
+ ],
814
+ timezone: 'America/Los_Angeles',
815
+ preAggregationsSchema: '',
816
+ filters: [{
817
+ member: 'visitors.createdAt',
818
+ operator: 'inDateRange',
819
+ values: ['2016-12-30', '2017-01-05']
820
+ }],
821
+ order: [{
822
+ id: 'visitors.checkinsTotal'
823
+ }],
824
+ });
825
+
826
+ const queryAndParams = query.buildSqlAndParams();
827
+ console.log(queryAndParams);
828
+ const preAggregationsDescription = query.preAggregations.preAggregationsDescription();
829
+ console.log(JSON.stringify(preAggregationsDescription, null, 2));
830
+
831
+ const queries = tempTablePreAggregations(preAggregationsDescription);
832
+
833
+ console.log(JSON.stringify(queries.concat(queryAndParams)));
834
+
835
+ return dbRunner.testQueries(
836
+ queries.concat([queryAndParams]).map(q => replaceTableName(q, preAggregationsDescription, 12342))
837
+ ).then(res => {
838
+ console.log(JSON.stringify(res));
839
+ res.should.be.deepEqual(
840
+ [
841
+ {
842
+ 'visitors__source': 'google',
843
+ 'visitors__checkins_total': '1'
844
+ },
845
+ {
846
+ 'visitors__source': 'some',
847
+ 'visitors__checkins_total': '5'
848
+ }
849
+ ]
850
+ );
851
+ });
852
+ }));
853
+
798
854
  it('partitioned hourly', () => compiler.compile().then(() => {
799
855
  const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
800
856
  measures: [
@@ -164,6 +164,10 @@ describe('SQL Generation', function test() {
164
164
  type: \`geo\`,
165
165
  latitude: { sql: \`latitude\` },
166
166
  longitude: { sql: \`longitude\` }
167
+ },
168
+ questionMark: {
169
+ sql: \`replace('some string question string???', 'string', 'with some ???')\`,
170
+ type: \`string\`
167
171
  }
168
172
  }
169
173
  })
@@ -1646,6 +1650,34 @@ describe('SQL Generation', function test() {
1646
1650
  ])
1647
1651
  );
1648
1652
 
1653
+ it(
1654
+ 'question mark filter',
1655
+ () => runQueryTest({
1656
+ measures: ['visitors.visitor_count'],
1657
+ dimensions: [],
1658
+ timeDimensions: [],
1659
+ timezone: 'America/Los_Angeles',
1660
+ filters: [{
1661
+ or: [{
1662
+ member: 'visitors.questionMark',
1663
+ operator: 'contains',
1664
+ values: ['with some']
1665
+ }, {
1666
+ member: 'visitors.questionMark',
1667
+ operator: 'equals',
1668
+ values: [null]
1669
+ }, {
1670
+ member: 'visitors.questionMark',
1671
+ operator: 'equals',
1672
+ values: [null, 'with some']
1673
+ }]
1674
+ }],
1675
+ order: []
1676
+ }, [
1677
+ { 'visitors__visitor_count': '6' }
1678
+ ])
1679
+ );
1680
+
1649
1681
  const baseQuery = {
1650
1682
  measures: [
1651
1683
  'visitors.countDistinctApproxRolling'