@tinybirdco/sdk 0.0.1
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 +518 -0
- package/bin/tinybird.js +7 -0
- package/dist/api/branches.d.ts +98 -0
- package/dist/api/branches.d.ts.map +1 -0
- package/dist/api/branches.js +203 -0
- package/dist/api/branches.js.map +1 -0
- package/dist/api/branches.test.d.ts +2 -0
- package/dist/api/branches.test.d.ts.map +1 -0
- package/dist/api/branches.test.js +286 -0
- package/dist/api/branches.test.js.map +1 -0
- package/dist/api/build.d.ts +130 -0
- package/dist/api/build.d.ts.map +1 -0
- package/dist/api/build.js +143 -0
- package/dist/api/build.js.map +1 -0
- package/dist/api/build.test.d.ts +2 -0
- package/dist/api/build.test.d.ts.map +1 -0
- package/dist/api/build.test.js +138 -0
- package/dist/api/build.test.js.map +1 -0
- package/dist/api/deploy.d.ts +39 -0
- package/dist/api/deploy.d.ts.map +1 -0
- package/dist/api/deploy.js +135 -0
- package/dist/api/deploy.js.map +1 -0
- package/dist/api/deploy.test.d.ts +2 -0
- package/dist/api/deploy.test.d.ts.map +1 -0
- package/dist/api/deploy.test.js +118 -0
- package/dist/api/deploy.test.js.map +1 -0
- package/dist/api/workspaces.d.ts +46 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +39 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/api/workspaces.test.d.ts +2 -0
- package/dist/api/workspaces.test.d.ts.map +1 -0
- package/dist/api/workspaces.test.js +65 -0
- package/dist/api/workspaces.test.js.map +1 -0
- package/dist/cli/auth.d.ts +86 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +284 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/branch-store.d.ts +53 -0
- package/dist/cli/branch-store.d.ts.map +1 -0
- package/dist/cli/branch-store.js +91 -0
- package/dist/cli/branch-store.js.map +1 -0
- package/dist/cli/branch-store.test.d.ts +2 -0
- package/dist/cli/branch-store.test.d.ts.map +1 -0
- package/dist/cli/branch-store.test.js +115 -0
- package/dist/cli/branch-store.test.js.map +1 -0
- package/dist/cli/commands/branch.d.ts +82 -0
- package/dist/cli/commands/branch.d.ts.map +1 -0
- package/dist/cli/commands/branch.js +215 -0
- package/dist/cli/commands/branch.js.map +1 -0
- package/dist/cli/commands/build.d.ts +43 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +138 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +78 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +226 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/init.d.ts +45 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +277 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/init.test.d.ts +2 -0
- package/dist/cli/commands/init.test.d.ts.map +1 -0
- package/dist/cli/commands/init.test.js +158 -0
- package/dist/cli/commands/init.test.js.map +1 -0
- package/dist/cli/commands/login.d.ts +37 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +64 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/config.d.ts +114 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +258 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/config.test.d.ts +2 -0
- package/dist/cli/config.test.d.ts.map +1 -0
- package/dist/cli/config.test.js +243 -0
- package/dist/cli/config.test.js.map +1 -0
- package/dist/cli/env.d.ts +29 -0
- package/dist/cli/env.d.ts.map +1 -0
- package/dist/cli/env.js +66 -0
- package/dist/cli/env.js.map +1 -0
- package/dist/cli/git.d.ts +29 -0
- package/dist/cli/git.d.ts.map +1 -0
- package/dist/cli/git.js +114 -0
- package/dist/cli/git.js.map +1 -0
- package/dist/cli/git.test.d.ts +2 -0
- package/dist/cli/git.test.d.ts.map +1 -0
- package/dist/cli/git.test.js +125 -0
- package/dist/cli/git.test.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +337 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/schema-validation.d.ts +95 -0
- package/dist/cli/utils/schema-validation.d.ts.map +1 -0
- package/dist/cli/utils/schema-validation.js +175 -0
- package/dist/cli/utils/schema-validation.js.map +1 -0
- package/dist/cli/utils/schema-validation.test.d.ts +5 -0
- package/dist/cli/utils/schema-validation.test.d.ts.map +1 -0
- package/dist/cli/utils/schema-validation.test.js +173 -0
- package/dist/cli/utils/schema-validation.test.js.map +1 -0
- package/dist/client/base.d.ts +116 -0
- package/dist/client/base.d.ts.map +1 -0
- package/dist/client/base.js +328 -0
- package/dist/client/base.js.map +1 -0
- package/dist/client/types.d.ts +137 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +43 -0
- package/dist/client/types.js.map +1 -0
- package/dist/generator/client.d.ts +44 -0
- package/dist/generator/client.d.ts.map +1 -0
- package/dist/generator/client.js +144 -0
- package/dist/generator/client.js.map +1 -0
- package/dist/generator/datasource.d.ts +57 -0
- package/dist/generator/datasource.d.ts.map +1 -0
- package/dist/generator/datasource.js +169 -0
- package/dist/generator/datasource.js.map +1 -0
- package/dist/generator/datasource.test.d.ts +2 -0
- package/dist/generator/datasource.test.d.ts.map +1 -0
- package/dist/generator/datasource.test.js +254 -0
- package/dist/generator/datasource.test.js.map +1 -0
- package/dist/generator/index.d.ts +131 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +121 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/index.test.d.ts +2 -0
- package/dist/generator/index.test.d.ts.map +1 -0
- package/dist/generator/index.test.js +175 -0
- package/dist/generator/index.test.js.map +1 -0
- package/dist/generator/loader.d.ts +156 -0
- package/dist/generator/loader.d.ts.map +1 -0
- package/dist/generator/loader.js +295 -0
- package/dist/generator/loader.js.map +1 -0
- package/dist/generator/pipe.d.ts +72 -0
- package/dist/generator/pipe.d.ts.map +1 -0
- package/dist/generator/pipe.js +174 -0
- package/dist/generator/pipe.js.map +1 -0
- package/dist/generator/pipe.test.d.ts +2 -0
- package/dist/generator/pipe.test.d.ts.map +1 -0
- package/dist/generator/pipe.test.js +393 -0
- package/dist/generator/pipe.test.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/infer/index.d.ts +202 -0
- package/dist/infer/index.d.ts.map +1 -0
- package/dist/infer/index.js +5 -0
- package/dist/infer/index.js.map +1 -0
- package/dist/schema/datasource.d.ts +135 -0
- package/dist/schema/datasource.d.ts.map +1 -0
- package/dist/schema/datasource.js +105 -0
- package/dist/schema/datasource.js.map +1 -0
- package/dist/schema/datasource.test.d.ts +2 -0
- package/dist/schema/datasource.test.d.ts.map +1 -0
- package/dist/schema/datasource.test.js +142 -0
- package/dist/schema/datasource.test.js.map +1 -0
- package/dist/schema/engines.d.ts +157 -0
- package/dist/schema/engines.d.ts.map +1 -0
- package/dist/schema/engines.js +155 -0
- package/dist/schema/engines.js.map +1 -0
- package/dist/schema/engines.test.d.ts +2 -0
- package/dist/schema/engines.test.d.ts.map +1 -0
- package/dist/schema/engines.test.js +221 -0
- package/dist/schema/engines.test.js.map +1 -0
- package/dist/schema/params.d.ts +106 -0
- package/dist/schema/params.d.ts.map +1 -0
- package/dist/schema/params.js +138 -0
- package/dist/schema/params.js.map +1 -0
- package/dist/schema/params.test.d.ts +2 -0
- package/dist/schema/params.test.d.ts.map +1 -0
- package/dist/schema/params.test.js +175 -0
- package/dist/schema/params.test.js.map +1 -0
- package/dist/schema/pipe.d.ts +436 -0
- package/dist/schema/pipe.d.ts.map +1 -0
- package/dist/schema/pipe.js +484 -0
- package/dist/schema/pipe.js.map +1 -0
- package/dist/schema/pipe.test.d.ts +2 -0
- package/dist/schema/pipe.test.d.ts.map +1 -0
- package/dist/schema/pipe.test.js +488 -0
- package/dist/schema/pipe.test.js.map +1 -0
- package/dist/schema/project.d.ts +202 -0
- package/dist/schema/project.d.ts.map +1 -0
- package/dist/schema/project.js +188 -0
- package/dist/schema/project.js.map +1 -0
- package/dist/schema/project.test.d.ts +2 -0
- package/dist/schema/project.test.d.ts.map +1 -0
- package/dist/schema/project.test.js +180 -0
- package/dist/schema/project.test.js.map +1 -0
- package/dist/schema/types.d.ts +140 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +174 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema/types.test.d.ts +2 -0
- package/dist/schema/types.test.d.ts.map +1 -0
- package/dist/schema/types.test.js +176 -0
- package/dist/schema/types.test.js.map +1 -0
- package/dist/test/handlers.d.ts +58 -0
- package/dist/test/handlers.d.ts.map +1 -0
- package/dist/test/handlers.js +62 -0
- package/dist/test/handlers.js.map +1 -0
- package/dist/test/setup.d.ts +5 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +11 -0
- package/dist/test/setup.js.map +1 -0
- package/package.json +57 -0
- package/src/api/branches.test.ts +377 -0
- package/src/api/branches.ts +334 -0
- package/src/api/build.test.ts +216 -0
- package/src/api/build.ts +266 -0
- package/src/api/deploy.test.ts +193 -0
- package/src/api/deploy.ts +163 -0
- package/src/api/workspaces.test.ts +81 -0
- package/src/api/workspaces.ts +77 -0
- package/src/cli/auth.ts +358 -0
- package/src/cli/branch-store.test.ts +139 -0
- package/src/cli/branch-store.ts +137 -0
- package/src/cli/commands/branch.ts +306 -0
- package/src/cli/commands/build.ts +183 -0
- package/src/cli/commands/dev.ts +334 -0
- package/src/cli/commands/init.test.ts +249 -0
- package/src/cli/commands/init.ts +323 -0
- package/src/cli/commands/login.ts +98 -0
- package/src/cli/config.test.ts +359 -0
- package/src/cli/config.ts +335 -0
- package/src/cli/env.ts +86 -0
- package/src/cli/git.test.ts +147 -0
- package/src/cli/git.ts +125 -0
- package/src/cli/index.ts +382 -0
- package/src/cli/utils/schema-validation.test.ts +222 -0
- package/src/cli/utils/schema-validation.ts +272 -0
- package/src/client/base.ts +414 -0
- package/src/client/types.ts +165 -0
- package/src/generator/client.ts +194 -0
- package/src/generator/datasource.test.ts +297 -0
- package/src/generator/datasource.ts +217 -0
- package/src/generator/index.test.ts +209 -0
- package/src/generator/index.ts +203 -0
- package/src/generator/loader.ts +406 -0
- package/src/generator/pipe.test.ts +441 -0
- package/src/generator/pipe.ts +220 -0
- package/src/index.ts +191 -0
- package/src/infer/index.ts +247 -0
- package/src/schema/datasource.test.ts +187 -0
- package/src/schema/datasource.ts +195 -0
- package/src/schema/engines.test.ts +247 -0
- package/src/schema/engines.ts +271 -0
- package/src/schema/params.test.ts +208 -0
- package/src/schema/params.ts +249 -0
- package/src/schema/pipe.test.ts +588 -0
- package/src/schema/pipe.ts +832 -0
- package/src/schema/project.test.ts +236 -0
- package/src/schema/project.ts +394 -0
- package/src/schema/types.test.ts +212 -0
- package/src/schema/types.ts +366 -0
- package/src/test/handlers.ts +79 -0
- package/src/test/setup.ts +13 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource definition for Tinybird
|
|
3
|
+
* Define table schemas as TypeScript with full type safety
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AnyTypeValidator } from "./types.js";
|
|
7
|
+
import type { EngineConfig } from "./engines.js";
|
|
8
|
+
|
|
9
|
+
// Symbol for brand typing
|
|
10
|
+
const DATASOURCE_BRAND = Symbol("tinybird.datasource");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A column can be defined as just a type validator,
|
|
14
|
+
* or with additional options like JSON path or default value
|
|
15
|
+
*/
|
|
16
|
+
export interface ColumnDefinition<T extends AnyTypeValidator = AnyTypeValidator> {
|
|
17
|
+
/** The column type */
|
|
18
|
+
type: T;
|
|
19
|
+
/** JSON path for extracting from nested JSON (e.g., '$.user.id') */
|
|
20
|
+
jsonPath?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Schema definition is a record of column names to type validators or column definitions
|
|
25
|
+
*/
|
|
26
|
+
export type SchemaDefinition = Record<string, AnyTypeValidator | ColumnDefinition>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Token configuration for datasource access
|
|
30
|
+
*/
|
|
31
|
+
export interface TokenConfig {
|
|
32
|
+
/** Token name */
|
|
33
|
+
name: string;
|
|
34
|
+
/** Permissions granted to this token */
|
|
35
|
+
permissions: readonly ("READ" | "APPEND")[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Options for defining a datasource
|
|
40
|
+
*/
|
|
41
|
+
export interface DatasourceOptions<TSchema extends SchemaDefinition> {
|
|
42
|
+
/** Human-readable description of the datasource */
|
|
43
|
+
description?: string;
|
|
44
|
+
/** Column schema definition */
|
|
45
|
+
schema: TSchema;
|
|
46
|
+
/** Table engine configuration */
|
|
47
|
+
engine?: EngineConfig;
|
|
48
|
+
/** Access tokens for this datasource */
|
|
49
|
+
tokens?: readonly TokenConfig[];
|
|
50
|
+
/** Workspaces to share this datasource with */
|
|
51
|
+
sharedWith?: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* Whether to generate JSON path expressions for columns.
|
|
54
|
+
* Set to false for datasources that are targets of materialized views.
|
|
55
|
+
* Defaults to true.
|
|
56
|
+
*/
|
|
57
|
+
jsonPaths?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A datasource definition with full type information
|
|
62
|
+
*/
|
|
63
|
+
export interface DatasourceDefinition<TSchema extends SchemaDefinition = SchemaDefinition> {
|
|
64
|
+
readonly [DATASOURCE_BRAND]: true;
|
|
65
|
+
/** Datasource name */
|
|
66
|
+
readonly _name: string;
|
|
67
|
+
/** Type marker for inference */
|
|
68
|
+
readonly _type: "datasource";
|
|
69
|
+
/** Schema definition */
|
|
70
|
+
readonly _schema: TSchema;
|
|
71
|
+
/** Full options */
|
|
72
|
+
readonly options: DatasourceOptions<TSchema>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Define a Tinybird datasource
|
|
77
|
+
*
|
|
78
|
+
* @param name - The datasource name (must be valid identifier)
|
|
79
|
+
* @param options - Datasource configuration including schema and engine
|
|
80
|
+
* @returns A datasource definition that can be used in a project
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* import { defineDatasource, t, engine } from '@tinybirdco/sdk';
|
|
85
|
+
*
|
|
86
|
+
* export const events = defineDatasource('events', {
|
|
87
|
+
* description: 'User event tracking data',
|
|
88
|
+
* schema: {
|
|
89
|
+
* timestamp: t.dateTime(),
|
|
90
|
+
* event_id: t.uuid(),
|
|
91
|
+
* user_id: t.string(),
|
|
92
|
+
* event_type: t.string().lowCardinality(),
|
|
93
|
+
* properties: t.json(),
|
|
94
|
+
* session_id: t.string().nullable(),
|
|
95
|
+
* },
|
|
96
|
+
* engine: engine.mergeTree({
|
|
97
|
+
* sortingKey: ['user_id', 'timestamp'],
|
|
98
|
+
* partitionKey: 'toYYYYMM(timestamp)',
|
|
99
|
+
* ttl: 'timestamp + INTERVAL 90 DAY',
|
|
100
|
+
* }),
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function defineDatasource<TSchema extends SchemaDefinition>(
|
|
105
|
+
name: string,
|
|
106
|
+
options: DatasourceOptions<TSchema>
|
|
107
|
+
): DatasourceDefinition<TSchema> {
|
|
108
|
+
// Validate name is a valid identifier
|
|
109
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Invalid datasource name: "${name}". Must start with a letter or underscore and contain only alphanumeric characters and underscores.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
[DATASOURCE_BRAND]: true,
|
|
117
|
+
_name: name,
|
|
118
|
+
_type: "datasource",
|
|
119
|
+
_schema: options.schema,
|
|
120
|
+
options,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if a value is a datasource definition
|
|
126
|
+
*/
|
|
127
|
+
export function isDatasourceDefinition(value: unknown): value is DatasourceDefinition {
|
|
128
|
+
return (
|
|
129
|
+
typeof value === "object" &&
|
|
130
|
+
value !== null &&
|
|
131
|
+
DATASOURCE_BRAND in value &&
|
|
132
|
+
(value as Record<symbol, unknown>)[DATASOURCE_BRAND] === true
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get the column type for a schema entry (handles both raw validators and column definitions)
|
|
138
|
+
*/
|
|
139
|
+
export function getColumnType(column: AnyTypeValidator | ColumnDefinition): AnyTypeValidator {
|
|
140
|
+
if ("type" in column && typeof column.type === "object") {
|
|
141
|
+
return column.type;
|
|
142
|
+
}
|
|
143
|
+
return column as AnyTypeValidator;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the JSON path for a column if defined
|
|
148
|
+
*/
|
|
149
|
+
export function getColumnJsonPath(column: AnyTypeValidator | ColumnDefinition): string | undefined {
|
|
150
|
+
if ("jsonPath" in column) {
|
|
151
|
+
return column.jsonPath;
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get all column names from a schema
|
|
158
|
+
*/
|
|
159
|
+
export function getColumnNames<TSchema extends SchemaDefinition>(
|
|
160
|
+
schema: TSchema
|
|
161
|
+
): (keyof TSchema)[] {
|
|
162
|
+
return Object.keys(schema) as (keyof TSchema)[];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Helper type to extract the schema from a datasource definition
|
|
167
|
+
*/
|
|
168
|
+
export type ExtractSchema<T> = T extends DatasourceDefinition<infer S> ? S : never;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Column definition helper for complex column configurations
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* import { defineDatasource, t, column } from '@tinybirdco/sdk';
|
|
176
|
+
*
|
|
177
|
+
* export const events = defineDatasource('events', {
|
|
178
|
+
* schema: {
|
|
179
|
+
* // Simple column
|
|
180
|
+
* id: t.string(),
|
|
181
|
+
* // Column with JSON extraction
|
|
182
|
+
* user_id: column(t.string(), { jsonPath: '$.user.id' }),
|
|
183
|
+
* },
|
|
184
|
+
* });
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export function column<T extends AnyTypeValidator>(
|
|
188
|
+
type: T,
|
|
189
|
+
options?: Omit<ColumnDefinition<T>, "type">
|
|
190
|
+
): ColumnDefinition<T> {
|
|
191
|
+
return {
|
|
192
|
+
type,
|
|
193
|
+
...options,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { engine, getEngineClause, getSortingKey, getPrimaryKey } from './engines.js';
|
|
3
|
+
|
|
4
|
+
describe('Engine Configurations', () => {
|
|
5
|
+
describe('MergeTree', () => {
|
|
6
|
+
it('creates MergeTree config', () => {
|
|
7
|
+
const config = engine.mergeTree({ sortingKey: ['id'] });
|
|
8
|
+
expect(config.type).toBe('MergeTree');
|
|
9
|
+
expect(config.sortingKey).toEqual(['id']);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('supports string sortingKey', () => {
|
|
13
|
+
const config = engine.mergeTree({ sortingKey: 'id' });
|
|
14
|
+
expect(config.sortingKey).toBe('id');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('supports partitionKey', () => {
|
|
18
|
+
const config = engine.mergeTree({
|
|
19
|
+
sortingKey: ['id'],
|
|
20
|
+
partitionKey: 'toYYYYMM(timestamp)',
|
|
21
|
+
});
|
|
22
|
+
expect(config.partitionKey).toBe('toYYYYMM(timestamp)');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('supports TTL', () => {
|
|
26
|
+
const config = engine.mergeTree({
|
|
27
|
+
sortingKey: ['id'],
|
|
28
|
+
ttl: 'timestamp + INTERVAL 90 DAY',
|
|
29
|
+
});
|
|
30
|
+
expect(config.ttl).toBe('timestamp + INTERVAL 90 DAY');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('ReplacingMergeTree', () => {
|
|
35
|
+
it('creates ReplacingMergeTree config', () => {
|
|
36
|
+
const config = engine.replacingMergeTree({ sortingKey: ['id'] });
|
|
37
|
+
expect(config.type).toBe('ReplacingMergeTree');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('supports version column', () => {
|
|
41
|
+
const config = engine.replacingMergeTree({
|
|
42
|
+
sortingKey: ['id'],
|
|
43
|
+
ver: 'updated_at',
|
|
44
|
+
});
|
|
45
|
+
expect(config.ver).toBe('updated_at');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('SummingMergeTree', () => {
|
|
50
|
+
it('creates SummingMergeTree config', () => {
|
|
51
|
+
const config = engine.summingMergeTree({ sortingKey: ['id'] });
|
|
52
|
+
expect(config.type).toBe('SummingMergeTree');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('supports columns to sum', () => {
|
|
56
|
+
const config = engine.summingMergeTree({
|
|
57
|
+
sortingKey: ['date'],
|
|
58
|
+
columns: ['count', 'total'],
|
|
59
|
+
});
|
|
60
|
+
expect(config.columns).toEqual(['count', 'total']);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('AggregatingMergeTree', () => {
|
|
65
|
+
it('creates AggregatingMergeTree config', () => {
|
|
66
|
+
const config = engine.aggregatingMergeTree({ sortingKey: ['id'] });
|
|
67
|
+
expect(config.type).toBe('AggregatingMergeTree');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('CollapsingMergeTree', () => {
|
|
72
|
+
it('creates CollapsingMergeTree config', () => {
|
|
73
|
+
const config = engine.collapsingMergeTree({
|
|
74
|
+
sortingKey: ['id'],
|
|
75
|
+
sign: 'sign_col',
|
|
76
|
+
});
|
|
77
|
+
expect(config.type).toBe('CollapsingMergeTree');
|
|
78
|
+
expect(config.sign).toBe('sign_col');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('VersionedCollapsingMergeTree', () => {
|
|
83
|
+
it('creates VersionedCollapsingMergeTree config', () => {
|
|
84
|
+
const config = engine.versionedCollapsingMergeTree({
|
|
85
|
+
sortingKey: ['id'],
|
|
86
|
+
sign: 'sign_col',
|
|
87
|
+
version: 'version_col',
|
|
88
|
+
});
|
|
89
|
+
expect(config.type).toBe('VersionedCollapsingMergeTree');
|
|
90
|
+
expect(config.sign).toBe('sign_col');
|
|
91
|
+
expect(config.version).toBe('version_col');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('getEngineClause', () => {
|
|
96
|
+
it('generates basic MergeTree clause', () => {
|
|
97
|
+
const config = engine.mergeTree({ sortingKey: ['id'] });
|
|
98
|
+
const clause = getEngineClause(config);
|
|
99
|
+
expect(clause).toContain('ENGINE "MergeTree"');
|
|
100
|
+
expect(clause).toContain('ENGINE_SORTING_KEY "id"');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('includes partition key', () => {
|
|
104
|
+
const config = engine.mergeTree({
|
|
105
|
+
sortingKey: ['id'],
|
|
106
|
+
partitionKey: 'toYYYYMM(timestamp)',
|
|
107
|
+
});
|
|
108
|
+
const clause = getEngineClause(config);
|
|
109
|
+
expect(clause).toContain('ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('includes TTL', () => {
|
|
113
|
+
const config = engine.mergeTree({
|
|
114
|
+
sortingKey: ['id'],
|
|
115
|
+
ttl: 'timestamp + INTERVAL 90 DAY',
|
|
116
|
+
});
|
|
117
|
+
const clause = getEngineClause(config);
|
|
118
|
+
expect(clause).toContain('ENGINE_TTL "timestamp + INTERVAL 90 DAY"');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('includes primary key when different from sorting key', () => {
|
|
122
|
+
const config = engine.mergeTree({
|
|
123
|
+
sortingKey: ['id', 'timestamp'],
|
|
124
|
+
primaryKey: ['id'],
|
|
125
|
+
});
|
|
126
|
+
const clause = getEngineClause(config);
|
|
127
|
+
expect(clause).toContain('ENGINE_SORTING_KEY "id, timestamp"');
|
|
128
|
+
expect(clause).toContain('ENGINE_PRIMARY_KEY "id"');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('includes ReplacingMergeTree version column', () => {
|
|
132
|
+
const config = engine.replacingMergeTree({
|
|
133
|
+
sortingKey: ['id'],
|
|
134
|
+
ver: 'updated_at',
|
|
135
|
+
});
|
|
136
|
+
const clause = getEngineClause(config);
|
|
137
|
+
expect(clause).toContain('ENGINE "ReplacingMergeTree"');
|
|
138
|
+
expect(clause).toContain('ENGINE_VER "updated_at"');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('includes SummingMergeTree columns', () => {
|
|
142
|
+
const config = engine.summingMergeTree({
|
|
143
|
+
sortingKey: ['date'],
|
|
144
|
+
columns: ['count', 'total'],
|
|
145
|
+
});
|
|
146
|
+
const clause = getEngineClause(config);
|
|
147
|
+
expect(clause).toContain('ENGINE "SummingMergeTree"');
|
|
148
|
+
expect(clause).toContain('ENGINE_SUMMING_COLUMNS "count, total"');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('includes CollapsingMergeTree sign column', () => {
|
|
152
|
+
const config = engine.collapsingMergeTree({
|
|
153
|
+
sortingKey: ['id'],
|
|
154
|
+
sign: 'sign_col',
|
|
155
|
+
});
|
|
156
|
+
const clause = getEngineClause(config);
|
|
157
|
+
expect(clause).toContain('ENGINE "CollapsingMergeTree"');
|
|
158
|
+
expect(clause).toContain('ENGINE_SIGN "sign_col"');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('includes VersionedCollapsingMergeTree sign and version', () => {
|
|
162
|
+
const config = engine.versionedCollapsingMergeTree({
|
|
163
|
+
sortingKey: ['id'],
|
|
164
|
+
sign: 'sign_col',
|
|
165
|
+
version: 'version_col',
|
|
166
|
+
});
|
|
167
|
+
const clause = getEngineClause(config);
|
|
168
|
+
expect(clause).toContain('ENGINE "VersionedCollapsingMergeTree"');
|
|
169
|
+
expect(clause).toContain('ENGINE_SIGN "sign_col"');
|
|
170
|
+
expect(clause).toContain('ENGINE_VERSION "version_col"');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('Helper functions', () => {
|
|
175
|
+
it('getSortingKey returns array from string', () => {
|
|
176
|
+
const config = engine.mergeTree({ sortingKey: 'id' });
|
|
177
|
+
expect(getSortingKey(config)).toEqual(['id']);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('getSortingKey returns array from array', () => {
|
|
181
|
+
const config = engine.mergeTree({ sortingKey: ['id', 'timestamp'] });
|
|
182
|
+
expect(getSortingKey(config)).toEqual(['id', 'timestamp']);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('getPrimaryKey defaults to sorting key', () => {
|
|
186
|
+
const config = engine.mergeTree({ sortingKey: ['id'] });
|
|
187
|
+
expect(getPrimaryKey(config)).toEqual(['id']);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('getPrimaryKey returns explicit primary key', () => {
|
|
191
|
+
const config = engine.mergeTree({
|
|
192
|
+
sortingKey: ['id', 'timestamp'],
|
|
193
|
+
primaryKey: ['id'],
|
|
194
|
+
});
|
|
195
|
+
expect(getPrimaryKey(config)).toEqual(['id']);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('ENGINE_SETTINGS string escaping', () => {
|
|
200
|
+
it('quotes string values in settings', () => {
|
|
201
|
+
const config = engine.mergeTree({
|
|
202
|
+
sortingKey: ['id'],
|
|
203
|
+
settings: {
|
|
204
|
+
storage_policy: 'tiered',
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
const clause = getEngineClause(config);
|
|
208
|
+
expect(clause).toContain("storage_policy='tiered'");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('does not quote numeric values in settings', () => {
|
|
212
|
+
const config = engine.mergeTree({
|
|
213
|
+
sortingKey: ['id'],
|
|
214
|
+
settings: {
|
|
215
|
+
index_granularity: 8192,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
const clause = getEngineClause(config);
|
|
219
|
+
expect(clause).toContain('index_granularity=8192');
|
|
220
|
+
expect(clause).not.toContain("index_granularity='8192'");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('escapes single quotes in string values', () => {
|
|
224
|
+
const config = engine.mergeTree({
|
|
225
|
+
sortingKey: ['id'],
|
|
226
|
+
settings: {
|
|
227
|
+
comment: "it's a test",
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
const clause = getEngineClause(config);
|
|
231
|
+
expect(clause).toContain("comment='it\\'s a test'");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('handles mixed string and numeric settings', () => {
|
|
235
|
+
const config = engine.mergeTree({
|
|
236
|
+
sortingKey: ['id'],
|
|
237
|
+
settings: {
|
|
238
|
+
storage_policy: 'default',
|
|
239
|
+
index_granularity: 8192,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
const clause = getEngineClause(config);
|
|
243
|
+
expect(clause).toContain("storage_policy='default'");
|
|
244
|
+
expect(clause).toContain('index_granularity=8192');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine configurations for Tinybird datasources
|
|
3
|
+
* ClickHouse table engines determine how data is stored and queried
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base configuration shared by all MergeTree engines
|
|
8
|
+
*/
|
|
9
|
+
export interface BaseMergeTreeConfig {
|
|
10
|
+
/** Columns used for sorting data within parts (required for all MergeTree engines) */
|
|
11
|
+
sortingKey: string | readonly string[];
|
|
12
|
+
/** Expression for partitioning data (e.g., 'toYYYYMM(timestamp)') */
|
|
13
|
+
partitionKey?: string;
|
|
14
|
+
/** Primary key columns (defaults to sortingKey if not specified) */
|
|
15
|
+
primaryKey?: string | readonly string[];
|
|
16
|
+
/** TTL expression for automatic data expiration (e.g., 'timestamp + INTERVAL 90 DAY') */
|
|
17
|
+
ttl?: string;
|
|
18
|
+
/** Additional engine settings */
|
|
19
|
+
settings?: Record<string, string | number | boolean>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* MergeTree engine configuration
|
|
24
|
+
* The most universal and functional table engine for high-load tasks
|
|
25
|
+
*/
|
|
26
|
+
export interface MergeTreeConfig extends BaseMergeTreeConfig {
|
|
27
|
+
type: "MergeTree";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* ReplacingMergeTree engine configuration
|
|
32
|
+
* Removes duplicate rows with the same sorting key during merges
|
|
33
|
+
*/
|
|
34
|
+
export interface ReplacingMergeTreeConfig extends BaseMergeTreeConfig {
|
|
35
|
+
type: "ReplacingMergeTree";
|
|
36
|
+
/** Optional version column - rows with highest version are kept */
|
|
37
|
+
ver?: string;
|
|
38
|
+
/** Optional flag to enable clean mode (ClickHouse 23.2+) */
|
|
39
|
+
isDeleted?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* SummingMergeTree engine configuration
|
|
44
|
+
* Sums numeric columns for rows with the same sorting key during merges
|
|
45
|
+
*/
|
|
46
|
+
export interface SummingMergeTreeConfig extends BaseMergeTreeConfig {
|
|
47
|
+
type: "SummingMergeTree";
|
|
48
|
+
/** Columns to sum (if not specified, all numeric columns are summed) */
|
|
49
|
+
columns?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* AggregatingMergeTree engine configuration
|
|
54
|
+
* For incremental data aggregation with AggregateFunction columns
|
|
55
|
+
*/
|
|
56
|
+
export interface AggregatingMergeTreeConfig extends BaseMergeTreeConfig {
|
|
57
|
+
type: "AggregatingMergeTree";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* CollapsingMergeTree engine configuration
|
|
62
|
+
* For collapsing rows that cancel each other out
|
|
63
|
+
*/
|
|
64
|
+
export interface CollapsingMergeTreeConfig extends BaseMergeTreeConfig {
|
|
65
|
+
type: "CollapsingMergeTree";
|
|
66
|
+
/** Column containing sign (1 for state, -1 for cancel) */
|
|
67
|
+
sign: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* VersionedCollapsingMergeTree engine configuration
|
|
72
|
+
* For collapsing with versioning when events may arrive out of order
|
|
73
|
+
*/
|
|
74
|
+
export interface VersionedCollapsingMergeTreeConfig extends BaseMergeTreeConfig {
|
|
75
|
+
type: "VersionedCollapsingMergeTree";
|
|
76
|
+
/** Column containing sign (1 for state, -1 for cancel) */
|
|
77
|
+
sign: string;
|
|
78
|
+
/** Column containing version number */
|
|
79
|
+
version: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Union type of all engine configurations
|
|
84
|
+
*/
|
|
85
|
+
export type EngineConfig =
|
|
86
|
+
| MergeTreeConfig
|
|
87
|
+
| ReplacingMergeTreeConfig
|
|
88
|
+
| SummingMergeTreeConfig
|
|
89
|
+
| AggregatingMergeTreeConfig
|
|
90
|
+
| CollapsingMergeTreeConfig
|
|
91
|
+
| VersionedCollapsingMergeTreeConfig;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Helper to normalize sorting key to array format
|
|
95
|
+
*/
|
|
96
|
+
function normalizeSortingKey(key: string | readonly string[]): readonly string[] {
|
|
97
|
+
return typeof key === "string" ? [key] : key;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Engine configuration builders
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { engine } from '@tinybirdco/sdk';
|
|
106
|
+
*
|
|
107
|
+
* // Basic MergeTree
|
|
108
|
+
* engine.mergeTree({
|
|
109
|
+
* sortingKey: ['user_id', 'timestamp'],
|
|
110
|
+
* partitionKey: 'toYYYYMM(timestamp)',
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* // ReplacingMergeTree for upserts
|
|
114
|
+
* engine.replacingMergeTree({
|
|
115
|
+
* sortingKey: ['id'],
|
|
116
|
+
* ver: 'updated_at',
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* // SummingMergeTree for counters
|
|
120
|
+
* engine.summingMergeTree({
|
|
121
|
+
* sortingKey: ['date', 'metric_name'],
|
|
122
|
+
* columns: ['value'],
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export const engine = {
|
|
127
|
+
/**
|
|
128
|
+
* MergeTree - The most universal engine for high-load tasks
|
|
129
|
+
* Best for: General-purpose analytics, logs, events
|
|
130
|
+
*/
|
|
131
|
+
mergeTree: (config: Omit<MergeTreeConfig, "type">): MergeTreeConfig => ({
|
|
132
|
+
type: "MergeTree",
|
|
133
|
+
...config,
|
|
134
|
+
}),
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* ReplacingMergeTree - Removes duplicates during background merges
|
|
138
|
+
* Best for: Maintaining latest state, upserts, slowly changing dimensions
|
|
139
|
+
*
|
|
140
|
+
* @param config.ver - Optional version column. Rows with highest version are kept.
|
|
141
|
+
*/
|
|
142
|
+
replacingMergeTree: (
|
|
143
|
+
config: Omit<ReplacingMergeTreeConfig, "type">
|
|
144
|
+
): ReplacingMergeTreeConfig => ({
|
|
145
|
+
type: "ReplacingMergeTree",
|
|
146
|
+
...config,
|
|
147
|
+
}),
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* SummingMergeTree - Sums numeric columns during background merges
|
|
151
|
+
* Best for: Counters, metrics aggregation, pre-aggregated data
|
|
152
|
+
*
|
|
153
|
+
* @param config.columns - Columns to sum. If not specified, all numeric columns are summed.
|
|
154
|
+
*/
|
|
155
|
+
summingMergeTree: (
|
|
156
|
+
config: Omit<SummingMergeTreeConfig, "type">
|
|
157
|
+
): SummingMergeTreeConfig => ({
|
|
158
|
+
type: "SummingMergeTree",
|
|
159
|
+
...config,
|
|
160
|
+
}),
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* AggregatingMergeTree - For incremental aggregation with AggregateFunction columns
|
|
164
|
+
* Best for: Materialized views, incremental aggregation pipelines
|
|
165
|
+
*/
|
|
166
|
+
aggregatingMergeTree: (
|
|
167
|
+
config: Omit<AggregatingMergeTreeConfig, "type">
|
|
168
|
+
): AggregatingMergeTreeConfig => ({
|
|
169
|
+
type: "AggregatingMergeTree",
|
|
170
|
+
...config,
|
|
171
|
+
}),
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* CollapsingMergeTree - For collapsing state/cancel row pairs
|
|
175
|
+
* Best for: Changelog-style updates, mutable data with deletes
|
|
176
|
+
*
|
|
177
|
+
* @param config.sign - Column containing 1 (state) or -1 (cancel)
|
|
178
|
+
*/
|
|
179
|
+
collapsingMergeTree: (
|
|
180
|
+
config: Omit<CollapsingMergeTreeConfig, "type">
|
|
181
|
+
): CollapsingMergeTreeConfig => ({
|
|
182
|
+
type: "CollapsingMergeTree",
|
|
183
|
+
...config,
|
|
184
|
+
}),
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* VersionedCollapsingMergeTree - Collapsing with versioning for out-of-order events
|
|
188
|
+
* Best for: Changelog-style updates with potential out-of-order arrival
|
|
189
|
+
*
|
|
190
|
+
* @param config.sign - Column containing 1 (state) or -1 (cancel)
|
|
191
|
+
* @param config.version - Column containing version number for ordering
|
|
192
|
+
*/
|
|
193
|
+
versionedCollapsingMergeTree: (
|
|
194
|
+
config: Omit<VersionedCollapsingMergeTreeConfig, "type">
|
|
195
|
+
): VersionedCollapsingMergeTreeConfig => ({
|
|
196
|
+
type: "VersionedCollapsingMergeTree",
|
|
197
|
+
...config,
|
|
198
|
+
}),
|
|
199
|
+
} as const;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get the sorting key as an array
|
|
203
|
+
*/
|
|
204
|
+
export function getSortingKey(config: EngineConfig): readonly string[] {
|
|
205
|
+
return normalizeSortingKey(config.sortingKey);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get the primary key as an array (defaults to sorting key)
|
|
210
|
+
*/
|
|
211
|
+
export function getPrimaryKey(config: EngineConfig): readonly string[] {
|
|
212
|
+
if (config.primaryKey) {
|
|
213
|
+
return normalizeSortingKey(config.primaryKey);
|
|
214
|
+
}
|
|
215
|
+
return getSortingKey(config);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Generate the engine clause for a datasource file
|
|
220
|
+
*/
|
|
221
|
+
export function getEngineClause(config: EngineConfig): string {
|
|
222
|
+
const parts: string[] = [`ENGINE "${config.type}"`];
|
|
223
|
+
|
|
224
|
+
if (config.partitionKey) {
|
|
225
|
+
parts.push(`ENGINE_PARTITION_KEY "${config.partitionKey}"`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const sortingKey = getSortingKey(config);
|
|
229
|
+
parts.push(`ENGINE_SORTING_KEY "${sortingKey.join(", ")}"`);
|
|
230
|
+
|
|
231
|
+
if (config.primaryKey) {
|
|
232
|
+
const primaryKey = getPrimaryKey(config);
|
|
233
|
+
parts.push(`ENGINE_PRIMARY_KEY "${primaryKey.join(", ")}"`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (config.ttl) {
|
|
237
|
+
parts.push(`ENGINE_TTL "${config.ttl}"`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Engine-specific options
|
|
241
|
+
if (config.type === "ReplacingMergeTree" && config.ver) {
|
|
242
|
+
parts.push(`ENGINE_VER "${config.ver}"`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (config.type === "CollapsingMergeTree" || config.type === "VersionedCollapsingMergeTree") {
|
|
246
|
+
parts.push(`ENGINE_SIGN "${config.sign}"`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (config.type === "VersionedCollapsingMergeTree") {
|
|
250
|
+
parts.push(`ENGINE_VERSION "${config.version}"`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (config.type === "SummingMergeTree" && config.columns && config.columns.length > 0) {
|
|
254
|
+
parts.push(`ENGINE_SUMMING_COLUMNS "${config.columns.join(", ")}"`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (config.settings && Object.keys(config.settings).length > 0) {
|
|
258
|
+
const settingsStr = Object.entries(config.settings)
|
|
259
|
+
.map(([k, v]) => {
|
|
260
|
+
if (typeof v === "string") {
|
|
261
|
+
const escaped = v.replace(/'/g, "\\'");
|
|
262
|
+
return `${k}='${escaped}'`;
|
|
263
|
+
}
|
|
264
|
+
return `${k}=${v}`;
|
|
265
|
+
})
|
|
266
|
+
.join(", ");
|
|
267
|
+
parts.push(`ENGINE_SETTINGS "${settingsStr}"`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return parts.join("\n");
|
|
271
|
+
}
|