@cubejs-backend/query-orchestrator 0.23.3 → 0.24.2

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 (96) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +2 -2
  3. package/dist/src/driver/BaseDriver.d.ts +36 -0
  4. package/dist/src/driver/BaseDriver.d.ts.map +1 -0
  5. package/dist/src/driver/BaseDriver.js +175 -0
  6. package/dist/src/driver/BaseDriver.js.map +1 -0
  7. package/dist/src/driver/index.d.ts +3 -0
  8. package/dist/src/driver/index.d.ts.map +1 -0
  9. package/dist/src/driver/index.js +15 -0
  10. package/dist/src/driver/index.js.map +1 -0
  11. package/dist/src/driver/utils.d.ts +2 -0
  12. package/dist/src/driver/utils.d.ts.map +1 -0
  13. package/dist/src/driver/utils.js +17 -0
  14. package/dist/src/driver/utils.js.map +1 -0
  15. package/dist/src/index.d.ts +3 -0
  16. package/dist/src/index.d.ts.map +1 -0
  17. package/dist/src/index.js +15 -0
  18. package/dist/src/index.js.map +1 -0
  19. package/dist/src/orchestrator/BaseQueueDriver.d.ts +4 -0
  20. package/dist/src/orchestrator/BaseQueueDriver.d.ts.map +1 -0
  21. package/dist/src/orchestrator/BaseQueueDriver.js +16 -0
  22. package/dist/src/orchestrator/BaseQueueDriver.js.map +1 -0
  23. package/dist/src/orchestrator/ContinueWaitError.d.ts +3 -0
  24. package/dist/src/orchestrator/ContinueWaitError.d.ts.map +1 -0
  25. package/dist/src/orchestrator/ContinueWaitError.js +10 -0
  26. package/dist/src/orchestrator/ContinueWaitError.js.map +1 -0
  27. package/dist/src/orchestrator/LocalCacheDriver.d.ts +8 -0
  28. package/dist/src/orchestrator/LocalCacheDriver.d.ts.map +1 -0
  29. package/dist/src/orchestrator/LocalCacheDriver.js +30 -0
  30. package/dist/src/orchestrator/LocalCacheDriver.js.map +1 -0
  31. package/dist/src/orchestrator/LocalQueueDriver.d.ts +57 -0
  32. package/dist/src/orchestrator/LocalQueueDriver.d.ts.map +1 -0
  33. package/dist/src/orchestrator/LocalQueueDriver.js +230 -0
  34. package/dist/src/orchestrator/LocalQueueDriver.js.map +1 -0
  35. package/dist/src/orchestrator/PreAggregations.d.ts +26 -0
  36. package/dist/src/orchestrator/PreAggregations.d.ts.map +1 -0
  37. package/dist/src/orchestrator/PreAggregations.js +565 -0
  38. package/dist/src/orchestrator/PreAggregations.js.map +1 -0
  39. package/dist/src/orchestrator/QueryCache.d.ts +51 -0
  40. package/dist/src/orchestrator/QueryCache.d.ts.map +1 -0
  41. package/dist/src/orchestrator/QueryCache.js +293 -0
  42. package/dist/src/orchestrator/QueryCache.js.map +1 -0
  43. package/dist/src/orchestrator/QueryOrchestrator.d.ts +27 -0
  44. package/dist/src/orchestrator/QueryOrchestrator.d.ts.map +1 -0
  45. package/dist/src/orchestrator/QueryOrchestrator.js +79 -0
  46. package/dist/src/orchestrator/QueryOrchestrator.js.map +1 -0
  47. package/dist/src/orchestrator/QueryQueue.d.ts +36 -0
  48. package/dist/src/orchestrator/QueryQueue.d.ts.map +1 -0
  49. package/dist/src/orchestrator/QueryQueue.js +351 -0
  50. package/dist/src/orchestrator/QueryQueue.js.map +1 -0
  51. package/dist/src/orchestrator/RedisCacheDriver.d.ts +12 -0
  52. package/dist/src/orchestrator/RedisCacheDriver.d.ts.map +1 -0
  53. package/dist/src/orchestrator/RedisCacheDriver.js +50 -0
  54. package/dist/src/orchestrator/RedisCacheDriver.js.map +1 -0
  55. package/dist/src/orchestrator/RedisFactory.d.ts +3 -0
  56. package/dist/src/orchestrator/RedisFactory.d.ts.map +1 -0
  57. package/dist/src/orchestrator/RedisFactory.js +45 -0
  58. package/dist/src/orchestrator/RedisFactory.js.map +1 -0
  59. package/dist/src/orchestrator/RedisPool.d.ts +10 -0
  60. package/dist/src/orchestrator/RedisPool.d.ts.map +1 -0
  61. package/dist/src/orchestrator/RedisPool.js +57 -0
  62. package/dist/src/orchestrator/RedisPool.js.map +1 -0
  63. package/dist/src/orchestrator/RedisQueueDriver.d.ts +47 -0
  64. package/dist/src/orchestrator/RedisQueueDriver.d.ts.map +1 -0
  65. package/dist/src/orchestrator/RedisQueueDriver.js +253 -0
  66. package/dist/src/orchestrator/RedisQueueDriver.js.map +1 -0
  67. package/dist/src/orchestrator/TimeoutError.d.ts +4 -0
  68. package/dist/src/orchestrator/TimeoutError.d.ts.map +1 -0
  69. package/dist/src/orchestrator/TimeoutError.js +7 -0
  70. package/dist/src/orchestrator/TimeoutError.js.map +1 -0
  71. package/dist/src/orchestrator/index.d.ts +14 -0
  72. package/dist/src/orchestrator/index.d.ts.map +1 -0
  73. package/dist/src/orchestrator/index.js +26 -0
  74. package/dist/src/orchestrator/index.js.map +1 -0
  75. package/driver/BaseDriver.js +5 -221
  76. package/driver/README.md +3 -0
  77. package/driver/utils.js +8 -12
  78. package/orchestrator/BaseQueueDriver.js +5 -8
  79. package/orchestrator/ContinueWaitError.js +6 -5
  80. package/orchestrator/LocalCacheDriver.js +5 -29
  81. package/orchestrator/LocalQueueDriver.js +5 -256
  82. package/orchestrator/PreAggregations.js +4 -764
  83. package/orchestrator/QueryCache.js +5 -381
  84. package/orchestrator/QueryOrchestrator.js +5 -100
  85. package/orchestrator/QueryQueue.js +5 -378
  86. package/orchestrator/README.md +3 -0
  87. package/orchestrator/RedisCacheDriver.js +5 -45
  88. package/orchestrator/RedisFactory.js +6 -46
  89. package/orchestrator/RedisPool.js +5 -49
  90. package/orchestrator/RedisQueueDriver.js +5 -283
  91. package/orchestrator/TimeoutError.js +6 -1
  92. package/package.json +29 -10
  93. package/test/integration/PreAggregations.test.js +0 -337
  94. package/test/integration/QueryQueueRedis.test.js +0 -6
  95. package/test/unit/QueryOrchestrator.test.js +0 -301
  96. package/test/unit/QueryQueue.test.js +0 -249
