@cubejs-backend/query-orchestrator 1.2.13 → 1.2.14
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/dist/src/orchestrator/PreAggregationLoadCache.d.ts +42 -0
- package/dist/src/orchestrator/PreAggregationLoadCache.d.ts.map +1 -0
- package/dist/src/orchestrator/PreAggregationLoadCache.js +172 -0
- package/dist/src/orchestrator/PreAggregationLoadCache.js.map +1 -0
- package/dist/src/orchestrator/PreAggregationLoader.d.ts +105 -0
- package/dist/src/orchestrator/PreAggregationLoader.d.ts.map +1 -0
- package/dist/src/orchestrator/PreAggregationLoader.js +742 -0
- package/dist/src/orchestrator/PreAggregationLoader.js.map +1 -0
- package/dist/src/orchestrator/PreAggregationPartitionRangeLoader.d.ts +65 -0
- package/dist/src/orchestrator/PreAggregationPartitionRangeLoader.d.ts.map +1 -0
- package/dist/src/orchestrator/PreAggregationPartitionRangeLoader.js +355 -0
- package/dist/src/orchestrator/PreAggregationPartitionRangeLoader.js.map +1 -0
- package/dist/src/orchestrator/PreAggregations.d.ts +35 -223
- package/dist/src/orchestrator/PreAggregations.d.ts.map +1 -1
- package/dist/src/orchestrator/PreAggregations.js +17 -1261
- package/dist/src/orchestrator/PreAggregations.js.map +1 -1
- package/dist/src/orchestrator/QueryCache.d.ts +14 -4
- package/dist/src/orchestrator/QueryCache.d.ts.map +1 -1
- package/dist/src/orchestrator/QueryCache.js +1 -1
- package/dist/src/orchestrator/QueryCache.js.map +1 -1
- package/dist/src/orchestrator/QueryQueue.js +1 -1
- package/dist/src/orchestrator/QueryQueue.js.map +1 -1
- package/dist/src/orchestrator/index.d.ts +3 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +3 -0
- package/dist/src/orchestrator/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -3,15 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PreAggregations = exports.
|
|
7
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
6
|
+
exports.PreAggregations = exports.tablesToVersionEntries = exports.getStructureVersion = exports.getLastUpdatedAtTimestamp = exports.version = exports.LAMBDA_TABLE_PREFIX = void 0;
|
|
8
7
|
const ramda_1 = __importDefault(require("ramda"));
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
9
|
const shared_1 = require("@cubejs-backend/shared");
|
|
10
|
-
const base_driver_1 = require("@cubejs-backend/base-driver");
|
|
11
10
|
const lru_cache_1 = __importDefault(require("lru-cache"));
|
|
12
11
|
const QueryCache_1 = require("./QueryCache");
|
|
13
|
-
const
|
|
14
|
-
const
|
|
12
|
+
const PreAggregationPartitionRangeLoader_1 = require("./PreAggregationPartitionRangeLoader");
|
|
13
|
+
const PreAggregationLoader_1 = require("./PreAggregationLoader");
|
|
14
|
+
const PreAggregationLoadCache_1 = require("./PreAggregationLoadCache");
|
|
15
15
|
/// Name of the inline table containing the lambda rows.
|
|
16
16
|
exports.LAMBDA_TABLE_PREFIX = 'lambda';
|
|
17
17
|
function encodeTimeStamp(time) {
|
|
@@ -42,11 +42,7 @@ function version(cacheKey) {
|
|
|
42
42
|
result += hashCharset.charAt(residue % 32);
|
|
43
43
|
return result;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
// Extra defence for drivers that don't expose now() yet.
|
|
47
|
-
function nowTimestamp(client) {
|
|
48
|
-
return client.nowTimestamp?.() ?? new Date().getTime();
|
|
49
|
-
}
|
|
45
|
+
exports.version = version;
|
|
50
46
|
// Returns the oldest timestamp, if any.
|
|
51
47
|
function getLastUpdatedAtTimestamp(timestamps) {
|
|
52
48
|
timestamps = timestamps.filter(t => t !== undefined);
|
|
@@ -71,6 +67,7 @@ function getStructureVersion(preAggregation) {
|
|
|
71
67
|
}
|
|
72
68
|
return version(versionArray.length === 1 ? versionArray[0] : versionArray);
|
|
73
69
|
}
|
|
70
|
+
exports.getStructureVersion = getStructureVersion;
|
|
74
71
|
const tablesToVersionEntries = (schema, tables) => ramda_1.default.sortBy(table => -table.last_updated_at, tables.map(table => {
|
|
75
72
|
const match = (table.table_name || table.TABLE_NAME).match(/(.+)_(.+)_(.+)_(.+)/);
|
|
76
73
|
if (!match) {
|
|
@@ -93,1247 +90,7 @@ const tablesToVersionEntries = (schema, tables) => ramda_1.default.sortBy(table
|
|
|
93
90
|
}
|
|
94
91
|
return entity;
|
|
95
92
|
}).filter(ramda_1.default.identity));
|
|
96
|
-
|
|
97
|
-
redisPrefix;
|
|
98
|
-
driverFactory;
|
|
99
|
-
queryCache;
|
|
100
|
-
// eslint-disable-next-line no-use-before-define
|
|
101
|
-
preAggregations;
|
|
102
|
-
queryResults;
|
|
103
|
-
externalDriverFactory;
|
|
104
|
-
requestId;
|
|
105
|
-
versionEntries;
|
|
106
|
-
tables;
|
|
107
|
-
tableColumnTypes;
|
|
108
|
-
// TODO this is in memory cache structure as well however it depends on
|
|
109
|
-
// data source only and load cache is per data source for now.
|
|
110
|
-
// Make it per data source key in case load cache scope is broaden.
|
|
111
|
-
queryStageState;
|
|
112
|
-
dataSource;
|
|
113
|
-
tablePrefixes;
|
|
114
|
-
constructor(redisPrefix, clientFactory, queryCache, preAggregations, options = { dataSource: 'default' }) {
|
|
115
|
-
this.redisPrefix = `${redisPrefix}_${options.dataSource}`;
|
|
116
|
-
this.dataSource = options.dataSource;
|
|
117
|
-
this.driverFactory = clientFactory;
|
|
118
|
-
this.queryCache = queryCache;
|
|
119
|
-
this.preAggregations = preAggregations;
|
|
120
|
-
this.queryResults = {};
|
|
121
|
-
this.externalDriverFactory = preAggregations.externalDriverFactory;
|
|
122
|
-
this.requestId = options.requestId;
|
|
123
|
-
this.tablePrefixes = options.tablePrefixes;
|
|
124
|
-
this.versionEntries = {};
|
|
125
|
-
this.tables = {};
|
|
126
|
-
this.tableColumnTypes = {};
|
|
127
|
-
}
|
|
128
|
-
async tablesFromCache(preAggregation, forceRenew) {
|
|
129
|
-
let tables = forceRenew ? null : await this.queryCache.getCacheDriver().get(this.tablesCachePrefixKey(preAggregation));
|
|
130
|
-
if (!tables) {
|
|
131
|
-
tables = await this.preAggregations.getLoadCacheQueue(this.dataSource).executeInQueue('query', `Fetch tables for ${preAggregation.preAggregationsSchema}`, {
|
|
132
|
-
preAggregation, requestId: this.requestId
|
|
133
|
-
}, 0, { requestId: this.requestId });
|
|
134
|
-
}
|
|
135
|
-
return tables;
|
|
136
|
-
}
|
|
137
|
-
async fetchTables(preAggregation) {
|
|
138
|
-
if (preAggregation.external && !this.externalDriverFactory) {
|
|
139
|
-
throw new Error('externalDriverFactory is not provided. Please use CUBEJS_DEV_MODE=true or provide Cube Store connection env variables for production usage.');
|
|
140
|
-
}
|
|
141
|
-
const newTables = await this.fetchTablesNoCache(preAggregation);
|
|
142
|
-
await this.queryCache.getCacheDriver().set(this.tablesCachePrefixKey(preAggregation), newTables, this.preAggregations.options.preAggregationsSchemaCacheExpire || 60 * 60);
|
|
143
|
-
return newTables;
|
|
144
|
-
}
|
|
145
|
-
async fetchTablesNoCache(preAggregation) {
|
|
146
|
-
const client = preAggregation.external ?
|
|
147
|
-
await this.externalDriverFactory() :
|
|
148
|
-
await this.driverFactory();
|
|
149
|
-
if (this.tablePrefixes && client.getPrefixTablesQuery && this.preAggregations.options.skipExternalCacheAndQueue) {
|
|
150
|
-
return client.getPrefixTablesQuery(preAggregation.preAggregationsSchema, this.tablePrefixes);
|
|
151
|
-
}
|
|
152
|
-
return client.getTablesQuery(preAggregation.preAggregationsSchema);
|
|
153
|
-
}
|
|
154
|
-
tablesCachePrefixKey(preAggregation) {
|
|
155
|
-
return this.queryCache.getKey('SQL_PRE_AGGREGATIONS_TABLES', `${preAggregation.dataSource}${preAggregation.preAggregationsSchema}${preAggregation.external ? '_EXT' : ''}`);
|
|
156
|
-
}
|
|
157
|
-
async getTablesQuery(preAggregation) {
|
|
158
|
-
const redisKey = this.tablesCachePrefixKey(preAggregation);
|
|
159
|
-
if (!this.tables[redisKey]) {
|
|
160
|
-
const tables = this.preAggregations.options.skipExternalCacheAndQueue && preAggregation.external ?
|
|
161
|
-
await this.fetchTablesNoCache(preAggregation) :
|
|
162
|
-
await this.tablesFromCache(preAggregation);
|
|
163
|
-
if (tables === undefined) {
|
|
164
|
-
throw new Error('Pre-aggregation tables are undefined.');
|
|
165
|
-
}
|
|
166
|
-
this.tables[redisKey] = tables;
|
|
167
|
-
}
|
|
168
|
-
return this.tables[redisKey];
|
|
169
|
-
}
|
|
170
|
-
async getTableColumnTypes(preAggregation, tableName) {
|
|
171
|
-
const prefixKey = this.tablesCachePrefixKey(preAggregation);
|
|
172
|
-
if (!this.tableColumnTypes[prefixKey]?.[tableName]) {
|
|
173
|
-
if (!this.preAggregations.options.skipExternalCacheAndQueue && preAggregation.external) {
|
|
174
|
-
throw new Error(`Lambda union with source data feature is supported only by external rollups stored in Cube Store but was invoked for '${preAggregation.preAggregationId}'`);
|
|
175
|
-
}
|
|
176
|
-
const client = await this.externalDriverFactory();
|
|
177
|
-
const columnTypes = await client.tableColumnTypes(tableName);
|
|
178
|
-
if (!this.tableColumnTypes[prefixKey]) {
|
|
179
|
-
this.tableColumnTypes[prefixKey] = {};
|
|
180
|
-
}
|
|
181
|
-
this.tableColumnTypes[prefixKey][tableName] = columnTypes;
|
|
182
|
-
}
|
|
183
|
-
return this.tableColumnTypes[prefixKey][tableName];
|
|
184
|
-
}
|
|
185
|
-
async calculateVersionEntries(preAggregation) {
|
|
186
|
-
let versionEntries = tablesToVersionEntries(preAggregation.preAggregationsSchema, await this.getTablesQuery(preAggregation));
|
|
187
|
-
// It presumes strong consistency guarantees for external pre-aggregation tables ingestion
|
|
188
|
-
if (!preAggregation.external) {
|
|
189
|
-
// eslint-disable-next-line
|
|
190
|
-
const [active, toProcess, queries] = await this.fetchQueryStageState();
|
|
191
|
-
const targetTableNamesInQueue = (Object.keys(queries))
|
|
192
|
-
// eslint-disable-next-line no-use-before-define
|
|
193
|
-
.map(q => PreAggregations.targetTableName(queries[q].query.newVersionEntry));
|
|
194
|
-
versionEntries = versionEntries.filter(
|
|
195
|
-
// eslint-disable-next-line no-use-before-define
|
|
196
|
-
e => targetTableNamesInQueue.indexOf(PreAggregations.targetTableName(e)) === -1);
|
|
197
|
-
}
|
|
198
|
-
const byContent = {};
|
|
199
|
-
const byStructure = {};
|
|
200
|
-
const byTableName = {};
|
|
201
|
-
versionEntries.forEach(e => {
|
|
202
|
-
const contentKey = `${e.table_name}_${e.content_version}`;
|
|
203
|
-
if (!byContent[contentKey]) {
|
|
204
|
-
byContent[contentKey] = e;
|
|
205
|
-
}
|
|
206
|
-
const structureKey = `${e.table_name}_${e.structure_version}`;
|
|
207
|
-
if (!byStructure[structureKey]) {
|
|
208
|
-
byStructure[structureKey] = e;
|
|
209
|
-
}
|
|
210
|
-
if (!byTableName[e.table_name]) {
|
|
211
|
-
byTableName[e.table_name] = e;
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
return { versionEntries, byContent, byStructure, byTableName };
|
|
215
|
-
}
|
|
216
|
-
async getVersionEntries(preAggregation) {
|
|
217
|
-
if (this.tablePrefixes && !this.tablePrefixes.find(p => preAggregation.tableName.split('.')[1].startsWith(p))) {
|
|
218
|
-
throw new Error(`Load cache tries to load table ${preAggregation.tableName} outside of tablePrefixes filter: ${this.tablePrefixes.join(', ')}`);
|
|
219
|
-
}
|
|
220
|
-
const redisKey = this.tablesCachePrefixKey(preAggregation);
|
|
221
|
-
if (!this.versionEntries[redisKey]) {
|
|
222
|
-
this.versionEntries[redisKey] = this.calculateVersionEntries(preAggregation).catch(e => {
|
|
223
|
-
delete this.versionEntries[redisKey];
|
|
224
|
-
throw e;
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
return this.versionEntries[redisKey];
|
|
228
|
-
}
|
|
229
|
-
async keyQueryResult(sqlQuery, waitForRenew, priority) {
|
|
230
|
-
const [query, values, queryOptions] = Array.isArray(sqlQuery) ? sqlQuery : [sqlQuery, [], {}];
|
|
231
|
-
if (!this.queryResults[this.queryCache.queryRedisKey([query, values])]) {
|
|
232
|
-
this.queryResults[this.queryCache.queryRedisKey([query, values])] = await this.queryCache.cacheQueryResult(query, values, [query, values], 60 * 60, {
|
|
233
|
-
renewalThreshold: this.queryCache.options.refreshKeyRenewalThreshold
|
|
234
|
-
|| queryOptions?.renewalThreshold || 2 * 60,
|
|
235
|
-
renewalKey: [query, values],
|
|
236
|
-
waitForRenew,
|
|
237
|
-
priority,
|
|
238
|
-
requestId: this.requestId,
|
|
239
|
-
dataSource: this.dataSource,
|
|
240
|
-
useInMemory: true,
|
|
241
|
-
external: queryOptions?.external
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
return this.queryResults[this.queryCache.queryRedisKey([query, values])];
|
|
245
|
-
}
|
|
246
|
-
hasKeyQueryResult(keyQuery) {
|
|
247
|
-
return !!this.queryResults[this.queryCache.queryRedisKey(keyQuery)];
|
|
248
|
-
}
|
|
249
|
-
async getQueryStage(stageQueryKey) {
|
|
250
|
-
const queue = await this.preAggregations.getQueue(this.dataSource);
|
|
251
|
-
await this.fetchQueryStageState(queue);
|
|
252
|
-
return queue.getQueryStage(stageQueryKey, undefined, this.queryStageState);
|
|
253
|
-
}
|
|
254
|
-
async fetchQueryStageState(queue) {
|
|
255
|
-
queue = queue || await this.preAggregations.getQueue(this.dataSource);
|
|
256
|
-
if (!this.queryStageState) {
|
|
257
|
-
this.queryStageState = await queue.fetchQueryStageState();
|
|
258
|
-
}
|
|
259
|
-
return this.queryStageState;
|
|
260
|
-
}
|
|
261
|
-
async reset(preAggregation) {
|
|
262
|
-
await this.tablesFromCache(preAggregation, true);
|
|
263
|
-
this.tables = {};
|
|
264
|
-
this.tableColumnTypes = {};
|
|
265
|
-
this.queryStageState = undefined;
|
|
266
|
-
this.versionEntries = {};
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
class PreAggregationLoader {
|
|
270
|
-
redisPrefix;
|
|
271
|
-
driverFactory;
|
|
272
|
-
logger;
|
|
273
|
-
queryCache;
|
|
274
|
-
loadCache;
|
|
275
|
-
// eslint-disable-next-line no-use-before-define
|
|
276
|
-
preAggregations;
|
|
277
|
-
preAggregation;
|
|
278
|
-
preAggregationsTablesToTempTables;
|
|
279
|
-
/**
|
|
280
|
-
* Determines whether current instance instantiated for a jobed build query
|
|
281
|
-
* (initialized by the /cubejs-system/v1/pre-aggregations/jobs endpoint) or
|
|
282
|
-
* not.
|
|
283
|
-
*/
|
|
284
|
-
isJob;
|
|
285
|
-
waitForRenew;
|
|
286
|
-
forceBuild;
|
|
287
|
-
orphanedTimeout;
|
|
288
|
-
externalDriverFactory;
|
|
289
|
-
requestId;
|
|
290
|
-
metadata;
|
|
291
|
-
structureVersionPersistTime;
|
|
292
|
-
externalRefresh;
|
|
293
|
-
constructor(redisPrefix, driverFactory, logger, queryCache,
|
|
294
|
-
// eslint-disable-next-line no-use-before-define
|
|
295
|
-
preAggregations, preAggregation, preAggregationsTablesToTempTables, loadCache, options = {}) {
|
|
296
|
-
this.redisPrefix = redisPrefix;
|
|
297
|
-
this.driverFactory = driverFactory;
|
|
298
|
-
this.logger = logger;
|
|
299
|
-
this.queryCache = queryCache;
|
|
300
|
-
this.loadCache = loadCache;
|
|
301
|
-
this.preAggregations = preAggregations;
|
|
302
|
-
this.preAggregation = preAggregation;
|
|
303
|
-
this.preAggregationsTablesToTempTables = preAggregationsTablesToTempTables;
|
|
304
|
-
this.isJob = !!options.isJob;
|
|
305
|
-
this.waitForRenew = options.waitForRenew;
|
|
306
|
-
this.forceBuild = options.forceBuild;
|
|
307
|
-
this.orphanedTimeout = options.orphanedTimeout;
|
|
308
|
-
this.externalDriverFactory = preAggregations.externalDriverFactory;
|
|
309
|
-
this.requestId = options.requestId;
|
|
310
|
-
this.metadata = options.metadata;
|
|
311
|
-
this.structureVersionPersistTime = preAggregations.structureVersionPersistTime;
|
|
312
|
-
this.externalRefresh = options.externalRefresh;
|
|
313
|
-
if (this.externalRefresh && this.waitForRenew) {
|
|
314
|
-
const message = 'Invalid configuration - when externalRefresh is true, it will not perform a renew, therefore you cannot wait for it using waitForRenew.';
|
|
315
|
-
if (['production', 'test'].includes((0, shared_1.getEnv)('nodeEnv'))) {
|
|
316
|
-
throw new Error(message);
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
this.logger('Invalid Configuration', {
|
|
320
|
-
requestId: this.requestId,
|
|
321
|
-
warning: message,
|
|
322
|
-
});
|
|
323
|
-
this.waitForRenew = false;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
async loadPreAggregation(throwOnMissingPartition) {
|
|
328
|
-
const notLoadedKey = (this.preAggregation.invalidateKeyQueries || [])
|
|
329
|
-
.find(keyQuery => !this.loadCache.hasKeyQueryResult(keyQuery));
|
|
330
|
-
if (this.isJob || !(notLoadedKey && !this.waitForRenew)) {
|
|
331
|
-
// Case 1: pre-agg build job processing.
|
|
332
|
-
// Case 2: either we have no data cached for this rollup or waitForRenew
|
|
333
|
-
// is true, either way, synchronously renew what data is needed so that
|
|
334
|
-
// the most current data will be returned fo the current request.
|
|
335
|
-
const result = await this.loadPreAggregationWithKeys();
|
|
336
|
-
const refreshKeyValues = await this.getInvalidationKeyValues();
|
|
337
|
-
return {
|
|
338
|
-
...result,
|
|
339
|
-
refreshKeyValues,
|
|
340
|
-
queryKey: this.isJob
|
|
341
|
-
// We need to return a queryKey value for the jobed build query
|
|
342
|
-
// (initialized by the /cubejs-system/v1/pre-aggregations/jobs
|
|
343
|
-
// endpoint) as a part of the response to make it possible to get a
|
|
344
|
-
// query result from the cache by the other API call.
|
|
345
|
-
? this.preAggregationQueryKey(refreshKeyValues)
|
|
346
|
-
: undefined,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
// Case 3: pre-agg is exists
|
|
351
|
-
const structureVersion = getStructureVersion(this.preAggregation);
|
|
352
|
-
const getVersionsStarted = new Date();
|
|
353
|
-
const { byStructure } = await this.loadCache.getVersionEntries(this.preAggregation);
|
|
354
|
-
this.logger('Load PreAggregations Tables', {
|
|
355
|
-
preAggregation: this.preAggregation,
|
|
356
|
-
requestId: this.requestId,
|
|
357
|
-
duration: (new Date().getTime() - getVersionsStarted.getTime())
|
|
358
|
-
});
|
|
359
|
-
const versionEntryByStructureVersion = byStructure[`${this.preAggregation.tableName}_${structureVersion}`];
|
|
360
|
-
if (this.externalRefresh) {
|
|
361
|
-
if (!versionEntryByStructureVersion && throwOnMissingPartition) {
|
|
362
|
-
// eslint-disable-next-line no-use-before-define
|
|
363
|
-
throw new Error(PreAggregations.noPreAggregationPartitionsBuiltMessage([this.preAggregation]));
|
|
364
|
-
}
|
|
365
|
-
if (!versionEntryByStructureVersion) {
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
// the rollups are being maintained independently of this instance of cube.js
|
|
370
|
-
// immediately return the latest rollup data that instance already has
|
|
371
|
-
return {
|
|
372
|
-
targetTableName: this.targetTableName(versionEntryByStructureVersion),
|
|
373
|
-
refreshKeyValues: [],
|
|
374
|
-
lastUpdatedAt: versionEntryByStructureVersion.last_updated_at,
|
|
375
|
-
buildRangeEnd: versionEntryByStructureVersion.build_range_end,
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (versionEntryByStructureVersion) {
|
|
380
|
-
// this triggers an asyncronous/background load of the pre-aggregation but immediately
|
|
381
|
-
// returns the latest data it already has
|
|
382
|
-
this.loadPreAggregationWithKeys().catch(e => {
|
|
383
|
-
if (!(e instanceof ContinueWaitError_1.ContinueWaitError)) {
|
|
384
|
-
this.logger('Error loading pre-aggregation', {
|
|
385
|
-
error: (e.stack || e),
|
|
386
|
-
preAggregation: this.preAggregation,
|
|
387
|
-
requestId: this.requestId
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
return {
|
|
392
|
-
targetTableName: this.targetTableName(versionEntryByStructureVersion),
|
|
393
|
-
refreshKeyValues: [],
|
|
394
|
-
lastUpdatedAt: versionEntryByStructureVersion.last_updated_at,
|
|
395
|
-
buildRangeEnd: versionEntryByStructureVersion.build_range_end,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
// no rollup has been built yet - build it synchronously as part of responding to this request
|
|
400
|
-
return this.loadPreAggregationWithKeys();
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
async loadPreAggregationWithKeys() {
|
|
405
|
-
const invalidationKeys = await this.getPartitionInvalidationKeyValues();
|
|
406
|
-
const contentVersion = this.contentVersion(invalidationKeys);
|
|
407
|
-
const structureVersion = getStructureVersion(this.preAggregation);
|
|
408
|
-
const versionEntries = await this.loadCache.getVersionEntries(this.preAggregation);
|
|
409
|
-
const getVersionEntryByContentVersion = ({ byContent }) => byContent[`${this.preAggregation.tableName}_${contentVersion}`];
|
|
410
|
-
const versionEntryByContentVersion = getVersionEntryByContentVersion(versionEntries);
|
|
411
|
-
if (versionEntryByContentVersion && !this.forceBuild) {
|
|
412
|
-
const targetTableName = this.targetTableName(versionEntryByContentVersion);
|
|
413
|
-
// No need to block here
|
|
414
|
-
this.updateLastTouch(targetTableName);
|
|
415
|
-
return {
|
|
416
|
-
targetTableName,
|
|
417
|
-
refreshKeyValues: [],
|
|
418
|
-
lastUpdatedAt: versionEntryByContentVersion.last_updated_at,
|
|
419
|
-
buildRangeEnd: versionEntryByContentVersion.build_range_end,
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
if (!this.waitForRenew && !this.forceBuild) {
|
|
423
|
-
const versionEntryByStructureVersion = versionEntries.byStructure[`${this.preAggregation.tableName}_${structureVersion}`];
|
|
424
|
-
if (versionEntryByStructureVersion) {
|
|
425
|
-
const targetTableName = this.targetTableName(versionEntryByStructureVersion);
|
|
426
|
-
// No need to block here
|
|
427
|
-
this.updateLastTouch(targetTableName);
|
|
428
|
-
return {
|
|
429
|
-
targetTableName,
|
|
430
|
-
refreshKeyValues: [],
|
|
431
|
-
lastUpdatedAt: versionEntryByStructureVersion.last_updated_at,
|
|
432
|
-
buildRangeEnd: versionEntryByStructureVersion.build_range_end,
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
const client = this.preAggregation.external ?
|
|
437
|
-
await this.externalDriverFactory() :
|
|
438
|
-
await this.driverFactory();
|
|
439
|
-
if (!versionEntries.versionEntries.length) {
|
|
440
|
-
await client.createSchemaIfNotExists(this.preAggregation.preAggregationsSchema);
|
|
441
|
-
}
|
|
442
|
-
// ensure we find appropriate structure version before invalidating anything
|
|
443
|
-
const versionEntry = versionEntries.byStructure[`${this.preAggregation.tableName}_${structureVersion}`] ||
|
|
444
|
-
versionEntries.byTableName[this.preAggregation.tableName];
|
|
445
|
-
const newVersionEntry = {
|
|
446
|
-
table_name: this.preAggregation.tableName,
|
|
447
|
-
structure_version: structureVersion,
|
|
448
|
-
content_version: contentVersion,
|
|
449
|
-
last_updated_at: nowTimestamp(client),
|
|
450
|
-
naming_version: 2,
|
|
451
|
-
};
|
|
452
|
-
const mostRecentResult = async () => {
|
|
453
|
-
await this.loadCache.reset(this.preAggregation);
|
|
454
|
-
const lastVersion = getVersionEntryByContentVersion(await this.loadCache.getVersionEntries(this.preAggregation));
|
|
455
|
-
if (!lastVersion) {
|
|
456
|
-
throw new Error(`Pre-aggregation table is not found for ${this.preAggregation.tableName} after it was successfully created`);
|
|
457
|
-
}
|
|
458
|
-
const targetTableName = this.targetTableName(lastVersion);
|
|
459
|
-
this.updateLastTouch(targetTableName);
|
|
460
|
-
return {
|
|
461
|
-
targetTableName,
|
|
462
|
-
refreshKeyValues: [],
|
|
463
|
-
lastUpdatedAt: lastVersion.last_updated_at,
|
|
464
|
-
buildRangeEnd: lastVersion.build_range_end,
|
|
465
|
-
};
|
|
466
|
-
};
|
|
467
|
-
if (this.forceBuild) {
|
|
468
|
-
this.logger('Force build pre-aggregation', {
|
|
469
|
-
preAggregation: this.preAggregation,
|
|
470
|
-
requestId: this.requestId,
|
|
471
|
-
metadata: this.metadata,
|
|
472
|
-
queryKey: this.preAggregationQueryKey(invalidationKeys),
|
|
473
|
-
newVersionEntry
|
|
474
|
-
});
|
|
475
|
-
if (this.isJob) {
|
|
476
|
-
// We don't want to wait for the jobed build query result. So we run the
|
|
477
|
-
// executeInQueue method and immediately return the LoadPreAggregationResult object.
|
|
478
|
-
this
|
|
479
|
-
.executeInQueue(invalidationKeys, this.priority(10), newVersionEntry)
|
|
480
|
-
.catch((e) => {
|
|
481
|
-
this.logger('Pre-aggregations build job error', {
|
|
482
|
-
preAggregation: this.preAggregation,
|
|
483
|
-
requestId: this.requestId,
|
|
484
|
-
newVersionEntry,
|
|
485
|
-
error: (e.stack || e),
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
const targetTableName = this.targetTableName(newVersionEntry);
|
|
489
|
-
this.updateLastTouch(targetTableName);
|
|
490
|
-
return {
|
|
491
|
-
targetTableName,
|
|
492
|
-
refreshKeyValues: [],
|
|
493
|
-
lastUpdatedAt: newVersionEntry.last_updated_at,
|
|
494
|
-
buildRangeEnd: this.preAggregation.buildRangeEnd,
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
await this.executeInQueue(invalidationKeys, this.priority(10), newVersionEntry);
|
|
499
|
-
return mostRecentResult();
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (versionEntry) {
|
|
503
|
-
if (versionEntry.structure_version !== newVersionEntry.structure_version) {
|
|
504
|
-
this.logger('Invalidating pre-aggregation structure', {
|
|
505
|
-
preAggregation: this.preAggregation,
|
|
506
|
-
requestId: this.requestId,
|
|
507
|
-
queryKey: this.preAggregationQueryKey(invalidationKeys),
|
|
508
|
-
newVersionEntry
|
|
509
|
-
});
|
|
510
|
-
await this.executeInQueue(invalidationKeys, this.priority(10), newVersionEntry);
|
|
511
|
-
return mostRecentResult();
|
|
512
|
-
}
|
|
513
|
-
else if (versionEntry.content_version !== newVersionEntry.content_version) {
|
|
514
|
-
if (this.waitForRenew) {
|
|
515
|
-
this.logger('Waiting for pre-aggregation renew', {
|
|
516
|
-
preAggregation: this.preAggregation,
|
|
517
|
-
requestId: this.requestId,
|
|
518
|
-
queryKey: this.preAggregationQueryKey(invalidationKeys),
|
|
519
|
-
newVersionEntry
|
|
520
|
-
});
|
|
521
|
-
await this.executeInQueue(invalidationKeys, this.priority(0), newVersionEntry);
|
|
522
|
-
return mostRecentResult();
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
this.scheduleRefresh(invalidationKeys, newVersionEntry);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
else {
|
|
530
|
-
this.logger('Creating pre-aggregation from scratch', {
|
|
531
|
-
preAggregation: this.preAggregation,
|
|
532
|
-
requestId: this.requestId,
|
|
533
|
-
queryKey: this.preAggregationQueryKey(invalidationKeys),
|
|
534
|
-
newVersionEntry
|
|
535
|
-
});
|
|
536
|
-
await this.executeInQueue(invalidationKeys, this.priority(10), newVersionEntry);
|
|
537
|
-
return mostRecentResult();
|
|
538
|
-
}
|
|
539
|
-
const targetTableName = this.targetTableName(versionEntry);
|
|
540
|
-
this.updateLastTouch(targetTableName);
|
|
541
|
-
return {
|
|
542
|
-
targetTableName,
|
|
543
|
-
refreshKeyValues: [],
|
|
544
|
-
lastUpdatedAt: versionEntry.last_updated_at,
|
|
545
|
-
buildRangeEnd: versionEntry.build_range_end,
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
updateLastTouch(tableName) {
|
|
549
|
-
this.preAggregations.updateLastTouch(tableName).catch(e => {
|
|
550
|
-
this.logger('Error on pre-aggregation touch', {
|
|
551
|
-
error: (e.stack || e), preAggregation: this.preAggregation, requestId: this.requestId,
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
contentVersion(invalidationKeys) {
|
|
556
|
-
const versionArray = [this.preAggregation.structureVersionLoadSql || this.preAggregation.loadSql];
|
|
557
|
-
if (this.preAggregation.indexesSql && this.preAggregation.indexesSql.length) {
|
|
558
|
-
versionArray.push(this.preAggregation.indexesSql);
|
|
559
|
-
}
|
|
560
|
-
if (this.preAggregation.streamOffset) {
|
|
561
|
-
versionArray.push(this.preAggregation.streamOffset);
|
|
562
|
-
}
|
|
563
|
-
if (this.preAggregation.outputColumnTypes) {
|
|
564
|
-
versionArray.push(this.preAggregation.outputColumnTypes);
|
|
565
|
-
}
|
|
566
|
-
versionArray.push(invalidationKeys);
|
|
567
|
-
return version(versionArray);
|
|
568
|
-
}
|
|
569
|
-
priority(defaultValue) {
|
|
570
|
-
return this.preAggregation.priority != null ? this.preAggregation.priority : defaultValue;
|
|
571
|
-
}
|
|
572
|
-
getInvalidationKeyValues() {
|
|
573
|
-
return Promise.all((this.preAggregation.invalidateKeyQueries || []).map((sqlQuery) => this.loadCache.keyQueryResult(sqlQuery, this.waitForRenew, this.priority(10))));
|
|
574
|
-
}
|
|
575
|
-
getPartitionInvalidationKeyValues() {
|
|
576
|
-
if (this.preAggregation.partitionInvalidateKeyQueries) {
|
|
577
|
-
return Promise.all((this.preAggregation.partitionInvalidateKeyQueries || []).map((sqlQuery) => this.loadCache.keyQueryResult(sqlQuery, this.waitForRenew, this.priority(10))));
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
return this.getInvalidationKeyValues();
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
scheduleRefresh(invalidationKeys, newVersionEntry) {
|
|
584
|
-
this.logger('Refreshing pre-aggregation content', {
|
|
585
|
-
preAggregation: this.preAggregation,
|
|
586
|
-
requestId: this.requestId,
|
|
587
|
-
queryKey: this.preAggregationQueryKey(invalidationKeys),
|
|
588
|
-
newVersionEntry
|
|
589
|
-
});
|
|
590
|
-
this.executeInQueue(invalidationKeys, this.priority(0), newVersionEntry)
|
|
591
|
-
.catch(e => {
|
|
592
|
-
if (!(e instanceof ContinueWaitError_1.ContinueWaitError)) {
|
|
593
|
-
this.logger('Error refreshing pre-aggregation', {
|
|
594
|
-
error: (e.stack || e), preAggregation: this.preAggregation, requestId: this.requestId
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
async executeInQueue(invalidationKeys, priority, newVersionEntry) {
|
|
600
|
-
const queue = await this.preAggregations.getQueue(this.preAggregation.dataSource);
|
|
601
|
-
return queue.executeInQueue('query', this.preAggregationQueryKey(invalidationKeys), {
|
|
602
|
-
preAggregation: this.preAggregation,
|
|
603
|
-
preAggregationsTablesToTempTables: this.preAggregationsTablesToTempTables,
|
|
604
|
-
newVersionEntry,
|
|
605
|
-
requestId: this.requestId,
|
|
606
|
-
invalidationKeys,
|
|
607
|
-
forceBuild: this.forceBuild,
|
|
608
|
-
isJob: this.isJob,
|
|
609
|
-
metadata: this.metadata,
|
|
610
|
-
orphanedTimeout: this.orphanedTimeout,
|
|
611
|
-
}, priority,
|
|
612
|
-
// eslint-disable-next-line no-use-before-define
|
|
613
|
-
{ stageQueryKey: PreAggregations.preAggregationQueryCacheKey(this.preAggregation), requestId: this.requestId });
|
|
614
|
-
}
|
|
615
|
-
preAggregationQueryKey(invalidationKeys) {
|
|
616
|
-
return this.preAggregation.indexesSql && this.preAggregation.indexesSql.length ?
|
|
617
|
-
[this.preAggregation.loadSql, this.preAggregation.indexesSql, invalidationKeys] :
|
|
618
|
-
[this.preAggregation.loadSql, invalidationKeys];
|
|
619
|
-
}
|
|
620
|
-
targetTableName(versionEntry) {
|
|
621
|
-
// eslint-disable-next-line no-use-before-define
|
|
622
|
-
return PreAggregations.targetTableName(versionEntry);
|
|
623
|
-
}
|
|
624
|
-
refresh(newVersionEntry, invalidationKeys, client) {
|
|
625
|
-
this.updateLastTouch(this.targetTableName(newVersionEntry));
|
|
626
|
-
let refreshStrategy = this.refreshStoreInSourceStrategy;
|
|
627
|
-
if (this.preAggregation.external) {
|
|
628
|
-
const readOnly = this.preAggregation.readOnly ||
|
|
629
|
-
client.config && client.config.readOnly ||
|
|
630
|
-
client.readOnly && (typeof client.readOnly === 'boolean' ? client.readOnly : client.readOnly());
|
|
631
|
-
if (readOnly) {
|
|
632
|
-
refreshStrategy = this.refreshReadOnlyExternalStrategy;
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
refreshStrategy = this.refreshWriteStrategy;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return (0, base_driver_1.cancelCombinator)(saveCancelFn => refreshStrategy.bind(this)(client, newVersionEntry, saveCancelFn, invalidationKeys));
|
|
639
|
-
}
|
|
640
|
-
logExecutingSql(payload) {
|
|
641
|
-
this.logger('Executing Load Pre Aggregation SQL', payload);
|
|
642
|
-
}
|
|
643
|
-
queryOptions(invalidationKeys, query, params, targetTableName, newVersionEntry) {
|
|
644
|
-
return {
|
|
645
|
-
queryKey: this.preAggregationQueryKey(invalidationKeys),
|
|
646
|
-
query,
|
|
647
|
-
values: params,
|
|
648
|
-
targetTableName,
|
|
649
|
-
requestId: this.requestId,
|
|
650
|
-
newVersionEntry,
|
|
651
|
-
buildRangeEnd: this.preAggregation.buildRangeEnd,
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
async refreshStoreInSourceStrategy(client, newVersionEntry, saveCancelFn, invalidationKeys) {
|
|
655
|
-
const [loadSql, params] = Array.isArray(this.preAggregation.loadSql) ? this.preAggregation.loadSql : [this.preAggregation.loadSql, []];
|
|
656
|
-
const targetTableName = this.targetTableName(newVersionEntry);
|
|
657
|
-
const query = QueryCache_1.QueryCache.replacePreAggregationTableNames(loadSql, this.preAggregationsTablesToTempTables).replace(this.preAggregation.tableName, targetTableName);
|
|
658
|
-
const queryOptions = this.queryOptions(invalidationKeys, query, params, targetTableName, newVersionEntry);
|
|
659
|
-
this.logExecutingSql(queryOptions);
|
|
660
|
-
try {
|
|
661
|
-
// TODO move index creation to the driver
|
|
662
|
-
await saveCancelFn(client.loadPreAggregationIntoTable(targetTableName, query, params, {
|
|
663
|
-
streamOffset: this.preAggregation.streamOffset,
|
|
664
|
-
outputColumnTypes: this.preAggregation.outputColumnTypes,
|
|
665
|
-
...queryOptions
|
|
666
|
-
}));
|
|
667
|
-
await this.createIndexes(client, newVersionEntry, saveCancelFn, queryOptions);
|
|
668
|
-
await this.loadCache.fetchTables(this.preAggregation);
|
|
669
|
-
}
|
|
670
|
-
finally {
|
|
671
|
-
// We must clean orphaned in any cases: success or exception
|
|
672
|
-
await this.dropOrphanedTables(client, targetTableName, saveCancelFn, false, queryOptions);
|
|
673
|
-
await this.loadCache.fetchTables(this.preAggregation);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
async refreshWriteStrategy(client, newVersionEntry, saveCancelFn, invalidationKeys) {
|
|
677
|
-
const capabilities = client?.capabilities();
|
|
678
|
-
const withTempTable = !(capabilities?.unloadWithoutTempTable);
|
|
679
|
-
const dropSourceTempTable = !capabilities?.streamingSource;
|
|
680
|
-
return this.runWriteStrategy(client, newVersionEntry, saveCancelFn, invalidationKeys, withTempTable, dropSourceTempTable);
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Runs export strategy with write access in data source
|
|
684
|
-
*/
|
|
685
|
-
async runWriteStrategy(client, newVersionEntry, saveCancelFn, invalidationKeys, withTempTable, dropSourceTempTable) {
|
|
686
|
-
if (withTempTable) {
|
|
687
|
-
await client.createSchemaIfNotExists(this.preAggregation.preAggregationsSchema);
|
|
688
|
-
}
|
|
689
|
-
const targetTableName = this.targetTableName(newVersionEntry);
|
|
690
|
-
const queryOptions = await this.prepareWriteStrategy(client, targetTableName, newVersionEntry, saveCancelFn, invalidationKeys, withTempTable);
|
|
691
|
-
try {
|
|
692
|
-
const tableData = await this.downloadExternalPreAggregation(client, newVersionEntry, saveCancelFn, queryOptions, withTempTable);
|
|
693
|
-
try {
|
|
694
|
-
await this.uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn, queryOptions);
|
|
695
|
-
}
|
|
696
|
-
finally {
|
|
697
|
-
if (tableData && tableData.release) {
|
|
698
|
-
await tableData.release();
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
finally {
|
|
703
|
-
await this.cleanupWriteStrategy(client, targetTableName, queryOptions, saveCancelFn, withTempTable, dropSourceTempTable);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* Cleanup tables after write strategy
|
|
708
|
-
*/
|
|
709
|
-
async cleanupWriteStrategy(client, targetTableName, queryOptions, saveCancelFn, withTempTable, dropSourceTempTable) {
|
|
710
|
-
if (withTempTable && dropSourceTempTable) {
|
|
711
|
-
await this.withDropLock(false, async () => {
|
|
712
|
-
this.logger('Dropping source temp table', queryOptions);
|
|
713
|
-
const actualTables = await client.getTablesQuery(this.preAggregation.preAggregationsSchema);
|
|
714
|
-
const mappedActualTables = actualTables.map(t => `${this.preAggregation.preAggregationsSchema}.${t.table_name || t.TABLE_NAME}`);
|
|
715
|
-
if (mappedActualTables.includes(targetTableName)) {
|
|
716
|
-
await client.dropTable(targetTableName);
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
// We must clean orphaned in any cases: success or exception
|
|
721
|
-
await this.loadCache.fetchTables(this.preAggregation);
|
|
722
|
-
await this.dropOrphanedTables(client, targetTableName, saveCancelFn, false, queryOptions);
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* Create table (if required) and prepares query options object
|
|
726
|
-
*/
|
|
727
|
-
async prepareWriteStrategy(client, targetTableName, newVersionEntry, saveCancelFn, invalidationKeys, withTempTable) {
|
|
728
|
-
if (withTempTable) {
|
|
729
|
-
const [loadSql, params] = Array.isArray(this.preAggregation.loadSql) ? this.preAggregation.loadSql : [this.preAggregation.loadSql, []];
|
|
730
|
-
const query = QueryCache_1.QueryCache.replacePreAggregationTableNames(loadSql, this.preAggregationsTablesToTempTables).replace(this.preAggregation.tableName, targetTableName);
|
|
731
|
-
const queryOptions = this.queryOptions(invalidationKeys, query, params, targetTableName, newVersionEntry);
|
|
732
|
-
this.logExecutingSql(queryOptions);
|
|
733
|
-
await saveCancelFn(client.loadPreAggregationIntoTable(targetTableName, query, params, {
|
|
734
|
-
streamOffset: this.preAggregation.streamOffset,
|
|
735
|
-
outputColumnTypes: this.preAggregation.outputColumnTypes,
|
|
736
|
-
...queryOptions
|
|
737
|
-
}));
|
|
738
|
-
return queryOptions;
|
|
739
|
-
}
|
|
740
|
-
else {
|
|
741
|
-
const [sql, params] = Array.isArray(this.preAggregation.sql) ? this.preAggregation.sql : [this.preAggregation.sql, []];
|
|
742
|
-
const queryOptions = this.queryOptions(invalidationKeys, sql, params, targetTableName, newVersionEntry);
|
|
743
|
-
this.logExecutingSql(queryOptions);
|
|
744
|
-
return queryOptions;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* Strategy to copy pre-aggregation from source db (for read-only permissions) to external data
|
|
749
|
-
*/
|
|
750
|
-
async refreshReadOnlyExternalStrategy(client, newVersionEntry, saveCancelFn, invalidationKeys) {
|
|
751
|
-
const [sql, params] = Array.isArray(this.preAggregation.sql) ? this.preAggregation.sql : [this.preAggregation.sql, []];
|
|
752
|
-
const queryOptions = this.queryOptions(invalidationKeys, sql, params, this.targetTableName(newVersionEntry), newVersionEntry);
|
|
753
|
-
this.logExecutingSql(queryOptions);
|
|
754
|
-
this.logger('Downloading external pre-aggregation via query', queryOptions);
|
|
755
|
-
const externalDriver = await this.externalDriverFactory();
|
|
756
|
-
const capabilities = externalDriver.capabilities && externalDriver.capabilities();
|
|
757
|
-
let tableData;
|
|
758
|
-
if (capabilities.csvImport && client.unloadFromQuery && await client.isUnloadSupported(this.getUnloadOptions())) {
|
|
759
|
-
tableData = await saveCancelFn(client.unloadFromQuery(sql, params, this.getUnloadOptions())).catch((error) => {
|
|
760
|
-
this.logger('Downloading external pre-aggregation via query error', { ...queryOptions, error: error.stack || error.message });
|
|
761
|
-
throw error;
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
else {
|
|
765
|
-
tableData = await saveCancelFn(client.downloadQueryResults(sql, params, {
|
|
766
|
-
streamOffset: this.preAggregation.streamOffset,
|
|
767
|
-
outputColumnTypes: this.preAggregation.outputColumnTypes,
|
|
768
|
-
...queryOptions,
|
|
769
|
-
...capabilities,
|
|
770
|
-
...this.getStreamingOptions(),
|
|
771
|
-
})).catch((error) => {
|
|
772
|
-
this.logger('Downloading external pre-aggregation via query error', { ...queryOptions, error: error.stack || error.message });
|
|
773
|
-
throw error;
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
this.logger('Downloading external pre-aggregation via query completed', {
|
|
777
|
-
...queryOptions,
|
|
778
|
-
isUnloadSupported: (0, base_driver_1.isDownloadTableCSVData)(tableData)
|
|
779
|
-
});
|
|
780
|
-
try {
|
|
781
|
-
await this.uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn, queryOptions);
|
|
782
|
-
}
|
|
783
|
-
finally {
|
|
784
|
-
if (tableData.release) {
|
|
785
|
-
await tableData.release();
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
await this.loadCache.fetchTables(this.preAggregation);
|
|
789
|
-
}
|
|
790
|
-
getUnloadOptions() {
|
|
791
|
-
return {
|
|
792
|
-
// Default: 16mb for Snowflake, Should be specified in MBs, because drivers convert it
|
|
793
|
-
maxFileSize: 64
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
getStreamingOptions() {
|
|
797
|
-
return {
|
|
798
|
-
// Default: 16384 (16KB), or 16 for objectMode streams. PostgreSQL/MySQL use object streams
|
|
799
|
-
highWaterMark: 10000
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* prepares download data for future cube store usage
|
|
804
|
-
*/
|
|
805
|
-
async downloadExternalPreAggregation(client, newVersionEntry, saveCancelFn, queryOptions, withTempTable) {
|
|
806
|
-
const table = this.targetTableName(newVersionEntry);
|
|
807
|
-
this.logger('Downloading external pre-aggregation', queryOptions);
|
|
808
|
-
try {
|
|
809
|
-
const externalDriver = await this.externalDriverFactory();
|
|
810
|
-
const capabilities = externalDriver.capabilities && externalDriver.capabilities();
|
|
811
|
-
let tableData;
|
|
812
|
-
if (withTempTable) {
|
|
813
|
-
tableData = await this.getTableDataWithTempTable(client, table, saveCancelFn, queryOptions, capabilities);
|
|
814
|
-
}
|
|
815
|
-
else {
|
|
816
|
-
tableData = await this.getTableDataWithoutTempTable(client, table, saveCancelFn, queryOptions, capabilities);
|
|
817
|
-
}
|
|
818
|
-
this.logger('Downloading external pre-aggregation completed', {
|
|
819
|
-
...queryOptions,
|
|
820
|
-
isUnloadSupported: (0, base_driver_1.isDownloadTableCSVData)(tableData)
|
|
821
|
-
});
|
|
822
|
-
return tableData;
|
|
823
|
-
}
|
|
824
|
-
catch (error) {
|
|
825
|
-
this.logger('Downloading external pre-aggregation error', {
|
|
826
|
-
...queryOptions,
|
|
827
|
-
error: error?.stack || error?.message
|
|
828
|
-
});
|
|
829
|
-
throw error;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* prepares download data when temp table = true
|
|
834
|
-
*/
|
|
835
|
-
async getTableDataWithTempTable(client, table, saveCancelFn, queryOptions, externalDriverCapabilities) {
|
|
836
|
-
let tableData;
|
|
837
|
-
if (externalDriverCapabilities.csvImport && client.unload && await client.isUnloadSupported(this.getUnloadOptions())) {
|
|
838
|
-
tableData = await saveCancelFn(client.unload(table, this.getUnloadOptions()));
|
|
839
|
-
}
|
|
840
|
-
else if (externalDriverCapabilities.streamImport && client.stream) {
|
|
841
|
-
tableData = await saveCancelFn(client.stream(`SELECT * FROM ${table}`, [], this.getStreamingOptions()));
|
|
842
|
-
if (client.unload) {
|
|
843
|
-
const stream = new StreamObjectsCounter_1.LargeStreamWarning(this.preAggregation.preAggregationId, (msg) => {
|
|
844
|
-
this.logger('Downloading external pre-aggregation warning', {
|
|
845
|
-
...queryOptions,
|
|
846
|
-
error: msg
|
|
847
|
-
});
|
|
848
|
-
});
|
|
849
|
-
tableData.rowStream.pipe(stream);
|
|
850
|
-
tableData.rowStream = stream;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
else {
|
|
854
|
-
tableData = await saveCancelFn(client.downloadTable(table, {
|
|
855
|
-
streamOffset: this.preAggregation.streamOffset,
|
|
856
|
-
outputColumnTypes: this.preAggregation.outputColumnTypes,
|
|
857
|
-
...externalDriverCapabilities
|
|
858
|
-
}));
|
|
859
|
-
}
|
|
860
|
-
if (!tableData.types) {
|
|
861
|
-
tableData.types = await saveCancelFn(client.tableColumnTypes(table));
|
|
862
|
-
}
|
|
863
|
-
return tableData;
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* prepares download data when temp table = false
|
|
867
|
-
*/
|
|
868
|
-
async getTableDataWithoutTempTable(client, table, saveCancelFn, queryOptions, externalDriverCapabilities) {
|
|
869
|
-
const [sql, params] = Array.isArray(this.preAggregation.sql) ? this.preAggregation.sql : [this.preAggregation.sql, []];
|
|
870
|
-
let tableData;
|
|
871
|
-
if (externalDriverCapabilities.csvImport && client.unload && await client.isUnloadSupported(this.getUnloadOptions())) {
|
|
872
|
-
return saveCancelFn(client.unload(table, { ...this.getUnloadOptions(), query: { sql, params } }));
|
|
873
|
-
}
|
|
874
|
-
else if (externalDriverCapabilities.streamImport && client.stream) {
|
|
875
|
-
tableData = await saveCancelFn(client.stream(sql, params, this.getStreamingOptions()));
|
|
876
|
-
if (client.unload) {
|
|
877
|
-
const stream = new StreamObjectsCounter_1.LargeStreamWarning(this.preAggregation.preAggregationId, (msg) => {
|
|
878
|
-
this.logger('Downloading external pre-aggregation warning', {
|
|
879
|
-
...queryOptions,
|
|
880
|
-
error: msg
|
|
881
|
-
});
|
|
882
|
-
});
|
|
883
|
-
tableData.rowStream.pipe(stream);
|
|
884
|
-
tableData.rowStream = stream;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
else {
|
|
888
|
-
tableData = { rows: await saveCancelFn(client.query(sql, params)) };
|
|
889
|
-
}
|
|
890
|
-
if (!tableData.types && client.queryColumnTypes) {
|
|
891
|
-
tableData.types = await saveCancelFn(client.queryColumnTypes(sql, params));
|
|
892
|
-
}
|
|
893
|
-
return tableData;
|
|
894
|
-
}
|
|
895
|
-
async uploadExternalPreAggregation(tableData, newVersionEntry, saveCancelFn, queryOptions) {
|
|
896
|
-
const externalDriver = await this.externalDriverFactory();
|
|
897
|
-
const table = this.targetTableName(newVersionEntry);
|
|
898
|
-
this.logger('Uploading external pre-aggregation', queryOptions);
|
|
899
|
-
await saveCancelFn(externalDriver.uploadTableWithIndexes(table, tableData.types, tableData, this.prepareIndexesSql(newVersionEntry, queryOptions), this.preAggregation.uniqueKeyColumns, queryOptions, {
|
|
900
|
-
aggregationsColumns: this.preAggregation.aggregationsColumns,
|
|
901
|
-
createTableIndexes: this.prepareCreateTableIndexes(newVersionEntry),
|
|
902
|
-
sealAt: this.preAggregation.sealAt
|
|
903
|
-
})).catch((error) => {
|
|
904
|
-
this.logger('Uploading external pre-aggregation error', { ...queryOptions, error: error?.stack || error?.message });
|
|
905
|
-
throw error;
|
|
906
|
-
});
|
|
907
|
-
this.logger('Uploading external pre-aggregation completed', queryOptions);
|
|
908
|
-
await this.loadCache.fetchTables(this.preAggregation);
|
|
909
|
-
await this.dropOrphanedTables(externalDriver, table, saveCancelFn, true, queryOptions);
|
|
910
|
-
}
|
|
911
|
-
async createIndexes(driver, newVersionEntry, saveCancelFn, queryOptions) {
|
|
912
|
-
const indexesSql = this.prepareIndexesSql(newVersionEntry, queryOptions);
|
|
913
|
-
for (let i = 0; i < indexesSql.length; i++) {
|
|
914
|
-
const [query, params] = indexesSql[i].sql;
|
|
915
|
-
await saveCancelFn(driver.query(query, params));
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
prepareIndexesSql(newVersionEntry, queryOptions) {
|
|
919
|
-
if (!this.preAggregation.indexesSql || !this.preAggregation.indexesSql.length) {
|
|
920
|
-
return [];
|
|
921
|
-
}
|
|
922
|
-
return this.preAggregation.indexesSql.map(({ sql, indexName }) => {
|
|
923
|
-
const [query, params] = sql;
|
|
924
|
-
const indexVersionEntry = {
|
|
925
|
-
...newVersionEntry,
|
|
926
|
-
table_name: indexName
|
|
927
|
-
};
|
|
928
|
-
this.logger('Creating pre-aggregation index', queryOptions);
|
|
929
|
-
const resultingSql = QueryCache_1.QueryCache.replacePreAggregationTableNames(query, this.preAggregationsTablesToTempTables.concat([
|
|
930
|
-
[this.preAggregation.tableName, { targetTableName: this.targetTableName(newVersionEntry) }],
|
|
931
|
-
[indexName, { targetTableName: this.targetTableName(indexVersionEntry) }]
|
|
932
|
-
]));
|
|
933
|
-
return { sql: [resultingSql, params] };
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
prepareCreateTableIndexes(newVersionEntry) {
|
|
937
|
-
if (!this.preAggregation.createTableIndexes || !this.preAggregation.createTableIndexes.length) {
|
|
938
|
-
return [];
|
|
939
|
-
}
|
|
940
|
-
return this.preAggregation.createTableIndexes.map(({ indexName, type, columns }) => {
|
|
941
|
-
const indexVersionEntry = {
|
|
942
|
-
...newVersionEntry,
|
|
943
|
-
table_name: indexName
|
|
944
|
-
};
|
|
945
|
-
return { indexName: this.targetTableName(indexVersionEntry), type, columns };
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
async withDropLock(external, lockFn) {
|
|
949
|
-
const lockKey = this.dropLockKey(external);
|
|
950
|
-
return this.queryCache.withLock(lockKey, 60 * 5, lockFn);
|
|
951
|
-
}
|
|
952
|
-
async dropOrphanedTables(client, justCreatedTable, saveCancelFn, external, queryOptions) {
|
|
953
|
-
await this.preAggregations.addTableUsed(justCreatedTable);
|
|
954
|
-
return this.withDropLock(external, async () => {
|
|
955
|
-
this.logger('Dropping orphaned tables', { ...queryOptions, external });
|
|
956
|
-
const actualTables = await client.getTablesQuery(this.preAggregation.preAggregationsSchema);
|
|
957
|
-
const versionEntries = tablesToVersionEntries(this.preAggregation.preAggregationsSchema, actualTables);
|
|
958
|
-
const versionEntriesToSave = ramda_1.default.pipe(ramda_1.default.groupBy(v => v.table_name), ramda_1.default.toPairs, ramda_1.default.map(p => p[1][0]))(versionEntries);
|
|
959
|
-
const structureVersionsToSave = ramda_1.default.pipe(ramda_1.default.filter((v) => (new Date().getTime() - v.last_updated_at <
|
|
960
|
-
this.structureVersionPersistTime * 1000)), ramda_1.default.groupBy(v => `${v.table_name}_${v.structure_version}`), ramda_1.default.toPairs, ramda_1.default.map(p => p[1][0]))(versionEntries);
|
|
961
|
-
const refreshEndReached = await this.preAggregations.getRefreshEndReached();
|
|
962
|
-
const toSave = this.preAggregations.dropPreAggregationsWithoutTouch && refreshEndReached
|
|
963
|
-
? (await this.preAggregations.tablesUsed())
|
|
964
|
-
.concat(await this.preAggregations.tablesTouched())
|
|
965
|
-
.concat([justCreatedTable])
|
|
966
|
-
: (await this.preAggregations.tablesUsed())
|
|
967
|
-
.concat(structureVersionsToSave.map(v => this.targetTableName(v)))
|
|
968
|
-
.concat(versionEntriesToSave.map(v => this.targetTableName(v)))
|
|
969
|
-
.concat([justCreatedTable]);
|
|
970
|
-
const toDrop = actualTables
|
|
971
|
-
.map(t => `${this.preAggregation.preAggregationsSchema}.${t.table_name || t.TABLE_NAME}`)
|
|
972
|
-
.filter(t => toSave.indexOf(t) === -1);
|
|
973
|
-
await Promise.all(toDrop.map(table => saveCancelFn(client.dropTable(table))));
|
|
974
|
-
this.logger('Dropping orphaned tables completed', {
|
|
975
|
-
...queryOptions,
|
|
976
|
-
external,
|
|
977
|
-
tablesToDrop: JSON.stringify(toDrop),
|
|
978
|
-
});
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
dropLockKey(external) {
|
|
982
|
-
return external
|
|
983
|
-
? 'drop-orphaned-tables-external'
|
|
984
|
-
: `drop-orphaned-tables:${this.preAggregation.dataSource}`;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
exports.PreAggregationLoader = PreAggregationLoader;
|
|
988
|
-
class PreAggregationPartitionRangeLoader {
|
|
989
|
-
redisPrefix;
|
|
990
|
-
driverFactory;
|
|
991
|
-
logger;
|
|
992
|
-
queryCache;
|
|
993
|
-
preAggregations;
|
|
994
|
-
preAggregation;
|
|
995
|
-
preAggregationsTablesToTempTables;
|
|
996
|
-
loadCache;
|
|
997
|
-
options;
|
|
998
|
-
/**
|
|
999
|
-
* Determines whether current instance instantiated for a jobed build query
|
|
1000
|
-
* (initialized by the /cubejs-system/v1/pre-aggregations/jobs endpoint) or
|
|
1001
|
-
* not.
|
|
1002
|
-
*/
|
|
1003
|
-
isJob;
|
|
1004
|
-
waitForRenew;
|
|
1005
|
-
requestId;
|
|
1006
|
-
lambdaQuery;
|
|
1007
|
-
dataSource;
|
|
1008
|
-
compilerCacheFn;
|
|
1009
|
-
constructor(redisPrefix, driverFactory, logger, queryCache,
|
|
1010
|
-
// eslint-disable-next-line no-use-before-define
|
|
1011
|
-
preAggregations, preAggregation, preAggregationsTablesToTempTables, loadCache, options = {
|
|
1012
|
-
maxPartitions: 10000,
|
|
1013
|
-
maxSourceRowLimit: 10000,
|
|
1014
|
-
}) {
|
|
1015
|
-
this.redisPrefix = redisPrefix;
|
|
1016
|
-
this.driverFactory = driverFactory;
|
|
1017
|
-
this.logger = logger;
|
|
1018
|
-
this.queryCache = queryCache;
|
|
1019
|
-
this.preAggregations = preAggregations;
|
|
1020
|
-
this.preAggregation = preAggregation;
|
|
1021
|
-
this.preAggregationsTablesToTempTables = preAggregationsTablesToTempTables;
|
|
1022
|
-
this.loadCache = loadCache;
|
|
1023
|
-
this.options = options;
|
|
1024
|
-
this.isJob = !!options.isJob;
|
|
1025
|
-
this.waitForRenew = options.waitForRenew;
|
|
1026
|
-
this.requestId = options.requestId;
|
|
1027
|
-
this.lambdaQuery = options.lambdaQuery;
|
|
1028
|
-
this.dataSource = preAggregation.dataSource;
|
|
1029
|
-
this.compilerCacheFn = options.compilerCacheFn || ((subKey, cacheFn) => cacheFn());
|
|
1030
|
-
}
|
|
1031
|
-
async loadRangeQuery(rangeQuery, partitionRange) {
|
|
1032
|
-
const [query, values, queryOptions] = rangeQuery;
|
|
1033
|
-
const invalidate = this.preAggregation.invalidateKeyQueries &&
|
|
1034
|
-
this.preAggregation.invalidateKeyQueries[0]
|
|
1035
|
-
? this.preAggregation.invalidateKeyQueries[0].slice(0, 2)
|
|
1036
|
-
: false;
|
|
1037
|
-
return this.queryCache.cacheQueryResult(query, values, QueryCache_1.QueryCache.queryCacheKey({
|
|
1038
|
-
query,
|
|
1039
|
-
values: values,
|
|
1040
|
-
invalidate,
|
|
1041
|
-
}), 24 * 60 * 60, {
|
|
1042
|
-
renewalThreshold: this.queryCache.options.refreshKeyRenewalThreshold
|
|
1043
|
-
|| queryOptions?.renewalThreshold || 24 * 60 * 60,
|
|
1044
|
-
waitForRenew: this.waitForRenew,
|
|
1045
|
-
priority: this.priority(10),
|
|
1046
|
-
requestId: this.requestId,
|
|
1047
|
-
dataSource: this.dataSource,
|
|
1048
|
-
useInMemory: true,
|
|
1049
|
-
external: queryOptions?.external,
|
|
1050
|
-
renewalKey: partitionRange ? await this.getInvalidationKeyValues(partitionRange) : null,
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
getInvalidationKeyValues(range) {
|
|
1054
|
-
const partitionTableName = PreAggregationPartitionRangeLoader.partitionTableName(this.preAggregation.tableName, this.preAggregation.partitionGranularity, range);
|
|
1055
|
-
return Promise.all((this.preAggregation.invalidateKeyQueries || []).map((sqlQuery) => (this.loadCache.keyQueryResult(this.replacePartitionSqlAndParams(sqlQuery, range, partitionTableName), this.waitForRenew, this.priority(10)))));
|
|
1056
|
-
}
|
|
1057
|
-
priority(defaultValue) {
|
|
1058
|
-
return this.preAggregation.priority != null ? this.preAggregation.priority : defaultValue;
|
|
1059
|
-
}
|
|
1060
|
-
async replaceQueryBuildRangeParams(queryValues) {
|
|
1061
|
-
if (queryValues?.find(p => p === shared_1.BUILD_RANGE_START_LOCAL || p === shared_1.BUILD_RANGE_END_LOCAL)) {
|
|
1062
|
-
const [buildRangeStart, buildRangeEnd] = await this.loadBuildRange();
|
|
1063
|
-
return queryValues?.map(param => {
|
|
1064
|
-
if (param === shared_1.BUILD_RANGE_START_LOCAL) {
|
|
1065
|
-
return (0, shared_1.utcToLocalTimeZone)(this.preAggregation.timezone, this.preAggregation.timestampFormat, buildRangeStart);
|
|
1066
|
-
}
|
|
1067
|
-
else if (param === shared_1.BUILD_RANGE_END_LOCAL) {
|
|
1068
|
-
return (0, shared_1.utcToLocalTimeZone)(this.preAggregation.timezone, this.preAggregation.timestampFormat, buildRangeEnd);
|
|
1069
|
-
}
|
|
1070
|
-
else {
|
|
1071
|
-
return param;
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
return null;
|
|
1076
|
-
}
|
|
1077
|
-
replacePartitionSqlAndParams(query, dateRange, partitionTableName) {
|
|
1078
|
-
const [sql, params, options] = query;
|
|
1079
|
-
const updateWindowToBoundary = options?.incremental && (0, shared_1.addSecondsToLocalTimestamp)(dateRange[1], this.preAggregation.timezone, options?.updateWindowSeconds || 0);
|
|
1080
|
-
return [sql.replace(this.preAggregation.tableName, partitionTableName), params?.map(param => {
|
|
1081
|
-
if (dateRange && param === shared_1.FROM_PARTITION_RANGE) {
|
|
1082
|
-
return PreAggregationPartitionRangeLoader.inDbTimeZone(this.preAggregation, dateRange[0]);
|
|
1083
|
-
}
|
|
1084
|
-
else if (dateRange && param === shared_1.TO_PARTITION_RANGE) {
|
|
1085
|
-
return PreAggregationPartitionRangeLoader.inDbTimeZone(this.preAggregation, dateRange[1]);
|
|
1086
|
-
}
|
|
1087
|
-
else {
|
|
1088
|
-
return param;
|
|
1089
|
-
}
|
|
1090
|
-
}), {
|
|
1091
|
-
...options,
|
|
1092
|
-
renewalThreshold: options?.incremental && updateWindowToBoundary < new Date() ?
|
|
1093
|
-
// if updateWindowToBoundary passed just moments ago we want to renew it earlier in case
|
|
1094
|
-
// of server and db clock don't match
|
|
1095
|
-
Math.min(Math.round((new Date().getTime() - updateWindowToBoundary.getTime()) / 1000), options?.renewalThresholdOutsideUpdateWindow) :
|
|
1096
|
-
options?.renewalThreshold
|
|
1097
|
-
}];
|
|
1098
|
-
}
|
|
1099
|
-
partitionPreAggregationDescription(range, buildRange) {
|
|
1100
|
-
const partitionTableName = PreAggregationPartitionRangeLoader.partitionTableName(this.preAggregation.tableName, this.preAggregation.partitionGranularity, range);
|
|
1101
|
-
const [_, buildRangeEnd] = buildRange;
|
|
1102
|
-
const loadRange = [...range];
|
|
1103
|
-
const partitionInvalidateKeyQueries = this.preAggregation.partitionInvalidateKeyQueries || this.preAggregation.invalidateKeyQueries;
|
|
1104
|
-
// `partitionInvalidateKeyQueries = []` in case of real time
|
|
1105
|
-
if ((!partitionInvalidateKeyQueries || partitionInvalidateKeyQueries.length > 0) && buildRangeEnd < range[1]) {
|
|
1106
|
-
loadRange[1] = buildRangeEnd;
|
|
1107
|
-
}
|
|
1108
|
-
const sealAt = (0, shared_1.addSecondsToLocalTimestamp)(loadRange[1], this.preAggregation.timezone, this.preAggregation.updateWindowSeconds || 0).toISOString();
|
|
1109
|
-
return {
|
|
1110
|
-
...this.preAggregation,
|
|
1111
|
-
tableName: partitionTableName,
|
|
1112
|
-
structureVersionLoadSql: this.preAggregation.loadSql &&
|
|
1113
|
-
this.replacePartitionSqlAndParams(this.preAggregation.loadSql, range, partitionTableName),
|
|
1114
|
-
loadSql: this.preAggregation.loadSql &&
|
|
1115
|
-
this.replacePartitionSqlAndParams(this.preAggregation.loadSql, loadRange, partitionTableName),
|
|
1116
|
-
sql: this.preAggregation.sql &&
|
|
1117
|
-
this.replacePartitionSqlAndParams(this.preAggregation.sql, loadRange, partitionTableName),
|
|
1118
|
-
invalidateKeyQueries: (this.preAggregation.invalidateKeyQueries || [])
|
|
1119
|
-
.map(q => this.replacePartitionSqlAndParams(q, range, partitionTableName)),
|
|
1120
|
-
partitionInvalidateKeyQueries: this.preAggregation.partitionInvalidateKeyQueries &&
|
|
1121
|
-
this.preAggregation.partitionInvalidateKeyQueries.map(q => this.replacePartitionSqlAndParams(q, range, partitionTableName)),
|
|
1122
|
-
indexesSql: (this.preAggregation.indexesSql || [])
|
|
1123
|
-
.map(q => ({ ...q, sql: this.replacePartitionSqlAndParams(q.sql, range, partitionTableName) })),
|
|
1124
|
-
previewSql: this.preAggregation.previewSql &&
|
|
1125
|
-
this.replacePartitionSqlAndParams(this.preAggregation.previewSql, range, partitionTableName),
|
|
1126
|
-
buildRangeStart: loadRange[0],
|
|
1127
|
-
buildRangeEnd: loadRange[1],
|
|
1128
|
-
sealAt, // Used only for kSql pre aggregations
|
|
1129
|
-
};
|
|
1130
|
-
}
|
|
1131
|
-
async loadPreAggregations() {
|
|
1132
|
-
if (this.preAggregation.partitionGranularity && !this.preAggregation.expandedPartition) {
|
|
1133
|
-
const loadPreAggregationsByPartitionRanges = async ({ buildRange, partitionRanges }) => {
|
|
1134
|
-
const partitionLoaders = partitionRanges.map(range => new PreAggregationLoader(this.redisPrefix, this.driverFactory, this.logger, this.queryCache, this.preAggregations, this.partitionPreAggregationDescription(range, buildRange), this.preAggregationsTablesToTempTables, this.loadCache, this.options));
|
|
1135
|
-
const resolveResults = await Promise.all(partitionLoaders.map(async (l, i) => {
|
|
1136
|
-
const result = await l.loadPreAggregation(false);
|
|
1137
|
-
return result && {
|
|
1138
|
-
...result,
|
|
1139
|
-
partitionRange: partitionRanges[i]
|
|
1140
|
-
};
|
|
1141
|
-
}));
|
|
1142
|
-
return { loadResults: resolveResults.filter(res => res !== null), partitionLoaders };
|
|
1143
|
-
};
|
|
1144
|
-
// eslint-disable-next-line prefer-const
|
|
1145
|
-
let loadResultAndLoaders = await loadPreAggregationsByPartitionRanges(await this.partitionRanges());
|
|
1146
|
-
if (this.options.externalRefresh && loadResultAndLoaders.loadResults.length === 0) {
|
|
1147
|
-
loadResultAndLoaders = await loadPreAggregationsByPartitionRanges(await this.partitionRanges(true));
|
|
1148
|
-
// In case there're no partitions ready at matched time dimension intersection then no data can be retrieved.
|
|
1149
|
-
// We need to provide any table so query can just execute successfully.
|
|
1150
|
-
if (loadResultAndLoaders.loadResults.length > 0) {
|
|
1151
|
-
loadResultAndLoaders.loadResults = [loadResultAndLoaders.loadResults[loadResultAndLoaders.loadResults.length - 1]];
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
if (this.options.externalRefresh && loadResultAndLoaders.loadResults.length === 0) {
|
|
1155
|
-
throw new Error(
|
|
1156
|
-
// eslint-disable-next-line no-use-before-define
|
|
1157
|
-
PreAggregations.noPreAggregationPartitionsBuiltMessage(loadResultAndLoaders.partitionLoaders.map(p => p.preAggregation)));
|
|
1158
|
-
}
|
|
1159
|
-
let { loadResults } = loadResultAndLoaders;
|
|
1160
|
-
let lambdaTable;
|
|
1161
|
-
let emptyResult = false;
|
|
1162
|
-
if (this.preAggregation.rollupLambdaId) {
|
|
1163
|
-
if (this.lambdaQuery && loadResults.length > 0) {
|
|
1164
|
-
const { buildRangeEnd, targetTableName } = loadResults[loadResults.length - 1];
|
|
1165
|
-
const lambdaTypes = await this.loadCache.getTableColumnTypes(this.preAggregation, targetTableName);
|
|
1166
|
-
lambdaTable = await this.downloadLambdaTable(buildRangeEnd, lambdaTypes);
|
|
1167
|
-
}
|
|
1168
|
-
const rollupLambdaResults = this.preAggregationsTablesToTempTables.filter(tempTableResult => tempTableResult[1].rollupLambdaId === this.preAggregation.rollupLambdaId);
|
|
1169
|
-
const filteredResults = loadResults.filter(r => (this.preAggregation.lastRollupLambda || (0, shared_1.reformatInIsoLocal)(r.buildRangeEnd) === (0, shared_1.reformatInIsoLocal)(r.partitionRange[1])) &&
|
|
1170
|
-
rollupLambdaResults.every(result => !result[1].buildRangeEnd || (0, shared_1.reformatInIsoLocal)(result[1].buildRangeEnd) < (0, shared_1.reformatInIsoLocal)(r.partitionRange[0])));
|
|
1171
|
-
if (filteredResults.length === 0) {
|
|
1172
|
-
emptyResult = true;
|
|
1173
|
-
loadResults = [loadResults[loadResults.length - 1]];
|
|
1174
|
-
}
|
|
1175
|
-
else {
|
|
1176
|
-
loadResults = filteredResults;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
const allTableTargetNames = loadResults.map(targetTableName => targetTableName.targetTableName);
|
|
1180
|
-
let lastUpdatedAt = getLastUpdatedAtTimestamp(loadResults.map(r => r.lastUpdatedAt));
|
|
1181
|
-
if (lambdaTable) {
|
|
1182
|
-
allTableTargetNames.push(lambdaTable.name);
|
|
1183
|
-
lastUpdatedAt = Date.now();
|
|
1184
|
-
}
|
|
1185
|
-
const unionTargetTableName = allTableTargetNames
|
|
1186
|
-
.map(targetTableName => `SELECT * FROM ${targetTableName}${emptyResult ? ' WHERE 1 = 0' : ''}`)
|
|
1187
|
-
.join(' UNION ALL ');
|
|
1188
|
-
return {
|
|
1189
|
-
targetTableName: allTableTargetNames.length === 1 && !emptyResult ? allTableTargetNames[0] : `(${unionTargetTableName})`,
|
|
1190
|
-
refreshKeyValues: loadResults.map(t => t.refreshKeyValues),
|
|
1191
|
-
lastUpdatedAt,
|
|
1192
|
-
buildRangeEnd: !emptyResult && loadResults.length && loadResults[loadResults.length - 1].buildRangeEnd,
|
|
1193
|
-
lambdaTable,
|
|
1194
|
-
rollupLambdaId: this.preAggregation.rollupLambdaId,
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
else {
|
|
1198
|
-
return new PreAggregationLoader(this.redisPrefix, this.driverFactory, this.logger, this.queryCache, this.preAggregations, this.preAggregation, this.preAggregationsTablesToTempTables, this.loadCache, this.options).loadPreAggregation(true);
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
/**
|
|
1202
|
-
* Downloads the lambda table from the source DB.
|
|
1203
|
-
*/
|
|
1204
|
-
async downloadLambdaTable(fromDate, lambdaTypes) {
|
|
1205
|
-
const { sqlAndParams, cacheKeyQueries } = this.lambdaQuery;
|
|
1206
|
-
const [query, params] = sqlAndParams;
|
|
1207
|
-
const values = params.map((p) => {
|
|
1208
|
-
if (p === shared_1.FROM_PARTITION_RANGE) {
|
|
1209
|
-
return fromDate;
|
|
1210
|
-
}
|
|
1211
|
-
if (p === shared_1.MAX_SOURCE_ROW_LIMIT) {
|
|
1212
|
-
return this.options.maxSourceRowLimit;
|
|
1213
|
-
}
|
|
1214
|
-
return p;
|
|
1215
|
-
});
|
|
1216
|
-
const { data } = await this.queryCache.renewQuery(query, values, cacheKeyQueries, 60 * 60, [query, values], undefined, {
|
|
1217
|
-
requestId: this.requestId,
|
|
1218
|
-
skipRefreshKeyWaitForRenew: false,
|
|
1219
|
-
dataSource: this.dataSource,
|
|
1220
|
-
external: false,
|
|
1221
|
-
useCsvQuery: true,
|
|
1222
|
-
lambdaTypes,
|
|
1223
|
-
});
|
|
1224
|
-
if (data.rowCount === this.options.maxSourceRowLimit) {
|
|
1225
|
-
throw new Error(`The maximum number of source rows ${this.options.maxSourceRowLimit} was reached for ${this.preAggregation.preAggregationId}`);
|
|
1226
|
-
}
|
|
1227
|
-
return {
|
|
1228
|
-
name: `${exports.LAMBDA_TABLE_PREFIX}_${this.preAggregation.tableName.replace('.', '_')}`,
|
|
1229
|
-
columns: data.types,
|
|
1230
|
-
csvRows: data.csvRows,
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
async partitionPreAggregations() {
|
|
1234
|
-
if (this.preAggregation.partitionGranularity && !this.preAggregation.expandedPartition) {
|
|
1235
|
-
const { buildRange, partitionRanges } = await this.partitionRanges();
|
|
1236
|
-
return this.compilerCacheFn(['partitions', JSON.stringify(buildRange)], () => partitionRanges.map(range => this.partitionPreAggregationDescription(range, buildRange)));
|
|
1237
|
-
}
|
|
1238
|
-
else {
|
|
1239
|
-
return [this.preAggregation];
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
async partitionRanges(ignoreMatchedDateRange) {
|
|
1243
|
-
const buildRange = await this.loadBuildRange();
|
|
1244
|
-
if (!buildRange[0] || !buildRange[1]) {
|
|
1245
|
-
return { buildRange, partitionRanges: [] };
|
|
1246
|
-
}
|
|
1247
|
-
let dateRange = PreAggregationPartitionRangeLoader.intersectDateRanges(buildRange, ignoreMatchedDateRange ? undefined : this.preAggregation.matchedTimeDimensionDateRange);
|
|
1248
|
-
if (!dateRange) {
|
|
1249
|
-
// If there's no date range intersection between query data range and pre-aggregation build range
|
|
1250
|
-
// use last partition so outer query can receive expected table structure.
|
|
1251
|
-
dateRange = [buildRange[1], buildRange[1]];
|
|
1252
|
-
}
|
|
1253
|
-
const partitionRanges = this.compilerCacheFn(['timeSeries', this.preAggregation.partitionGranularity, JSON.stringify(dateRange), `${this.preAggregation.timestampPrecision}`], () => PreAggregationPartitionRangeLoader.timeSeries(this.preAggregation.partitionGranularity, dateRange, this.preAggregation.timestampPrecision));
|
|
1254
|
-
if (partitionRanges.length > this.options.maxPartitions) {
|
|
1255
|
-
throw new Error(`Pre-aggregation '${this.preAggregation.tableName}' requested to build ${partitionRanges.length} partitions which exceeds the maximum number of partitions per pre-aggregation of ${this.options.maxPartitions}`);
|
|
1256
|
-
}
|
|
1257
|
-
return { buildRange: dateRange, partitionRanges };
|
|
1258
|
-
}
|
|
1259
|
-
async loadBuildRange() {
|
|
1260
|
-
const { preAggregationStartEndQueries } = this.preAggregation;
|
|
1261
|
-
const [startDate, endDate] = await Promise.all(preAggregationStartEndQueries.map(async (rangeQuery) => PreAggregationPartitionRangeLoader.extractDate(await this.loadRangeQuery(rangeQuery))));
|
|
1262
|
-
if (!this.preAggregation.partitionGranularity) {
|
|
1263
|
-
return this.orNowIfEmpty([startDate, endDate]);
|
|
1264
|
-
}
|
|
1265
|
-
const wholeSeriesRanges = PreAggregationPartitionRangeLoader.timeSeries(this.preAggregation.partitionGranularity, this.orNowIfEmpty([startDate, endDate]), this.preAggregation.timestampPrecision);
|
|
1266
|
-
const [rangeStart, rangeEnd] = await Promise.all(preAggregationStartEndQueries.map(async (rangeQuery, i) => PreAggregationPartitionRangeLoader.extractDate(await this.loadRangeQuery(rangeQuery, i === 0 ? wholeSeriesRanges[0] : wholeSeriesRanges[wholeSeriesRanges.length - 1]))));
|
|
1267
|
-
return this.orNowIfEmpty([rangeStart, rangeEnd]);
|
|
1268
|
-
}
|
|
1269
|
-
now() {
|
|
1270
|
-
return (0, shared_1.utcToLocalTimeZone)(this.preAggregation.timezone, 'YYYY-MM-DDTHH:mm:ss.SSS', new Date().toJSON().substring(0, 23));
|
|
1271
|
-
}
|
|
1272
|
-
orNowIfEmpty(dateRange) {
|
|
1273
|
-
if (!dateRange[0] && !dateRange[1]) {
|
|
1274
|
-
const now = this.now();
|
|
1275
|
-
return [now, now];
|
|
1276
|
-
}
|
|
1277
|
-
if (!dateRange[0]) {
|
|
1278
|
-
return [dateRange[1], dateRange[1]];
|
|
1279
|
-
}
|
|
1280
|
-
if (!dateRange[1]) {
|
|
1281
|
-
return [dateRange[0], dateRange[0]];
|
|
1282
|
-
}
|
|
1283
|
-
return dateRange;
|
|
1284
|
-
}
|
|
1285
|
-
static checkDataRangeType(range) {
|
|
1286
|
-
if (!range) {
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
if (range.length !== 2) {
|
|
1290
|
-
throw new Error(`Date range expected to be an array with 2 elements but ${range} found`);
|
|
1291
|
-
}
|
|
1292
|
-
if (typeof range[0] !== 'string' || typeof range[1] !== 'string') {
|
|
1293
|
-
throw new Error(`Date range expected to be a string array but ${range} found`);
|
|
1294
|
-
}
|
|
1295
|
-
if ((range[0].length !== 23 && range[0].length !== 26) || (range[1].length !== 23 && range[0].length !== 26)) {
|
|
1296
|
-
throw new Error(`Date range expected to be in YYYY-MM-DDTHH:mm:ss.SSS format but ${range} found`);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
static intersectDateRanges(rangeA, rangeB) {
|
|
1300
|
-
PreAggregationPartitionRangeLoader.checkDataRangeType(rangeA);
|
|
1301
|
-
PreAggregationPartitionRangeLoader.checkDataRangeType(rangeB);
|
|
1302
|
-
if (!rangeB) {
|
|
1303
|
-
return rangeA;
|
|
1304
|
-
}
|
|
1305
|
-
if (!rangeA) {
|
|
1306
|
-
return rangeB;
|
|
1307
|
-
}
|
|
1308
|
-
const from = rangeA[0] > rangeB[0] ? rangeA[0] : rangeB[0];
|
|
1309
|
-
const to = rangeA[1] < rangeB[1] ? rangeA[1] : rangeB[1];
|
|
1310
|
-
if (from > to) {
|
|
1311
|
-
return null;
|
|
1312
|
-
}
|
|
1313
|
-
return [
|
|
1314
|
-
from,
|
|
1315
|
-
to,
|
|
1316
|
-
];
|
|
1317
|
-
}
|
|
1318
|
-
static timeSeries(granularity, dateRange, timestampPrecision) {
|
|
1319
|
-
return (0, shared_1.timeSeries)(granularity, dateRange, {
|
|
1320
|
-
timestampPrecision
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
|
-
static partitionTableName(tableName, partitionGranularity, dateRange) {
|
|
1324
|
-
const partitionSuffix = dateRange[0].substring(0, partitionGranularity === 'hour' ? 13 : 10).replace(/[-T:]/g, '');
|
|
1325
|
-
return `${tableName}${partitionSuffix}`;
|
|
1326
|
-
}
|
|
1327
|
-
static inDbTimeZone(preAggregationDescription, timestamp) {
|
|
1328
|
-
return (0, shared_1.inDbTimeZone)(preAggregationDescription.timezone, preAggregationDescription.timestampFormat, timestamp);
|
|
1329
|
-
}
|
|
1330
|
-
static extractDate(data) {
|
|
1331
|
-
return (0, shared_1.extractDate)(data);
|
|
1332
|
-
}
|
|
1333
|
-
static FROM_PARTITION_RANGE = shared_1.FROM_PARTITION_RANGE;
|
|
1334
|
-
static TO_PARTITION_RANGE = shared_1.TO_PARTITION_RANGE;
|
|
1335
|
-
}
|
|
1336
|
-
exports.PreAggregationPartitionRangeLoader = PreAggregationPartitionRangeLoader;
|
|
93
|
+
exports.tablesToVersionEntries = tablesToVersionEntries;
|
|
1337
94
|
class PreAggregations {
|
|
1338
95
|
redisPrefix;
|
|
1339
96
|
driverFactory;
|
|
@@ -1417,7 +174,7 @@ class PreAggregations {
|
|
|
1417
174
|
*/
|
|
1418
175
|
async isPartitionExist(request, external, dataSource = 'default', schema, table, key, token) {
|
|
1419
176
|
// fetching tables
|
|
1420
|
-
const loadCache = new PreAggregationLoadCache(
|
|
177
|
+
const loadCache = new PreAggregationLoadCache_1.PreAggregationLoadCache(() => this.driverFactory(dataSource), this.queryCache, this, {
|
|
1421
178
|
requestId: request,
|
|
1422
179
|
dataSource,
|
|
1423
180
|
tablePrefixes: external ? null : [schema],
|
|
@@ -1465,7 +222,7 @@ class PreAggregations {
|
|
|
1465
222
|
const getLoadCacheByDataSource = (dataSource = 'default', preAggregationSchema) => {
|
|
1466
223
|
if (!loadCacheByDataSource[`${dataSource}_${preAggregationSchema}`]) {
|
|
1467
224
|
loadCacheByDataSource[`${dataSource}_${preAggregationSchema}`] =
|
|
1468
|
-
new PreAggregationLoadCache(
|
|
225
|
+
new PreAggregationLoadCache_1.PreAggregationLoadCache(() => this.driverFactory(dataSource), this.queryCache, this, {
|
|
1469
226
|
requestId: queryBody.requestId,
|
|
1470
227
|
dataSource,
|
|
1471
228
|
tablePrefixes:
|
|
@@ -1480,7 +237,7 @@ class PreAggregations {
|
|
|
1480
237
|
};
|
|
1481
238
|
let queryParamsReplacement = null;
|
|
1482
239
|
const preAggregationsTablesToTempTablesPromise = preAggregations.map((p, i) => (preAggregationsTablesToTempTables) => {
|
|
1483
|
-
const loader = new PreAggregationPartitionRangeLoader(
|
|
240
|
+
const loader = new PreAggregationPartitionRangeLoader_1.PreAggregationPartitionRangeLoader(() => this.driverFactory(p.dataSource || 'default'), this.logger, this.queryCache, this, p, preAggregationsTablesToTempTables, getLoadCacheByDataSource(p.dataSource, p.preAggregationsSchema), {
|
|
1484
241
|
maxPartitions: this.options.maxPartitions,
|
|
1485
242
|
maxSourceRowLimit: this.options.maxSourceRowLimit,
|
|
1486
243
|
isJob: queryBody.isJob,
|
|
@@ -1544,7 +301,7 @@ class PreAggregations {
|
|
|
1544
301
|
const getLoadCacheByDataSource = (dataSource = 'default', preAggregationSchema) => {
|
|
1545
302
|
if (!loadCacheByDataSource[`${dataSource}_${preAggregationSchema}`]) {
|
|
1546
303
|
loadCacheByDataSource[`${dataSource}_${preAggregationSchema}`] =
|
|
1547
|
-
new PreAggregationLoadCache(
|
|
304
|
+
new PreAggregationLoadCache_1.PreAggregationLoadCache(() => this.driverFactory(dataSource), this.queryCache, this, {
|
|
1548
305
|
requestId: queryBody.requestId,
|
|
1549
306
|
dataSource,
|
|
1550
307
|
});
|
|
@@ -1552,7 +309,7 @@ class PreAggregations {
|
|
|
1552
309
|
return loadCacheByDataSource[`${dataSource}_${preAggregationSchema}`];
|
|
1553
310
|
};
|
|
1554
311
|
const expandedPreAggregations = await Promise.all(preAggregations.map(p => {
|
|
1555
|
-
const loader = new PreAggregationPartitionRangeLoader(
|
|
312
|
+
const loader = new PreAggregationPartitionRangeLoader_1.PreAggregationPartitionRangeLoader(() => this.driverFactory(p.dataSource || 'default'), this.logger, this.queryCache, this, p, [], getLoadCacheByDataSource(p.dataSource, p.preAggregationsSchema), {
|
|
1556
313
|
maxPartitions: this.options.maxPartitions,
|
|
1557
314
|
maxSourceRowLimit: this.options.maxSourceRowLimit,
|
|
1558
315
|
waitForRenew: queryBody.renewQuery,
|
|
@@ -1577,7 +334,7 @@ class PreAggregations {
|
|
|
1577
334
|
if (!this.queue[dataSource]) {
|
|
1578
335
|
this.queue[dataSource] = QueryCache_1.QueryCache.createQueue(`SQL_PRE_AGGREGATIONS_${this.redisPrefix}_${dataSource}`, () => this.driverFactory(dataSource), (client, q) => {
|
|
1579
336
|
const { preAggregation, preAggregationsTablesToTempTables, newVersionEntry, requestId, invalidationKeys, buildRangeEnd } = q;
|
|
1580
|
-
const loader = new PreAggregationLoader(
|
|
337
|
+
const loader = new PreAggregationLoader_1.PreAggregationLoader(() => this.driverFactory(dataSource), this.logger, this.queryCache, this, preAggregation, preAggregationsTablesToTempTables, new PreAggregationLoadCache_1.PreAggregationLoadCache(() => this.driverFactory(dataSource), this.queryCache, this, {
|
|
1581
338
|
requestId,
|
|
1582
339
|
dataSource,
|
|
1583
340
|
}), { requestId, externalRefresh: this.externalRefresh, buildRangeEnd });
|
|
@@ -1608,7 +365,7 @@ class PreAggregations {
|
|
|
1608
365
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
1609
366
|
() => ({}), (_, q) => {
|
|
1610
367
|
const { preAggregation, requestId } = q;
|
|
1611
|
-
const loadCache = new PreAggregationLoadCache(
|
|
368
|
+
const loadCache = new PreAggregationLoadCache_1.PreAggregationLoadCache(() => this.driverFactory(dataSource), this.queryCache, this, {
|
|
1612
369
|
requestId,
|
|
1613
370
|
dataSource,
|
|
1614
371
|
});
|
|
@@ -1655,7 +412,7 @@ class PreAggregations {
|
|
|
1655
412
|
const getLoadCacheByDataSource = (dataSource = 'default', preAggregationSchema) => {
|
|
1656
413
|
if (!loadCacheByDataSource[`${dataSource}_${preAggregationSchema}`]) {
|
|
1657
414
|
loadCacheByDataSource[`${dataSource}_${preAggregationSchema}`] =
|
|
1658
|
-
new PreAggregationLoadCache(
|
|
415
|
+
new PreAggregationLoadCache_1.PreAggregationLoadCache(() => this.driverFactory(dataSource), this.queryCache, this, {
|
|
1659
416
|
requestId,
|
|
1660
417
|
dataSource,
|
|
1661
418
|
});
|
|
@@ -1677,8 +434,7 @@ class PreAggregations {
|
|
|
1677
434
|
}
|
|
1678
435
|
async getQueueState(dataSource) {
|
|
1679
436
|
const queue = await this.getQueue(dataSource);
|
|
1680
|
-
|
|
1681
|
-
return queries;
|
|
437
|
+
return queue.getQueries();
|
|
1682
438
|
}
|
|
1683
439
|
async cancelQueriesFromQueue(queryKeys, dataSource) {
|
|
1684
440
|
const queue = await this.getQueue(dataSource);
|