@hypequery/clickhouse 2.0.0 → 2.0.2
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/dist/core/features/aggregations.d.ts +27 -1
- package/dist/core/features/aggregations.d.ts.map +1 -1
- package/dist/core/features/aggregations.js +43 -3
- package/dist/core/query-builder.d.ts +1 -0
- package/dist/core/query-builder.d.ts.map +1 -1
- package/dist/core/query-builder.js +3 -0
- package/dist/core/tests/integration/setup.d.ts +2 -10
- package/dist/core/tests/integration/setup.d.ts.map +1 -1
- package/dist/core/tests/integration/setup.js +30 -251
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +2 -1
- package/dist/datasets.d.ts +41 -0
- package/dist/datasets.d.ts.map +1 -0
- package/dist/datasets.js +387 -0
- package/dist/migrations/config/index.d.ts +3 -0
- package/dist/migrations/config/index.d.ts.map +1 -0
- package/dist/migrations/config/index.js +1 -0
- package/dist/migrations/config/types.d.ts +45 -0
- package/dist/migrations/config/types.d.ts.map +1 -0
- package/dist/migrations/config/types.js +28 -0
- package/dist/migrations/diff/diff.d.ts +11 -0
- package/dist/migrations/diff/diff.d.ts.map +1 -0
- package/dist/migrations/diff/diff.js +240 -0
- package/dist/migrations/diff/index.d.ts +3 -0
- package/dist/migrations/diff/index.d.ts.map +1 -0
- package/dist/migrations/diff/index.js +1 -0
- package/dist/migrations/diff/types.d.ts +74 -0
- package/dist/migrations/diff/types.d.ts.map +1 -0
- package/dist/migrations/diff/types.js +1 -0
- package/dist/migrations/introspect/index.d.ts +3 -0
- package/dist/migrations/introspect/index.d.ts.map +1 -0
- package/dist/migrations/introspect/index.js +1 -0
- package/dist/migrations/introspect/pull-schema.d.ts +23 -0
- package/dist/migrations/introspect/pull-schema.d.ts.map +1 -0
- package/dist/migrations/introspect/pull-schema.js +135 -0
- package/dist/migrations/plan/index.d.ts +3 -0
- package/dist/migrations/plan/index.d.ts.map +1 -0
- package/dist/migrations/plan/index.js +1 -0
- package/dist/migrations/plan/plan.d.ts +12 -0
- package/dist/migrations/plan/plan.d.ts.map +1 -0
- package/dist/migrations/plan/plan.js +416 -0
- package/dist/migrations/plan/types.d.ts +93 -0
- package/dist/migrations/plan/types.d.ts.map +1 -0
- package/dist/migrations/plan/types.js +1 -0
- package/dist/migrations/schema/column.d.ts +71 -0
- package/dist/migrations/schema/column.d.ts.map +1 -0
- package/dist/migrations/schema/column.js +123 -0
- package/dist/migrations/schema/define.d.ts +24 -0
- package/dist/migrations/schema/define.d.ts.map +1 -0
- package/dist/migrations/schema/define.js +47 -0
- package/dist/migrations/schema/index.d.ts +4 -0
- package/dist/migrations/schema/index.d.ts.map +1 -0
- package/dist/migrations/schema/index.js +2 -0
- package/dist/migrations/schema/types.d.ts +74 -0
- package/dist/migrations/schema/types.d.ts.map +1 -0
- package/dist/migrations/schema/types.js +1 -0
- package/dist/migrations/snapshot/index.d.ts +3 -0
- package/dist/migrations/snapshot/index.d.ts.map +1 -0
- package/dist/migrations/snapshot/index.js +1 -0
- package/dist/migrations/snapshot/serialize.d.ts +21 -0
- package/dist/migrations/snapshot/serialize.d.ts.map +1 -0
- package/dist/migrations/snapshot/serialize.js +127 -0
- package/dist/migrations/snapshot/types.d.ts +47 -0
- package/dist/migrations/snapshot/types.d.ts.map +1 -0
- package/dist/migrations/snapshot/types.js +1 -0
- package/dist/migrations/sql/index.d.ts +4 -0
- package/dist/migrations/sql/index.d.ts.map +1 -0
- package/dist/migrations/sql/index.js +2 -0
- package/dist/migrations/sql/render.d.ts +10 -0
- package/dist/migrations/sql/render.d.ts.map +1 -0
- package/dist/migrations/sql/render.js +347 -0
- package/dist/migrations/sql/types.d.ts +53 -0
- package/dist/migrations/sql/types.d.ts.map +1 -0
- package/dist/migrations/sql/types.js +1 -0
- package/dist/migrations/sql/write.d.ts +10 -0
- package/dist/migrations/sql/write.d.ts.map +1 -0
- package/dist/migrations/sql/write.js +35 -0
- package/dist/semantic-backend.d.ts +7 -0
- package/dist/semantic-backend.d.ts.map +1 -0
- package/dist/semantic-backend.js +208 -0
- package/dist/types/base.d.ts +1 -0
- package/dist/types/base.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/datasets.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickHouse Semantic Backend
|
|
3
|
+
*
|
|
4
|
+
* This module implements the SemanticBackend interface from @hypequery/datasets
|
|
5
|
+
* for ClickHouse databases. It translates database-agnostic semantic plans
|
|
6
|
+
* (PlanNode) into ClickHouse-specific SQL and executes them.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - @hypequery/datasets: Owns semantic planning, validation, and execution protocol
|
|
10
|
+
* - @hypequery/clickhouse: Implements SQL translation and execution for ClickHouse
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createDatasetClient } from '@hypequery/datasets';
|
|
15
|
+
* import { createBackend } from '@hypequery/clickhouse';
|
|
16
|
+
*
|
|
17
|
+
* const analytics = createDatasetClient({
|
|
18
|
+
* backend: createBackend({
|
|
19
|
+
* url: process.env.CLICKHOUSE_URL,
|
|
20
|
+
* username: process.env.CLICKHOUSE_USER,
|
|
21
|
+
* password: process.env.CLICKHOUSE_PASSWORD,
|
|
22
|
+
* database: process.env.CLICKHOUSE_DATABASE,
|
|
23
|
+
* })
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import { createQueryBuilder } from './core/query-builder.js';
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// ClickHouse SQL Generation Utilities
|
|
30
|
+
// =============================================================================
|
|
31
|
+
const GRAIN_FUNCTIONS = {
|
|
32
|
+
day: 'toStartOfDay',
|
|
33
|
+
week: 'toStartOfWeek',
|
|
34
|
+
month: 'toStartOfMonth',
|
|
35
|
+
quarter: 'toStartOfQuarter',
|
|
36
|
+
year: 'toStartOfYear',
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* SQL Literal Rendering
|
|
40
|
+
* Safely escapes values for direct SQL inclusion
|
|
41
|
+
*/
|
|
42
|
+
function renderLiteral(value) {
|
|
43
|
+
if (value === null)
|
|
44
|
+
return 'NULL';
|
|
45
|
+
if (typeof value === 'number')
|
|
46
|
+
return String(value);
|
|
47
|
+
if (typeof value === 'boolean')
|
|
48
|
+
return value ? '1' : '0';
|
|
49
|
+
// SQL string escaping: single quotes are escaped by doubling them
|
|
50
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Time Grain Rendering
|
|
54
|
+
* Converts semantic grain to ClickHouse function
|
|
55
|
+
*/
|
|
56
|
+
function renderGrain(field, unit) {
|
|
57
|
+
return `${GRAIN_FUNCTIONS[unit]}(${field})`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Filter Rendering - Applies filters using query builder WHERE clauses
|
|
61
|
+
* This is the preferred method when working with the query builder
|
|
62
|
+
*/
|
|
63
|
+
function applyFilters(builder, filters) {
|
|
64
|
+
let qb = builder;
|
|
65
|
+
for (const filter of filters) {
|
|
66
|
+
qb = qb.where(filter.field, filter.operator, filter.value);
|
|
67
|
+
}
|
|
68
|
+
return qb;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Type guard to check if value is a valid literal for SQL rendering
|
|
72
|
+
*/
|
|
73
|
+
function isLiteralValue(value) {
|
|
74
|
+
return (typeof value === 'string' ||
|
|
75
|
+
typeof value === 'number' ||
|
|
76
|
+
typeof value === 'boolean' ||
|
|
77
|
+
value === null);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Safely render a filter value as a SQL literal
|
|
81
|
+
* Throws if value is not a valid literal type
|
|
82
|
+
*/
|
|
83
|
+
function renderFilterValue(value) {
|
|
84
|
+
if (!isLiteralValue(value)) {
|
|
85
|
+
throw new Error(`Invalid filter value type: ${typeof value}`);
|
|
86
|
+
}
|
|
87
|
+
return renderLiteral(value);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Filter Condition Rendering - Converts filter to SQL WHERE clause string
|
|
91
|
+
* Used for filtered aggregations (IF conditions) where query builder can't be used
|
|
92
|
+
*/
|
|
93
|
+
function renderFilterCondition(filter) {
|
|
94
|
+
const { field, operator, value } = filter;
|
|
95
|
+
switch (operator) {
|
|
96
|
+
case 'eq':
|
|
97
|
+
return `${field} = ${renderFilterValue(value)}`;
|
|
98
|
+
case 'neq':
|
|
99
|
+
return `${field} != ${renderFilterValue(value)}`;
|
|
100
|
+
case 'gt':
|
|
101
|
+
return `${field} > ${renderFilterValue(value)}`;
|
|
102
|
+
case 'gte':
|
|
103
|
+
return `${field} >= ${renderFilterValue(value)}`;
|
|
104
|
+
case 'lt':
|
|
105
|
+
return `${field} < ${renderFilterValue(value)}`;
|
|
106
|
+
case 'lte':
|
|
107
|
+
return `${field} <= ${renderFilterValue(value)}`;
|
|
108
|
+
case 'like':
|
|
109
|
+
return `${field} LIKE ${renderFilterValue(value)}`;
|
|
110
|
+
case 'in':
|
|
111
|
+
case 'notIn': {
|
|
112
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
113
|
+
throw new Error(`"${operator}" filters require a non-empty array.`);
|
|
114
|
+
}
|
|
115
|
+
const values = value.map(renderFilterValue).join(', ');
|
|
116
|
+
const op = operator === 'in' ? 'IN' : 'NOT IN';
|
|
117
|
+
return `${field} ${op} (${values})`;
|
|
118
|
+
}
|
|
119
|
+
case 'between': {
|
|
120
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
121
|
+
throw new Error('"between" filters require a two-item array.');
|
|
122
|
+
}
|
|
123
|
+
return `${field} BETWEEN ${renderFilterValue(value[0])} AND ${renderFilterValue(value[1])}`;
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
throw new Error(`Unsupported filter operator "${operator}".`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Filtered Aggregation Field Rendering
|
|
131
|
+
* Generates ClickHouse IF() expressions for conditional aggregations
|
|
132
|
+
* Example: SUM(if(status = 'completed', amount, 0))
|
|
133
|
+
*/
|
|
134
|
+
function renderFilteredAggregationField(aggregation) {
|
|
135
|
+
if (!aggregation.filters?.length) {
|
|
136
|
+
return aggregation.field;
|
|
137
|
+
}
|
|
138
|
+
// Combine multiple filters with AND
|
|
139
|
+
const condition = aggregation.filters
|
|
140
|
+
.map(renderFilterCondition)
|
|
141
|
+
.map((part) => `(${part})`)
|
|
142
|
+
.join(' AND ');
|
|
143
|
+
// Use appropriate fallback: 0 for SUM, NULL for others
|
|
144
|
+
const fallback = aggregation.aggregation === 'sum' ? '0' : 'NULL';
|
|
145
|
+
return `if(${condition}, ${aggregation.field}, ${fallback})`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Expression Rendering
|
|
149
|
+
* Converts semantic expressions (formulas) to SQL
|
|
150
|
+
* Used for derived metrics
|
|
151
|
+
*/
|
|
152
|
+
function renderExpression(expression) {
|
|
153
|
+
switch (expression.kind) {
|
|
154
|
+
case 'ref':
|
|
155
|
+
return expression.name;
|
|
156
|
+
case 'literal':
|
|
157
|
+
return renderLiteral(expression.value);
|
|
158
|
+
case 'binary': {
|
|
159
|
+
const operators = {
|
|
160
|
+
add: '+',
|
|
161
|
+
subtract: '-',
|
|
162
|
+
multiply: '*',
|
|
163
|
+
divide: '/',
|
|
164
|
+
};
|
|
165
|
+
const op = operators[expression.operator];
|
|
166
|
+
const left = renderExpression(expression.left);
|
|
167
|
+
const right = renderExpression(expression.right);
|
|
168
|
+
return `(${left}) ${op} (${right})`;
|
|
169
|
+
}
|
|
170
|
+
case 'function': {
|
|
171
|
+
const args = expression.args.map(renderExpression);
|
|
172
|
+
// Special case functions with custom SQL
|
|
173
|
+
if (expression.name === 'nullIfZero') {
|
|
174
|
+
return `NULLIF(${args[0]}, 0)`;
|
|
175
|
+
}
|
|
176
|
+
if (expression.name === 'coalesce') {
|
|
177
|
+
return `COALESCE(${args.join(', ')})`;
|
|
178
|
+
}
|
|
179
|
+
// Standard functions
|
|
180
|
+
const functionMap = {
|
|
181
|
+
round: 'ROUND',
|
|
182
|
+
floor: 'FLOOR',
|
|
183
|
+
ceil: 'CEIL',
|
|
184
|
+
};
|
|
185
|
+
const fn = functionMap[expression.name];
|
|
186
|
+
if (!fn) {
|
|
187
|
+
throw new Error(`Unsupported function: ${expression.name}`);
|
|
188
|
+
}
|
|
189
|
+
return `${fn}(${args.join(', ')})`;
|
|
190
|
+
}
|
|
191
|
+
default:
|
|
192
|
+
throw new Error('Unsupported semantic expression.');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// Query Builder Integration
|
|
197
|
+
// =============================================================================
|
|
198
|
+
/**
|
|
199
|
+
* Apply Aggregations
|
|
200
|
+
* Translates semantic aggregations to query builder method calls
|
|
201
|
+
*/
|
|
202
|
+
function applyAggregations(builder, plan) {
|
|
203
|
+
let qb = builder;
|
|
204
|
+
for (const aggregation of plan.aggregations) {
|
|
205
|
+
const field = renderFilteredAggregationField(aggregation);
|
|
206
|
+
const { name, aggregation: aggType } = aggregation;
|
|
207
|
+
switch (aggType) {
|
|
208
|
+
case 'sum':
|
|
209
|
+
qb = qb.sum(field, name);
|
|
210
|
+
break;
|
|
211
|
+
case 'count':
|
|
212
|
+
qb = qb.count(field, name);
|
|
213
|
+
break;
|
|
214
|
+
case 'countDistinct':
|
|
215
|
+
qb = qb.countDistinct(field, name);
|
|
216
|
+
break;
|
|
217
|
+
case 'avg':
|
|
218
|
+
qb = qb.avg(field, name);
|
|
219
|
+
break;
|
|
220
|
+
case 'min':
|
|
221
|
+
qb = qb.min(field, name);
|
|
222
|
+
break;
|
|
223
|
+
case 'max':
|
|
224
|
+
qb = qb.max(field, name);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return qb;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Append Order/Limit/Offset
|
|
232
|
+
* Applies result modifiers to query builder
|
|
233
|
+
*/
|
|
234
|
+
function appendOrderLimitOffset(builder, plan) {
|
|
235
|
+
let qb = builder;
|
|
236
|
+
// Order by
|
|
237
|
+
for (const order of plan.orderBy ?? []) {
|
|
238
|
+
qb = qb.orderBy(order.field, order.direction.toUpperCase());
|
|
239
|
+
}
|
|
240
|
+
// Pagination
|
|
241
|
+
if (plan.limit != null)
|
|
242
|
+
qb = qb.limit(plan.limit);
|
|
243
|
+
if (plan.offset != null)
|
|
244
|
+
qb = qb.offset(plan.offset);
|
|
245
|
+
return qb;
|
|
246
|
+
}
|
|
247
|
+
// =============================================================================
|
|
248
|
+
// Plan Translation to SQL
|
|
249
|
+
// =============================================================================
|
|
250
|
+
/**
|
|
251
|
+
* Build Aggregate Query
|
|
252
|
+
* Translates semantic aggregate plan to ClickHouse query builder
|
|
253
|
+
*/
|
|
254
|
+
function buildAggregateQuery(queryBuilder, plan) {
|
|
255
|
+
let qb = queryBuilder.table(plan.source);
|
|
256
|
+
// Build SELECT and GROUP BY for dimensions
|
|
257
|
+
const selectParts = [];
|
|
258
|
+
const groupByParts = [];
|
|
259
|
+
// Time grain (period column)
|
|
260
|
+
if (plan.grain) {
|
|
261
|
+
const grainSql = renderGrain(plan.grain.field, plan.grain.unit);
|
|
262
|
+
selectParts.push(`${grainSql} AS ${plan.grain.output}`);
|
|
263
|
+
groupByParts.push(plan.grain.output);
|
|
264
|
+
}
|
|
265
|
+
// Dimensions
|
|
266
|
+
for (const dimension of plan.dimensions) {
|
|
267
|
+
const columnSql = dimension.field === dimension.name
|
|
268
|
+
? dimension.name
|
|
269
|
+
: `${dimension.field} AS ${dimension.name}`;
|
|
270
|
+
selectParts.push(columnSql);
|
|
271
|
+
groupByParts.push(dimension.name);
|
|
272
|
+
}
|
|
273
|
+
// Apply SELECT clause
|
|
274
|
+
if (selectParts.length > 0) {
|
|
275
|
+
qb = qb.select(selectParts);
|
|
276
|
+
}
|
|
277
|
+
// Apply aggregations (measures)
|
|
278
|
+
qb = applyAggregations(qb, plan);
|
|
279
|
+
// Apply GROUP BY
|
|
280
|
+
if (groupByParts.length > 0) {
|
|
281
|
+
qb = qb.groupBy(groupByParts);
|
|
282
|
+
}
|
|
283
|
+
// Apply tenant filter (auto-injected)
|
|
284
|
+
if (plan.tenant) {
|
|
285
|
+
qb = qb.where(plan.tenant.field, 'eq', plan.tenant.value);
|
|
286
|
+
}
|
|
287
|
+
// Apply user filters
|
|
288
|
+
qb = applyFilters(qb, plan.filters);
|
|
289
|
+
// Apply order/limit/offset
|
|
290
|
+
return appendOrderLimitOffset(qb, plan);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Build Derived Metric SQL
|
|
294
|
+
* Generates CTE-based query for derived metrics (formulas over base metrics)
|
|
295
|
+
*
|
|
296
|
+
* Note: Uses string concatenation for outer query since query builder
|
|
297
|
+
* doesn't support CTEs natively. Inner query uses query builder for safety.
|
|
298
|
+
*/
|
|
299
|
+
function buildDerivedSQL(queryBuilder, plan) {
|
|
300
|
+
if (plan.input.kind !== 'aggregate') {
|
|
301
|
+
throw new Error('ClickHouse datasets currently supports derived metrics over aggregate input plans only.');
|
|
302
|
+
}
|
|
303
|
+
// Build inner aggregate query using query builder
|
|
304
|
+
const inputQuery = buildAggregateQuery(queryBuilder, plan.input);
|
|
305
|
+
const { sql, parameters } = inputQuery.toSQLWithParams();
|
|
306
|
+
// Passthrough columns (grain + dimensions)
|
|
307
|
+
const passthrough = [
|
|
308
|
+
...(plan.input.grain ? [plan.input.grain.output] : []),
|
|
309
|
+
...plan.input.dimensions.map((dim) => dim.name),
|
|
310
|
+
];
|
|
311
|
+
// Derived metric calculations
|
|
312
|
+
const metricSelects = plan.metrics.map((metric) => `${renderExpression(metric.expression)} AS ${metric.name}`);
|
|
313
|
+
// Build outer query with CTE
|
|
314
|
+
const allSelects = [...passthrough, ...metricSelects];
|
|
315
|
+
let outerSql = `WITH base AS (${sql}) SELECT ${allSelects.join(', ')} FROM base`;
|
|
316
|
+
// ORDER BY
|
|
317
|
+
if (plan.orderBy?.length) {
|
|
318
|
+
const orderClauses = plan.orderBy.map((order) => `${order.field} ${order.direction.toUpperCase()}`);
|
|
319
|
+
outerSql += ` ORDER BY ${orderClauses.join(', ')}`;
|
|
320
|
+
}
|
|
321
|
+
// LIMIT and OFFSET
|
|
322
|
+
if (plan.limit != null) {
|
|
323
|
+
outerSql += ` LIMIT ${plan.limit}`;
|
|
324
|
+
}
|
|
325
|
+
if (plan.offset != null) {
|
|
326
|
+
outerSql += ` OFFSET ${plan.offset}`;
|
|
327
|
+
}
|
|
328
|
+
return { sql: outerSql, parameters };
|
|
329
|
+
}
|
|
330
|
+
// =============================================================================
|
|
331
|
+
// Semantic Backend Implementation
|
|
332
|
+
// =============================================================================
|
|
333
|
+
/**
|
|
334
|
+
* Create ClickHouse Semantic Backend
|
|
335
|
+
*
|
|
336
|
+
* Creates a SemanticBackend implementation that translates database-agnostic
|
|
337
|
+
* semantic plans into ClickHouse SQL and executes them.
|
|
338
|
+
*
|
|
339
|
+
* @param config - ClickHouse connection configuration
|
|
340
|
+
* @returns SemanticBackend interface for executing semantic queries
|
|
341
|
+
*/
|
|
342
|
+
export function createBackend(config) {
|
|
343
|
+
const queryBuilder = createQueryBuilder(config);
|
|
344
|
+
return {
|
|
345
|
+
/**
|
|
346
|
+
* Execute a semantic plan and return results
|
|
347
|
+
*/
|
|
348
|
+
async execute(plan) {
|
|
349
|
+
const start = Date.now();
|
|
350
|
+
if (plan.kind === 'aggregate') {
|
|
351
|
+
// Base metrics: use query builder for full safety
|
|
352
|
+
const query = buildAggregateQuery(queryBuilder, plan);
|
|
353
|
+
const { sql } = query.toSQLWithParams();
|
|
354
|
+
const data = await query.execute();
|
|
355
|
+
return {
|
|
356
|
+
data,
|
|
357
|
+
meta: {
|
|
358
|
+
sql,
|
|
359
|
+
timingMs: Date.now() - start,
|
|
360
|
+
tenant: plan.tenant?.value,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
// Derived metrics: CTE query with formulas
|
|
365
|
+
const { sql, parameters } = buildDerivedSQL(queryBuilder, plan);
|
|
366
|
+
const data = await queryBuilder.rawQuery(sql, parameters);
|
|
367
|
+
const tenant = plan.input.kind === 'aggregate' ? plan.input.tenant?.value : undefined;
|
|
368
|
+
return {
|
|
369
|
+
data,
|
|
370
|
+
meta: {
|
|
371
|
+
sql,
|
|
372
|
+
timingMs: Date.now() - start,
|
|
373
|
+
tenant,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
},
|
|
377
|
+
/**
|
|
378
|
+
* Generate SQL without executing
|
|
379
|
+
*/
|
|
380
|
+
async explain(plan) {
|
|
381
|
+
if (plan.kind === 'aggregate') {
|
|
382
|
+
return { sql: buildAggregateQuery(queryBuilder, plan).toSQLWithParams().sql };
|
|
383
|
+
}
|
|
384
|
+
return { sql: buildDerivedSQL(queryBuilder, plan).sql };
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { DEFAULT_MIGRATIONS_OUT_DIR, DEFAULT_MIGRATIONS_PREFIX, DEFAULT_MIGRATIONS_TABLE, defineConfig, resolveClickHouseConfig, } from './types.js';
|
|
2
|
+
export type { ClickHouseClusterConfig, ClickHouseMigrationDbCredentials, ClickHouseMigrationDirectoryConfig, HypequeryClickHouseConfig, MigrationFilePrefix, ResolvedHypequeryClickHouseConfig, } from './types.js';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/migrations/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,YAAY,EACZ,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,uBAAuB,EACvB,gCAAgC,EAChC,kCAAkC,EAClC,yBAAyB,EACzB,mBAAmB,EACnB,iCAAiC,GAClC,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DEFAULT_MIGRATIONS_OUT_DIR, DEFAULT_MIGRATIONS_PREFIX, DEFAULT_MIGRATIONS_TABLE, defineConfig, resolveClickHouseConfig, } from './types.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const DEFAULT_MIGRATIONS_OUT_DIR = "./migrations";
|
|
2
|
+
export declare const DEFAULT_MIGRATIONS_TABLE = "_hypequery_migrations";
|
|
3
|
+
export declare const DEFAULT_MIGRATIONS_PREFIX: "timestamp";
|
|
4
|
+
export type MigrationFilePrefix = typeof DEFAULT_MIGRATIONS_PREFIX;
|
|
5
|
+
export interface ClickHouseMigrationDirectoryConfig {
|
|
6
|
+
out: string;
|
|
7
|
+
table: string;
|
|
8
|
+
prefix: MigrationFilePrefix;
|
|
9
|
+
}
|
|
10
|
+
export interface ClickHouseMigrationDbCredentials {
|
|
11
|
+
host: string;
|
|
12
|
+
port?: number;
|
|
13
|
+
username: string;
|
|
14
|
+
password?: string;
|
|
15
|
+
database: string;
|
|
16
|
+
secure?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ClickHouseClusterConfig {
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
export interface HypequeryClickHouseConfig {
|
|
22
|
+
dialect: 'clickhouse';
|
|
23
|
+
schema: string;
|
|
24
|
+
migrations?: Partial<ClickHouseMigrationDirectoryConfig>;
|
|
25
|
+
dbCredentials: ClickHouseMigrationDbCredentials;
|
|
26
|
+
cluster?: ClickHouseClusterConfig;
|
|
27
|
+
}
|
|
28
|
+
export interface ResolvedHypequeryClickHouseConfig extends Omit<HypequeryClickHouseConfig, 'migrations'> {
|
|
29
|
+
migrations: ClickHouseMigrationDirectoryConfig;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Defines a ClickHouse migration configuration while preserving literal TypeScript types.
|
|
33
|
+
*
|
|
34
|
+
* Use this from `hypequery.config.ts` so the CLI can load database credentials,
|
|
35
|
+
* schema entry points, and migration output settings from one typed object.
|
|
36
|
+
*/
|
|
37
|
+
export declare function defineConfig(config: HypequeryClickHouseConfig): HypequeryClickHouseConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Applies default migration settings to a user-provided ClickHouse config.
|
|
40
|
+
*
|
|
41
|
+
* This keeps CLI and programmatic callers aligned on the default output directory,
|
|
42
|
+
* migration table name, and timestamp-based file prefix strategy.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveClickHouseConfig(config: HypequeryClickHouseConfig): ResolvedHypequeryClickHouseConfig;
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/migrations/config/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,iBAAiB,CAAC;AACzD,eAAO,MAAM,wBAAwB,0BAA0B,CAAC;AAChE,eAAO,MAAM,yBAAyB,EAAG,WAAoB,CAAC;AAE9D,MAAM,MAAM,mBAAmB,GAAG,OAAO,yBAAyB,CAAC;AAEnE,MAAM,WAAW,kCAAkC;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED,MAAM,WAAW,gCAAgC;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC,kCAAkC,CAAC,CAAC;IACzD,aAAa,EAAE,gCAAgC,CAAC;IAChD,OAAO,CAAC,EAAE,uBAAuB,CAAC;CACnC;AAED,MAAM,WAAW,iCACf,SAAQ,IAAI,CAAC,yBAAyB,EAAE,YAAY,CAAC;IACrD,UAAU,EAAE,kCAAkC,CAAC;CAChD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,yBAAyB,GAAG,yBAAyB,CAEzF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,yBAAyB,GAChC,iCAAiC,CASnC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const DEFAULT_MIGRATIONS_OUT_DIR = './migrations';
|
|
2
|
+
export const DEFAULT_MIGRATIONS_TABLE = '_hypequery_migrations';
|
|
3
|
+
export const DEFAULT_MIGRATIONS_PREFIX = 'timestamp';
|
|
4
|
+
/**
|
|
5
|
+
* Defines a ClickHouse migration configuration while preserving literal TypeScript types.
|
|
6
|
+
*
|
|
7
|
+
* Use this from `hypequery.config.ts` so the CLI can load database credentials,
|
|
8
|
+
* schema entry points, and migration output settings from one typed object.
|
|
9
|
+
*/
|
|
10
|
+
export function defineConfig(config) {
|
|
11
|
+
return config;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Applies default migration settings to a user-provided ClickHouse config.
|
|
15
|
+
*
|
|
16
|
+
* This keeps CLI and programmatic callers aligned on the default output directory,
|
|
17
|
+
* migration table name, and timestamp-based file prefix strategy.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveClickHouseConfig(config) {
|
|
20
|
+
return {
|
|
21
|
+
...config,
|
|
22
|
+
migrations: {
|
|
23
|
+
out: config.migrations?.out ?? DEFAULT_MIGRATIONS_OUT_DIR,
|
|
24
|
+
table: config.migrations?.table ?? DEFAULT_MIGRATIONS_TABLE,
|
|
25
|
+
prefix: config.migrations?.prefix ?? DEFAULT_MIGRATIONS_PREFIX,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snapshot } from '../snapshot/types.js';
|
|
2
|
+
import type { SnapshotDiffResult } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Computes migration operations needed to move from one snapshot to another.
|
|
5
|
+
*
|
|
6
|
+
* The diff layer is deliberately SQL-free. It emits structured operations,
|
|
7
|
+
* warnings for potentially expensive changes, and unsupported-change diagnostics
|
|
8
|
+
* that the SQL renderer refuses to render automatically.
|
|
9
|
+
*/
|
|
10
|
+
export declare function diffSnapshots(previousSnapshot: Snapshot, nextSnapshot: Snapshot): SnapshotDiffResult;
|
|
11
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/migrations/diff/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAQV,kBAAkB,EAGnB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,gBAAgB,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,GAAG,kBAAkB,CAqCpG"}
|