@@ -1,768 +1,8 @@
1
- const crypto = require('crypto');
2
- const R = require('ramda');
3
- const { cancelCombinator } = require('../driver/utils');
4
- const RedisCacheDriver = require('./RedisCacheDriver');
5
- const LocalCacheDriver = require('./LocalCacheDriver');
1
+ const { PreAggregations } = require('../dist/src/orchestrator/PreAggregations');
6
2
 
7
- const QueryCache = require('./QueryCache');
8
- const ContinueWaitError = require('./ContinueWaitError');
9
-
10
- function encodeTimeStamp(time) {
11
- return Math.floor(time / 1000).toString(32);
12
- }
13
-
14
- function version(cacheKey, isDigestKey = false) {
15
- let result = '';
16
- const hashCharset = 'abcdefghijklmnopqrstuvwxyz012345';
17
- let digestBuffer = Number(cacheKey);
18
- if (!isDigestKey) {
19
- digestBuffer = crypto.createHash('md5').update(JSON.stringify(cacheKey)).digest();
20
- }
21
-
22
- let residue = 0;
23
- let shiftCounter = 0;
24
- for (let i = 0; i < 5; i++) {
25
- const byte = digestBuffer.readUInt8(i);
26
- shiftCounter += 8;
27
- // eslint-disable-next-line operator-assignment,no-bitwise
28
- residue = (byte << (shiftCounter - 8)) | residue;
29
- // eslint-disable-next-line no-bitwise
30
- while (residue >> 5) {
31
- result += hashCharset.charAt(residue % 32);
32
- shiftCounter -= 5;
33
- // eslint-disable-next-line operator-assignment,no-bitwise
34
- residue = residue >> 5;
35
- }
36
- }
37
- result += hashCharset.charAt(residue % 32);
38
- return result;
39
- }
40
-
41
- const tablesToVersionEntries = (schema, tables) => R.sortBy(
42
- table => -table.last_updated_at,
43
- tables.map(table => {
44
- const match = (table.table_name || table.TABLE_NAME).match(/(.+)_(.+)_(.+)_(.+)/);
45
-
46
- if (!match) {
47
- return null;
48
- }
49
-
50
- const entity = {
51
- table_name: `${schema}.${match[1]}`,
52
- content_version: match[2],
53
- structure_version: match[3]
54
- };
55
-
56
- if (match[4].length < 13) {
57
- entity.last_updated_at = parseInt(match[4], 32) * 1000;
58
- entity.naming_version = 2;
59
- } else {
60
- entity.last_updated_at = parseInt(match[4], 10);
61
- }
62
-
63
- return entity;
64
- }).filter(R.identity)
3
+ process.emitWarning(
4
+ 'Using absolute import with @cubejs-backend/query-orchestrator is deprecated',
5
+ 'DeprecationWarning'
65
6
  );
66
7
 
67
- class PreAggregationLoadCache {
68
- constructor(redisPrefix, clientFactory, queryCache, preAggregations, options) {
69
- options = options || {};
70
- this.redisPrefix = redisPrefix;
71
- this.driverFactory = clientFactory;
72
- this.queryCache = queryCache;
73
- this.preAggregations = preAggregations;
74
- this.queryResults = {};
75
- this.cacheDriver = preAggregations.cacheDriver;
76
- this.externalDriverFactory = preAggregations.externalDriverFactory;
77
- this.requestId = options.requestId;
78
- }
79
-
80
- async tablesFromCache(preAggregation, forceRenew) {
81
- let tables = forceRenew ? null : await this.cacheDriver.get(this.tablesRedisKey(preAggregation));
82
- if (!tables) {
83
- tables = await this.preAggregations.getLoadCacheQueue().executeInQueue(
84
- 'query',
85
- `Fetch tables for ${preAggregation.preAggregationsSchema}`,
86
- {
87
- preAggregation, requestId: this.requestId
88
- },
89
- 0,
90
- { requestId: this.requestId }
91
- );
92
- }
93
- return tables;
94
- }
95
-
96
- async fetchTables(preAggregation) {
97
- if (preAggregation.external && !this.externalDriverFactory) {
98
- throw new Error('externalDriverFactory should be set in order to use external pre-aggregations');
99
- }
100
- const client = preAggregation.external ?
101
- await this.externalDriverFactory() :
102
- await this.driverFactory();
103
- const newTables = await client.getTablesQuery(preAggregation.preAggregationsSchema);
104
- await this.cacheDriver.set(
105
- this.tablesRedisKey(preAggregation),
106
- newTables,
107
- this.preAggregations.options.preAggregationsSchemaCacheExpire || 60 * 60
108
- );
109
- return newTables;
110
- }
111
-
112
- tablesRedisKey(preAggregation) {
113
- return `SQL_PRE_AGGREGATIONS_TABLES_${this.redisPrefix}${preAggregation.external ? '_EXT' : ''}`;
114
- }
115
-
116
- async getTablesQuery(preAggregation) {
117
- if (!this.tables) {
118
- this.tables = await this.tablesFromCache(preAggregation);
119
- }
120
- return this.tables;
121
- }
122
-
123
- async getVersionEntries(preAggregation) {
124
- if (!this.versionEntries) {
125
- this.versionEntries = tablesToVersionEntries(
126
- preAggregation.preAggregationsSchema,
127
- await this.getTablesQuery(preAggregation)
128
- );
129
- }
130
- const [active, toProcess, queries] = await this.fetchQueryStageState();
131
- const targetTableNamesInQueue = (Object.keys(queries))
132
- // eslint-disable-next-line no-use-before-define
133
- .map(q => PreAggregations.targetTableName(queries[q].query.newVersionEntry));
134
- return this.versionEntries.filter(
135
- // eslint-disable-next-line no-use-before-define
136
- e => targetTableNamesInQueue.indexOf(PreAggregations.targetTableName(e)) === -1
137
- );
138
- }
139
-
140
- async keyQueryResult(keyQuery, waitForRenew, priority, renewalThreshold) {
141
- if (!this.queryResults[this.queryCache.queryRedisKey(keyQuery)]) {
142
- this.queryResults[this.queryCache.queryRedisKey(keyQuery)] = await this.queryCache.cacheQueryResult(
143
- Array.isArray(keyQuery) ? keyQuery[0] : keyQuery,
144
- Array.isArray(keyQuery) ? keyQuery[1] : [],
145
- keyQuery,
146
- 60 * 60,
147
- {
148
- renewalThreshold:
149
- this.queryCache.options.refreshKeyRenewalThreshold ||
150
- renewalThreshold ||
151
- 2 * 60,
152
- renewalKey: keyQuery,
153
- waitForRenew,
154
- priority,
155
- requestId: this.requestId
156
- }
157
- );
158
- }
159
- return this.queryResults[this.queryCache.queryRedisKey(keyQuery)];
160
- }
161
-
162
- hasKeyQueryResult(keyQuery) {
163
- return !!this.queryResults[this.queryCache.queryRedisKey(keyQuery)];
164
- }
165
-
166
- async getQueryStage(stageQueryKey) {
167
- const queue = this.preAggregations.getQueue();
168
- await this.fetchQueryStageState(queue);
169
- return queue.getQueryStage(stageQueryKey, undefined, this.queryStageState);
170
- }
171
-
172
- async fetchQueryStageState(queue) {
173
- queue = queue || this.preAggregations.getQueue();
174
- if (!this.queryStageState) {
175
- this.queryStageState = await queue.fetchQueryStageState();
176
- }
177
- return this.queryStageState;
178
- }
179
-
180
- async reset(preAggregation) {
181
- this.tables = undefined;
182
- this.queryStageState = undefined;
183
- this.versionEntries = undefined;
184
- await this.cacheDriver.remove(this.tablesRedisKey(preAggregation));
185
- }
186
- }
187
-
188
- class PreAggregationLoader {
189
- constructor(
190
- redisPrefix,
191
- clientFactory,
192
- logger,
193
- queryCache,
194
- preAggregations,
195
- preAggregation,
196
- preAggregationsTablesToTempTables,
197
- loadCache,
198
- options
199
- ) {
200
- options = options || {};
201
- this.redisPrefix = redisPrefix;
202
- this.driverFactory = clientFactory;
203
- this.logger = logger;
204
- this.queryCache = queryCache;
205
- this.preAggregations = preAggregations;
206
- this.preAggregation = preAggregation;
207
- this.preAggregationsTablesToTempTables = preAggregationsTablesToTempTables;
208
- this.loadCache = loadCache;
209
- this.waitForRenew = options.waitForRenew;
210
- this.externalDriverFactory = preAggregations.externalDriverFactory;
211
- this.requestId = options.requestId;
212
- this.structureVersionPersistTime = preAggregations.structureVersionPersistTime;
213
- this.externalRefresh = options.externalRefresh;
214
- if (this.externalRefresh && this.waitForRenew) {
215
- const message = 'Invalid configuration - when externalRefresh is true, it will not perform a renew, therefore you cannot wait for it using waitForRenew.';
216
- if (['production', 'test'].includes(process.env.NODE_ENV)) {
217
- throw new Error(message);
218
- } else {
219
- this.logger('Invalid Configuration', {
220
- requestId: this.requestId,
221
- warning: message,
222
- });
223
- this.waitForRenew = false;
224
- }
225
- }
226
- }
227
-
228
- async loadPreAggregation() {
229
- const notLoadedKey = (this.preAggregation.invalidateKeyQueries || [])
230
- .find(keyQuery => !this.loadCache.hasKeyQueryResult(keyQuery));
231
- if (notLoadedKey && !this.waitForRenew) {
232
- const structureVersion = this.structureVersion();
233
-
234
- const getVersionsStarted = new Date();
235
- const versionEntries = await this.loadCache.getVersionEntries(this.preAggregation);
236
- this.logger('Load PreAggregations Tables', {
237
- preAggregation: this.preAggregation,
238
- requestId: this.requestId,
239
- duration: (new Date().getTime() - getVersionsStarted.getTime())
240
- });
241
-
242
- const versionEntryByStructureVersion = versionEntries.find(
243
- v => v.table_name === this.preAggregation.tableName && v.structure_version === structureVersion
244
- );
245
- if (this.externalRefresh) {
246
- if (!versionEntryByStructureVersion) {
247
- throw new Error('One or more pre-aggregation tables could not be found to satisfy that query');
248
- }
249
-
250
- // the rollups are being maintained independently of this instance of cube.js,
251
- // immediately return the latest data it already has
252
- return this.targetTableName(versionEntryByStructureVersion);
253
- }
254
-
255
- if (versionEntryByStructureVersion) {
256
- // this triggers an asyncronous/background load of the pre-aggregation but immediately
257
- // returns the latest data it already has
258
- this.loadPreAggregationWithKeys().catch(e => {
259
- if (!(e instanceof ContinueWaitError)) {
260
- this.logger('Error loading pre-aggregation', {
261
- error: (e.stack || e),
262
- preAggregation: this.preAggregation,
263
- requestId: this.requestId
264
- });
265
- }
266
- });
267
- return this.targetTableName(versionEntryByStructureVersion);
268
- } else {
269
- // no rollup has been built yet - build it syncronously as part of responding to this request
270
- return this.loadPreAggregationWithKeys();
271
- }
272
- } else {
273
- // either we have no data cached for this rollup or waitForRenew is true, either way,
274
- // syncronously renew what data is needed so that the most current data will be returned for the current request
275
- return {
276
- targetTableName: await this.loadPreAggregationWithKeys(),
277
- refreshKeyValues: await this.getInvalidationKeyValues()
278
- };
279
- }
280
- }
281
-
282
- async loadPreAggregationWithKeys() {
283
- const invalidationKeys = await this.getInvalidationKeyValues();
284
- const contentVersion = this.contentVersion(invalidationKeys);
285
- const structureVersion = this.structureVersion();
286
-
287
- const versionEntries = await this.loadCache.getVersionEntries(this.preAggregation);
288
-
289
- const getVersionEntryByContentVersion = (entries) => entries.find(
290
- v => v.table_name === this.preAggregation.tableName && v.content_version === contentVersion
291
- );
292
-
293
- const versionEntryByContentVersion = getVersionEntryByContentVersion(versionEntries);
294
- if (versionEntryByContentVersion) {
295
- return this.targetTableName(versionEntryByContentVersion);
296
- }
297
-
298
- // TODO this check can be redundant due to structure version is already checked in loadPreAggregation()
299
- if (
300
- !this.waitForRenew &&
301
- // eslint-disable-next-line no-use-before-define
302
- await this.loadCache.getQueryStage(PreAggregations.preAggregationQueryCacheKey(this.preAggregation))
303
- ) {
304
- const versionEntryByStructureVersion = versionEntries.find(
305
- v => v.table_name === this.preAggregation.tableName && v.structure_version === structureVersion
306
- );
307
- if (versionEntryByStructureVersion) {
308
- return this.targetTableName(versionEntryByStructureVersion);
309
- }
310
- }
311
-
312
- if (!versionEntries.length) {
313
- const client = this.preAggregation.external ?
314
- await this.externalDriverFactory() :
315
- await this.driverFactory();
316
- await client.createSchemaIfNotExists(this.preAggregation.preAggregationsSchema);
317
- }
318
- // ensure we find appropriate structure version before invalidating anything
319
- const versionEntry = versionEntries.find(
320
- v => v.table_name === this.preAggregation.tableName && v.structure_version === structureVersion
321
- ) || versionEntries.find(
322
- e => e.table_name === this.preAggregation.tableName
323
- );
324
-
325
- const newVersionEntry = {
326
- table_name: this.preAggregation.tableName,
327
- structure_version: structureVersion,
328
- content_version: contentVersion,
329
- last_updated_at: new Date().getTime(),
330
- naming_version: 2,
331
- };
332
-
333
- const mostRecentTargetTableName = async () => {
334
- await this.loadCache.reset(this.preAggregation);
335
- const lastVersion = getVersionEntryByContentVersion(
336
- await this.loadCache.getVersionEntries(this.preAggregation)
337
- );
338
- if (!lastVersion) {
339
- throw new Error(`Pre-aggregation table is not found for ${this.preAggregation.tableName} after it was successfully created. It usually means database silently truncates table names due to max name length.`);
340
- }
341
- return this.targetTableName(lastVersion);
342
- };
343
-
344
- if (versionEntry) {
345
- if (versionEntry.structure_version !== newVersionEntry.structure_version) {
346
- this.logger('Invalidating pre-aggregation structure', {
347
- preAggregation: this.preAggregation,
348
- requestId: this.requestId,
349
- queryKey: this.preAggregationQueryKey(invalidationKeys),
350
- newVersionEntry
351
- });
352
- await this.executeInQueue(invalidationKeys, this.priority(10), newVersionEntry);
353
- return mostRecentTargetTableName();
354
- } else if (versionEntry.content_version !== newVersionEntry.content_version) {
355
- if (this.waitForRenew) {
356
- this.logger('Waiting for pre-aggregation renew', {
357
- preAggregation: this.preAggregation,
358
- requestId: this.requestId,
359
- queryKey: this.preAggregationQueryKey(invalidationKeys),
360
- newVersionEntry
361
- });
362
- await this.executeInQueue(invalidationKeys, this.priority(0), newVersionEntry);
363
- return mostRecentTargetTableName();
364
- } else {
365
- this.scheduleRefresh(invalidationKeys, newVersionEntry);
366
- }
367
- }
368
- } else {
369
- this.logger('Creating pre-aggregation from scratch', {
370
- preAggregation: this.preAggregation,
371
- requestId: this.requestId,
372
- queryKey: this.preAggregationQueryKey(invalidationKeys),
373
- newVersionEntry
374
- });
375
- await this.executeInQueue(invalidationKeys, this.priority(10), newVersionEntry);
376
- return mostRecentTargetTableName();
377
- }
378
- return this.targetTableName(versionEntry);
379
- }
380
-
381
- contentVersion(invalidationKeys) {
382
- return version(
383
- this.preAggregation.indexesSql && this.preAggregation.indexesSql.length ?
384
- [this.preAggregation.loadSql, this.preAggregation.indexesSql, invalidationKeys] :
385
- [this.preAggregation.loadSql, invalidationKeys]
386
- );
387
- }
388
-
389
- structureVersion() {
390
- return version(
391
- this.preAggregation.indexesSql && this.preAggregation.indexesSql.length ?
392
- [this.preAggregation.loadSql, this.preAggregation.indexesSql] :
393
- this.preAggregation.loadSql
394
- );
395
- }
396
-
397
- priority(defaultValue) {
398
- return this.preAggregation.priority != null ? this.preAggregation.priority : defaultValue;
399
- }
400
-
401
- getInvalidationKeyValues() {
402
- return Promise.all(
403
- (this.preAggregation.invalidateKeyQueries || [])
404
- .map(
405
- (keyQuery, i) => this.loadCache.keyQueryResult(
406
- keyQuery,
407
- this.waitForRenew,
408
- this.priority(10),
409
- (this.preAggregation.refreshKeyRenewalThresholds || [])[i]
410
- )
411
- )
412
- );
413
- }
414
-
415
- scheduleRefresh(invalidationKeys, newVersionEntry) {
416
- this.logger('Refreshing pre-aggregation content', {
417
- preAggregation: this.preAggregation,
418
- requestId: this.requestId,
419
- queryKey: this.preAggregationQueryKey(invalidationKeys),
420
- newVersionEntry
421
- });
422
- this.executeInQueue(invalidationKeys, this.priority(0), newVersionEntry)
423
- .catch(e => {
424
- if (!(e instanceof ContinueWaitError)) {
425
- this.logger('Error refreshing pre-aggregation', {
426
- error: (e.stack || e), preAggregation: this.preAggregation, requestId: this.requestId
427
- });
428
- }
429
- });
430
- }
431
-
432
- executeInQueue(invalidationKeys, priority, newVersionEntry) {
433
- return this.preAggregations.getQueue().executeInQueue(
434
- 'query',
435
- this.preAggregationQueryKey(invalidationKeys),
436
- {
437
- preAggregation: this.preAggregation,
438
- preAggregationsTablesToTempTables: this.preAggregationsTablesToTempTables,
439
- newVersionEntry,
440
- requestId: this.requestId,
441
- invalidationKeys
442
- },
443
- priority,
444
- // eslint-disable-next-line no-use-before-define
445
- { stageQueryKey: PreAggregations.preAggregationQueryCacheKey(this.preAggregation), requestId: this.requestId }
446
- );
447
- }
448
-
449
- preAggregationQueryKey(invalidationKeys) {
450
- return [this.preAggregation.loadSql, invalidationKeys];
451
- }
452
-
453
- targetTableName(versionEntry) {
454
- // eslint-disable-next-line no-use-before-define
455
- return PreAggregations.targetTableName(versionEntry);
456
- }
457
-
458
- refresh(newVersionEntry, invalidationKeys) {
459
- return (client) => {
460
- let refreshStrategy = this.refreshImplStoreInSourceStrategy;
461
- if (this.preAggregation.external) {
462
- const readOnly =
463
- client.config && client.config.readOnly ||
464
- client.readOnly && (typeof client.readOnly === 'boolean' ? client.readOnly : client.readOnly());
465
- refreshStrategy = readOnly ?
466
- this.refreshImplStreamExternalStrategy : this.refreshImplTempTableExternalStrategy;
467
- }
468
- return cancelCombinator(
469
- saveCancelFn => refreshStrategy.bind(this)(client, newVersionEntry, saveCancelFn, invalidationKeys)
470
- );
471
- };
472
- }
473
-
474
- logExecutingSql(invalidationKeys, query, params, targetTableName, newVersionEntry) {
475
- this.logger(
476
- 'Executing Load Pre Aggregation SQL',
477
- this.queryOptions(invalidationKeys, query, params, targetTableName, newVersionEntry)
478
- );
479
- }
480
-
481
- queryOptions(invalidationKeys, query, params, targetTableName, newVersionEntry) {
482
- return {
483
- queryKey: this.preAggregationQueryKey(invalidationKeys),
484
- query,
485
- values: params,
486
- targetTableName,
487
- requestId: this.requestId,
488
- newVersionEntry,
489
- };
490
- }
491
-
492
- async refreshImplStoreInSourceStrategy(client, newVersionEntry, saveCancelFn, invalidationKeys) {
493
- const [loadSql, params] =
494
- Array.isArray(this.preAggregation.loadSql) ? this.preAggregation.loadSql : [this.preAggregation.loadSql, []];
495
- const targetTableName = this.targetTableName(newVersionEntry);
496
- const query = QueryCache.replacePreAggregationTableNames(loadSql, this.preAggregationsTablesToTempTables)
497
- .replace(
498
- this.preAggregation.tableName,
499
- targetTableName
500
- );
501
- this.logExecutingSql(invalidationKeys, query, params, targetTableName, newVersionEntry);
502
- await saveCancelFn(client.loadPreAggregationIntoTable(
503
- targetTableName,
504
- query,
505
- params,
506
- this.queryOptions(invalidationKeys, query, params, targetTableName, newVersionEntry)
507
- ));
508
- await this.createIndexes(client, newVersionEntry, saveCancelFn);
509
- await this.loadCache.reset(this.preAggregation);
510
- await this.dropOrphanedTables(client, targetTableName, saveCancelFn);
511
- await this.loadCache.reset(this.preAggregation);
512
- }
513
-
514
- async refreshImplTempTableExternalStrategy(client, newVersionEntry, saveCancelFn, invalidationKeys) {
515
- const [loadSql, params] =
516
- Array.isArray(this.preAggregation.loadSql) ? this.preAggregation.loadSql : [this.preAggregation.loadSql, []];
517
- await client.createSchemaIfNotExists(this.preAggregation.preAggregationsSchema);
518
- const targetTableName = this.targetTableName(newVersionEntry);
519
- const query = QueryCache.replacePreAggregationTableNames(loadSql, this.preAggregationsTablesToTempTables)
520
- .replace(
521
- this.preAggregation.tableName,
522
- targetTableName
523
- );
524
- this.logExecutingSql(invalidationKeys, query, params, targetTableName, newVersionEntry);
525
- await saveCancelFn(client.loadPreAggregationIntoTable(
526
- targetTableName,
527
- query,
528
- params,
529
- this.queryOptions(invalidationKeys, query, params, targetTableName, newVersionEntry)
530
- ));
531
- const tableData = await this.downloadTempExternalPreAggregation(client, newVersionEntry, saveCancelFn);
532
- await this.uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn);
533
- await this.loadCache.reset(this.preAggregation);
534
- await this.dropOrphanedTables(client, targetTableName, saveCancelFn);
535
- }
536
-
537
- async refreshImplStreamExternalStrategy(client, newVersionEntry, saveCancelFn, invalidationKeys) {
538
- const [sql, params] =
539
- Array.isArray(this.preAggregation.sql) ? this.preAggregation.sql : [this.preAggregation.sql, []];
540
- if (!client.downloadQueryResults) {
541
- throw new Error('Can\'t load external pre-aggregation: source driver doesn\'t support downloadQueryResults()');
542
- }
543
-
544
- this.logExecutingSql(invalidationKeys, sql, params, this.targetTableName(newVersionEntry), newVersionEntry);
545
- this.logger('Downloading external pre-aggregation via query', {
546
- preAggregation: this.preAggregation,
547
- requestId: this.requestId
548
- });
549
- const tableData = await saveCancelFn(client.downloadQueryResults(
550
- sql,
551
- params,
552
- this.queryOptions(invalidationKeys, sql, params, this.targetTableName(newVersionEntry), newVersionEntry)
553
- ));
554
- await this.uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn);
555
- await this.loadCache.reset(this.preAggregation);
556
- }
557
-
558
- async downloadTempExternalPreAggregation(client, newVersionEntry, saveCancelFn) {
559
- if (!client.downloadTable) {
560
- throw new Error('Can\'t load external pre-aggregation: source driver doesn\'t support downloadTable()');
561
- }
562
- const table = this.targetTableName(newVersionEntry);
563
- this.logger('Downloading external pre-aggregation', {
564
- preAggregation: this.preAggregation,
565
- requestId: this.requestId
566
- });
567
- const tableData = await saveCancelFn(client.downloadTable(table));
568
- tableData.types = await saveCancelFn(client.tableColumnTypes(table));
569
- return tableData;
570
- }
571
-
572
- async uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn) {
573
- const table = this.targetTableName(newVersionEntry);
574
- const externalDriver = await this.externalDriverFactory();
575
- if (!externalDriver.uploadTable) {
576
- throw new Error('Can\'t load external pre-aggregation: destination driver doesn\'t support uploadTable()');
577
- }
578
- this.logger('Uploading external pre-aggregation', {
579
- preAggregation: this.preAggregation,
580
- requestId: this.requestId
581
- });
582
- await saveCancelFn(externalDriver.uploadTable(table, tableData.types, tableData));
583
- await this.createIndexes(externalDriver, newVersionEntry, saveCancelFn);
584
- await this.loadCache.reset(this.preAggregation);
585
- await this.dropOrphanedTables(externalDriver, table, saveCancelFn);
586
- }
587
-
588
- async createIndexes(driver, newVersionEntry, saveCancelFn) {
589
- if (!this.preAggregation.indexesSql || !this.preAggregation.indexesSql.length) {
590
- return;
591
- }
592
- for (let i = 0; i < this.preAggregation.indexesSql.length; i++) {
593
- const { sql, indexName } = this.preAggregation.indexesSql[i];
594
- const [query, params] = sql;
595
- const indexVersionEntry = {
596
- ...newVersionEntry,
597
- table_name: indexName
598
- };
599
- this.logger('Creating pre-aggregation index', {
600
- preAggregation: this.preAggregation,
601
- requestId: this.requestId,
602
- sql
603
- });
604
- await saveCancelFn(driver.query(
605
- QueryCache.replacePreAggregationTableNames(
606
- query,
607
- this.preAggregationsTablesToTempTables.concat([
608
- [this.preAggregation.tableName, { targetTableName: this.targetTableName(newVersionEntry) }],
609
- [indexName, { targetTableName: this.targetTableName(indexVersionEntry) }]
610
- ])
611
- ),
612
- params
613
- ));
614
- }
615
- }
616
-
617
- async dropOrphanedTables(client, justCreatedTable, saveCancelFn) {
618
- await this.preAggregations.addTableUsed(justCreatedTable);
619
- const actualTables = await client.getTablesQuery(this.preAggregation.preAggregationsSchema);
620
- const versionEntries = tablesToVersionEntries(this.preAggregation.preAggregationsSchema, actualTables);
621
- const versionEntriesToSave = R.pipe(
622
- R.groupBy(v => v.table_name),
623
- R.toPairs,
624
- R.map(p => p[1][0])
625
- )(versionEntries);
626
-
627
- const structureVersionsToSave = R.pipe(
628
- R.filter(v => new Date().getTime() - v.last_updated_at < this.structureVersionPersistTime * 1000),
629
- R.groupBy(v => `${v.table_name}_${v.structure_version}`),
630
- R.toPairs,
631
- R.map(p => p[1][0])
632
- )(versionEntries);
633
-
634
- const tablesToSave =
635
- (await this.preAggregations.tablesUsed())
636
- .concat(structureVersionsToSave.map(v => this.targetTableName(v)))
637
- .concat(versionEntriesToSave.map(v => this.targetTableName(v)))
638
- .concat([justCreatedTable]);
639
- const toDrop = actualTables
640
- .map(t => `${this.preAggregation.preAggregationsSchema}.${t.table_name || t.TABLE_NAME}`)
641
- .filter(t => tablesToSave.indexOf(t) === -1);
642
- this.logger('Dropping orphaned tables', {
643
- tablesToDrop: JSON.stringify(toDrop),
644
- requestId: this.requestId
645
- });
646
- await Promise.all(toDrop.map(table => saveCancelFn(client.dropTable(table))));
647
- }
648
- }
649
-
650
- class PreAggregations {
651
- constructor(redisPrefix, clientFactory, logger, queryCache, options) {
652
- this.options = options || {};
653
- this.redisPrefix = redisPrefix;
654
- this.driverFactory = clientFactory;
655
- this.logger = logger;
656
- this.queryCache = queryCache;
657
- this.cacheDriver = options.cacheAndQueueDriver === 'redis' ?
658
- new RedisCacheDriver({ pool: options.redisPool }) :
659
- new LocalCacheDriver();
660
- this.externalDriverFactory = options.externalDriverFactory;
661
- this.structureVersionPersistTime = options.structureVersionPersistTime || 60 * 60 * 24 * 30;
662
- this.usedTablePersistTime = options.usedTablePersistTime || 600;
663
- this.externalRefresh = options.externalRefresh;
664
- }
665
-
666
- tablesUsedRedisKey(tableName) {
667
- return `SQL_PRE_AGGREGATIONS_${this.redisPrefix}_TABLES_USED_${tableName}`;
668
- }
669
-
670
- async addTableUsed(tableName) {
671
- return this.cacheDriver.set(this.tablesUsedRedisKey(tableName), true, this.usedTablePersistTime);
672
- }
673
-
674
- async tablesUsed() {
675
- return (await this.cacheDriver.keysStartingWith(this.tablesUsedRedisKey('')))
676
- .map(k => k.replace(this.tablesUsedRedisKey(''), ''));
677
- }
678
-
679
- loadAllPreAggregationsIfNeeded(queryBody) {
680
- const preAggregations = queryBody.preAggregations || [];
681
- const loadCache = new PreAggregationLoadCache(this.redisPrefix, this.driverFactory, this.queryCache, this, {
682
- requestId: queryBody.requestId
683
- });
684
- return preAggregations.map(p => (preAggregationsTablesToTempTables) => {
685
- const loader = new PreAggregationLoader(
686
- this.redisPrefix,
687
- this.driverFactory,
688
- this.logger,
689
- this.queryCache,
690
- this,
691
- p,
692
- preAggregationsTablesToTempTables,
693
- loadCache,
694
- { waitForRenew: queryBody.renewQuery, requestId: queryBody.requestId, externalRefresh: this.externalRefresh }
695
- );
696
- const preAggregationPromise = () => loader.loadPreAggregation().then(async targetTableName => {
697
- const usedPreAggregation = typeof targetTableName === 'string' ? { targetTableName } : targetTableName;
698
- await this.addTableUsed(usedPreAggregation.targetTableName);
699
- return [p.tableName, usedPreAggregation];
700
- });
701
- return preAggregationPromise().then(res => preAggregationsTablesToTempTables.concat([res]));
702
- }).reduce((promise, fn) => promise.then(fn), Promise.resolve([]));
703
- }
704
-
705
- getQueue() {
706
- if (!this.queue) {
707
- this.queue = QueryCache.createQueue(`SQL_PRE_AGGREGATIONS_${this.redisPrefix}`, this.driverFactory, (client, q) => {
708
- const {
709
- preAggregation, preAggregationsTablesToTempTables, newVersionEntry, requestId, invalidationKeys
710
- } = q;
711
- const loader = new PreAggregationLoader(
712
- this.redisPrefix,
713
- this.driverFactory,
714
- this.logger,
715
- this.queryCache,
716
- this,
717
- preAggregation,
718
- preAggregationsTablesToTempTables,
719
- new PreAggregationLoadCache(this.redisPrefix, this.driverFactory, this.queryCache, this, { requestId }),
720
- { requestId, externalRefresh: this.externalRefresh }
721
- );
722
- return loader.refresh(newVersionEntry, invalidationKeys)(client);
723
- }, {
724
- concurrency: 1,
725
- logger: this.logger,
726
- cacheAndQueueDriver: this.options.cacheAndQueueDriver,
727
- redisPool: this.options.redisPool,
728
- ...this.options.queueOptions
729
- });
730
- }
731
- return this.queue;
732
- }
733
-
734
- getLoadCacheQueue() {
735
- if (!this.loadCacheQueue) {
736
- this.loadCacheQueue = QueryCache.createQueue(`SQL_PRE_AGGREGATIONS_CACHE_${this.redisPrefix}`, () => {}, (_, q) => {
737
- const {
738
- preAggregation,
739
- requestId
740
- } = q;
741
- const loadCache = new PreAggregationLoadCache(this.redisPrefix, this.driverFactory, this.queryCache, this,
742
- { requestId });
743
- return loadCache.fetchTables(preAggregation);
744
- }, {
745
- concurrency: 4,
746
- logger: this.logger,
747
- cacheAndQueueDriver: this.options.cacheAndQueueDriver,
748
- redisPool: this.options.redisPool,
749
- ...this.options.loadCacheQueueOptions
750
- });
751
- }
752
- return this.loadCacheQueue;
753
- }
754
-
755
- static preAggregationQueryCacheKey(preAggregation) {
756
- return preAggregation.tableName;
757
- }
758
-
759
- static targetTableName(versionEntry) {
760
- if (versionEntry.naming_version === 2) {
761
- return `${versionEntry.table_name}_${versionEntry.content_version}_${versionEntry.structure_version}_${encodeTimeStamp(versionEntry.last_updated_at)}`;
762
- }
763
-
764
- return `${versionEntry.table_name}_${versionEntry.content_version}_${versionEntry.structure_version}_${versionEntry.last_updated_at}`;
765
- }
766
- }
767
-
768
8
  module.exports = PreAggregations;