@transitive-sdk/clickhouse 0.1.0
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 +98 -0
- package/package.json +30 -0
package/index.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const { createClient } = require('@clickhouse/client');
|
|
2
|
+
|
|
3
|
+
/** Singleton ClickHouse client wrapper with multi-tenant table support */
|
|
4
|
+
class ClickHouse {
|
|
5
|
+
init({url, dbName, user, password} = {}) {
|
|
6
|
+
const _url = url || process.env.CLICKHOUSE_URL || 'http://localhost:8123';
|
|
7
|
+
const _dbName = dbName || process.env.CLICKHOUSE_DB || 'default';
|
|
8
|
+
const _user = user || process.env.CLICKHOUSE_USER || 'default';
|
|
9
|
+
const _password = password || process.env.CLICKHOUSE_PASSWORD || '';
|
|
10
|
+
console.debug(`Creating ClickHouse client for URL: ${_url}, DB: ${_dbName}, User: ${_user}`);
|
|
11
|
+
this._client = createClient({
|
|
12
|
+
url: _url,
|
|
13
|
+
max_open_connections: 10,
|
|
14
|
+
database: _dbName,
|
|
15
|
+
username: _user,
|
|
16
|
+
password: _password,
|
|
17
|
+
clickhouse_settings: {
|
|
18
|
+
// https://clickhouse.com/docs/en/operations/settings/settings#async-insert
|
|
19
|
+
async_insert: 1,
|
|
20
|
+
// https://clickhouse.com/docs/en/operations/settings/settings#wait-for-async-insert
|
|
21
|
+
wait_for_async_insert: 1,
|
|
22
|
+
// https://clickhouse.com/docs/en/operations/settings/settings#async-insert-max-data-size
|
|
23
|
+
async_insert_max_data_size: '1000000',
|
|
24
|
+
// https://clickhouse.com/docs/en/operations/settings/settings#async-insert-busy-timeout-ms
|
|
25
|
+
async_insert_busy_timeout_ms: 1000,
|
|
26
|
+
// Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
|
|
27
|
+
date_time_input_format: 'best_effort',
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get client() {
|
|
33
|
+
if (this._client == undefined) {
|
|
34
|
+
console.warn('Cannot access ClickHouse client before init() is called');
|
|
35
|
+
throw new Error('ClickHouse client not initialized');
|
|
36
|
+
}
|
|
37
|
+
return this._client;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Create a table if it does not already exist
|
|
41
|
+
* adding OrgId and DeviceId columns to the schema
|
|
42
|
+
* for multi-tenancy support.
|
|
43
|
+
* @param {string} tableName - name of the table to create
|
|
44
|
+
* @param {Array<string>} columns - array of column definitions and indexes, e.g. ['Timestamp DateTime CODEC(ZSTD(1))', 'Value Float32 CODEC(ZSTD(1))']
|
|
45
|
+
* @param {Array<string>} settings - array of table settings, e.g. ['ENGINE = MergeTree()', 'ORDER BY (Timestamp)']
|
|
46
|
+
*/
|
|
47
|
+
async createTable(tableName, columns, settings = []) {
|
|
48
|
+
const fullSchema = [
|
|
49
|
+
...columns,
|
|
50
|
+
'OrgId String CODEC(ZSTD(1))',
|
|
51
|
+
'DeviceId String CODEC(ZSTD(1))',
|
|
52
|
+
'INDEX idx_orgid (OrgId) TYPE bloom_filter(0.01) GRANULARITY 1',
|
|
53
|
+
'INDEX idx_deviceid (DeviceId) TYPE bloom_filter(0.01) GRANULARITY 1'
|
|
54
|
+
];
|
|
55
|
+
const query = `CREATE TABLE IF NOT EXISTS ${tableName} (${fullSchema.join(', ')}) ${settings.join(' ')}`;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
return await this.client.exec({
|
|
59
|
+
query,
|
|
60
|
+
clickhouse_settings: {
|
|
61
|
+
wait_end_of_query: 1,
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Error executing query:', error.message);
|
|
66
|
+
console.debug('Query was:', query);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Insert rows into a multi-tenant table, adding OrgId and DeviceId to each row
|
|
72
|
+
* @param {string} tableName - name of the table to insert into
|
|
73
|
+
* @param {Array<Object>} rows - array of row objects to insert (JSON each item)
|
|
74
|
+
* @param {string} orgId - organization ID to add to each row
|
|
75
|
+
* @param {string} deviceId - device ID to add to each row
|
|
76
|
+
*/
|
|
77
|
+
async insert(tableName, rows, orgId, deviceId) {
|
|
78
|
+
// assert that orgId and deviceId are provided
|
|
79
|
+
if (!orgId || !deviceId) {
|
|
80
|
+
throw new Error('Both orgId and deviceId must be provided for multi-tenant insert');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Augment each row with OrgId and DeviceId
|
|
84
|
+
const rowsWithIds = rows.map(row => ({
|
|
85
|
+
...row,
|
|
86
|
+
OrgId: orgId,
|
|
87
|
+
DeviceId: deviceId
|
|
88
|
+
}));
|
|
89
|
+
return await this.client.insert({
|
|
90
|
+
table: tableName,
|
|
91
|
+
values: rowsWithIds,
|
|
92
|
+
format: 'JSONEachRow'
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const instance = new ClickHouse();
|
|
98
|
+
module.exports = instance;
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@transitive-sdk/clickhouse",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A tiny ClickHouse utility class for use in the Transitive framework.",
|
|
5
|
+
"homepage": "https://transitiverobotics.com",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/transitiverobotics/transitive-utils.git",
|
|
9
|
+
"directory": "clickhouse"
|
|
10
|
+
},
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Christian Fritz",
|
|
13
|
+
"email": "christian@transitiverobotics.com"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"robotics"
|
|
17
|
+
],
|
|
18
|
+
"license": "Apache-2.0",
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"main": "index.js",
|
|
23
|
+
"scripts": {
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@clickhouse/client": "^1.12.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
}
|
|
30
|
+
}
|