@hypequery/clickhouse 0.0.0-canary-20260306132943
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/LICENSE +201 -0
- package/README-CLI.md +123 -0
- package/README.md +316 -0
- package/dist/cli/bin.js +285 -0
- package/dist/cli/generate-types.d.ts +5 -0
- package/dist/cli/generate-types.js +218 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2 -0
- package/dist/core/cache/cache-manager.d.ts +4 -0
- package/dist/core/cache/cache-manager.d.ts.map +1 -0
- package/dist/core/cache/cache-manager.js +176 -0
- package/dist/core/cache/controller.d.ts +15 -0
- package/dist/core/cache/controller.d.ts.map +1 -0
- package/dist/core/cache/controller.js +58 -0
- package/dist/core/cache/key.d.ts +11 -0
- package/dist/core/cache/key.d.ts.map +1 -0
- package/dist/core/cache/key.js +26 -0
- package/dist/core/cache/providers/memory-lru.d.ts +31 -0
- package/dist/core/cache/providers/memory-lru.d.ts.map +1 -0
- package/dist/core/cache/providers/memory-lru.js +156 -0
- package/dist/core/cache/providers/noop.d.ts +7 -0
- package/dist/core/cache/providers/noop.d.ts.map +1 -0
- package/dist/core/cache/providers/noop.js +11 -0
- package/dist/core/cache/runtime-context.d.ts +30 -0
- package/dist/core/cache/runtime-context.d.ts.map +1 -0
- package/dist/core/cache/runtime-context.js +58 -0
- package/dist/core/cache/serialization.d.ts +6 -0
- package/dist/core/cache/serialization.d.ts.map +1 -0
- package/dist/core/cache/serialization.js +166 -0
- package/dist/core/cache/types.d.ts +52 -0
- package/dist/core/cache/types.d.ts.map +1 -0
- package/dist/core/cache/types.js +1 -0
- package/dist/core/cache/utils.d.ts +9 -0
- package/dist/core/cache/utils.d.ts.map +1 -0
- package/dist/core/cache/utils.js +30 -0
- package/dist/core/connection.d.ts +112 -0
- package/dist/core/connection.d.ts.map +1 -0
- package/dist/core/connection.js +150 -0
- package/dist/core/cross-filter.d.ts +73 -0
- package/dist/core/cross-filter.d.ts.map +1 -0
- package/dist/core/cross-filter.js +142 -0
- package/dist/core/env/auto-client.browser.d.ts +3 -0
- package/dist/core/env/auto-client.browser.d.ts.map +1 -0
- package/dist/core/env/auto-client.browser.js +3 -0
- package/dist/core/env/auto-client.d.ts +9 -0
- package/dist/core/env/auto-client.d.ts.map +1 -0
- package/dist/core/env/auto-client.js +21 -0
- package/dist/core/features/aggregations.d.ts +98 -0
- package/dist/core/features/aggregations.d.ts.map +1 -0
- package/dist/core/features/aggregations.js +36 -0
- package/dist/core/features/analytics.d.ts +81 -0
- package/dist/core/features/analytics.d.ts.map +1 -0
- package/dist/core/features/analytics.js +45 -0
- package/dist/core/features/cross-filtering.d.ts +11 -0
- package/dist/core/features/cross-filtering.d.ts.map +1 -0
- package/dist/core/features/cross-filtering.js +90 -0
- package/dist/core/features/executor.d.ts +21 -0
- package/dist/core/features/executor.d.ts.map +1 -0
- package/dist/core/features/executor.js +146 -0
- package/dist/core/features/filtering.d.ts +99 -0
- package/dist/core/features/filtering.d.ts.map +1 -0
- package/dist/core/features/filtering.js +118 -0
- package/dist/core/features/joins.d.ts +26 -0
- package/dist/core/features/joins.d.ts.map +1 -0
- package/dist/core/features/joins.js +17 -0
- package/dist/core/features/query-modifiers.d.ts +116 -0
- package/dist/core/features/query-modifiers.d.ts.map +1 -0
- package/dist/core/features/query-modifiers.js +51 -0
- package/dist/core/formatters/sql-formatter.d.ts +9 -0
- package/dist/core/formatters/sql-formatter.d.ts.map +1 -0
- package/dist/core/formatters/sql-formatter.js +131 -0
- package/dist/core/join-relationships.d.ts +51 -0
- package/dist/core/join-relationships.d.ts.map +1 -0
- package/dist/core/join-relationships.js +54 -0
- package/dist/core/query-builder.d.ts +236 -0
- package/dist/core/query-builder.d.ts.map +1 -0
- package/dist/core/query-builder.js +495 -0
- package/dist/core/tests/index.d.ts +2 -0
- package/dist/core/tests/index.d.ts.map +1 -0
- package/dist/core/tests/index.js +1 -0
- package/dist/core/tests/integration/setup.d.ts +48 -0
- package/dist/core/tests/integration/setup.d.ts.map +1 -0
- package/dist/core/tests/integration/setup.js +349 -0
- package/dist/core/tests/integration/test-config.d.ts +15 -0
- package/dist/core/tests/integration/test-config.d.ts.map +1 -0
- package/dist/core/tests/integration/test-config.js +14 -0
- package/dist/core/tests/integration/test-data.json +190 -0
- package/dist/core/tests/test-utils.d.ts +44 -0
- package/dist/core/tests/test-utils.d.ts.map +1 -0
- package/dist/core/tests/test-utils.js +65 -0
- package/dist/core/types/builder-state.d.ts +27 -0
- package/dist/core/types/builder-state.d.ts.map +1 -0
- package/dist/core/types/builder-state.js +1 -0
- package/dist/core/types/select-types.d.ts +33 -0
- package/dist/core/types/select-types.d.ts.map +1 -0
- package/dist/core/types/select-types.js +1 -0
- package/dist/core/types/type-helpers.d.ts +5 -0
- package/dist/core/types/type-helpers.d.ts.map +1 -0
- package/dist/core/types/type-helpers.js +1 -0
- package/dist/core/utils/logger.d.ts +43 -0
- package/dist/core/utils/logger.d.ts.map +1 -0
- package/dist/core/utils/logger.js +104 -0
- package/dist/core/utils/predicate-builder.d.ts +33 -0
- package/dist/core/utils/predicate-builder.d.ts.map +1 -0
- package/dist/core/utils/predicate-builder.js +95 -0
- package/dist/core/utils/sql-expressions.d.ts +77 -0
- package/dist/core/utils/sql-expressions.d.ts.map +1 -0
- package/dist/core/utils/sql-expressions.js +54 -0
- package/dist/core/utils/streaming-helpers.d.ts +2 -0
- package/dist/core/utils/streaming-helpers.d.ts.map +1 -0
- package/dist/core/utils/streaming-helpers.js +137 -0
- package/dist/core/utils.d.ts +3 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +29 -0
- package/dist/core/validators/filter-validator.d.ts +9 -0
- package/dist/core/validators/filter-validator.d.ts.map +1 -0
- package/dist/core/validators/filter-validator.js +19 -0
- package/dist/core/validators/value-validator.d.ts +7 -0
- package/dist/core/validators/value-validator.d.ts.map +1 -0
- package/dist/core/validators/value-validator.js +47 -0
- package/dist/formatters/index.d.ts +2 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/types/base.d.ts +50 -0
- package/dist/types/base.d.ts.map +1 -0
- package/dist/types/base.js +1 -0
- package/dist/types/clickhouse-types.d.ts +17 -0
- package/dist/types/clickhouse-types.d.ts.map +1 -0
- package/dist/types/clickhouse-types.js +1 -0
- package/dist/types/filters.d.ts +53 -0
- package/dist/types/filters.d.ts.map +1 -0
- package/dist/types/filters.js +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/schema.d.ts +19 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { ClickHouseConnection } from './connection.js';
|
|
2
|
+
import { SQLFormatter } from './formatters/sql-formatter.js';
|
|
3
|
+
import { AggregationFeature } from './features/aggregations.js';
|
|
4
|
+
import { JoinFeature } from './features/joins.js';
|
|
5
|
+
import { FilteringFeature } from './features/filtering.js';
|
|
6
|
+
import { AnalyticsFeature } from './features/analytics.js';
|
|
7
|
+
import { ExecutorFeature } from './features/executor.js';
|
|
8
|
+
import { QueryModifiersFeature } from './features/query-modifiers.js';
|
|
9
|
+
import { FilterValidator } from './validators/filter-validator.js';
|
|
10
|
+
import { createPredicateBuilder, } from './utils/predicate-builder.js';
|
|
11
|
+
import { CrossFilteringFeature } from './features/cross-filtering.js';
|
|
12
|
+
import { executeWithCache } from './cache/cache-manager.js';
|
|
13
|
+
import { substituteParameters } from './utils.js';
|
|
14
|
+
import { mergeCacheOptionsPartial, initializeCacheRuntime } from './cache/utils.js';
|
|
15
|
+
/**
|
|
16
|
+
* Type guard to check if a config is a client-based configuration.
|
|
17
|
+
*/
|
|
18
|
+
export function isClientConfig(config) {
|
|
19
|
+
return 'client' in config && config.client !== undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A type-safe query builder for ClickHouse databases.
|
|
23
|
+
* The builder carries a single state object that encodes scope, output, and schema metadata.
|
|
24
|
+
*/
|
|
25
|
+
export class QueryBuilder {
|
|
26
|
+
static relationships;
|
|
27
|
+
config = {};
|
|
28
|
+
tableName;
|
|
29
|
+
state;
|
|
30
|
+
formatter = new SQLFormatter();
|
|
31
|
+
aggregations;
|
|
32
|
+
joins;
|
|
33
|
+
filtering;
|
|
34
|
+
analytics;
|
|
35
|
+
executor;
|
|
36
|
+
modifiers;
|
|
37
|
+
crossFiltering;
|
|
38
|
+
runtime;
|
|
39
|
+
cacheOptions;
|
|
40
|
+
constructor(tableName, state, runtime) {
|
|
41
|
+
this.tableName = tableName;
|
|
42
|
+
this.state = state;
|
|
43
|
+
this.runtime = runtime;
|
|
44
|
+
this.aggregations = new AggregationFeature(this);
|
|
45
|
+
this.joins = new JoinFeature(this);
|
|
46
|
+
this.filtering = new FilteringFeature(this);
|
|
47
|
+
this.analytics = new AnalyticsFeature(this);
|
|
48
|
+
this.executor = new ExecutorFeature(this);
|
|
49
|
+
this.modifiers = new QueryModifiersFeature(this);
|
|
50
|
+
this.crossFiltering = new CrossFilteringFeature(this);
|
|
51
|
+
}
|
|
52
|
+
fork(state, config) {
|
|
53
|
+
const builder = new QueryBuilder(this.tableName, state, this.runtime);
|
|
54
|
+
builder.config = { ...config };
|
|
55
|
+
builder.cacheOptions = this.cacheOptions;
|
|
56
|
+
return builder;
|
|
57
|
+
}
|
|
58
|
+
debug() {
|
|
59
|
+
console.log('Current Type:', {
|
|
60
|
+
state: this.state,
|
|
61
|
+
config: this.config
|
|
62
|
+
});
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
cache(options) {
|
|
66
|
+
if (options === false) {
|
|
67
|
+
this.cacheOptions = { mode: 'no-store', ttlMs: 0, staleTtlMs: 0, cacheTimeMs: 0 };
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
this.cacheOptions = mergeCacheOptionsPartial(this.cacheOptions, options);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
// --- Analytics Helper: Add a CTE.
|
|
74
|
+
withCTE(alias, subquery) {
|
|
75
|
+
this.config = this.analytics.addCTE(alias, subquery);
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
// --- Analytics Helper: Add a scalar WITH alias.
|
|
79
|
+
withScalar(alias, expressionBuilder) {
|
|
80
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(alias)) {
|
|
81
|
+
throw new Error(`Invalid scalar alias "${alias}". Use an unquoted SQL identifier (letters, numbers, underscore; cannot start with a number).`);
|
|
82
|
+
}
|
|
83
|
+
const expression = expressionBuilder(createPredicateBuilder());
|
|
84
|
+
const nextConfig = this.analytics.addScalar(alias, expression);
|
|
85
|
+
const nextState = {
|
|
86
|
+
...this.state,
|
|
87
|
+
scalars: {
|
|
88
|
+
...this.state.scalars,
|
|
89
|
+
[alias]: undefined,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
const builder = new QueryBuilder(this.tableName, nextState, this.runtime);
|
|
93
|
+
builder.config = { ...nextConfig };
|
|
94
|
+
builder.cacheOptions = this.cacheOptions;
|
|
95
|
+
return builder;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Groups results by a time interval using a specified ClickHouse function.
|
|
99
|
+
*
|
|
100
|
+
* @param column - The column containing the date or timestamp.
|
|
101
|
+
* @param interval - The interval value. For example, "1 day" or "15 minute".
|
|
102
|
+
* This is only used when the method is 'toStartOfInterval'.
|
|
103
|
+
* @param method - The time bucketing function to use.
|
|
104
|
+
* Defaults to 'toStartOfInterval'.
|
|
105
|
+
* Other valid values include 'toStartOfMinute', 'toStartOfHour',
|
|
106
|
+
* 'toStartOfDay', 'toStartOfWeek', 'toStartOfMonth', 'toStartOfQuarter', and 'toStartOfYear'.
|
|
107
|
+
* @returns The current QueryBuilder instance.
|
|
108
|
+
*/
|
|
109
|
+
groupByTimeInterval(column, interval, method = 'toStartOfInterval') {
|
|
110
|
+
this.config = this.analytics.addTimeInterval(String(column), interval, method);
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
// --- Analytics Helper: Add a raw SQL fragment.
|
|
114
|
+
raw(sql) {
|
|
115
|
+
// Use raw() to inject SQL that isn't supported by the builder.
|
|
116
|
+
// Use with caution.
|
|
117
|
+
this.config.having = this.config.having || [];
|
|
118
|
+
this.config.having.push(sql);
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
// --- Analytics Helper: Add query settings.
|
|
122
|
+
settings(opts) {
|
|
123
|
+
this.config = this.analytics.addSettings(opts);
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
applyCrossFilters(crossFilter) {
|
|
127
|
+
const normalized = crossFilter;
|
|
128
|
+
this.config = this.crossFiltering.applyCrossFilters(normalized);
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
select(columnsOrAsterisk) {
|
|
132
|
+
if (columnsOrAsterisk === '*') {
|
|
133
|
+
const nextState = {
|
|
134
|
+
...this.state,
|
|
135
|
+
output: {}
|
|
136
|
+
};
|
|
137
|
+
const nextConfig = {
|
|
138
|
+
...this.config,
|
|
139
|
+
select: ['*'],
|
|
140
|
+
orderBy: this.config.orderBy?.map(({ column, direction }) => ({
|
|
141
|
+
column: String(column),
|
|
142
|
+
direction
|
|
143
|
+
}))
|
|
144
|
+
};
|
|
145
|
+
return this.fork(nextState, nextConfig);
|
|
146
|
+
}
|
|
147
|
+
const columns = columnsOrAsterisk;
|
|
148
|
+
const processedColumns = columns.map(col => {
|
|
149
|
+
if (typeof col === 'object' && col !== null && '__type' in col) {
|
|
150
|
+
return col.toSql();
|
|
151
|
+
}
|
|
152
|
+
return String(col);
|
|
153
|
+
});
|
|
154
|
+
const nextState = {
|
|
155
|
+
...this.state,
|
|
156
|
+
output: {}
|
|
157
|
+
};
|
|
158
|
+
const nextConfig = {
|
|
159
|
+
...this.config,
|
|
160
|
+
select: processedColumns,
|
|
161
|
+
orderBy: this.config.orderBy?.map(({ column, direction }) => ({
|
|
162
|
+
column: String(column),
|
|
163
|
+
direction
|
|
164
|
+
}))
|
|
165
|
+
};
|
|
166
|
+
return this.fork(nextState, nextConfig);
|
|
167
|
+
}
|
|
168
|
+
selectConst(...columns) {
|
|
169
|
+
return this.select(columns);
|
|
170
|
+
}
|
|
171
|
+
sum(column, alias) {
|
|
172
|
+
return this.applyAggregation(column, alias, 'sum', (col, finalAlias) => this.aggregations.sum(col, finalAlias));
|
|
173
|
+
}
|
|
174
|
+
count(column, alias) {
|
|
175
|
+
return this.applyAggregation(column, alias, 'count', (col, finalAlias) => this.aggregations.count(col, finalAlias));
|
|
176
|
+
}
|
|
177
|
+
avg(column, alias) {
|
|
178
|
+
return this.applyAggregation(column, alias, 'avg', (col, finalAlias) => this.aggregations.avg(col, finalAlias));
|
|
179
|
+
}
|
|
180
|
+
min(column, alias) {
|
|
181
|
+
return this.applyAggregation(column, alias, 'min', (col, finalAlias) => this.aggregations.min(col, finalAlias));
|
|
182
|
+
}
|
|
183
|
+
max(column, alias) {
|
|
184
|
+
return this.applyAggregation(column, alias, 'max', (col, finalAlias) => this.aggregations.max(col, finalAlias));
|
|
185
|
+
}
|
|
186
|
+
applyAggregation(column, alias, suffix, updater) {
|
|
187
|
+
const columnName = String(column);
|
|
188
|
+
const finalAlias = (alias || `${columnName}_${suffix}`);
|
|
189
|
+
const nextState = {
|
|
190
|
+
...this.state,
|
|
191
|
+
output: {}
|
|
192
|
+
};
|
|
193
|
+
const nextConfig = updater(columnName, finalAlias);
|
|
194
|
+
return this.fork(nextState, nextConfig);
|
|
195
|
+
}
|
|
196
|
+
// Make needed properties accessible to features
|
|
197
|
+
getTableName() {
|
|
198
|
+
return this.tableName;
|
|
199
|
+
}
|
|
200
|
+
getFormatter() {
|
|
201
|
+
return this.formatter;
|
|
202
|
+
}
|
|
203
|
+
getRuntimeContext() {
|
|
204
|
+
return this.runtime;
|
|
205
|
+
}
|
|
206
|
+
getCacheOptions() {
|
|
207
|
+
return this.cacheOptions;
|
|
208
|
+
}
|
|
209
|
+
getExecutor() {
|
|
210
|
+
return this.executor;
|
|
211
|
+
}
|
|
212
|
+
// Delegate execution methods to feature
|
|
213
|
+
toSQL() {
|
|
214
|
+
return this.executor.toSQL();
|
|
215
|
+
}
|
|
216
|
+
toSQLWithParams() {
|
|
217
|
+
return this.executor.toSQLWithParams();
|
|
218
|
+
}
|
|
219
|
+
execute(options) {
|
|
220
|
+
return executeWithCache(this, options);
|
|
221
|
+
}
|
|
222
|
+
async stream() {
|
|
223
|
+
return this.executor.stream();
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Processes each row in a stream with the provided callback function
|
|
227
|
+
* @param callback Function to call for each row in the stream
|
|
228
|
+
*/
|
|
229
|
+
async streamForEach(callback) {
|
|
230
|
+
const stream = await this.stream();
|
|
231
|
+
const reader = stream.getReader();
|
|
232
|
+
try {
|
|
233
|
+
while (true) {
|
|
234
|
+
const { done, value: rows } = await reader.read();
|
|
235
|
+
if (done)
|
|
236
|
+
break;
|
|
237
|
+
for (const row of rows) {
|
|
238
|
+
await callback(row);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
reader.releaseLock();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
validateFilterValue(column, operator, value) {
|
|
247
|
+
// Handle tuple columns
|
|
248
|
+
if (Array.isArray(column)) {
|
|
249
|
+
// For tuple operations, we don't validate individual column types
|
|
250
|
+
// as they might be cross-table references
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// Skip validation for advanced IN operators - they handle their own validation
|
|
254
|
+
const advancedInOperators = [
|
|
255
|
+
'globalIn', 'globalNotIn', 'inSubquery', 'globalInSubquery',
|
|
256
|
+
'inTable', 'globalInTable', 'inTuple', 'globalInTuple'
|
|
257
|
+
];
|
|
258
|
+
if (advancedInOperators.includes(operator)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const columnName = String(column);
|
|
262
|
+
if (FilterValidator.validateJoinedColumn(columnName))
|
|
263
|
+
return;
|
|
264
|
+
const baseColumns = this.state.base;
|
|
265
|
+
const columnType = baseColumns[columnName];
|
|
266
|
+
FilterValidator.validateFilterCondition({ column: columnName, operator, value }, columnType);
|
|
267
|
+
}
|
|
268
|
+
where(columnOrColumns, operator, value) {
|
|
269
|
+
if (typeof columnOrColumns === 'function') {
|
|
270
|
+
const expression = columnOrColumns(createPredicateBuilder());
|
|
271
|
+
this.config = this.filtering.addExpressionCondition('AND', expression);
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
if (operator === undefined) {
|
|
275
|
+
throw new Error('Operator is required when specifying a column for where()');
|
|
276
|
+
}
|
|
277
|
+
// Handle tuple operations
|
|
278
|
+
if (Array.isArray(columnOrColumns) && (operator === 'inTuple' || operator === 'globalInTuple')) {
|
|
279
|
+
const columns = columnOrColumns;
|
|
280
|
+
this.validateFilterValue(columns, operator, value);
|
|
281
|
+
this.config = this.filtering.addCondition('AND', columns.map(String), operator, value);
|
|
282
|
+
return this;
|
|
283
|
+
}
|
|
284
|
+
const column = columnOrColumns;
|
|
285
|
+
this.validateFilterValue(column, operator, value);
|
|
286
|
+
this.config = this.filtering.addCondition('AND', String(column), operator, value);
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
orWhere(columnOrColumns, operator, value) {
|
|
290
|
+
if (typeof columnOrColumns === 'function') {
|
|
291
|
+
const expression = columnOrColumns(createPredicateBuilder());
|
|
292
|
+
this.config = this.filtering.addExpressionCondition('OR', expression);
|
|
293
|
+
return this;
|
|
294
|
+
}
|
|
295
|
+
if (operator === undefined) {
|
|
296
|
+
throw new Error('Operator is required when specifying a column for orWhere()');
|
|
297
|
+
}
|
|
298
|
+
if (Array.isArray(columnOrColumns) && (operator === 'inTuple' || operator === 'globalInTuple')) {
|
|
299
|
+
const columns = columnOrColumns;
|
|
300
|
+
this.validateFilterValue(columns, operator, value);
|
|
301
|
+
this.config = this.filtering.addCondition('OR', columns.map(String), operator, value);
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
const column = columnOrColumns;
|
|
305
|
+
this.validateFilterValue(column, operator, value);
|
|
306
|
+
this.config = this.filtering.addCondition('OR', String(column), operator, value);
|
|
307
|
+
return this;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Creates a parenthesized group of WHERE conditions joined with AND/OR operators.
|
|
311
|
+
* @param {Function} callback - Function that builds the conditions within the group
|
|
312
|
+
* @returns {this} The current QueryBuilder instance
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* builder.whereGroup(qb => {
|
|
316
|
+
* qb.where('status', 'eq', 'active').orWhere('status', 'eq', 'pending');
|
|
317
|
+
* })
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
whereGroup(callback) {
|
|
321
|
+
this.config = this.filtering.startWhereGroup();
|
|
322
|
+
callback(this);
|
|
323
|
+
this.config = this.filtering.endWhereGroup();
|
|
324
|
+
return this;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Creates a parenthesized group of WHERE conditions joined with OR operator.
|
|
328
|
+
* @param {Function} callback - Function that builds the conditions within the group
|
|
329
|
+
* @returns {this} The current QueryBuilder instance
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* builder.orWhereGroup(qb => {
|
|
333
|
+
* qb.where('status', 'eq', 'active').orWhere('status', 'eq', 'pending');
|
|
334
|
+
* })
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
orWhereGroup(callback) {
|
|
338
|
+
this.config = this.filtering.startOrWhereGroup();
|
|
339
|
+
callback(this);
|
|
340
|
+
this.config = this.filtering.endWhereGroup();
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Adds a GROUP BY clause.
|
|
345
|
+
* @param {keyof T | Array<keyof T>} columns - Column(s) to group by
|
|
346
|
+
* @returns {this} The current QueryBuilder instance
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* builder.groupBy(['category', 'status'])
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
groupBy(columns) {
|
|
353
|
+
const normalized = Array.isArray(columns) ? columns.map(String) : String(columns);
|
|
354
|
+
this.config = this.modifiers.addGroupBy(normalized);
|
|
355
|
+
return this;
|
|
356
|
+
}
|
|
357
|
+
limit(count) {
|
|
358
|
+
this.config = this.modifiers.addLimit(count);
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
offset(count) {
|
|
362
|
+
this.config = this.modifiers.addOffset(count);
|
|
363
|
+
return this;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Adds an ORDER BY clause.
|
|
367
|
+
* @param {keyof T} column - The column to order by
|
|
368
|
+
* @param {OrderDirection} [direction='ASC'] - The sort direction
|
|
369
|
+
* @returns {this} The current QueryBuilder instance
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* builder.orderBy('created_at', 'DESC')
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
orderBy(column, direction = 'ASC') {
|
|
376
|
+
this.config = this.modifiers.addOrderBy(String(column), direction);
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Adds a HAVING clause for filtering grouped results.
|
|
381
|
+
* @param {string} condition - The HAVING condition
|
|
382
|
+
* @returns {this} The current QueryBuilder instance
|
|
383
|
+
* @example
|
|
384
|
+
* ```ts
|
|
385
|
+
* builder.having('COUNT(*) > 5')
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
having(condition, parameters) {
|
|
389
|
+
this.config = this.modifiers.addHaving(condition, parameters);
|
|
390
|
+
return this;
|
|
391
|
+
}
|
|
392
|
+
distinct() {
|
|
393
|
+
this.config = this.modifiers.setDistinct();
|
|
394
|
+
return this;
|
|
395
|
+
}
|
|
396
|
+
whereBetween(column, [min, max]) {
|
|
397
|
+
if (min === null || max === null) {
|
|
398
|
+
throw new Error('BETWEEN values cannot be null');
|
|
399
|
+
}
|
|
400
|
+
return this.where(column, 'between', [min, max]);
|
|
401
|
+
}
|
|
402
|
+
innerJoin(table, leftColumn, rightColumn, alias) {
|
|
403
|
+
return this.applyJoin('INNER', table, leftColumn, rightColumn, alias);
|
|
404
|
+
}
|
|
405
|
+
leftJoin(table, leftColumn, rightColumn, alias) {
|
|
406
|
+
return this.applyJoin('LEFT', table, leftColumn, rightColumn, alias);
|
|
407
|
+
}
|
|
408
|
+
rightJoin(table, leftColumn, rightColumn, alias) {
|
|
409
|
+
return this.applyJoin('RIGHT', table, leftColumn, rightColumn, alias);
|
|
410
|
+
}
|
|
411
|
+
fullJoin(table, leftColumn, rightColumn, alias) {
|
|
412
|
+
return this.applyJoin('FULL', table, leftColumn, rightColumn, alias);
|
|
413
|
+
}
|
|
414
|
+
applyJoin(type, table, leftColumn, rightColumn, alias) {
|
|
415
|
+
const nextState = {
|
|
416
|
+
...this.state,
|
|
417
|
+
aliases: alias ? { ...this.state.aliases, [alias]: table } : this.state.aliases
|
|
418
|
+
};
|
|
419
|
+
const nextConfig = this.joins.addJoin(type, table, String(leftColumn), rightColumn, alias);
|
|
420
|
+
return this.fork(nextState, nextConfig);
|
|
421
|
+
}
|
|
422
|
+
// Make config accessible to features
|
|
423
|
+
getConfig() {
|
|
424
|
+
return this.config;
|
|
425
|
+
}
|
|
426
|
+
static setJoinRelationships(relationships) {
|
|
427
|
+
this.relationships = relationships;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Apply a predefined join relationship
|
|
431
|
+
*/
|
|
432
|
+
withRelation(name, options) {
|
|
433
|
+
const relationships = QueryBuilder.relationships;
|
|
434
|
+
if (!relationships) {
|
|
435
|
+
throw new Error('Join relationships have not been initialized. Call QueryBuilder.setJoinRelationships first.');
|
|
436
|
+
}
|
|
437
|
+
const path = relationships.get(name);
|
|
438
|
+
if (!path) {
|
|
439
|
+
throw new Error(`Join relationship '${name}' not found`);
|
|
440
|
+
}
|
|
441
|
+
const applyJoin = (joinPath) => {
|
|
442
|
+
const type = options?.type || joinPath.type || 'INNER';
|
|
443
|
+
const alias = options?.alias || joinPath.alias;
|
|
444
|
+
const table = String(joinPath.to);
|
|
445
|
+
const rightColumn = `${table}.${joinPath.rightColumn}`;
|
|
446
|
+
this.config = this.joins.addJoin(type, table, joinPath.leftColumn, rightColumn, alias);
|
|
447
|
+
};
|
|
448
|
+
if (Array.isArray(path)) {
|
|
449
|
+
path.forEach(applyJoin);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
applyJoin(path);
|
|
453
|
+
}
|
|
454
|
+
return this;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function deriveNamespace(config) {
|
|
458
|
+
if (isClientConfig(config)) {
|
|
459
|
+
return 'client';
|
|
460
|
+
}
|
|
461
|
+
const host = 'host' in config ? config.host : 'unknown-host';
|
|
462
|
+
const database = 'database' in config ? config.database : 'default';
|
|
463
|
+
const username = 'username' in config ? config.username : 'default';
|
|
464
|
+
return `${host || 'unknown-host'}|${database || 'default'}|${username || 'default'}`;
|
|
465
|
+
}
|
|
466
|
+
export function createQueryBuilder(config) {
|
|
467
|
+
const { cache: cacheConfig, ...connectionConfig } = config;
|
|
468
|
+
ClickHouseConnection.initialize(connectionConfig);
|
|
469
|
+
const namespace = cacheConfig?.namespace || deriveNamespace(connectionConfig);
|
|
470
|
+
const { runtime, cacheController } = initializeCacheRuntime(cacheConfig, namespace);
|
|
471
|
+
return {
|
|
472
|
+
cache: cacheController,
|
|
473
|
+
async rawQuery(sql, params = []) {
|
|
474
|
+
const client = ClickHouseConnection.getClient();
|
|
475
|
+
const finalSQL = substituteParameters(sql, params);
|
|
476
|
+
const result = await client.query({
|
|
477
|
+
query: finalSQL,
|
|
478
|
+
format: 'JSONEachRow'
|
|
479
|
+
});
|
|
480
|
+
return result.json();
|
|
481
|
+
},
|
|
482
|
+
table(tableName) {
|
|
483
|
+
const state = {
|
|
484
|
+
schema: {},
|
|
485
|
+
tables: tableName,
|
|
486
|
+
output: {},
|
|
487
|
+
baseTable: tableName,
|
|
488
|
+
base: {},
|
|
489
|
+
aliases: {},
|
|
490
|
+
scalars: {}
|
|
491
|
+
};
|
|
492
|
+
return new QueryBuilder(tableName, state, runtime);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/tests/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './test-utils.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare const TEST_CONNECTION_CONFIG: {
|
|
2
|
+
host: string;
|
|
3
|
+
user: string;
|
|
4
|
+
password: string;
|
|
5
|
+
database: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const initializeTestConnection: () => Promise<{
|
|
8
|
+
cache: import("../../cache/controller.js").CacheController;
|
|
9
|
+
rawQuery<TResult = any>(sql: string, params?: unknown[]): Promise<TResult[][]>;
|
|
10
|
+
table<TableName extends never>(tableName: TableName): import("../../query-builder.js").SelectQB<import("../../types/builder-state.js").SchemaDefinition<unknown>, TableName, import("../../../index.js").TableRecord<import("../../types/builder-state.js").SchemaDefinition<unknown>[TableName]>, TableName>;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const ensureConnectionInitialized: () => import("@clickhouse/client").ClickHouseClient | import("@clickhouse/client-web").ClickHouseClient;
|
|
13
|
+
export declare const isDockerAvailable: () => Promise<boolean>;
|
|
14
|
+
export declare const isDockerComposeAvailable: () => Promise<boolean>;
|
|
15
|
+
export declare const isContainerRunning: (containerName: string) => Promise<boolean>;
|
|
16
|
+
export declare const isClickHouseReady: () => Promise<boolean>;
|
|
17
|
+
export declare const startClickHouseContainer: () => Promise<void>;
|
|
18
|
+
export declare const waitForClickHouse: (maxAttempts?: number, retryInterval?: number) => Promise<void>;
|
|
19
|
+
export declare const stopClickHouseContainer: () => Promise<void>;
|
|
20
|
+
export interface TestSchemaType {
|
|
21
|
+
test_table: Array<{
|
|
22
|
+
id: number;
|
|
23
|
+
name: string;
|
|
24
|
+
category: string;
|
|
25
|
+
price: number;
|
|
26
|
+
created_at: string;
|
|
27
|
+
is_active: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
users: Array<{
|
|
30
|
+
id: number;
|
|
31
|
+
user_name: string;
|
|
32
|
+
email: string;
|
|
33
|
+
status: string;
|
|
34
|
+
created_at: string;
|
|
35
|
+
}>;
|
|
36
|
+
orders: Array<{
|
|
37
|
+
id: number;
|
|
38
|
+
user_id: number;
|
|
39
|
+
product_id: number;
|
|
40
|
+
quantity: number;
|
|
41
|
+
total: number;
|
|
42
|
+
status: string;
|
|
43
|
+
created_at: string;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
46
|
+
export declare const TEST_DATA: TestSchemaType;
|
|
47
|
+
export declare const setupTestDatabase: () => Promise<void>;
|
|
48
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../../src/core/tests/integration/setup.ts"],"names":[],"mappings":"AAgDA,eAAO,MAAM,sBAAsB;;;;;CAAS,CAAC;AAG7C,eAAO,MAAM,wBAAwB;;;;EAyBpC,CAAC;AAGF,eAAO,MAAM,2BAA2B,yGAevC,CAAC;AAGF,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,OAAO,CAOzD,CAAC;AAGF,eAAO,MAAM,wBAAwB,QAAa,OAAO,CAAC,OAAO,CAahE,CAAC;AAGF,eAAO,MAAM,kBAAkB,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,OAAO,CAO/E,CAAC;AAGF,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,OAAO,CAQzD,CAAC;AAGF,eAAO,MAAM,wBAAwB,QAAa,OAAO,CAAC,IAAI,CAwC7D,CAAC;AAGF,eAAO,MAAM,iBAAiB,GAC5B,oBAAgB,EAChB,sBAAoB,KACnB,OAAO,CAAC,IAAI,CAad,CAAC;AAGF,eAAO,MAAM,uBAAuB,QAAa,OAAO,CAAC,IAAI,CA0B5D,CAAC;AAGF,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,KAAK,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH,MAAM,EAAE,KAAK,CAAC;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAkDD,eAAO,MAAM,SAAS,EAAE,cAAoC,CAAC;AAK7D,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,IAAI,CAkGtD,CAAC"}
|