@housekit/orm 0.1.47 → 0.1.49
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 +120 -5
- package/dist/builders/delete.js +112 -0
- package/dist/builders/insert.d.ts +0 -91
- package/dist/builders/insert.js +393 -0
- package/dist/builders/prepared.d.ts +1 -2
- package/dist/builders/prepared.js +30 -0
- package/dist/builders/select.d.ts +0 -161
- package/dist/builders/select.js +562 -0
- package/dist/builders/select.types.js +1 -0
- package/dist/builders/update.js +136 -0
- package/dist/client.d.ts +0 -6
- package/dist/client.js +140 -0
- package/dist/codegen/zod.js +107 -0
- package/dist/column.d.ts +1 -25
- package/dist/column.js +133 -0
- package/dist/compiler.d.ts +0 -7
- package/dist/compiler.js +513 -0
- package/dist/core.js +6 -0
- package/dist/data-types.d.ts +0 -61
- package/dist/data-types.js +127 -0
- package/dist/dictionary.d.ts +0 -149
- package/dist/dictionary.js +158 -0
- package/dist/engines.d.ts +0 -385
- package/dist/engines.js +292 -0
- package/dist/expressions.d.ts +0 -10
- package/dist/expressions.js +268 -0
- package/dist/external.d.ts +0 -112
- package/dist/external.js +224 -0
- package/dist/index.d.ts +0 -51
- package/dist/index.js +139 -6853
- package/dist/logger.js +36 -0
- package/dist/materialized-views.d.ts +0 -188
- package/dist/materialized-views.js +380 -0
- package/dist/metadata.js +59 -0
- package/dist/modules/aggregates.d.ts +0 -164
- package/dist/modules/aggregates.js +121 -0
- package/dist/modules/array.d.ts +0 -98
- package/dist/modules/array.js +71 -0
- package/dist/modules/conditional.d.ts +0 -84
- package/dist/modules/conditional.js +138 -0
- package/dist/modules/conversion.d.ts +0 -147
- package/dist/modules/conversion.js +109 -0
- package/dist/modules/geo.d.ts +0 -164
- package/dist/modules/geo.js +112 -0
- package/dist/modules/hash.js +4 -0
- package/dist/modules/index.js +12 -0
- package/dist/modules/json.d.ts +0 -106
- package/dist/modules/json.js +76 -0
- package/dist/modules/math.d.ts +0 -16
- package/dist/modules/math.js +16 -0
- package/dist/modules/string.d.ts +0 -136
- package/dist/modules/string.js +89 -0
- package/dist/modules/time.d.ts +0 -123
- package/dist/modules/time.js +91 -0
- package/dist/modules/types.d.ts +0 -133
- package/dist/modules/types.js +114 -0
- package/dist/modules/window.js +140 -0
- package/dist/relational.d.ts +0 -82
- package/dist/relational.js +290 -0
- package/dist/relations.js +21 -0
- package/dist/schema-builder.d.ts +0 -90
- package/dist/schema-builder.js +140 -0
- package/dist/table.d.ts +0 -42
- package/dist/table.js +406 -0
- package/dist/utils/background-batcher.js +75 -0
- package/dist/utils/batch-transform.js +51 -0
- package/dist/utils/binary-reader.d.ts +0 -6
- package/dist/utils/binary-reader.js +334 -0
- package/dist/utils/binary-serializer.d.ts +0 -125
- package/dist/utils/binary-serializer.js +637 -0
- package/dist/utils/binary-worker-code.js +1 -0
- package/dist/utils/binary-worker-pool.d.ts +0 -34
- package/dist/utils/binary-worker-pool.js +206 -0
- package/dist/utils/binary-worker.d.ts +0 -11
- package/dist/utils/binary-worker.js +63 -0
- package/dist/utils/insert-processing.d.ts +0 -2
- package/dist/utils/insert-processing.js +163 -0
- package/dist/utils/lru-cache.js +30 -0
- package/package.json +68 -3
package/dist/engines.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
export const Engine = {
|
|
2
|
+
MergeTree: (options) => ({
|
|
3
|
+
type: 'MergeTree',
|
|
4
|
+
...options
|
|
5
|
+
}),
|
|
6
|
+
ReplacingMergeTree: (versionColumn, isDeletedColumn, options) => ({
|
|
7
|
+
type: 'ReplacingMergeTree',
|
|
8
|
+
versionColumn,
|
|
9
|
+
isDeletedColumn,
|
|
10
|
+
...options
|
|
11
|
+
}),
|
|
12
|
+
SummingMergeTree: (columns, options) => ({
|
|
13
|
+
type: 'SummingMergeTree',
|
|
14
|
+
columns,
|
|
15
|
+
...options
|
|
16
|
+
}),
|
|
17
|
+
AggregatingMergeTree: (options) => ({
|
|
18
|
+
type: 'AggregatingMergeTree',
|
|
19
|
+
...options
|
|
20
|
+
}),
|
|
21
|
+
CollapsingMergeTree: (signColumn, options) => ({
|
|
22
|
+
type: 'CollapsingMergeTree',
|
|
23
|
+
signColumn,
|
|
24
|
+
...options
|
|
25
|
+
}),
|
|
26
|
+
VersionedCollapsingMergeTree: (signColumn, versionColumn, options) => ({
|
|
27
|
+
type: 'VersionedCollapsingMergeTree',
|
|
28
|
+
signColumn,
|
|
29
|
+
versionColumn,
|
|
30
|
+
...options
|
|
31
|
+
}),
|
|
32
|
+
GraphiteMergeTree: (configSection, options) => ({
|
|
33
|
+
type: 'GraphiteMergeTree',
|
|
34
|
+
configSection,
|
|
35
|
+
...options
|
|
36
|
+
}),
|
|
37
|
+
ReplicatedMergeTree: (config) => ({
|
|
38
|
+
type: 'ReplicatedMergeTree',
|
|
39
|
+
zkPath: config?.zkPath ?? '/clickhouse/tables/{shard}/{database}/{table}',
|
|
40
|
+
replicaName: config?.replicaName ?? '{replica}',
|
|
41
|
+
baseEngine: config?.baseEngine,
|
|
42
|
+
versionColumn: config?.versionColumn,
|
|
43
|
+
isDeletedColumn: config?.isDeletedColumn,
|
|
44
|
+
sumColumns: config?.sumColumns,
|
|
45
|
+
signColumn: config?.signColumn,
|
|
46
|
+
enableLightweightDeletes: config?.enableLightweightDeletes
|
|
47
|
+
}),
|
|
48
|
+
Buffer: (targetTable, opts) => ({
|
|
49
|
+
type: 'Buffer',
|
|
50
|
+
database: 'currentDatabase()',
|
|
51
|
+
table: targetTable.$table,
|
|
52
|
+
layers: opts.layers ?? 16,
|
|
53
|
+
minTime: opts.minTime ?? 10,
|
|
54
|
+
maxTime: opts.maxTime ?? 100,
|
|
55
|
+
minRows: opts.minRows,
|
|
56
|
+
maxRows: opts.maxRows,
|
|
57
|
+
minBytes: opts.minBytes ?? 10_000_000,
|
|
58
|
+
maxBytes: opts.maxBytes ?? 100_000_000
|
|
59
|
+
}),
|
|
60
|
+
BufferExplicit: (config) => ({
|
|
61
|
+
type: 'Buffer',
|
|
62
|
+
...config
|
|
63
|
+
}),
|
|
64
|
+
Distributed: (config) => ({
|
|
65
|
+
type: 'Distributed',
|
|
66
|
+
shardingKey: config.shardingKey ?? 'rand()',
|
|
67
|
+
...config
|
|
68
|
+
}),
|
|
69
|
+
Null: () => ({
|
|
70
|
+
type: 'Null'
|
|
71
|
+
}),
|
|
72
|
+
Log: () => ({
|
|
73
|
+
type: 'Log'
|
|
74
|
+
}),
|
|
75
|
+
TinyLog: () => ({
|
|
76
|
+
type: 'TinyLog'
|
|
77
|
+
}),
|
|
78
|
+
Memory: (config) => ({
|
|
79
|
+
type: 'Memory',
|
|
80
|
+
...config
|
|
81
|
+
}),
|
|
82
|
+
Join: (strictness, joinType, keys) => ({
|
|
83
|
+
type: 'Join',
|
|
84
|
+
strictness,
|
|
85
|
+
joinType,
|
|
86
|
+
keys
|
|
87
|
+
}),
|
|
88
|
+
Dictionary: (dictionaryName) => ({
|
|
89
|
+
type: 'Dictionary',
|
|
90
|
+
dictionaryName
|
|
91
|
+
}),
|
|
92
|
+
File: (format, compression) => ({
|
|
93
|
+
type: 'File',
|
|
94
|
+
format,
|
|
95
|
+
compression
|
|
96
|
+
}),
|
|
97
|
+
URL: (url, format, compression) => ({
|
|
98
|
+
type: 'URL',
|
|
99
|
+
url,
|
|
100
|
+
format,
|
|
101
|
+
compression
|
|
102
|
+
}),
|
|
103
|
+
S3: (config) => ({
|
|
104
|
+
type: 'S3',
|
|
105
|
+
...config
|
|
106
|
+
}),
|
|
107
|
+
Kafka: (config) => ({
|
|
108
|
+
type: 'Kafka',
|
|
109
|
+
...config
|
|
110
|
+
}),
|
|
111
|
+
PostgreSQL: (config) => ({
|
|
112
|
+
type: 'PostgreSQL',
|
|
113
|
+
...config
|
|
114
|
+
}),
|
|
115
|
+
MySQL: (config) => ({
|
|
116
|
+
type: 'MySQL',
|
|
117
|
+
...config
|
|
118
|
+
})
|
|
119
|
+
};
|
|
120
|
+
export function renderEngineSQL(engine) {
|
|
121
|
+
if (!engine)
|
|
122
|
+
return 'MergeTree()';
|
|
123
|
+
switch (engine.type) {
|
|
124
|
+
case 'MergeTree':
|
|
125
|
+
return 'MergeTree()';
|
|
126
|
+
case 'ReplacingMergeTree': {
|
|
127
|
+
const args = [];
|
|
128
|
+
if (engine.versionColumn)
|
|
129
|
+
args.push(engine.versionColumn);
|
|
130
|
+
if (engine.isDeletedColumn)
|
|
131
|
+
args.push(engine.isDeletedColumn);
|
|
132
|
+
return `ReplacingMergeTree(${args.join(', ')})`;
|
|
133
|
+
}
|
|
134
|
+
case 'SummingMergeTree':
|
|
135
|
+
if (engine.columns && engine.columns.length > 0) {
|
|
136
|
+
return `SummingMergeTree(${engine.columns.join(', ')})`;
|
|
137
|
+
}
|
|
138
|
+
return 'SummingMergeTree()';
|
|
139
|
+
case 'AggregatingMergeTree':
|
|
140
|
+
return 'AggregatingMergeTree()';
|
|
141
|
+
case 'CollapsingMergeTree':
|
|
142
|
+
return `CollapsingMergeTree(${engine.signColumn})`;
|
|
143
|
+
case 'VersionedCollapsingMergeTree':
|
|
144
|
+
return `VersionedCollapsingMergeTree(${engine.signColumn}, ${engine.versionColumn})`;
|
|
145
|
+
case 'GraphiteMergeTree':
|
|
146
|
+
return `GraphiteMergeTree('${engine.configSection}')`;
|
|
147
|
+
case 'ReplicatedMergeTree': {
|
|
148
|
+
const zkPath = engine.zkPath ?? '/clickhouse/tables/{shard}/{database}/{table}';
|
|
149
|
+
const replicaName = engine.replicaName ?? '{replica}';
|
|
150
|
+
let baseName = 'ReplicatedMergeTree';
|
|
151
|
+
if (engine.baseEngine) {
|
|
152
|
+
baseName = `Replicated${engine.baseEngine}`;
|
|
153
|
+
}
|
|
154
|
+
const args = [`'${zkPath}'`, `'${replicaName}'`];
|
|
155
|
+
switch (engine.baseEngine) {
|
|
156
|
+
case 'ReplacingMergeTree':
|
|
157
|
+
if (engine.versionColumn)
|
|
158
|
+
args.push(engine.versionColumn);
|
|
159
|
+
if (engine.isDeletedColumn)
|
|
160
|
+
args.push(engine.isDeletedColumn);
|
|
161
|
+
break;
|
|
162
|
+
case 'SummingMergeTree':
|
|
163
|
+
if (engine.sumColumns && engine.sumColumns.length > 0) {
|
|
164
|
+
args.push(`(${engine.sumColumns.join(', ')})`);
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
case 'CollapsingMergeTree':
|
|
168
|
+
case 'VersionedCollapsingMergeTree':
|
|
169
|
+
if (engine.signColumn)
|
|
170
|
+
args.push(engine.signColumn);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
if (engine.baseEngine === 'VersionedCollapsingMergeTree' && engine.versionColumn) {
|
|
174
|
+
args.push(engine.versionColumn);
|
|
175
|
+
}
|
|
176
|
+
return `${baseName}(${args.join(', ')})`;
|
|
177
|
+
}
|
|
178
|
+
case 'Buffer':
|
|
179
|
+
return `Buffer(${engine.database}, ${engine.table}, ${engine.layers}, ${engine.minTime}, ${engine.maxTime}, ${engine.minRows}, ${engine.maxRows}, ${engine.minBytes}, ${engine.maxBytes})`;
|
|
180
|
+
case 'Distributed': {
|
|
181
|
+
const shardKey = engine.shardingKey ?? 'rand()';
|
|
182
|
+
if (engine.policyName) {
|
|
183
|
+
return `Distributed('${engine.cluster}', '${engine.database}', '${engine.table}', ${shardKey}, '${engine.policyName}')`;
|
|
184
|
+
}
|
|
185
|
+
return `Distributed('${engine.cluster}', '${engine.database}', '${engine.table}', ${shardKey})`;
|
|
186
|
+
}
|
|
187
|
+
case 'Null':
|
|
188
|
+
return 'Null()';
|
|
189
|
+
case 'Log':
|
|
190
|
+
return 'Log()';
|
|
191
|
+
case 'TinyLog':
|
|
192
|
+
return 'TinyLog()';
|
|
193
|
+
case 'Memory': {
|
|
194
|
+
const args = [];
|
|
195
|
+
if (engine.maxRows !== undefined)
|
|
196
|
+
args.push(`max_rows = ${engine.maxRows}`);
|
|
197
|
+
if (engine.maxBytes !== undefined)
|
|
198
|
+
args.push(`max_bytes = ${engine.maxBytes}`);
|
|
199
|
+
if (engine.compress !== undefined)
|
|
200
|
+
args.push(`compress = ${engine.compress ? 1 : 0}`);
|
|
201
|
+
if (args.length > 0) {
|
|
202
|
+
return `Memory(${args.join(', ')})`;
|
|
203
|
+
}
|
|
204
|
+
return 'Memory()';
|
|
205
|
+
}
|
|
206
|
+
case 'Join':
|
|
207
|
+
return `Join(${engine.strictness}, ${engine.joinType}, ${engine.keys.join(', ')})`;
|
|
208
|
+
case 'Dictionary':
|
|
209
|
+
return `Dictionary('${engine.dictionaryName}')`;
|
|
210
|
+
case 'File': {
|
|
211
|
+
if (engine.compression && engine.compression !== 'none') {
|
|
212
|
+
return `File('${engine.format}', '${engine.compression}')`;
|
|
213
|
+
}
|
|
214
|
+
return `File('${engine.format}')`;
|
|
215
|
+
}
|
|
216
|
+
case 'URL': {
|
|
217
|
+
if (engine.compression && engine.compression !== 'none') {
|
|
218
|
+
return `URL('${engine.url}', '${engine.format}', '${engine.compression}')`;
|
|
219
|
+
}
|
|
220
|
+
return `URL('${engine.url}', '${engine.format}')`;
|
|
221
|
+
}
|
|
222
|
+
case 'S3': {
|
|
223
|
+
const args = [`'${engine.path}'`];
|
|
224
|
+
if (engine.accessKeyId && engine.secretAccessKey) {
|
|
225
|
+
args.push(`'${engine.accessKeyId}'`, `'${engine.secretAccessKey}'`);
|
|
226
|
+
}
|
|
227
|
+
args.push(`'${engine.format}'`);
|
|
228
|
+
if (engine.compression && engine.compression !== 'none') {
|
|
229
|
+
args.push(`'${engine.compression}'`);
|
|
230
|
+
}
|
|
231
|
+
return `S3(${args.join(', ')})`;
|
|
232
|
+
}
|
|
233
|
+
case 'Kafka': {
|
|
234
|
+
const topics = Array.isArray(engine.topicList) ? engine.topicList.join(', ') : engine.topicList;
|
|
235
|
+
let sql = `Kafka() SETTINGS kafka_broker_list = '${engine.brokerList}', kafka_topic_list = '${topics}', kafka_group_name = '${engine.groupName}', kafka_format = '${engine.format}'`;
|
|
236
|
+
if (engine.numConsumers !== undefined) {
|
|
237
|
+
sql += `, kafka_num_consumers = ${engine.numConsumers}`;
|
|
238
|
+
}
|
|
239
|
+
if (engine.maxBlockSize !== undefined) {
|
|
240
|
+
sql += `, kafka_max_block_size = ${engine.maxBlockSize}`;
|
|
241
|
+
}
|
|
242
|
+
if (engine.skipBroken !== undefined) {
|
|
243
|
+
sql += `, kafka_skip_broken_messages = ${engine.skipBroken}`;
|
|
244
|
+
}
|
|
245
|
+
return sql;
|
|
246
|
+
}
|
|
247
|
+
case 'PostgreSQL':
|
|
248
|
+
return `PostgreSQL('${engine.host}:${engine.port}', '${engine.database}', '${engine.table}', '${engine.user}', '${engine.password}'${engine.schema ? `, '${engine.schema}'` : ''})`;
|
|
249
|
+
case 'MySQL':
|
|
250
|
+
return `MySQL('${engine.host}:${engine.port}', '${engine.database}', '${engine.table}', '${engine.user}', '${engine.password}')`;
|
|
251
|
+
default:
|
|
252
|
+
const _exhaustive = engine;
|
|
253
|
+
return 'MergeTree()';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
export function isMergeTreeFamily(engine) {
|
|
257
|
+
if (!engine)
|
|
258
|
+
return true;
|
|
259
|
+
if (typeof engine === 'string')
|
|
260
|
+
return /MergeTree/i.test(engine);
|
|
261
|
+
return [
|
|
262
|
+
'MergeTree',
|
|
263
|
+
'ReplacingMergeTree',
|
|
264
|
+
'SummingMergeTree',
|
|
265
|
+
'AggregatingMergeTree',
|
|
266
|
+
'CollapsingMergeTree',
|
|
267
|
+
'VersionedCollapsingMergeTree',
|
|
268
|
+
'GraphiteMergeTree',
|
|
269
|
+
'ReplicatedMergeTree'
|
|
270
|
+
].includes(engine.type);
|
|
271
|
+
}
|
|
272
|
+
export function isReplicatedEngine(engine) {
|
|
273
|
+
if (!engine)
|
|
274
|
+
return false;
|
|
275
|
+
if (typeof engine === 'string')
|
|
276
|
+
return /Replicated/i.test(engine);
|
|
277
|
+
return engine.type === 'ReplicatedMergeTree';
|
|
278
|
+
}
|
|
279
|
+
export function getVersionColumn(engine) {
|
|
280
|
+
if (!engine || typeof engine === 'string')
|
|
281
|
+
return undefined;
|
|
282
|
+
if (engine.type === 'ReplacingMergeTree') {
|
|
283
|
+
return engine.versionColumn;
|
|
284
|
+
}
|
|
285
|
+
if (engine.type === 'ReplicatedMergeTree' && engine.baseEngine === 'ReplacingMergeTree') {
|
|
286
|
+
return engine.versionColumn;
|
|
287
|
+
}
|
|
288
|
+
if (engine.type === 'VersionedCollapsingMergeTree') {
|
|
289
|
+
return engine.versionColumn;
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
package/dist/expressions.d.ts
CHANGED
|
@@ -37,17 +37,7 @@ export declare namespace sql {
|
|
|
37
37
|
var raw: (rawSql: string) => SQLExpression;
|
|
38
38
|
var join: (expressions: (SQLExpression | ClickHouseColumn | SQLValue)[], separator?: SQLExpression | string) => SQLExpression;
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Typed SQL helper that preserves types for columns from a table definition.
|
|
42
|
-
*/
|
|
43
40
|
export declare function typedSQL<T extends Record<string, ClickHouseColumn>>(strings: TemplateStringsArray, ...args: Array<SQLValue | SQLExpression | T[keyof T]>): SQL<any>;
|
|
44
|
-
/**
|
|
45
|
-
* Generic function call helper for any ClickHouse function
|
|
46
|
-
* @param name Function name
|
|
47
|
-
* @param args Function arguments
|
|
48
|
-
* @example fn('hex', md5(users.email))
|
|
49
|
-
* @example fn('length', users.interests)
|
|
50
|
-
*/
|
|
51
41
|
export declare function fn(name: string, ...args: (ClickHouseColumn | SQLExpression | SQLValue)[]): SQLExpression;
|
|
52
42
|
export declare function eq<T>(col: ClickHouseColumn<T, any, any> | SQLExpression, val: T | SQLValue | ClickHouseColumn | SQLExpression): SQLExpression<any>;
|
|
53
43
|
export declare function ne<T>(col: ClickHouseColumn<T, any, any> | SQLExpression, val: T | SQLValue | ClickHouseColumn | SQLExpression): SQLExpression<any>;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { ClickHouseColumn } from './core';
|
|
2
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
function inferType(val, contextCol) {
|
|
4
|
+
if (contextCol) {
|
|
5
|
+
if (contextCol.type === 'UUID')
|
|
6
|
+
return 'UUID';
|
|
7
|
+
if (contextCol.type.startsWith('Int') || contextCol.type.startsWith('UInt'))
|
|
8
|
+
return contextCol.type;
|
|
9
|
+
if (contextCol.type.startsWith('Float') || contextCol.type.startsWith('Decimal'))
|
|
10
|
+
return contextCol.type;
|
|
11
|
+
}
|
|
12
|
+
if (val === null)
|
|
13
|
+
return 'String';
|
|
14
|
+
if (typeof val === 'number')
|
|
15
|
+
return Number.isInteger(val) ? 'Int32' : 'Float64';
|
|
16
|
+
if (typeof val === 'boolean')
|
|
17
|
+
return 'Bool';
|
|
18
|
+
if (val instanceof Date)
|
|
19
|
+
return 'DateTime';
|
|
20
|
+
if (Array.isArray(val)) {
|
|
21
|
+
if (val.length > 0)
|
|
22
|
+
return `Array(${inferType(val[0])})`;
|
|
23
|
+
return 'Array(String)';
|
|
24
|
+
}
|
|
25
|
+
if (typeof val === 'string' && val.length === 36 && UUID_REGEX.test(val)) {
|
|
26
|
+
return 'UUID';
|
|
27
|
+
}
|
|
28
|
+
if (typeof val === 'object')
|
|
29
|
+
return 'String';
|
|
30
|
+
return 'String';
|
|
31
|
+
}
|
|
32
|
+
export class SQL {
|
|
33
|
+
queryChunks;
|
|
34
|
+
params;
|
|
35
|
+
_type;
|
|
36
|
+
constructor(queryChunks, params) {
|
|
37
|
+
this.queryChunks = queryChunks;
|
|
38
|
+
this.params = params;
|
|
39
|
+
}
|
|
40
|
+
as(alias) {
|
|
41
|
+
const cloned = new SQL([...this.queryChunks], [...this.params]);
|
|
42
|
+
cloned._alias = alias;
|
|
43
|
+
return cloned;
|
|
44
|
+
}
|
|
45
|
+
walk(visitor) {
|
|
46
|
+
let structure = '';
|
|
47
|
+
let lastColumn = null;
|
|
48
|
+
for (let i = 0; i < this.queryChunks.length; i++) {
|
|
49
|
+
structure += this.queryChunks[i];
|
|
50
|
+
if (i < this.params.length) {
|
|
51
|
+
const param = this.params[i];
|
|
52
|
+
const isColumnByInstanceof = param instanceof ClickHouseColumn;
|
|
53
|
+
const isColumnByProperties = param && typeof param === 'object' &&
|
|
54
|
+
'name' in param && 'type' in param && 'toSQL' in param;
|
|
55
|
+
if (isColumnByInstanceof || isColumnByProperties) {
|
|
56
|
+
const col = param;
|
|
57
|
+
lastColumn = col;
|
|
58
|
+
structure += col.tableName ? `${col.tableName}.${col.name}` : col.name;
|
|
59
|
+
}
|
|
60
|
+
else if (typeof param === 'object' && param !== null && 'walk' in param) {
|
|
61
|
+
structure += param.walk(visitor);
|
|
62
|
+
lastColumn = null;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
if (param !== undefined) {
|
|
66
|
+
const type = inferType(param, lastColumn || undefined);
|
|
67
|
+
visitor(param, type);
|
|
68
|
+
structure += `{:${type}}`;
|
|
69
|
+
}
|
|
70
|
+
lastColumn = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return structure;
|
|
75
|
+
}
|
|
76
|
+
formatColumn(col, options) {
|
|
77
|
+
if (options?.ignoreTablePrefix) {
|
|
78
|
+
return `\`${col.name}\``;
|
|
79
|
+
}
|
|
80
|
+
return col.tableName ? `\`${col.tableName}\`.\`${col.name}\`` : `\`${col.name}\``;
|
|
81
|
+
}
|
|
82
|
+
toSQL(options) {
|
|
83
|
+
const finalParams = {};
|
|
84
|
+
let sql = '';
|
|
85
|
+
let lastColumn = null;
|
|
86
|
+
for (let i = 0; i < this.queryChunks.length; i++) {
|
|
87
|
+
sql += this.queryChunks[i];
|
|
88
|
+
if (i < this.params.length) {
|
|
89
|
+
const param = this.params[i];
|
|
90
|
+
const isColumnByInstanceof = param instanceof ClickHouseColumn;
|
|
91
|
+
const isColumnByProperties = param && typeof param === 'object' &&
|
|
92
|
+
'name' in param &&
|
|
93
|
+
'type' in param &&
|
|
94
|
+
'toSQL' in param &&
|
|
95
|
+
typeof param.name === 'string' &&
|
|
96
|
+
typeof param.type === 'string' &&
|
|
97
|
+
typeof param.toSQL === 'function';
|
|
98
|
+
const isColumn = isColumnByInstanceof || isColumnByProperties;
|
|
99
|
+
if (isColumn) {
|
|
100
|
+
lastColumn = param;
|
|
101
|
+
sql += this.formatColumn(lastColumn, options);
|
|
102
|
+
}
|
|
103
|
+
else if (typeof param === 'object' && param !== null && 'toSQL' in param) {
|
|
104
|
+
const res = param.toSQL(options);
|
|
105
|
+
sql += res.sql;
|
|
106
|
+
Object.assign(finalParams, res.params);
|
|
107
|
+
lastColumn = null;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
if (param === undefined) {
|
|
111
|
+
if (options?.table?.$columns) {
|
|
112
|
+
const paramIndex = i;
|
|
113
|
+
const context = paramIndex > 0 ? ` (parameter ${paramIndex} in expression)` : '';
|
|
114
|
+
const sqlContext = sql.substring(Math.max(0, sql.length - 50));
|
|
115
|
+
const possibleColumnNames = Object.keys(options.table.$columns);
|
|
116
|
+
const availableColumns = possibleColumnNames.slice(0, 10).join(', ');
|
|
117
|
+
const commonNames = ['name', 'email', 'id', 'userId', 'firstName', 'lastName'];
|
|
118
|
+
const suggestedColumns = commonNames
|
|
119
|
+
.filter(name => possibleColumnNames.includes(name))
|
|
120
|
+
.slice(0, 3)
|
|
121
|
+
.join(', ');
|
|
122
|
+
throw new Error(`Cannot use undefined value in SQL expression${context}. ` +
|
|
123
|
+
`A table column is undefined (e.g., users.name is undefined when calling concat(users.name, ...)).\n\n` +
|
|
124
|
+
`This usually happens when:\n` +
|
|
125
|
+
` 1. The Proxy didn't resolve the column correctly\n` +
|
|
126
|
+
` 2. The column name doesn't match (check camelCase vs snake_case)\n` +
|
|
127
|
+
` 3. The column doesn't exist on the table\n\n` +
|
|
128
|
+
`Make sure you're accessing columns correctly: table.columnName (e.g., users.email)\n` +
|
|
129
|
+
(suggestedColumns ? `Suggested columns: ${suggestedColumns}\n` : '') +
|
|
130
|
+
`Available columns: ${availableColumns}${possibleColumnNames.length > 10 ? '...' : ''}\n` +
|
|
131
|
+
`SQL context: ...${sqlContext}`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const paramIndex = i;
|
|
135
|
+
const context = paramIndex > 0 ? ` (parameter ${paramIndex} in expression)` : '';
|
|
136
|
+
const sqlContext = sql.substring(Math.max(0, sql.length - 50));
|
|
137
|
+
throw new Error(`Cannot use undefined value in SQL expression${context}. ` +
|
|
138
|
+
`This usually means:\n` +
|
|
139
|
+
` 1. A table column is undefined (e.g., users.name is undefined when calling concat(users.name, ...))\n` +
|
|
140
|
+
` 2. A property wasn't returned from a previous query\n` +
|
|
141
|
+
` 3. The column doesn't exist on the table\n\n` +
|
|
142
|
+
`Make sure you're accessing columns correctly: table.columnName (e.g., users.email)\n` +
|
|
143
|
+
`SQL context: ...${sqlContext}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const paramName = `p_${Object.keys(finalParams).length + 1}`;
|
|
147
|
+
let finalVal = param;
|
|
148
|
+
if (param instanceof Date) {
|
|
149
|
+
const iso = param.toISOString();
|
|
150
|
+
const [datePart, timePart] = iso.split('T');
|
|
151
|
+
const [time] = timePart.split('.');
|
|
152
|
+
finalVal = `${datePart} ${time}`;
|
|
153
|
+
}
|
|
154
|
+
else if (typeof param === 'object' && param !== null && !Array.isArray(param)) {
|
|
155
|
+
finalVal = JSON.stringify(param);
|
|
156
|
+
}
|
|
157
|
+
finalParams[paramName] = finalVal;
|
|
158
|
+
const type = inferType(param, lastColumn || undefined);
|
|
159
|
+
sql += `{${paramName}:${type}}`;
|
|
160
|
+
lastColumn = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { sql, params: finalParams };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function sql(strings, ...args) {
|
|
168
|
+
return new SQL([...strings], args);
|
|
169
|
+
}
|
|
170
|
+
sql.raw = function (rawSql) {
|
|
171
|
+
return {
|
|
172
|
+
_type: undefined,
|
|
173
|
+
toSQL: () => ({ sql: rawSql, params: {} }),
|
|
174
|
+
walk: () => rawSql,
|
|
175
|
+
as: function (alias) {
|
|
176
|
+
return {
|
|
177
|
+
...this,
|
|
178
|
+
_alias: alias
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
sql.join = function (expressions, separator = sql `, `) {
|
|
184
|
+
if (expressions.length === 0)
|
|
185
|
+
return sql ``;
|
|
186
|
+
const chunks = [''];
|
|
187
|
+
const params = [];
|
|
188
|
+
const sep = typeof separator === 'string' ? sql.raw(separator) : separator;
|
|
189
|
+
expressions.forEach((expr, i) => {
|
|
190
|
+
params.push(expr);
|
|
191
|
+
if (i < expressions.length - 1) {
|
|
192
|
+
params.push(sep);
|
|
193
|
+
chunks.push('', '');
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
chunks.push('');
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return new SQL(chunks, params);
|
|
200
|
+
};
|
|
201
|
+
export function typedSQL(strings, ...args) {
|
|
202
|
+
return new SQL([...strings], args);
|
|
203
|
+
}
|
|
204
|
+
export function fn(name, ...args) {
|
|
205
|
+
const chunks = [`${name}(`];
|
|
206
|
+
const params = [];
|
|
207
|
+
args.forEach((arg, i) => {
|
|
208
|
+
params.push(arg);
|
|
209
|
+
if (i < args.length - 1) {
|
|
210
|
+
chunks.push(', ');
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
chunks.push(')');
|
|
214
|
+
return new SQL(chunks, params);
|
|
215
|
+
}
|
|
216
|
+
export function eq(col, val) {
|
|
217
|
+
return sql `${col} = ${val}`;
|
|
218
|
+
}
|
|
219
|
+
export function ne(col, val) {
|
|
220
|
+
return sql `${col} != ${val}`;
|
|
221
|
+
}
|
|
222
|
+
export function gt(col, val) {
|
|
223
|
+
return sql `${col} > ${val}`;
|
|
224
|
+
}
|
|
225
|
+
export function gte(col, val) {
|
|
226
|
+
return sql `${col} >= ${val}`;
|
|
227
|
+
}
|
|
228
|
+
export function lt(col, val) {
|
|
229
|
+
return sql `${col} < ${val}`;
|
|
230
|
+
}
|
|
231
|
+
export function lte(col, val) {
|
|
232
|
+
return sql `${col} <= ${val}`;
|
|
233
|
+
}
|
|
234
|
+
export function inArray(col, values) {
|
|
235
|
+
if (Array.isArray(values)) {
|
|
236
|
+
if (values.length === 0)
|
|
237
|
+
return sql `1=0`;
|
|
238
|
+
}
|
|
239
|
+
return sql `${col} IN ${values}`;
|
|
240
|
+
}
|
|
241
|
+
export function notInArray(col, values) {
|
|
242
|
+
if (Array.isArray(values)) {
|
|
243
|
+
if (values.length === 0)
|
|
244
|
+
return sql `1=1`;
|
|
245
|
+
}
|
|
246
|
+
return sql `${col} NOT IN ${values}`;
|
|
247
|
+
}
|
|
248
|
+
export function between(col, min, max) {
|
|
249
|
+
return sql `${col} BETWEEN ${min} AND ${max}`;
|
|
250
|
+
}
|
|
251
|
+
export function notBetween(col, min, max) {
|
|
252
|
+
return sql `${col} NOT BETWEEN ${min} AND ${max}`;
|
|
253
|
+
}
|
|
254
|
+
export function has(col, value) {
|
|
255
|
+
return sql `has(${col}, ${value})`;
|
|
256
|
+
}
|
|
257
|
+
export function hasAll(col, values) {
|
|
258
|
+
return sql `hasAll(${col}, ${values})`;
|
|
259
|
+
}
|
|
260
|
+
export function hasAny(col, values) {
|
|
261
|
+
return sql `hasAny(${col}, ${values})`;
|
|
262
|
+
}
|
|
263
|
+
export function asc(col) {
|
|
264
|
+
return { col, dir: 'ASC' };
|
|
265
|
+
}
|
|
266
|
+
export function desc(col) {
|
|
267
|
+
return { col, dir: 'DESC' };
|
|
268
|
+
}
|