@housekit/orm 0.1.47 → 0.1.48
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 +34 -0
- 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
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
import { ClickHouseColumn } from '../core';
|
|
2
|
+
import { sql, eq } from '../expressions';
|
|
3
|
+
import { and } from '../modules/conditional';
|
|
4
|
+
import { QueryCompiler } from '../compiler';
|
|
5
|
+
export class ClickHouseQueryBuilder {
|
|
6
|
+
client;
|
|
7
|
+
_select = null;
|
|
8
|
+
_selectResolver = null;
|
|
9
|
+
_table = null;
|
|
10
|
+
_prewhere = null;
|
|
11
|
+
_sample = null;
|
|
12
|
+
_settings = null;
|
|
13
|
+
_distinct = false;
|
|
14
|
+
_joins = [];
|
|
15
|
+
_arrayJoins = [];
|
|
16
|
+
_ctes = [];
|
|
17
|
+
_where = null;
|
|
18
|
+
_limit = null;
|
|
19
|
+
_offset = null;
|
|
20
|
+
_orderBy = [];
|
|
21
|
+
_groupBy = [];
|
|
22
|
+
_having = null;
|
|
23
|
+
_final = false;
|
|
24
|
+
_windows = {};
|
|
25
|
+
_suggestions = [];
|
|
26
|
+
constructor(client) {
|
|
27
|
+
this.client = client;
|
|
28
|
+
}
|
|
29
|
+
select(fields) {
|
|
30
|
+
if (fields) {
|
|
31
|
+
if (typeof fields === 'function') {
|
|
32
|
+
if (!this._table) {
|
|
33
|
+
this._selectResolver = fields;
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
this._select = fields(this._table.$columns);
|
|
37
|
+
this._selectResolver = null;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
this._select = fields;
|
|
41
|
+
this._selectResolver = null;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
from(tableOrSubquery, alias = 'subquery') {
|
|
47
|
+
if (tableOrSubquery instanceof ClickHouseQueryBuilder) {
|
|
48
|
+
return this.fromSubquery(tableOrSubquery, alias);
|
|
49
|
+
}
|
|
50
|
+
this._table = tableOrSubquery;
|
|
51
|
+
const defaultFinal = tableOrSubquery.$options?.defaultFinal;
|
|
52
|
+
if (defaultFinal !== undefined) {
|
|
53
|
+
this._final = Boolean(defaultFinal);
|
|
54
|
+
}
|
|
55
|
+
this.resolveSelect();
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
fromSubquery(subquery, alias) {
|
|
59
|
+
this._table = this.createSubqueryTable(alias, subquery);
|
|
60
|
+
this._final = false;
|
|
61
|
+
this.resolveSelect();
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
resolveSelect() {
|
|
65
|
+
if (!this._selectResolver)
|
|
66
|
+
return;
|
|
67
|
+
if (!this._table) {
|
|
68
|
+
throw new Error('Call .from() before using callback select');
|
|
69
|
+
}
|
|
70
|
+
this._select = this._selectResolver(this._table.$columns);
|
|
71
|
+
this._selectResolver = null;
|
|
72
|
+
}
|
|
73
|
+
where(expression) {
|
|
74
|
+
if (!expression)
|
|
75
|
+
return this;
|
|
76
|
+
if (typeof expression === 'object' && 'toSQL' in expression) {
|
|
77
|
+
this._where = expression;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
if (typeof expression === 'object') {
|
|
81
|
+
const table = this._table;
|
|
82
|
+
if (!table)
|
|
83
|
+
return this;
|
|
84
|
+
const chunks = [];
|
|
85
|
+
const columns = table.$columns || table;
|
|
86
|
+
for (const [key, value] of Object.entries(expression)) {
|
|
87
|
+
const column = table[key] || columns?.[key];
|
|
88
|
+
if (column && value !== undefined) {
|
|
89
|
+
chunks.push(eq(column, value));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (chunks.length > 0) {
|
|
93
|
+
const combined = chunks.length === 1 ? chunks[0] : and(...chunks);
|
|
94
|
+
if (combined)
|
|
95
|
+
this._where = combined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
orderBy(col, dir = 'ASC') {
|
|
101
|
+
if (col && typeof col === 'object' && 'col' in col && 'dir' in col) {
|
|
102
|
+
this._orderBy.push(col);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this._orderBy.push({ col: col, dir });
|
|
106
|
+
}
|
|
107
|
+
if (this._table && col instanceof ClickHouseColumn) {
|
|
108
|
+
const opts = this._table.$options;
|
|
109
|
+
const orderByOpts = opts?.orderBy;
|
|
110
|
+
if (orderByOpts) {
|
|
111
|
+
const target = (Array.isArray(orderByOpts) ? orderByOpts : [orderByOpts]).map((c) => c.toString().trim());
|
|
112
|
+
if (!target.includes(col.name)) {
|
|
113
|
+
this._suggestions.push(`Ordering by ${col.name} not in physical ORDER BY; may be inefficient.`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
groupBy(...cols) {
|
|
120
|
+
this._groupBy = cols;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
having(expression) {
|
|
124
|
+
this._having = expression;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
limit(val) {
|
|
128
|
+
this._limit = val;
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
offset(val) {
|
|
132
|
+
this._offset = val;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
distinct() {
|
|
136
|
+
this._distinct = true;
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
innerJoin(table, on) {
|
|
140
|
+
this._joins.push({ type: 'INNER', table: table.$table, on });
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
leftJoin(table, on) {
|
|
144
|
+
this._joins.push({ type: 'LEFT', table: table.$table, on });
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
rightJoin(table, on) {
|
|
148
|
+
this._joins.push({ type: 'RIGHT', table: table.$table, on });
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
fullJoin(table, on) {
|
|
152
|
+
this._joins.push({ type: 'FULL', table: table.$table, on });
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
crossJoin(table) {
|
|
156
|
+
this._joins.push({ type: 'CROSS', table: table.$table, on: null });
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
globalJoin(type, table, on) {
|
|
160
|
+
this._joins.push({ type: `GLOBAL ${type}`, table: table.$table, on });
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
globalInnerJoin(table, on) {
|
|
164
|
+
return this.globalJoin('INNER', table, on);
|
|
165
|
+
}
|
|
166
|
+
globalLeftJoin(table, on) {
|
|
167
|
+
return this.globalJoin('LEFT', table, on);
|
|
168
|
+
}
|
|
169
|
+
anyJoin(type, table, on) {
|
|
170
|
+
this._joins.push({ type: `ANY ${type}`, table: table.$table, on });
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
anyInnerJoin(table, on) {
|
|
174
|
+
return this.anyJoin('INNER', table, on);
|
|
175
|
+
}
|
|
176
|
+
anyLeftJoin(table, on) {
|
|
177
|
+
return this.anyJoin('LEFT', table, on);
|
|
178
|
+
}
|
|
179
|
+
allJoin(type, table, on) {
|
|
180
|
+
this._joins.push({ type: `ALL ${type}`, table: table.$table, on });
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
asofJoin(table, on) {
|
|
184
|
+
this._joins.push({ type: 'LEFT ASOF', table: table.$table, on });
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
asofInnerJoin(table, on) {
|
|
188
|
+
this._joins.push({ type: 'INNER ASOF', table: table.$table, on });
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
semiJoin(table, on) {
|
|
192
|
+
this._joins.push({ type: 'LEFT SEMI', table: table.$table, on });
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
antiJoin(table, on) {
|
|
196
|
+
this._joins.push({ type: 'LEFT ANTI', table: table.$table, on });
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
globalAsofJoin(table, on) {
|
|
200
|
+
this._joins.push({ type: 'GLOBAL LEFT ASOF', table: table.$table, on });
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
globalAnyJoin(type, table, on) {
|
|
204
|
+
this._joins.push({ type: `GLOBAL ANY ${type}`, table: table.$table, on });
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
arrayJoin(column, ...additionalColumns) {
|
|
208
|
+
this._arrayJoins.push({ column });
|
|
209
|
+
for (const col of additionalColumns) {
|
|
210
|
+
this._arrayJoins.push({ column: col });
|
|
211
|
+
}
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
arrayJoinMultiple(...columns) {
|
|
215
|
+
for (const col of columns) {
|
|
216
|
+
this._arrayJoins.push({ column: col });
|
|
217
|
+
}
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
arrayJoinAs(column, alias) {
|
|
221
|
+
this._arrayJoins.push({ column, alias });
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
with(name, query) {
|
|
225
|
+
this._ctes.push({ name, query });
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
prewhere(expression) {
|
|
229
|
+
this._prewhere = expression;
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
232
|
+
sample(ratio, offset) {
|
|
233
|
+
this._sample = { ratio, offset };
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
$with(alias, queryBuilder) {
|
|
237
|
+
const virtualColumns = {};
|
|
238
|
+
const selection = queryBuilder._select;
|
|
239
|
+
const sourceTable = queryBuilder._table;
|
|
240
|
+
if (selection && Object.keys(selection).length > 0) {
|
|
241
|
+
for (const key of Object.keys(selection)) {
|
|
242
|
+
const val = selection[key];
|
|
243
|
+
if (val instanceof ClickHouseColumn) {
|
|
244
|
+
const col = new ClickHouseColumn(key, val.type, val.isNull, val.meta);
|
|
245
|
+
col.tableName = alias;
|
|
246
|
+
virtualColumns[key] = col;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const col = new ClickHouseColumn(key, 'String');
|
|
250
|
+
col.tableName = alias;
|
|
251
|
+
virtualColumns[key] = col;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (sourceTable) {
|
|
256
|
+
for (const [key, col] of Object.entries(sourceTable.$columns)) {
|
|
257
|
+
const column = col;
|
|
258
|
+
const clone = new ClickHouseColumn(column.name, column.type, column.isNull, column.meta);
|
|
259
|
+
clone.tableName = alias;
|
|
260
|
+
virtualColumns[key] = clone;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const cteOptions = { kind: 'cte' };
|
|
264
|
+
const cteTable = {
|
|
265
|
+
$table: alias,
|
|
266
|
+
$columns: virtualColumns,
|
|
267
|
+
$options: cteOptions,
|
|
268
|
+
toSQL: () => ''
|
|
269
|
+
};
|
|
270
|
+
Object.assign(cteTable, virtualColumns);
|
|
271
|
+
this.with(alias, queryBuilder);
|
|
272
|
+
const register = (mainQuery) => {
|
|
273
|
+
mainQuery.with(alias, queryBuilder);
|
|
274
|
+
return mainQuery;
|
|
275
|
+
};
|
|
276
|
+
return { cteTable, register };
|
|
277
|
+
}
|
|
278
|
+
final() {
|
|
279
|
+
this._final = true;
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
window(name, definition) {
|
|
283
|
+
this._windows[name] = definition;
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
settings(settings) {
|
|
287
|
+
this._settings = settings;
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
createSubqueryTable(alias, subquery) {
|
|
291
|
+
const buildVirtualColumns = (aliasName) => {
|
|
292
|
+
const virtualColumns = {};
|
|
293
|
+
const subState = subquery.getState();
|
|
294
|
+
const selection = subState.select;
|
|
295
|
+
const sourceTable = subState.table;
|
|
296
|
+
if (selection && Object.keys(selection).length > 0) {
|
|
297
|
+
for (const [key, val] of Object.entries(selection)) {
|
|
298
|
+
if (val instanceof ClickHouseColumn) {
|
|
299
|
+
const col = new ClickHouseColumn(key, val.type, val.isNull, val.meta);
|
|
300
|
+
col.tableName = aliasName;
|
|
301
|
+
virtualColumns[key] = col;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
const col = new ClickHouseColumn(key, 'String');
|
|
305
|
+
col.tableName = aliasName;
|
|
306
|
+
virtualColumns[key] = col;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else if (sourceTable) {
|
|
311
|
+
for (const [key, col] of Object.entries(sourceTable.$columns)) {
|
|
312
|
+
const column = col;
|
|
313
|
+
const clone = new ClickHouseColumn(key, column.type, column.isNull, column.meta);
|
|
314
|
+
clone.tableName = aliasName;
|
|
315
|
+
virtualColumns[key] = clone;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return virtualColumns;
|
|
319
|
+
};
|
|
320
|
+
const buildTable = (aliasName) => {
|
|
321
|
+
const columns = buildVirtualColumns(aliasName);
|
|
322
|
+
const table = {
|
|
323
|
+
$table: aliasName,
|
|
324
|
+
$columns: columns,
|
|
325
|
+
$options: { kind: 'subquery', subquery },
|
|
326
|
+
toSQL: () => '',
|
|
327
|
+
as: (nextAlias) => buildTable(nextAlias)
|
|
328
|
+
};
|
|
329
|
+
Object.entries(columns).forEach(([key, col]) => { table[key] = col; });
|
|
330
|
+
return table;
|
|
331
|
+
};
|
|
332
|
+
return buildTable(alias);
|
|
333
|
+
}
|
|
334
|
+
getState() {
|
|
335
|
+
this.resolveSelect();
|
|
336
|
+
return {
|
|
337
|
+
select: this._select,
|
|
338
|
+
table: this._table,
|
|
339
|
+
prewhere: this._prewhere,
|
|
340
|
+
sample: this._sample,
|
|
341
|
+
settings: this._settings,
|
|
342
|
+
distinct: this._distinct,
|
|
343
|
+
joins: this._joins,
|
|
344
|
+
arrayJoins: this._arrayJoins,
|
|
345
|
+
ctes: this._ctes,
|
|
346
|
+
where: this._where,
|
|
347
|
+
limit: this._limit,
|
|
348
|
+
offset: this._offset,
|
|
349
|
+
orderBy: this._orderBy,
|
|
350
|
+
groupBy: this._groupBy,
|
|
351
|
+
having: this._having,
|
|
352
|
+
final: this._final,
|
|
353
|
+
windows: this._windows,
|
|
354
|
+
suggestions: this._suggestions
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
toSQL() {
|
|
358
|
+
const compiler = new QueryCompiler();
|
|
359
|
+
const { sql, params, suggestions } = compiler.compileSelect(this.getState());
|
|
360
|
+
return { query: sql, params, suggestions };
|
|
361
|
+
}
|
|
362
|
+
async then(onfulfilled, onrejected) {
|
|
363
|
+
const compiler = new QueryCompiler();
|
|
364
|
+
try {
|
|
365
|
+
const { cachedQuery, values } = compiler.compileWithCache(this.getState(), this.client);
|
|
366
|
+
const data = (await cachedQuery.execute(values));
|
|
367
|
+
if (onfulfilled) {
|
|
368
|
+
return Promise.resolve(onfulfilled(data));
|
|
369
|
+
}
|
|
370
|
+
return Promise.resolve(data);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
if (onrejected) {
|
|
374
|
+
return Promise.resolve(onrejected(error));
|
|
375
|
+
}
|
|
376
|
+
return Promise.reject(error);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async *[Symbol.asyncIterator]() {
|
|
380
|
+
const compiler = new QueryCompiler();
|
|
381
|
+
const { cachedQuery, values } = compiler.compileWithCache(this.getState(), this.client);
|
|
382
|
+
const query_params = {};
|
|
383
|
+
for (let i = 0; i < values.length; i++) {
|
|
384
|
+
query_params[`p_${i + 1}`] = values[i];
|
|
385
|
+
}
|
|
386
|
+
const resultSet = await this.client.query({
|
|
387
|
+
query: cachedQuery.sql,
|
|
388
|
+
query_params,
|
|
389
|
+
format: 'JSONEachRow',
|
|
390
|
+
});
|
|
391
|
+
const reader = resultSet.stream();
|
|
392
|
+
let buffer = '';
|
|
393
|
+
for await (const chunk of reader) {
|
|
394
|
+
buffer += chunk.toString();
|
|
395
|
+
const lines = buffer.split('\n');
|
|
396
|
+
buffer = lines.pop() ?? '';
|
|
397
|
+
for (const line of lines) {
|
|
398
|
+
if (line.trim()) {
|
|
399
|
+
try {
|
|
400
|
+
yield JSON.parse(line);
|
|
401
|
+
}
|
|
402
|
+
catch (e) {
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (buffer.trim()) {
|
|
408
|
+
try {
|
|
409
|
+
yield JSON.parse(buffer);
|
|
410
|
+
}
|
|
411
|
+
catch (e) {
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
stream() {
|
|
416
|
+
return this[Symbol.asyncIterator]();
|
|
417
|
+
}
|
|
418
|
+
async native() {
|
|
419
|
+
console.warn('[HouseKit] native() is deprecated. RowBinary streaming is not supported by @clickhouse/client 1.15+. Using JSONEachRow instead.');
|
|
420
|
+
return this.then(data => data);
|
|
421
|
+
}
|
|
422
|
+
async vector() {
|
|
423
|
+
console.warn('[HouseKit] vector() is deprecated. RowBinary streaming is not supported by @clickhouse/client 1.15+. Using JSONEachRow instead.');
|
|
424
|
+
const rows = await this.then(data => data);
|
|
425
|
+
if (rows.length === 0)
|
|
426
|
+
return {};
|
|
427
|
+
const result = {};
|
|
428
|
+
const keys = Object.keys(rows[0]);
|
|
429
|
+
for (const key of keys) {
|
|
430
|
+
result[key] = rows.map((row) => row[key]);
|
|
431
|
+
}
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
async explain() {
|
|
435
|
+
const { query, params } = this.toSQL();
|
|
436
|
+
const resultSet = await this.client.query({
|
|
437
|
+
query: `EXPLAIN ${query}`,
|
|
438
|
+
query_params: params
|
|
439
|
+
});
|
|
440
|
+
return resultSet.json();
|
|
441
|
+
}
|
|
442
|
+
async explainPipeline() {
|
|
443
|
+
const { query, params } = this.toSQL();
|
|
444
|
+
const resultSet = await this.client.query({
|
|
445
|
+
query: `EXPLAIN PIPELINE ${query}`,
|
|
446
|
+
query_params: params
|
|
447
|
+
});
|
|
448
|
+
return resultSet.json();
|
|
449
|
+
}
|
|
450
|
+
async findFirst() {
|
|
451
|
+
const result = await this.limit(1).then();
|
|
452
|
+
return result.length > 0 ? result[0] : null;
|
|
453
|
+
}
|
|
454
|
+
async findUnique() {
|
|
455
|
+
const result = await this.limit(1).then();
|
|
456
|
+
return result.length > 0 ? result[0] : null;
|
|
457
|
+
}
|
|
458
|
+
async findMany(options) {
|
|
459
|
+
let query = this;
|
|
460
|
+
if (options?.limit) {
|
|
461
|
+
query = query.limit(options.limit);
|
|
462
|
+
}
|
|
463
|
+
if (options?.offset) {
|
|
464
|
+
query = query.offset(options.offset);
|
|
465
|
+
}
|
|
466
|
+
return query.then();
|
|
467
|
+
}
|
|
468
|
+
async findManyCursor(options) {
|
|
469
|
+
let query = this;
|
|
470
|
+
if (options?.limit) {
|
|
471
|
+
query = query.limit(options.limit);
|
|
472
|
+
}
|
|
473
|
+
if (options?.cursor) {
|
|
474
|
+
const { column, value, direction = 'ASC' } = options.cursor;
|
|
475
|
+
const operator = direction === 'ASC' ? '>' : '<';
|
|
476
|
+
query = query.where(sql `${column} ${operator} ${value}`);
|
|
477
|
+
query = query.orderBy(column, direction);
|
|
478
|
+
}
|
|
479
|
+
return query.then();
|
|
480
|
+
}
|
|
481
|
+
async count(table, options) {
|
|
482
|
+
let query;
|
|
483
|
+
if (table) {
|
|
484
|
+
query = new ClickHouseQueryBuilder(this.client)
|
|
485
|
+
.from(table)
|
|
486
|
+
.select({ count: sql `count(*)` });
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
const countQuery = new ClickHouseQueryBuilder(this.client);
|
|
490
|
+
countQuery._table = this._table;
|
|
491
|
+
countQuery._where = this._where;
|
|
492
|
+
countQuery._joins = [...this._joins];
|
|
493
|
+
countQuery._ctes = [...this._ctes];
|
|
494
|
+
countQuery._prewhere = this._prewhere;
|
|
495
|
+
countQuery._sample = this._sample;
|
|
496
|
+
countQuery._distinct = this._distinct;
|
|
497
|
+
countQuery._groupBy = [...this._groupBy];
|
|
498
|
+
countQuery._having = this._having;
|
|
499
|
+
countQuery._final = this._final;
|
|
500
|
+
countQuery._windows = { ...this._windows };
|
|
501
|
+
countQuery._settings = this._settings ? { ...this._settings } : null;
|
|
502
|
+
countQuery._suggestions = [...this._suggestions];
|
|
503
|
+
countQuery._select = { count: sql `count(*)` };
|
|
504
|
+
query = countQuery;
|
|
505
|
+
}
|
|
506
|
+
if (options?.where) {
|
|
507
|
+
query = query.where(options.where);
|
|
508
|
+
}
|
|
509
|
+
const result = await query.limit(1).then();
|
|
510
|
+
return result[0]?.count || 0;
|
|
511
|
+
}
|
|
512
|
+
async exists(options) {
|
|
513
|
+
let query = this.limit(1);
|
|
514
|
+
if (options?.where) {
|
|
515
|
+
query = query.where(options.where);
|
|
516
|
+
}
|
|
517
|
+
const result = await query.then();
|
|
518
|
+
return result.length > 0;
|
|
519
|
+
}
|
|
520
|
+
async findManyWithMeta(options) {
|
|
521
|
+
const limit = options?.limit || 50;
|
|
522
|
+
const offset = options?.offset || 0;
|
|
523
|
+
let query = this.limit(limit + 1).offset(offset);
|
|
524
|
+
const results = await query.then();
|
|
525
|
+
const hasMore = results.length > limit;
|
|
526
|
+
const data = hasMore ? results.slice(0, -1) : results;
|
|
527
|
+
const result = {
|
|
528
|
+
data,
|
|
529
|
+
hasMore
|
|
530
|
+
};
|
|
531
|
+
if (options?.includeTotal) {
|
|
532
|
+
const total = await this.count();
|
|
533
|
+
result.total = total;
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
async findAsMap(keyColumn, options) {
|
|
538
|
+
const results = await this.findMany(options);
|
|
539
|
+
const map = new Map();
|
|
540
|
+
for (const record of results) {
|
|
541
|
+
const key = record[keyColumn instanceof ClickHouseColumn ? keyColumn.name : 'key'];
|
|
542
|
+
if (key !== undefined && key !== null) {
|
|
543
|
+
map.set(key, record);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return map;
|
|
547
|
+
}
|
|
548
|
+
async findGrouped(groupColumn, options) {
|
|
549
|
+
const results = await this.findMany(options);
|
|
550
|
+
const grouped = {};
|
|
551
|
+
for (const record of results) {
|
|
552
|
+
const key = record[groupColumn instanceof ClickHouseColumn ? groupColumn.name : 'group'];
|
|
553
|
+
if (key !== undefined && key !== null) {
|
|
554
|
+
if (!grouped[key]) {
|
|
555
|
+
grouped[key] = [];
|
|
556
|
+
}
|
|
557
|
+
grouped[key].push(record);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return grouped;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { eq } from '../expressions';
|
|
2
|
+
import { and } from '../modules/conditional';
|
|
3
|
+
export class ClickHouseUpdateBuilder {
|
|
4
|
+
client;
|
|
5
|
+
table;
|
|
6
|
+
_set = {};
|
|
7
|
+
_where = null;
|
|
8
|
+
_lastMutationId = null;
|
|
9
|
+
constructor(client, table) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
this.table = table;
|
|
12
|
+
}
|
|
13
|
+
set(values) {
|
|
14
|
+
this._set = values;
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
where(expression) {
|
|
18
|
+
if (!expression)
|
|
19
|
+
return this;
|
|
20
|
+
if (typeof expression === 'object' && 'toSQL' in expression) {
|
|
21
|
+
this._where = expression;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
if (typeof expression === 'object') {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
for (const [key, value] of Object.entries(expression)) {
|
|
27
|
+
const column = this.table.$columns[key];
|
|
28
|
+
if (column && value !== undefined) {
|
|
29
|
+
chunks.push(eq(column, value));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (chunks.length > 0) {
|
|
33
|
+
const combined = chunks.length === 1 ? chunks[0] : and(...chunks);
|
|
34
|
+
if (combined)
|
|
35
|
+
this._where = combined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
toSQL() {
|
|
41
|
+
if (this.table.$options.appendOnly !== false) {
|
|
42
|
+
throw new Error(`UPDATE is blocked for append-only table ${this.table.$table}. Set appendOnly: false to allow.`);
|
|
43
|
+
}
|
|
44
|
+
if (!this._where) {
|
|
45
|
+
throw new Error("❌ UPDATE requires a WHERE clause in ClickHouse (safety first!)");
|
|
46
|
+
}
|
|
47
|
+
if (Object.keys(this._set).length === 0) {
|
|
48
|
+
throw new Error("❌ UPDATE requires at least one field to set");
|
|
49
|
+
}
|
|
50
|
+
const params = {};
|
|
51
|
+
const setParts = [];
|
|
52
|
+
let paramCounter = 0;
|
|
53
|
+
for (const [key, value] of Object.entries(this._set)) {
|
|
54
|
+
const col = this.table.$columns[key];
|
|
55
|
+
if (!col)
|
|
56
|
+
continue;
|
|
57
|
+
let finalValue = value;
|
|
58
|
+
if (col.meta?.isJson && typeof value === 'object' && value !== null) {
|
|
59
|
+
finalValue = JSON.stringify(value);
|
|
60
|
+
}
|
|
61
|
+
const paramName = `p_${key}_${++paramCounter}`;
|
|
62
|
+
params[paramName] = finalValue;
|
|
63
|
+
setParts.push(`\`${key}\` = {${paramName}:String}`);
|
|
64
|
+
}
|
|
65
|
+
const whereRes = this._where.toSQL({ ignoreTablePrefix: true });
|
|
66
|
+
Object.assign(params, whereRes.params);
|
|
67
|
+
const tableName = this.table.$table;
|
|
68
|
+
return {
|
|
69
|
+
query: `ALTER TABLE \`${tableName}\` UPDATE ${setParts.join(', ')} WHERE ${whereRes.sql}`,
|
|
70
|
+
params
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async execute() {
|
|
74
|
+
const { query, params } = this.toSQL();
|
|
75
|
+
await this.client.command({
|
|
76
|
+
query,
|
|
77
|
+
query_params: params,
|
|
78
|
+
});
|
|
79
|
+
this._lastMutationId = await fetchLatestMutationId(this.client, this.table.$table);
|
|
80
|
+
}
|
|
81
|
+
async wait(options) {
|
|
82
|
+
if (!this._lastMutationId) {
|
|
83
|
+
await this.execute();
|
|
84
|
+
}
|
|
85
|
+
if (!this._lastMutationId)
|
|
86
|
+
return;
|
|
87
|
+
await waitForMutationCompletion(this.client, this.table.$table, this._lastMutationId, options);
|
|
88
|
+
}
|
|
89
|
+
async then(onfulfilled, onrejected) {
|
|
90
|
+
try {
|
|
91
|
+
await this.execute();
|
|
92
|
+
if (onfulfilled) {
|
|
93
|
+
return Promise.resolve(onfulfilled());
|
|
94
|
+
}
|
|
95
|
+
return Promise.resolve();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (onrejected) {
|
|
99
|
+
return Promise.resolve(onrejected(error));
|
|
100
|
+
}
|
|
101
|
+
return Promise.reject(error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function fetchLatestMutationId(client, tableName) {
|
|
106
|
+
const result = await client.query({
|
|
107
|
+
query: `SELECT mutation_id FROM system.mutations WHERE database = currentDatabase() AND table = {table:String} ORDER BY create_time DESC LIMIT 1 FORMAT JSONEachRow`,
|
|
108
|
+
query_params: { table: tableName }
|
|
109
|
+
});
|
|
110
|
+
const rows = await result.json();
|
|
111
|
+
return rows[0]?.mutation_id ?? null;
|
|
112
|
+
}
|
|
113
|
+
async function waitForMutationCompletion(client, tableName, mutationId, options) {
|
|
114
|
+
const pollInterval = options?.pollIntervalMs ?? 500;
|
|
115
|
+
const timeout = options?.timeoutMs ?? 60_000;
|
|
116
|
+
const start = Date.now();
|
|
117
|
+
while (true) {
|
|
118
|
+
const result = await client.query({
|
|
119
|
+
query: `SELECT is_done, latest_failed_part, latest_fail_reason FROM system.mutations WHERE database = currentDatabase() AND table = {table:String} AND mutation_id = {mid:String} FORMAT JSONEachRow`,
|
|
120
|
+
query_params: { table: tableName, mid: mutationId }
|
|
121
|
+
});
|
|
122
|
+
const rows = await result.json();
|
|
123
|
+
const row = rows[0];
|
|
124
|
+
if (!row)
|
|
125
|
+
return;
|
|
126
|
+
if (row.latest_fail_reason) {
|
|
127
|
+
throw new Error(`Mutation ${mutationId} failed: ${row.latest_fail_reason}`);
|
|
128
|
+
}
|
|
129
|
+
if (row.is_done === 1)
|
|
130
|
+
return;
|
|
131
|
+
if (Date.now() - start > timeout) {
|
|
132
|
+
throw new Error(`Mutation ${mutationId} not completed after ${timeout}ms`);
|
|
133
|
+
}
|
|
134
|
+
await new Promise(res => setTimeout(res, pollInterval));
|
|
135
|
+
}
|
|
136
|
+
}
|