@transitive-sdk/clickhouse 0.3.6 → 0.3.8
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/index.js +7 -17
- package/package.json +1 -1
- package/test/clickhouse.test.js +38 -7
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@ const { topicToPath, topicMatch } = require('@transitive-sdk/datacache');
|
|
|
8
8
|
// Default TTL in days for mqtt_history table
|
|
9
9
|
const DEFAULT_TTL_DAYS = 30;
|
|
10
10
|
|
|
11
|
-
// Shared multi-tenant schema components used by createTable and
|
|
11
|
+
// Shared multi-tenant schema components used by createTable and enableHistory
|
|
12
12
|
const MULTI_TENANT_SCHEMA = {
|
|
13
13
|
// Column definitions for OrgId and DeviceId
|
|
14
14
|
columns: [
|
|
@@ -109,13 +109,15 @@ class ClickHouse {
|
|
|
109
109
|
* @param {Array<string>} columns - array of column definitions and indexes, e.g. ['Timestamp DateTime CODEC(ZSTD(1))', 'Value Float32 CODEC(ZSTD(1))']
|
|
110
110
|
* @param {Array<string>} settings - array of table settings, e.g. ['ENGINE = MergeTree()', 'ORDER BY (Timestamp)']
|
|
111
111
|
*/
|
|
112
|
-
async createTable(tableName, columns, settings = []) {
|
|
112
|
+
async createTable(tableName, columns, settings = ['ORDER BY (OrgId, DeviceId)']) {
|
|
113
113
|
const fullSchema = [
|
|
114
114
|
...columns,
|
|
115
115
|
...MULTI_TENANT_SCHEMA.columns,
|
|
116
116
|
...MULTI_TENANT_SCHEMA.indexes
|
|
117
117
|
];
|
|
118
|
-
const query = `CREATE TABLE IF NOT EXISTS ${tableName}
|
|
118
|
+
const query = `CREATE TABLE IF NOT EXISTS ${tableName}
|
|
119
|
+
(${fullSchema.join(', ')})
|
|
120
|
+
${settings.join(' ')}`;
|
|
119
121
|
|
|
120
122
|
try {
|
|
121
123
|
return await this.client.exec({
|
|
@@ -346,29 +348,17 @@ class ClickHouse {
|
|
|
346
348
|
topicSelector,
|
|
347
349
|
since = undefined,
|
|
348
350
|
until = undefined,
|
|
349
|
-
orderBy = 'Timestamp
|
|
351
|
+
orderBy = 'Timestamp DESC',
|
|
350
352
|
limit = 1000
|
|
351
353
|
} = options;
|
|
352
354
|
|
|
353
355
|
const path = topicToPath(topicSelector);
|
|
354
356
|
|
|
355
357
|
// interpret wildcards
|
|
356
|
-
// const where = [];
|
|
357
|
-
// _.forEach(path, (value, i) => {
|
|
358
|
-
// if (!['+','#'].includes(value[0])) {
|
|
359
|
-
// // it's a constant, filter by it
|
|
360
|
-
// where.push(`TopicParts[${i + 1}] = '${value}'`);
|
|
361
|
-
// // Note that ClickHouse/SQL index starting at 1, not 0
|
|
362
|
-
// }
|
|
363
|
-
// });
|
|
364
358
|
const where = path2where(path);
|
|
365
|
-
|
|
366
359
|
since && where.push(`Timestamp >= fromUnixTimestamp64Milli(${since.getTime()})`);
|
|
367
360
|
until && where.push(`Timestamp <= fromUnixTimestamp64Milli(${until.getTime()})`);
|
|
368
|
-
|
|
369
|
-
const whereStatement = where.length > 0
|
|
370
|
-
? `WHERE ${where.join(' AND ')}`
|
|
371
|
-
: '';
|
|
361
|
+
const whereStatement = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
|
|
372
362
|
|
|
373
363
|
const result = await this.client.query({
|
|
374
364
|
query: `SELECT Payload,TopicParts,Timestamp FROM default.${this.mqttHistoryTable} ${
|
package/package.json
CHANGED
package/test/clickhouse.test.js
CHANGED
|
@@ -33,6 +33,7 @@ const interceptInserts = () => {
|
|
|
33
33
|
const queryRowsByOrg = async (org, options = {}) =>
|
|
34
34
|
await clickhouse.queryMQTTHistory({
|
|
35
35
|
topicSelector: `/${org}/+/+/+/+/+`,
|
|
36
|
+
orderBy: 'Timestamp ASC',
|
|
36
37
|
...options
|
|
37
38
|
});
|
|
38
39
|
|
|
@@ -67,6 +68,19 @@ describe('ClickHouse', function() {
|
|
|
67
68
|
await clickhouse.registerMqttTopicForStorage(STANDARD_TOPIC_PATTERN);
|
|
68
69
|
});
|
|
69
70
|
|
|
71
|
+
describe('basics', () => {
|
|
72
|
+
it('creates tables without crashing', async () => {
|
|
73
|
+
const result = await clickhouse.createTable('test_tmp',
|
|
74
|
+
['text String']);
|
|
75
|
+
|
|
76
|
+
// clean up
|
|
77
|
+
await clickhouse.client.command({
|
|
78
|
+
query: `DROP TABLE IF EXISTS test_tmp`,
|
|
79
|
+
clickhouse_settings: { wait_end_of_query: 1 }
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
70
84
|
describe('enableHistory', () => {
|
|
71
85
|
it('should create the mqtt_history table', async () => {
|
|
72
86
|
const result = await clickhouse.client.query({
|
|
@@ -232,12 +246,14 @@ describe('ClickHouse', function() {
|
|
|
232
246
|
|
|
233
247
|
it('queries with wild cards', async () => {
|
|
234
248
|
const rows = await clickhouse.queryMQTTHistory({
|
|
249
|
+
orderBy: 'Timestamp ASC',
|
|
235
250
|
topicSelector: `/${org}/+/+/+/+/+` });
|
|
236
251
|
assert(rows.length > 0);
|
|
237
252
|
});
|
|
238
253
|
|
|
239
254
|
it('queries with multiple selectors', async () => {
|
|
240
255
|
const [row] = await clickhouse.queryMQTTHistory({
|
|
256
|
+
orderBy: 'Timestamp ASC',
|
|
241
257
|
topicSelector: `/${org}/+/+/capdata/+/+` });
|
|
242
258
|
assert.strictEqual(row.DeviceId, 'device1');
|
|
243
259
|
assert.deepEqual(row.SubTopic, ['data']);
|
|
@@ -247,6 +263,7 @@ describe('ClickHouse', function() {
|
|
|
247
263
|
|
|
248
264
|
it('queries based on sub-topic selectors', async () => {
|
|
249
265
|
const [row] = await clickhouse.queryMQTTHistory({
|
|
266
|
+
orderBy: 'Timestamp ASC',
|
|
250
267
|
topicSelector: `/${org}/+/+/+/+/data2` });
|
|
251
268
|
assert.strictEqual(row.DeviceId, 'device1');
|
|
252
269
|
assert.deepStrictEqual(row.Payload, { y: 2 });
|
|
@@ -254,12 +271,14 @@ describe('ClickHouse', function() {
|
|
|
254
271
|
|
|
255
272
|
it('queries based on sub-topic selectors with wildcards', async () => {
|
|
256
273
|
const [row] = await clickhouse.queryMQTTHistory({
|
|
274
|
+
orderBy: 'Timestamp ASC',
|
|
257
275
|
topicSelector: `/${org}/+/+/+/+/+/sub2/+` });
|
|
258
276
|
assert.deepStrictEqual(row.SubTopic[2], 'sub3.1');
|
|
259
277
|
});
|
|
260
278
|
|
|
261
279
|
it('queries based on multiple sub-topic selectors with wildcards', async () => {
|
|
262
280
|
const rows = await clickhouse.queryMQTTHistory({
|
|
281
|
+
orderBy: 'Timestamp ASC',
|
|
263
282
|
topicSelector: `/${org}/+/+/+/+/sub1/+/+` });
|
|
264
283
|
assert.strictEqual(rows[0].SubTopic.length, 3);
|
|
265
284
|
assert.strictEqual(rows[0].SubTopic[2], 'sub3.1');
|
|
@@ -268,6 +287,7 @@ describe('ClickHouse', function() {
|
|
|
268
287
|
|
|
269
288
|
it('returns the history', async () => {
|
|
270
289
|
const rows = await clickhouse.queryMQTTHistory({
|
|
290
|
+
orderBy: 'Timestamp ASC',
|
|
271
291
|
topicSelector: `/${org}/+/+/+/+/data/+/+` });
|
|
272
292
|
assert.deepStrictEqual(rows.length, 2);
|
|
273
293
|
assert.deepStrictEqual(rows[0].Payload, {x: 1});
|
|
@@ -277,6 +297,7 @@ describe('ClickHouse', function() {
|
|
|
277
297
|
|
|
278
298
|
it('handles null values', async () => {
|
|
279
299
|
const rows = await clickhouse.queryMQTTHistory({
|
|
300
|
+
orderBy: 'Timestamp ASC',
|
|
280
301
|
topicSelector: `/${org}/+/+/+/+/willBeNull` });
|
|
281
302
|
assert.strictEqual(rows.at(-1).Payload, null);
|
|
282
303
|
});
|
|
@@ -301,7 +322,8 @@ describe('ClickHouse', function() {
|
|
|
301
322
|
for (let i = 0; i < ROWS; i++) {
|
|
302
323
|
rows.push({
|
|
303
324
|
Timestamp: new Date(now + i * GAP), // use current date to avoid immediate TTL cleanup
|
|
304
|
-
TopicParts: [`org${i % 50}`, `device${i % 1000}`, '@myscope',
|
|
325
|
+
TopicParts: [`org${i % 50}`, `device${i % 1000}`, '@myscope',
|
|
326
|
+
`cap${i % 100}`, `1.${i % 100}.0`, `data_${i % 1000}`, i],
|
|
305
327
|
Payload: { i },
|
|
306
328
|
})
|
|
307
329
|
}
|
|
@@ -332,7 +354,7 @@ describe('ClickHouse', function() {
|
|
|
332
354
|
limit: 2 * ROWS,
|
|
333
355
|
});
|
|
334
356
|
assert.equal(rows.length, ROWS);
|
|
335
|
-
assert(rows[0].Timestamp
|
|
357
|
+
assert(rows[0].Timestamp > rows[1].Timestamp);
|
|
336
358
|
assertTimelimit(ROWS / 100);
|
|
337
359
|
});
|
|
338
360
|
|
|
@@ -347,7 +369,7 @@ describe('ClickHouse', function() {
|
|
|
347
369
|
|
|
348
370
|
it('quickly filters by DeviceId', async () => {
|
|
349
371
|
const rows = await clickhouse.queryMQTTHistory({
|
|
350
|
-
topicSelector: `/+/device123
|
|
372
|
+
topicSelector: `/+/device123/+/+/+/+`,
|
|
351
373
|
limit: 2 * ROWS,
|
|
352
374
|
});
|
|
353
375
|
assert.equal(rows.length, ROWS / 1000);
|
|
@@ -356,16 +378,25 @@ describe('ClickHouse', function() {
|
|
|
356
378
|
|
|
357
379
|
it('quickly filters by CapabilityName', async () => {
|
|
358
380
|
const rows = await clickhouse.queryMQTTHistory({
|
|
359
|
-
topicSelector: `/+/+/+/cap34
|
|
381
|
+
topicSelector: `/+/+/+/cap34/+/+`,
|
|
360
382
|
limit: 2 * ROWS,
|
|
361
383
|
});
|
|
362
384
|
assert.equal(rows.length, ROWS / 100);
|
|
363
385
|
assertTimelimit(ROWS / 1000);
|
|
364
386
|
});
|
|
365
387
|
|
|
388
|
+
it('quickly filters by SubTopic', async () => {
|
|
389
|
+
const rows = await clickhouse.queryMQTTHistory({
|
|
390
|
+
topicSelector: `/+/+/+/+/+/data_123`,
|
|
391
|
+
limit: 2 * ROWS,
|
|
392
|
+
});
|
|
393
|
+
assert.equal(rows.length, ROWS / 1000);
|
|
394
|
+
assertTimelimit(ROWS / 1000);
|
|
395
|
+
});
|
|
396
|
+
|
|
366
397
|
it('quickly filters by time: since', async () => {
|
|
367
398
|
const rows = await clickhouse.queryMQTTHistory({
|
|
368
|
-
topicSelector:
|
|
399
|
+
topicSelector: `/+/+/+/+/+/+`,
|
|
369
400
|
since: new Date(now + (ROWS - 400) * GAP),
|
|
370
401
|
limit: 2 * ROWS,
|
|
371
402
|
});
|
|
@@ -375,7 +406,7 @@ describe('ClickHouse', function() {
|
|
|
375
406
|
|
|
376
407
|
it('quickly filters by time: until', async () => {
|
|
377
408
|
const rows = await clickhouse.queryMQTTHistory({
|
|
378
|
-
topicSelector:
|
|
409
|
+
topicSelector: `/+/+/+/+/+/+`,
|
|
379
410
|
until: new Date(now + 400 * GAP),
|
|
380
411
|
limit: 2 * ROWS,
|
|
381
412
|
});
|
|
@@ -385,7 +416,7 @@ describe('ClickHouse', function() {
|
|
|
385
416
|
|
|
386
417
|
it('quickly filters by org and time: since', async () => {
|
|
387
418
|
const rows = await clickhouse.queryMQTTHistory({
|
|
388
|
-
topicSelector: `/org23
|
|
419
|
+
topicSelector: `/org23/+/+/+/+/+`,
|
|
389
420
|
since: new Date(now + (ROWS - 400) * GAP),
|
|
390
421
|
limit: 2 * ROWS,
|
|
391
422
|
});
|