@cleverbrush/knex-clickhouse 1.1.11 → 3.0.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/README.md +128 -2
- package/dist/index.d.ts +19 -6
- package/dist/index.js +2 -298
- package/dist/index.js.map +1 -0
- package/package.json +15 -7
- package/dist/makeEscape.js +0 -158
package/README.md
CHANGED
|
@@ -1,3 +1,129 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @cleverbrush/knex-clickhouse
|
|
2
|
+
<!-- coverage-badge-start -->
|
|
3
|
+

|
|
4
|
+
<!-- coverage-badge-end -->
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
A [Knex](https://knexjs.org/) dialect for [ClickHouse](https://clickhouse.com/). Uses the official `@clickhouse/client` library to connect to ClickHouse and lets you build queries with Knex's familiar API.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @cleverbrush/knex-clickhouse
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Peer dependency:** `knex >= 3.1.0`
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { getClickhouseConnection } from '@cleverbrush/knex-clickhouse';
|
|
20
|
+
|
|
21
|
+
const db = getClickhouseConnection({
|
|
22
|
+
connection: {
|
|
23
|
+
url: 'http://localhost:8123',
|
|
24
|
+
user: 'default',
|
|
25
|
+
password: '',
|
|
26
|
+
database: 'my_database'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Select
|
|
31
|
+
const [rows] = await db('my_table').select('*').where('id', 1);
|
|
32
|
+
|
|
33
|
+
// Insert
|
|
34
|
+
await db('my_table').insert({ id: 1, name: 'Alice' });
|
|
35
|
+
|
|
36
|
+
// Raw queries
|
|
37
|
+
const [rows] = await db.raw('SELECT * FROM my_table WHERE id = ?', [1]);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
### `getClickhouseConnection(config?, clickHouseSettings?)`
|
|
43
|
+
|
|
44
|
+
Creates and returns a Knex instance configured with the ClickHouse dialect.
|
|
45
|
+
|
|
46
|
+
**Parameters:**
|
|
47
|
+
|
|
48
|
+
| Name | Type | Description |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `config` | `Knex.Config & AdditionalClientOptions` | Knex configuration with optional retry settings |
|
|
51
|
+
| `clickHouseSettings` | `ClickHouseSettings` | Optional ClickHouse-specific settings |
|
|
52
|
+
|
|
53
|
+
### `ClickhouseKnexClient`
|
|
54
|
+
|
|
55
|
+
The Knex client class for ClickHouse. Can be used directly if you need more control over Knex initialization:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import Knex from 'knex';
|
|
59
|
+
import { ClickhouseKnexClient } from '@cleverbrush/knex-clickhouse';
|
|
60
|
+
|
|
61
|
+
const db = Knex({
|
|
62
|
+
client: ClickhouseKnexClient,
|
|
63
|
+
connection: {
|
|
64
|
+
url: 'http://localhost:8123',
|
|
65
|
+
user: 'default',
|
|
66
|
+
password: '',
|
|
67
|
+
database: 'my_database'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Query Methods
|
|
73
|
+
|
|
74
|
+
Standard Knex query methods are supported:
|
|
75
|
+
|
|
76
|
+
- `select()` / `first()` / `pluck()` — read queries
|
|
77
|
+
- `insert()` — standard inserts
|
|
78
|
+
- `update()` — update records
|
|
79
|
+
- `del()` — delete records
|
|
80
|
+
- `raw()` — raw SQL queries (auto-detected as SELECT when the query starts with `SELECT` or `WITH`)
|
|
81
|
+
|
|
82
|
+
### `insertToClickhouse(rows)`
|
|
83
|
+
|
|
84
|
+
An extended method for inserting rows with ClickHouse-specific data types like arrays and tuples:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
await db('my_table').insertToClickhouse([
|
|
88
|
+
[1, 'Alice', [10, 20, 30]],
|
|
89
|
+
[2, 'Bob', [40, 50]]
|
|
90
|
+
]);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Retry Configuration
|
|
94
|
+
|
|
95
|
+
By default, queries are retried up to 10 times on `ECONNRESET` errors with exponential backoff. You can customize or disable this:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const db = getClickhouseConnection({
|
|
99
|
+
connection: { /* ... */ },
|
|
100
|
+
retry: {
|
|
101
|
+
maxRetries: 5,
|
|
102
|
+
minDelay: 200,
|
|
103
|
+
delayFactor: 2,
|
|
104
|
+
delayRandomizationPercent: 0.1,
|
|
105
|
+
shouldRetry: (error) => error.code === 'ECONNRESET'
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Disable retries
|
|
110
|
+
const db = getClickhouseConnection({
|
|
111
|
+
connection: { /* ... */ },
|
|
112
|
+
retry: null
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Type Parsing
|
|
117
|
+
|
|
118
|
+
The client automatically parses ClickHouse types to JavaScript types:
|
|
119
|
+
|
|
120
|
+
| ClickHouse Type | JavaScript Type |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `Date`, `DateTime`, `DateTime('UTC')` | `Date` |
|
|
123
|
+
| `Nullable(Date)`, `Nullable(DateTime)` | `Date` |
|
|
124
|
+
| `LowCardinality(Date)`, `LowCardinality(DateTime)` | `Date` |
|
|
125
|
+
| `UInt64`, `UInt32` | `number` |
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
BSD-3-Clause
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import Knex from 'knex';
|
|
2
|
-
import ClickhouseDriver, { ClickHouseClient, type ClickHouseSettings } from '@clickhouse/client';
|
|
3
1
|
import { retry } from '@cleverbrush/async';
|
|
4
|
-
|
|
2
|
+
import ClickhouseDriver, { type ClickHouseClient, type ClickHouseSettings } from '@clickhouse/client';
|
|
3
|
+
import knex, { type Knex } from 'knex';
|
|
4
|
+
export type { ClickHouseClient };
|
|
5
|
+
declare const KnexClient: typeof Knex.Client;
|
|
5
6
|
type AdditionalClientOptions = {
|
|
6
7
|
/**
|
|
7
8
|
* Options for retrying the query,
|
|
@@ -13,10 +14,10 @@ type AdditionalClientOptions = {
|
|
|
13
14
|
*/
|
|
14
15
|
retry?: Parameters<typeof retry>[1];
|
|
15
16
|
};
|
|
16
|
-
export declare class ClickhouseKnexClient extends
|
|
17
|
+
export declare class ClickhouseKnexClient extends KnexClient {
|
|
17
18
|
#private;
|
|
18
19
|
_driver(): typeof ClickhouseDriver;
|
|
19
|
-
constructor(config?: Knex.
|
|
20
|
+
constructor(config?: Knex.Config<any> & AdditionalClientOptions);
|
|
20
21
|
_escapeBinding(value: any): any;
|
|
21
22
|
/**
|
|
22
23
|
*
|
|
@@ -38,4 +39,16 @@ export declare class ClickhouseKnexClient extends Knex.Client {
|
|
|
38
39
|
*
|
|
39
40
|
* @returns {import('knex').Knex<any, unknown[]>}
|
|
40
41
|
*/
|
|
41
|
-
export declare const getClickhouseConnection: (config?: Knex.
|
|
42
|
+
export declare const getClickhouseConnection: (config?: Knex.Config<any> & AdditionalClientOptions, clickHouseSettings?: ClickHouseSettings,
|
|
43
|
+
/**
|
|
44
|
+
* If defined, this function will be called before each query
|
|
45
|
+
* and awaited if it returns a promise.
|
|
46
|
+
*
|
|
47
|
+
* Could be useful in some cases. For example you want first to check
|
|
48
|
+
* if the server is not in idle state (for example Clickhouse Cloud have idling options)
|
|
49
|
+
* and if it is, you want to wake it up before sending the query.
|
|
50
|
+
*
|
|
51
|
+
* if the function throws an error this error will not be caught and will propagate to the caller.
|
|
52
|
+
* Therefore the query will not be executed.
|
|
53
|
+
*/
|
|
54
|
+
preQueryCallback?: (connection: ClickHouseClient) => Promise<void>) => knex.Knex<any, unknown[]>;
|
package/dist/index.js
CHANGED
|
@@ -1,298 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { retry } from '@cleverbrush/async';
|
|
4
|
-
import { deepExtend } from '@cleverbrush/deep';
|
|
5
|
-
import { makeEscape } from './makeEscape.js';
|
|
6
|
-
const parseDate = (value) => new Date(Date.parse(value));
|
|
7
|
-
// this function is taken from knex npm package
|
|
8
|
-
function arrayString(arr, esc) {
|
|
9
|
-
let result = '';
|
|
10
|
-
for (let i = 0; i < arr.length; i++) {
|
|
11
|
-
if (i > 0)
|
|
12
|
-
result += ',';
|
|
13
|
-
const val = arr[i];
|
|
14
|
-
if (val === null || typeof val === 'undefined') {
|
|
15
|
-
result += 'NULL';
|
|
16
|
-
}
|
|
17
|
-
else if (Array.isArray(val)) {
|
|
18
|
-
result += arrayString(val, esc);
|
|
19
|
-
}
|
|
20
|
-
else if (typeof val === 'number') {
|
|
21
|
-
result += val;
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
result += esc(val);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return `[${result}]`;
|
|
28
|
-
}
|
|
29
|
-
export class ClickhouseKnexClient extends Knex.Client {
|
|
30
|
-
_driver() {
|
|
31
|
-
return ClickhouseDriver;
|
|
32
|
-
}
|
|
33
|
-
#retryOptions;
|
|
34
|
-
constructor(config = {}) {
|
|
35
|
-
const { retry: retryOptions } = config;
|
|
36
|
-
super(config);
|
|
37
|
-
this.#retryOptions = retryOptions
|
|
38
|
-
? deepExtend({
|
|
39
|
-
maxRetries: 10,
|
|
40
|
-
delayFactor: 2,
|
|
41
|
-
minDelay: 100,
|
|
42
|
-
delayRandomizationPercent: 0.1,
|
|
43
|
-
shouldRetry: (error) => error.code === 'ECONNRESET'
|
|
44
|
-
}, retryOptions)
|
|
45
|
-
: null;
|
|
46
|
-
}
|
|
47
|
-
#typeParsers = {
|
|
48
|
-
Date: parseDate,
|
|
49
|
-
DateTime: parseDate,
|
|
50
|
-
'LowCardinality(Date)': parseDate,
|
|
51
|
-
'LowCardinality(DateTime)': parseDate,
|
|
52
|
-
'Nullable(Date)': parseDate,
|
|
53
|
-
'Nullable(DateTime)': parseDate,
|
|
54
|
-
"DateTime('UTC')": parseDate,
|
|
55
|
-
"Nullable(DateTime('UTC'))": parseDate,
|
|
56
|
-
'LowCardinality(Nullable(Date))': parseDate,
|
|
57
|
-
'LowCardinality(Nullable(DateTime))': parseDate,
|
|
58
|
-
UInt64: (value) => Number(value),
|
|
59
|
-
UInt32: (value) => Number(value)
|
|
60
|
-
};
|
|
61
|
-
#selectQueryRegex = /^(\s+)?(select|with)/i;
|
|
62
|
-
_escapeBinding(value) {
|
|
63
|
-
const escapeBinding = makeEscape({
|
|
64
|
-
escapeArray(val, esc) {
|
|
65
|
-
return arrayString(val, esc);
|
|
66
|
-
},
|
|
67
|
-
escapeString(str) {
|
|
68
|
-
let escaped = "'";
|
|
69
|
-
for (let i = 0; i < str.length; i++) {
|
|
70
|
-
const c = str[i];
|
|
71
|
-
if (c === "'") {
|
|
72
|
-
escaped += c + c;
|
|
73
|
-
}
|
|
74
|
-
else if (c === '\\') {
|
|
75
|
-
escaped += c + c;
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
escaped += c;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
escaped += "'";
|
|
82
|
-
return escaped;
|
|
83
|
-
},
|
|
84
|
-
escapeObject(val, prepareValue, timezone, seen = []) {
|
|
85
|
-
if (val && typeof val.toPostgres === 'function') {
|
|
86
|
-
seen = seen || [];
|
|
87
|
-
if (seen.indexOf(val) !== -1) {
|
|
88
|
-
throw new Error(`circular reference detected while preparing "${val}" for query`);
|
|
89
|
-
}
|
|
90
|
-
seen.push(val);
|
|
91
|
-
return prepareValue(val.toPostgres(prepareValue), seen);
|
|
92
|
-
}
|
|
93
|
-
return JSON.stringify(val);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
const result = escapeBinding(value);
|
|
97
|
-
if (typeof result === 'string') {
|
|
98
|
-
return result.replace(/\\/g, '\\\\');
|
|
99
|
-
}
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
*
|
|
104
|
-
* @param {import('@clickhouse/client').QueryParams} obj
|
|
105
|
-
* @returns
|
|
106
|
-
*/
|
|
107
|
-
async _query(connection, obj) {
|
|
108
|
-
if (!obj || typeof obj === 'string') {
|
|
109
|
-
obj = { sql: obj };
|
|
110
|
-
}
|
|
111
|
-
else if (!obj.sql) {
|
|
112
|
-
throw new Error('The query is empty');
|
|
113
|
-
}
|
|
114
|
-
let { method } = obj;
|
|
115
|
-
if (method === 'raw' && this.#selectQueryRegex.test(obj.sql)) {
|
|
116
|
-
method = 'select';
|
|
117
|
-
}
|
|
118
|
-
const query = Array.isArray(obj.bindings) && obj.bindings.length > 0
|
|
119
|
-
? obj.sql
|
|
120
|
-
.split('?')
|
|
121
|
-
.map((x, i) => `${x}${obj.bindings.length > i
|
|
122
|
-
? this._escapeBinding(obj.bindings[i])
|
|
123
|
-
: ''}`)
|
|
124
|
-
.join('')
|
|
125
|
-
: obj.sql;
|
|
126
|
-
let response;
|
|
127
|
-
const queryParams = {
|
|
128
|
-
clickhouse_settings: {
|
|
129
|
-
exact_rows_before_limit: true
|
|
130
|
-
},
|
|
131
|
-
query,
|
|
132
|
-
format: 'JSONCompact'
|
|
133
|
-
};
|
|
134
|
-
obj.finalQuery = query;
|
|
135
|
-
const { preQueryCallback } = this.config;
|
|
136
|
-
if (typeof preQueryCallback === 'function') {
|
|
137
|
-
await Promise.resolve(preQueryCallback(connection));
|
|
138
|
-
}
|
|
139
|
-
switch (method) {
|
|
140
|
-
case 'select':
|
|
141
|
-
case 'first':
|
|
142
|
-
case 'pluck':
|
|
143
|
-
{
|
|
144
|
-
const p = (await (this.#retryOptions
|
|
145
|
-
? retry(() => connection.query(queryParams), this.#retryOptions)
|
|
146
|
-
: connection.query(queryParams)));
|
|
147
|
-
response = await p.json();
|
|
148
|
-
const { data, meta, ...rest } = response;
|
|
149
|
-
obj.response = [data, meta, { ...rest }];
|
|
150
|
-
}
|
|
151
|
-
break;
|
|
152
|
-
default:
|
|
153
|
-
response = await (this.#retryOptions
|
|
154
|
-
? retry(() => connection.exec(queryParams), this.#retryOptions)
|
|
155
|
-
: connection.exec(queryParams));
|
|
156
|
-
}
|
|
157
|
-
return obj;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
*
|
|
161
|
-
* @param {import('@clickhouse/client').ResultSet} obj
|
|
162
|
-
* @param {*} runner
|
|
163
|
-
* @returns
|
|
164
|
-
*/
|
|
165
|
-
processResponse(obj, runner) {
|
|
166
|
-
if (obj == null)
|
|
167
|
-
return null;
|
|
168
|
-
const { response } = obj;
|
|
169
|
-
const [rows, fields, meta] = response || [[]];
|
|
170
|
-
const parsers = Array.isArray(fields)
|
|
171
|
-
? fields.map((field) => typeof this.#typeParsers[field.type] === 'function'
|
|
172
|
-
? this.#typeParsers[field.type]
|
|
173
|
-
: (x) => x)
|
|
174
|
-
: fields;
|
|
175
|
-
const rowToObj = (row) => fields.reduce((acc, field, index) => ({
|
|
176
|
-
...acc,
|
|
177
|
-
[field.name]: parsers[index](row[index])
|
|
178
|
-
}), {});
|
|
179
|
-
if (obj.output)
|
|
180
|
-
return obj.output.call(runner, rows, fields);
|
|
181
|
-
let { method } = obj;
|
|
182
|
-
if (method === 'raw' && this.#selectQueryRegex.test(obj.sql)) {
|
|
183
|
-
method = 'select';
|
|
184
|
-
}
|
|
185
|
-
switch (method) {
|
|
186
|
-
case 'select':
|
|
187
|
-
return [rows.map((r) => rowToObj(r)), fields, meta];
|
|
188
|
-
case 'first':
|
|
189
|
-
return [rowToObj(rows[0]), fields, meta];
|
|
190
|
-
case 'pluck': {
|
|
191
|
-
const pluckIndex = fields.findIndex((val) => val.name === obj.pluck);
|
|
192
|
-
if (pluckIndex === -1)
|
|
193
|
-
return rows;
|
|
194
|
-
return [rows.map((row) => row[pluckIndex]), obj.pluck, meta];
|
|
195
|
-
}
|
|
196
|
-
case 'insert':
|
|
197
|
-
case 'del':
|
|
198
|
-
case 'update':
|
|
199
|
-
case 'counter':
|
|
200
|
-
return response;
|
|
201
|
-
default:
|
|
202
|
-
return response;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* @type {import('@clickhouse/client').ClickHouseClient}
|
|
207
|
-
*/
|
|
208
|
-
#connection;
|
|
209
|
-
// Get a raw connection, called by the `pool` whenever a new
|
|
210
|
-
// connection needs to be added to the pool.
|
|
211
|
-
async acquireRawConnection() {
|
|
212
|
-
if (!this.#connection) {
|
|
213
|
-
const connection = ClickhouseDriver.createClient({
|
|
214
|
-
// @ts-ignore
|
|
215
|
-
url: this.config.connection.url,
|
|
216
|
-
// @ts-ignore
|
|
217
|
-
username: this.config.connection.user,
|
|
218
|
-
// @ts-ignore
|
|
219
|
-
password: this.config.connection.password,
|
|
220
|
-
// @ts-ignore
|
|
221
|
-
database: this.config.connection.database,
|
|
222
|
-
compression: { request: true, response: true },
|
|
223
|
-
// @ts-ignore
|
|
224
|
-
clickhouse_settings: this.config.clickHouseSettings
|
|
225
|
-
});
|
|
226
|
-
// @ts-ignore
|
|
227
|
-
connection.connected = true;
|
|
228
|
-
this.#connection = connection;
|
|
229
|
-
}
|
|
230
|
-
return this.#connection;
|
|
231
|
-
}
|
|
232
|
-
async destroyRawConnection(connection) {
|
|
233
|
-
connection.close();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
Object.assign(ClickhouseKnexClient.prototype, {
|
|
237
|
-
dialect: 'clikhouse',
|
|
238
|
-
driverName: 'clikhouse',
|
|
239
|
-
canCancelQuery: true
|
|
240
|
-
});
|
|
241
|
-
const registerKnexClickhouseExtensions = (knex) => {
|
|
242
|
-
Knex.QueryBuilder.extend('insertToClickhouse', function (rows) {
|
|
243
|
-
const getTuple = (r, p = ['(', ')']) => p[0] +
|
|
244
|
-
r
|
|
245
|
-
.map((c) => {
|
|
246
|
-
if (c === null) {
|
|
247
|
-
return 'NULL';
|
|
248
|
-
}
|
|
249
|
-
if (typeof c === 'string') {
|
|
250
|
-
return knex.raw('?', [c]).toQuery();
|
|
251
|
-
}
|
|
252
|
-
if (Array.isArray(c)) {
|
|
253
|
-
return getTuple(c, ['[', ']']);
|
|
254
|
-
}
|
|
255
|
-
if (typeof c === 'object') {
|
|
256
|
-
const keys = Object.keys(c);
|
|
257
|
-
return `[${keys
|
|
258
|
-
.map((k) => `(${knex.raw('?', [k]).toQuery()}, ${knex
|
|
259
|
-
.raw('?', [c[k]])
|
|
260
|
-
.toQuery()})`)
|
|
261
|
-
.join(', ')}]`;
|
|
262
|
-
}
|
|
263
|
-
return c;
|
|
264
|
-
})
|
|
265
|
-
.join(', ') +
|
|
266
|
-
p[1];
|
|
267
|
-
const values = rows.map((r) => getTuple(r));
|
|
268
|
-
// @ts-ignore
|
|
269
|
-
return knex.raw(`INSERT INTO ${this._single.table} VALUES ${values}`);
|
|
270
|
-
});
|
|
271
|
-
};
|
|
272
|
-
/**
|
|
273
|
-
*
|
|
274
|
-
* @returns {import('knex').Knex<any, unknown[]>}
|
|
275
|
-
*/
|
|
276
|
-
export const getClickhouseConnection = (config = {}, clickHouseSettings,
|
|
277
|
-
/**
|
|
278
|
-
* If defined, this function will be called before each query
|
|
279
|
-
* and awaited if it returns a promise.
|
|
280
|
-
*
|
|
281
|
-
* Could be useful in some cases. For example you want first to check
|
|
282
|
-
* if the server is not in idle state (for example Clickhouse Cloud have idling options)
|
|
283
|
-
* and if it is, you want to wake it up before sending the query.
|
|
284
|
-
*
|
|
285
|
-
* if the function throws an error this error will not be caught and will propagate to the caller.
|
|
286
|
-
* Therefore the query will not be executed.
|
|
287
|
-
*/
|
|
288
|
-
preQueryCallback) => {
|
|
289
|
-
const connection = Knex({
|
|
290
|
-
...config,
|
|
291
|
-
client: ClickhouseKnexClient,
|
|
292
|
-
// @ts-ignore
|
|
293
|
-
clickHouseSettings,
|
|
294
|
-
preQueryCallback
|
|
295
|
-
});
|
|
296
|
-
registerKnexClickhouseExtensions(connection);
|
|
297
|
-
return connection;
|
|
298
|
-
};
|
|
1
|
+
import{retry as k}from"@cleverbrush/async";import{deepExtend as q}from"@cleverbrush/deep";import x from"@clickhouse/client";import S from"knex";var m=/[\0\b\t\n\r\x1a"'\\]/g,D={"\0":"\\0","\b":"\\b"," ":"\\t","\n":"\\n","\r":"\\r","":"\\Z",'"':'\\"',"'":"\\'","\\":"\\\\"},A=r=>function e(n,s={}){return r(n,e,s)},E=(r,e,n)=>r&&typeof r.toSQL=="function"?r.toSQL(n):JSON.stringify(r),C=r=>{m.lastIndex=0;let e=0,n="",s=null;for(;s=m.exec(r);)n+=r.slice(e,s.index)+D[s[0]],e=m.lastIndex;return e===0?`'${r}'`:e<r.length?`'${n}${r.slice(e)}'`:`'${n}'`},N=r=>`X${C(r.toString("hex"))}`,w=(r,e,n)=>{let s="";for(let t=0;t<r.length;t++){let a=r[t];Array.isArray(a)?s+=(t===0?"":", ")+"("+w(a,e,n)+")":s+=(t===0?"":", ")+e(a,n)}return s},f=(r,e)=>{let n=r.toString();for(;n.length<e;)n=`0${n}`;return n},O=r=>{if(r==="Z")return 0;let e=r.match(/([+\-\s])(\d\d):?(\d\d)?/);return e?(e[1]==="-"?-1:1)*(parseInt(e[2],10)+(e[3]?parseInt(e[3],10):0)/60)*60:!1},b=(r,e,n={})=>{let s=n.timeZone||"local",t=new Date(r),a,i,c,o,y,u,l;if(s==="local")a=t.getFullYear(),i=t.getMonth()+1,c=t.getDate(),o=t.getHours(),y=t.getMinutes(),u=t.getSeconds(),l=t.getMilliseconds();else{let d=O(s);d!==!1&&d!==0&&t.setTime(t.getTime()+d*6e4),a=t.getUTCFullYear(),i=t.getUTCMonth()+1,c=t.getUTCDate(),o=t.getUTCHours(),y=t.getUTCMinutes(),u=t.getUTCSeconds(),l=t.getUTCMilliseconds()}return`${f(a,4)}-${f(i,2)}-${f(c,2)} ${f(o,2)}:${f(y,2)}:${f(u,2)}.${f(l,3)}`},T=(r={})=>{let e=r.escapeDate||b,n=r.escapeArray||w,s=r.escapeBuffer||N,t=r.escapeString||C,a=r.escapeObject||E,i=r.wrap||A;function c(o,y,u){if(o==null)return"NULL";switch(typeof o){case"boolean":return o?"true":"false";case"number":return o+"";case"object":if(o instanceof Date)o=e(o,y,u);else return Array.isArray(o)?n(o,y,u):Buffer.isBuffer(o)?s(o,y,u):a(o,y,u)}return t(o,y,u)}return i?i(c):c};var p=r=>new Date(Date.parse(r)),U=S.Client;function $(r,e){let n="";for(let s=0;s<r.length;s++){s>0&&(n+=",");let t=r[s];t===null||typeof t>"u"?n+="NULL":Array.isArray(t)?n+=$(t,e):typeof t=="number"?n+=t:n+=e(t)}return`[${n}]`}var g=class extends U{_driver(){return x}#e;constructor(e={}){let{retry:n}=e;super(e),this.#e=n?q({maxRetries:10,delayFactor:2,minDelay:100,delayRandomizationPercent:.1,shouldRetry:s=>s.code==="ECONNRESET"},n):void 0}#n={Date:p,DateTime:p,"LowCardinality(Date)":p,"LowCardinality(DateTime)":p,"Nullable(Date)":p,"Nullable(DateTime)":p,"DateTime('UTC')":p,"Nullable(DateTime('UTC'))":p,"LowCardinality(Nullable(Date))":p,"LowCardinality(Nullable(DateTime))":p,UInt64:e=>Number(e),UInt32:e=>Number(e)};#r=/^(\s+)?(select|with)/i;_escapeBinding(e){let s=T({escapeArray(t,a){return $(t,a)},escapeString(t){let a="'";for(let i=0;i<t.length;i++){let c=t[i];c==="'"||c==="\\"?a+=c+c:a+=c}return a+="'",a},escapeObject(t,a,i,c=[]){if(t&&typeof t.toPostgres=="function"){if(c=c||[],c.indexOf(t)!==-1)throw new Error(`circular reference detected while preparing "${t}" for query`);return c.push(t),a(t.toPostgres(a),c)}return JSON.stringify(t)}})(e);return typeof s=="string"?s.replace(/\\/g,"\\\\"):s}async _query(e,n){if(!n||typeof n=="string")n={sql:n};else if(!n.sql)throw new Error("The query is empty");let{method:s}=n;s==="raw"&&this.#r.test(n.sql)&&(s="select");let t=Array.isArray(n.bindings)&&n.bindings.length>0?n.sql.split("?").map((o,y)=>`${o}${n.bindings.length>y?this._escapeBinding(n.bindings[y]):""}`).join(""):n.sql,a,i={clickhouse_settings:{exact_rows_before_limit:!0},query:t,format:"JSONCompact"};n.finalQuery=t;let{preQueryCallback:c}=this.config;switch(typeof c=="function"&&await Promise.resolve(c(e)),s){case"select":case"first":case"pluck":{a=await(await(this.#e?k(()=>e.query(i),this.#e):e.query(i))).json();let{data:y,meta:u,...l}=a;n.response=[y,u,{...l}]}break;default:a=await(this.#e?k(()=>e.exec(i),this.#e):e.exec(i))}return n}processResponse(e,n){if(e==null)return null;let{response:s}=e,[t,a,i]=s||[[]],c=Array.isArray(a)?a.map(u=>typeof this.#n[u.type]=="function"?this.#n[u.type]:l=>l):a,o=u=>{let l={};return a.forEach((d,h)=>{l[d.name]=c[h](u[h])}),l};if(e.output)return e.output.call(n,t,a);let{method:y}=e;switch(y==="raw"&&this.#r.test(e.sql)&&(y="select"),y){case"select":return[t.map(u=>o(u)),a,i];case"first":return[o(t[0]),a,i];case"pluck":{let u=a.findIndex(l=>l.name===e.pluck);return u===-1?t:[t.map(l=>l[u]),e.pluck,i]}case"insert":case"del":case"update":case"counter":return s;default:return s}}#t;async acquireRawConnection(){if(!this.#t){let e=x.createClient({url:this.config.connection.url,username:this.config.connection.user,password:this.config.connection.password,database:this.config.connection.database,compression:{request:!0,response:!0},clickhouse_settings:this.config.clickHouseSettings});e.connected=!0,this.#t=e}return this.#t}async destroyRawConnection(e){e.close()}};Object.assign(g.prototype,{dialect:"clikhouse",driverName:"clikhouse",canCancelQuery:!0});var L=r=>{r.QueryBuilder.extend("insertToClickhouse",function(e){let n=(t,a=["(",")"])=>a[0]+t.map(i=>i===null?"NULL":typeof i=="string"?r.raw("?",[i]).toQuery():Array.isArray(i)?n(i,["[","]"]):typeof i=="object"?`[${Object.keys(i).map(o=>`(${r.raw("?",[o]).toQuery()}, ${r.raw("?",[i[o]]).toQuery()})`).join(", ")}]`:i).join(", ")+a[1],s=e.map(t=>n(t));return r.raw(`INSERT INTO ${this._single.table} VALUES ${s}`)})},H=(r={},e,n)=>{let s=S({...r,client:g,clickHouseSettings:e,preQueryCallback:n});return L(s),s};export{g as ClickhouseKnexClient,H as getClickhouseConnection};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/makeEscape.ts"],"sourcesContent":["import { retry } from '@cleverbrush/async';\nimport { deepExtend } from '@cleverbrush/deep';\nimport ClickhouseDriver, {\n type ClickHouseClient,\n type ClickHouseSettings\n} from '@clickhouse/client';\nimport knex, { type Knex } from 'knex';\nimport { makeEscape } from './makeEscape.js';\n\nexport type { ClickHouseClient };\n\nconst parseDate = (value: any) => new Date(Date.parse(value));\n\n// Access the Client class from the knex runtime object (CJS compat)\nconst KnexClient: typeof Knex.Client = (knex as any).Client;\n\n// this function is taken from knex npm package\nfunction arrayString(arr: any[], esc: (val: any) => string) {\n let result = '';\n for (let i = 0; i < arr.length; i++) {\n if (i > 0) result += ',';\n const val = arr[i];\n if (val === null || typeof val === 'undefined') {\n result += 'NULL';\n } else if (Array.isArray(val)) {\n result += arrayString(val, esc);\n } else if (typeof val === 'number') {\n result += val;\n } else {\n result += esc(val);\n }\n }\n return `[${result}]`;\n}\n\ntype AdditionalClientOptions = {\n /**\n * Options for retrying the query,\n * by default it will retry 10 times with a delay factor of 2 and a minimum delay of\n * 100ms and delay randomization of 10%.\n * and will retry only if the error code is 'ECONNRESET',\n * you can override this behavior by passing your own options.\n * If this option is set to `null` then the query will not be retried.\n */\n retry?: Parameters<typeof retry>[1];\n};\n\nexport class ClickhouseKnexClient extends KnexClient {\n _driver() {\n return ClickhouseDriver;\n }\n\n #retryOptions: Parameters<typeof retry>[1];\n\n constructor(config: Knex.Config<any> & AdditionalClientOptions = {}) {\n const { retry: retryOptions } = config;\n\n super(config);\n\n this.#retryOptions = retryOptions\n ? deepExtend(\n {\n maxRetries: 10,\n delayFactor: 2,\n minDelay: 100,\n delayRandomizationPercent: 0.1,\n shouldRetry: (error: any) => error.code === 'ECONNRESET'\n },\n retryOptions\n )\n : undefined;\n }\n\n #typeParsers = {\n Date: parseDate,\n DateTime: parseDate,\n 'LowCardinality(Date)': parseDate,\n 'LowCardinality(DateTime)': parseDate,\n 'Nullable(Date)': parseDate,\n 'Nullable(DateTime)': parseDate,\n \"DateTime('UTC')\": parseDate,\n \"Nullable(DateTime('UTC'))\": parseDate,\n 'LowCardinality(Nullable(Date))': parseDate,\n 'LowCardinality(Nullable(DateTime))': parseDate,\n UInt64: (value: any) => Number(value),\n UInt32: (value: any) => Number(value)\n };\n\n #selectQueryRegex = /^(\\s+)?(select|with)/i;\n\n _escapeBinding(value: any) {\n const escapeBinding = makeEscape({\n escapeArray(val: any, esc: any) {\n return arrayString(val, esc);\n },\n escapeString(str: string) {\n let escaped = \"'\";\n for (let i = 0; i < str.length; i++) {\n const c = str[i];\n if (c === \"'\") {\n escaped += c + c;\n } else if (c === '\\\\') {\n escaped += c + c;\n } else {\n escaped += c;\n }\n }\n escaped += \"'\";\n return escaped;\n },\n escapeObject(\n val: any,\n prepareValue: any,\n _timezone: any,\n seen: any[] = []\n ) {\n if (val && typeof val.toPostgres === 'function') {\n seen = seen || [];\n if (seen.indexOf(val) !== -1) {\n throw new Error(\n `circular reference detected while preparing \"${val}\" for query`\n );\n }\n seen.push(val);\n return prepareValue(val.toPostgres(prepareValue), seen);\n }\n return JSON.stringify(val);\n }\n });\n\n const result = escapeBinding(value);\n\n if (typeof result === 'string') {\n return result.replace(/\\\\/g, '\\\\\\\\');\n }\n\n return result;\n }\n\n /**\n *\n * @param {import('@clickhouse/client').QueryParams} obj\n * @returns\n */\n async _query(connection: ClickHouseClient, obj: any) {\n if (!obj || typeof obj === 'string') {\n obj = { sql: obj };\n } else if (!obj.sql) {\n throw new Error('The query is empty');\n }\n\n let { method } = obj;\n\n if (method === 'raw' && this.#selectQueryRegex.test(obj.sql)) {\n method = 'select';\n }\n\n const query =\n Array.isArray(obj.bindings) && obj.bindings.length > 0\n ? obj.sql\n .split('?')\n .map(\n (x: any, i: number) =>\n `${x}${\n obj.bindings.length > i\n ? this._escapeBinding(obj.bindings[i])\n : ''\n }`\n )\n .join('')\n : obj.sql;\n\n let response;\n const queryParams = {\n clickhouse_settings: {\n exact_rows_before_limit: true\n },\n query,\n format: 'JSONCompact'\n };\n obj.finalQuery = query;\n\n const { preQueryCallback } = this.config as any;\n\n if (typeof preQueryCallback === 'function') {\n await Promise.resolve(preQueryCallback(connection));\n }\n\n switch (method) {\n case 'select':\n case 'first':\n case 'pluck':\n {\n const p = (await (this.#retryOptions\n ? retry(\n () => connection.query(queryParams as any),\n this.#retryOptions\n )\n : connection.query(queryParams as any))) as any;\n\n response = await p.json();\n\n const { data, meta, ...rest } = response;\n\n obj.response = [data, meta, { ...rest }];\n }\n break;\n default:\n response = await (this.#retryOptions\n ? retry(\n () => connection.exec(queryParams as any),\n this.#retryOptions\n )\n : connection.exec(queryParams as any));\n }\n\n return obj;\n }\n\n /**\n *\n * @param {import('@clickhouse/client').ResultSet} obj\n * @param {*} runner\n * @returns\n */\n processResponse(obj: any, runner: any) {\n if (obj == null) return null;\n const { response } = obj;\n const [rows, fields, meta] = response || [[]];\n\n const parsers = Array.isArray(fields)\n ? fields.map((field: any) =>\n typeof (this.#typeParsers as Record<string, (v: any) => any>)[\n field.type\n ] === 'function'\n ? (this.#typeParsers as Record<string, (v: any) => any>)[\n field.type\n ]\n : (x: any) => x\n )\n : fields;\n\n const rowToObj = (row: any) => {\n const result: Record<string, unknown> = {};\n\n fields.forEach((field: any, index: number) => {\n result[field.name] = parsers[index](row[index]);\n });\n\n return result;\n };\n\n if (obj.output) return obj.output.call(runner, rows, fields);\n\n let { method } = obj;\n\n if (method === 'raw' && this.#selectQueryRegex.test(obj.sql)) {\n method = 'select';\n }\n\n switch (method) {\n case 'select':\n return [rows.map((r: any) => rowToObj(r)), fields, meta];\n case 'first':\n return [rowToObj(rows[0]), fields, meta];\n case 'pluck': {\n const pluckIndex = fields.findIndex(\n (val: any) => val.name === obj.pluck\n );\n if (pluckIndex === -1) return rows;\n return [\n rows.map((row: any) => row[pluckIndex]),\n obj.pluck,\n meta\n ];\n }\n case 'insert':\n case 'del':\n case 'update':\n case 'counter':\n return response;\n default:\n return response;\n }\n }\n\n /**\n * @type {import('@clickhouse/client').ClickHouseClient}\n */\n #connection: any;\n\n // Get a raw connection, called by the `pool` whenever a new\n // connection needs to be added to the pool.\n async acquireRawConnection() {\n if (!this.#connection) {\n const connection = ClickhouseDriver.createClient({\n url: (this.config as any).connection.url,\n username: (this.config as any).connection.user,\n password: (this.config as any).connection.password,\n database: (this.config as any).connection.database,\n compression: { request: true, response: true },\n clickhouse_settings: (this.config as any).clickHouseSettings\n });\n\n // @ts-expect-error\n connection.connected = true;\n\n this.#connection = connection;\n }\n\n return this.#connection;\n }\n\n async destroyRawConnection(connection: any) {\n connection.close();\n }\n}\n\nObject.assign(ClickhouseKnexClient.prototype, {\n dialect: 'clikhouse',\n driverName: 'clikhouse',\n canCancelQuery: true\n});\n\nconst registerKnexClickhouseExtensions = (knex: any) => {\n (knex as any).QueryBuilder.extend(\n 'insertToClickhouse',\n function (this: any, rows: any) {\n const getTuple = (r: any, p = ['(', ')']) =>\n p[0] +\n r\n .map((c: any) => {\n if (c === null) {\n return 'NULL';\n }\n if (typeof c === 'string') {\n return knex.raw('?', [c]).toQuery();\n }\n\n if (Array.isArray(c)) {\n return getTuple(c, ['[', ']']);\n }\n\n if (typeof c === 'object') {\n const keys = Object.keys(c);\n return `[${keys\n .map(\n k =>\n `(${knex.raw('?', [k]).toQuery()}, ${knex\n .raw('?', [c[k]])\n .toQuery()})`\n )\n .join(', ')}]`;\n }\n\n return c;\n })\n .join(', ') +\n p[1];\n const values = rows.map((r: any) => getTuple(r));\n return knex.raw(\n `INSERT INTO ${(this as any)._single.table} VALUES ${values}`\n );\n }\n );\n};\n\n/**\n *\n * @returns {import('knex').Knex<any, unknown[]>}\n */\nexport const getClickhouseConnection = (\n config: Knex.Config<any> & AdditionalClientOptions = {},\n clickHouseSettings?: ClickHouseSettings,\n /**\n * If defined, this function will be called before each query\n * and awaited if it returns a promise.\n *\n * Could be useful in some cases. For example you want first to check\n * if the server is not in idle state (for example Clickhouse Cloud have idling options)\n * and if it is, you want to wake it up before sending the query.\n *\n * if the function throws an error this error will not be caught and will propagate to the caller.\n * Therefore the query will not be executed.\n */\n preQueryCallback?: (connection: ClickHouseClient) => Promise<void>\n) => {\n const connection = knex({\n ...config,\n client: ClickhouseKnexClient as any,\n clickHouseSettings,\n preQueryCallback\n } as any);\n\n registerKnexClickhouseExtensions(connection);\n\n return connection;\n};\n","/*\n * NOTE: functions below are taken from knex npm package,\n * unfortunately they are not exported, so we have to copy them here\n * because they are useful when it comes to write your own knex dialect\n */\n\n// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional regex for escaping control characters in strings\nconst charsRegex = /[\\0\\b\\t\\n\\r\\x1a\"'\\\\]/g;\nconst charsMap = {\n '\\0': '\\\\0',\n '\\b': '\\\\b',\n '\\t': '\\\\t',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\x1a': '\\\\Z',\n '\"': '\\\\\"',\n \"'\": \"\\\\'\",\n '\\\\': '\\\\\\\\'\n};\n\nconst wrapEscape = (escapeFn: (val: any, finalEscape: any, ctx: any) => any) =>\n function finalEscape(val: any, ctx: any = {}) {\n return escapeFn(val, finalEscape, ctx);\n };\n\nconst escapeObject = (val: any, _finalEscape: any, ctx: any) => {\n if (val && typeof val.toSQL === 'function') {\n return val.toSQL(ctx);\n }\n return JSON.stringify(val);\n};\n\nconst escapeString = (val: any) => {\n charsRegex.lastIndex = 0;\n let chunkIndex = 0;\n let escapedVal = '';\n let match: RegExpExecArray | null = null;\n\n // biome-ignore lint/suspicious/noAssignInExpressions: intentional assignment in while condition to find matches in the string\n while ((match = charsRegex.exec(val))) {\n escapedVal +=\n val.slice(chunkIndex, match.index) + (charsMap as any)[match[0]];\n chunkIndex = charsRegex.lastIndex;\n }\n\n if (chunkIndex === 0) {\n // Nothing was escaped\n return `'${val}'`;\n }\n\n if (chunkIndex < val.length) {\n return `'${escapedVal}${val.slice(chunkIndex)}'`;\n }\n\n return `'${escapedVal}'`;\n};\n\nconst bufferToString = (buffer: Buffer) =>\n `X${escapeString(buffer.toString('hex'))}`;\n\nconst arrayToList = (array: any[], finalEscape: any, ctx: any) => {\n let sql = '';\n for (let i = 0; i < array.length; i++) {\n const val = array[i];\n if (Array.isArray(val)) {\n sql +=\n (i === 0 ? '' : ', ') +\n '(' +\n arrayToList(val, finalEscape, ctx) +\n ')';\n } else {\n sql += (i === 0 ? '' : ', ') + finalEscape(val, ctx);\n }\n }\n return sql;\n};\n\nconst zeroPad = (number: number, length: number) => {\n let numberStr = number.toString();\n while (numberStr.length < length) {\n numberStr = `0${numberStr}`;\n }\n return numberStr;\n};\n\nconst convertTimezone = (tz: any) => {\n if (tz === 'Z') {\n return 0;\n }\n const m = tz.match(/([+\\-\\s])(\\d\\d):?(\\d\\d)?/);\n if (m) {\n return (\n (m[1] === '-' ? -1 : 1) *\n (parseInt(m[2], 10) + (m[3] ? parseInt(m[3], 10) : 0) / 60) *\n 60\n );\n }\n return false;\n};\n\nconst dateToString = (date: any, _finalEscape: any, ctx: any = {}) => {\n const timeZone = ctx.timeZone || 'local';\n\n const dt = new Date(date);\n let year;\n let month;\n let day;\n let hour;\n let minute;\n let second;\n let millisecond;\n\n if (timeZone === 'local') {\n year = dt.getFullYear();\n month = dt.getMonth() + 1;\n day = dt.getDate();\n hour = dt.getHours();\n minute = dt.getMinutes();\n second = dt.getSeconds();\n millisecond = dt.getMilliseconds();\n } else {\n const tz = convertTimezone(timeZone);\n\n if (tz !== false && tz !== 0) {\n dt.setTime(dt.getTime() + tz * 60000);\n }\n\n year = dt.getUTCFullYear();\n month = dt.getUTCMonth() + 1;\n day = dt.getUTCDate();\n hour = dt.getUTCHours();\n minute = dt.getUTCMinutes();\n second = dt.getUTCSeconds();\n millisecond = dt.getUTCMilliseconds();\n }\n\n // YYYY-MM-DD HH:mm:ss.mmm\n return `${zeroPad(year, 4)}-${zeroPad(month, 2)}-${zeroPad(\n day,\n 2\n )} ${zeroPad(hour, 2)}:${zeroPad(minute, 2)}:${zeroPad(\n second,\n 2\n )}.${zeroPad(millisecond, 3)}`;\n};\n\nexport const makeEscape = (config: any = {}) => {\n const finalEscapeDate = config.escapeDate || dateToString;\n const finalEscapeArray = config.escapeArray || arrayToList;\n const finalEscapeBuffer = config.escapeBuffer || bufferToString;\n const finalEscapeString = config.escapeString || escapeString;\n const finalEscapeObject = config.escapeObject || escapeObject;\n const finalWrap = config.wrap || wrapEscape;\n\n function escapeFn(val: any, finalEscape: any, ctx: any) {\n if (val === undefined || val === null) {\n return 'NULL';\n }\n switch (typeof val) {\n case 'boolean':\n return val ? 'true' : 'false';\n case 'number':\n return val + '';\n case 'object':\n if (val instanceof Date) {\n val = finalEscapeDate(val, finalEscape, ctx);\n } else if (Array.isArray(val)) {\n return finalEscapeArray(val, finalEscape, ctx);\n } else if (Buffer.isBuffer(val)) {\n return finalEscapeBuffer(val, finalEscape, ctx);\n } else {\n return finalEscapeObject(val, finalEscape, ctx);\n }\n }\n return finalEscapeString(val, finalEscape, ctx);\n }\n\n return finalWrap ? finalWrap(escapeFn) : escapeFn;\n};\n"],"mappings":"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","names":["retry","deepExtend","ClickhouseDriver","knex","charsRegex","charsMap","wrapEscape","escapeFn","finalEscape","val","ctx","escapeObject","_finalEscape","escapeString","chunkIndex","escapedVal","match","bufferToString","buffer","arrayToList","array","sql","i","zeroPad","number","length","numberStr","convertTimezone","tz","m","dateToString","date","timeZone","dt","year","month","day","hour","minute","second","millisecond","makeEscape","config","finalEscapeDate","finalEscapeArray","finalEscapeBuffer","finalEscapeString","finalEscapeObject","finalWrap","parseDate","value","KnexClient","knex","arrayString","arr","esc","result","i","val","ClickhouseKnexClient","ClickhouseDriver","#retryOptions","config","retryOptions","deepExtend","error","#typeParsers","#selectQueryRegex","makeEscape","str","escaped","prepareValue","_timezone","seen","connection","obj","method","query","x","response","queryParams","preQueryCallback","retry","data","meta","rest","runner","rows","fields","parsers","field","rowToObj","row","index","r","pluckIndex","#connection","registerKnexClickhouseExtensions","getTuple","p","c","k","values","getClickhouseConnection","clickHouseSettings"]}
|
package/package.json
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
"email": "andrew_zol@cleverbrush.com"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@cleverbrush/async": "^
|
|
9
|
-
"@cleverbrush/deep": "^
|
|
10
|
-
"@clickhouse/client": "^1.
|
|
8
|
+
"@cleverbrush/async": "^3.0.0",
|
|
9
|
+
"@cleverbrush/deep": "^3.0.0",
|
|
10
|
+
"@clickhouse/client": "^1.18.2"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
13
|
"knex": ">=3.1.0"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
|
-
"homepage": "
|
|
19
|
+
"homepage": "https://docs.cleverbrush.com/knex-schema",
|
|
20
20
|
"keywords": [
|
|
21
21
|
"knex",
|
|
22
22
|
"query builder",
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
],
|
|
26
26
|
"license": "BSD 3-Clause",
|
|
27
27
|
"main": "./dist/index.js",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": false,
|
|
28
35
|
"name": "@cleverbrush/knex-clickhouse",
|
|
29
36
|
"readme": "https://github.com/cleverbrush/framework/tree/master/libs/knex-clickhouse#readme",
|
|
30
37
|
"repository": {
|
|
@@ -32,10 +39,11 @@
|
|
|
32
39
|
"url": "github:cleverbrush/framework"
|
|
33
40
|
},
|
|
34
41
|
"scripts": {
|
|
35
|
-
"watch": "tsc --watch",
|
|
36
|
-
"build": "tsc"
|
|
42
|
+
"watch": "tsc --build --watch",
|
|
43
|
+
"build": "tsup && rm -f tsconfig.build.tsbuildinfo && tsc --project tsconfig.build.json --emitDeclarationOnly",
|
|
44
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
37
45
|
},
|
|
38
46
|
"type": "module",
|
|
39
47
|
"types": "./dist/index.d.ts",
|
|
40
|
-
"version": "
|
|
48
|
+
"version": "3.0.0"
|
|
41
49
|
}
|
package/dist/makeEscape.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* NOTE: functions below are taken from knex npm package,
|
|
3
|
-
* unfortunately they are not exported, so we have to copy them here
|
|
4
|
-
* because they are useful when it comes to write your own knex dialect
|
|
5
|
-
*/
|
|
6
|
-
const charsRegex = /[\0\b\t\n\r\x1a"'\\]/g; // eslint-disable-line no-control-regex
|
|
7
|
-
const charsMap = {
|
|
8
|
-
'\0': '\\0',
|
|
9
|
-
'\b': '\\b',
|
|
10
|
-
'\t': '\\t',
|
|
11
|
-
'\n': '\\n',
|
|
12
|
-
'\r': '\\r',
|
|
13
|
-
'\x1a': '\\Z',
|
|
14
|
-
'"': '\\"',
|
|
15
|
-
"'": "\\'",
|
|
16
|
-
'\\': '\\\\'
|
|
17
|
-
};
|
|
18
|
-
const wrapEscape = (escapeFn) => function finalEscape(val, ctx = {}) {
|
|
19
|
-
return escapeFn(val, finalEscape, ctx);
|
|
20
|
-
};
|
|
21
|
-
const escapeObject = (val, finalEscape, ctx) => {
|
|
22
|
-
if (val && typeof val.toSQL === 'function') {
|
|
23
|
-
return val.toSQL(ctx);
|
|
24
|
-
}
|
|
25
|
-
return JSON.stringify(val);
|
|
26
|
-
};
|
|
27
|
-
// eslint-disable-next-line
|
|
28
|
-
const escapeString = (val) => {
|
|
29
|
-
// eslint-disable-next-line
|
|
30
|
-
let chunkIndex = (charsRegex.lastIndex = 0);
|
|
31
|
-
let escapedVal = '';
|
|
32
|
-
let match;
|
|
33
|
-
// eslint-disable-next-line
|
|
34
|
-
while ((match = charsRegex.exec(val))) {
|
|
35
|
-
escapedVal += val.slice(chunkIndex, match.index) + charsMap[match[0]];
|
|
36
|
-
chunkIndex = charsRegex.lastIndex;
|
|
37
|
-
}
|
|
38
|
-
if (chunkIndex === 0) {
|
|
39
|
-
// Nothing was escaped
|
|
40
|
-
return `'${val}'`;
|
|
41
|
-
}
|
|
42
|
-
if (chunkIndex < val.length) {
|
|
43
|
-
return `'${escapedVal}${val.slice(chunkIndex)}'`;
|
|
44
|
-
}
|
|
45
|
-
return `'${escapedVal}'`;
|
|
46
|
-
};
|
|
47
|
-
const bufferToString = (buffer) => `X${escapeString(buffer.toString('hex'))}`;
|
|
48
|
-
const arrayToList = (array, finalEscape, ctx) => {
|
|
49
|
-
let sql = '';
|
|
50
|
-
for (let i = 0; i < array.length; i++) {
|
|
51
|
-
const val = array[i];
|
|
52
|
-
if (Array.isArray(val)) {
|
|
53
|
-
sql +=
|
|
54
|
-
// eslint-disable-next-line
|
|
55
|
-
(i === 0 ? '' : ', ') +
|
|
56
|
-
'(' +
|
|
57
|
-
arrayToList(val, finalEscape, ctx) +
|
|
58
|
-
')';
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
sql += (i === 0 ? '' : ', ') + finalEscape(val, ctx);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return sql;
|
|
65
|
-
};
|
|
66
|
-
const zeroPad = (number, length) => {
|
|
67
|
-
number = number.toString();
|
|
68
|
-
while (number.length < length) {
|
|
69
|
-
number = `0${number}`;
|
|
70
|
-
}
|
|
71
|
-
return number;
|
|
72
|
-
};
|
|
73
|
-
const convertTimezone = (tz) => {
|
|
74
|
-
if (tz === 'Z') {
|
|
75
|
-
return 0;
|
|
76
|
-
}
|
|
77
|
-
const m = tz.match(/([+\-\s])(\d\d):?(\d\d)?/);
|
|
78
|
-
if (m) {
|
|
79
|
-
return (
|
|
80
|
-
// eslint-disable-next-line
|
|
81
|
-
(m[1] == '-' ? -1 : 1) *
|
|
82
|
-
(parseInt(m[2], 10) + (m[3] ? parseInt(m[3], 10) : 0) / 60) *
|
|
83
|
-
60);
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
};
|
|
87
|
-
const dateToString = (date, finalEscape, ctx = {}) => {
|
|
88
|
-
// @ts-ignore
|
|
89
|
-
const timeZone = ctx.timeZone || 'local';
|
|
90
|
-
const dt = new Date(date);
|
|
91
|
-
let year;
|
|
92
|
-
let month;
|
|
93
|
-
let day;
|
|
94
|
-
let hour;
|
|
95
|
-
let minute;
|
|
96
|
-
let second;
|
|
97
|
-
let millisecond;
|
|
98
|
-
if (timeZone === 'local') {
|
|
99
|
-
year = dt.getFullYear();
|
|
100
|
-
month = dt.getMonth() + 1;
|
|
101
|
-
day = dt.getDate();
|
|
102
|
-
hour = dt.getHours();
|
|
103
|
-
minute = dt.getMinutes();
|
|
104
|
-
second = dt.getSeconds();
|
|
105
|
-
millisecond = dt.getMilliseconds();
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
const tz = convertTimezone(timeZone);
|
|
109
|
-
if (tz !== false && tz !== 0) {
|
|
110
|
-
dt.setTime(dt.getTime() + tz * 60000);
|
|
111
|
-
}
|
|
112
|
-
year = dt.getUTCFullYear();
|
|
113
|
-
month = dt.getUTCMonth() + 1;
|
|
114
|
-
day = dt.getUTCDate();
|
|
115
|
-
hour = dt.getUTCHours();
|
|
116
|
-
minute = dt.getUTCMinutes();
|
|
117
|
-
second = dt.getUTCSeconds();
|
|
118
|
-
millisecond = dt.getUTCMilliseconds();
|
|
119
|
-
}
|
|
120
|
-
// YYYY-MM-DD HH:mm:ss.mmm
|
|
121
|
-
return `${zeroPad(year, 4)}-${zeroPad(month, 2)}-${zeroPad(day, 2)} ${zeroPad(hour, 2)}:${zeroPad(minute, 2)}:${zeroPad(second, 2)}.${zeroPad(millisecond, 3)}`;
|
|
122
|
-
};
|
|
123
|
-
export const makeEscape = (config = {}) => {
|
|
124
|
-
const finalEscapeDate = config.escapeDate || dateToString;
|
|
125
|
-
const finalEscapeArray = config.escapeArray || arrayToList;
|
|
126
|
-
const finalEscapeBuffer = config.escapeBuffer || bufferToString;
|
|
127
|
-
const finalEscapeString = config.escapeString || escapeString;
|
|
128
|
-
const finalEscapeObject = config.escapeObject || escapeObject;
|
|
129
|
-
const finalWrap = config.wrap || wrapEscape;
|
|
130
|
-
function escapeFn(val, finalEscape, ctx) {
|
|
131
|
-
if (val === undefined || val === null) {
|
|
132
|
-
return 'NULL';
|
|
133
|
-
}
|
|
134
|
-
// eslint-disable-next-line
|
|
135
|
-
switch (typeof val) {
|
|
136
|
-
case 'boolean':
|
|
137
|
-
return val ? 'true' : 'false';
|
|
138
|
-
case 'number':
|
|
139
|
-
// eslint-disable-next-line
|
|
140
|
-
return val + '';
|
|
141
|
-
case 'object':
|
|
142
|
-
if (val instanceof Date) {
|
|
143
|
-
val = finalEscapeDate(val, finalEscape, ctx);
|
|
144
|
-
}
|
|
145
|
-
else if (Array.isArray(val)) {
|
|
146
|
-
return finalEscapeArray(val, finalEscape, ctx);
|
|
147
|
-
}
|
|
148
|
-
else if (Buffer.isBuffer(val)) {
|
|
149
|
-
return finalEscapeBuffer(val, finalEscape, ctx);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
return finalEscapeObject(val, finalEscape, ctx);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return finalEscapeString(val, finalEscape, ctx);
|
|
156
|
-
}
|
|
157
|
-
return finalWrap ? finalWrap(escapeFn) : escapeFn;
|
|
158
|
-
};
|