@intentius/chant-lexicon-sql 0.112.0 → 0.113.0
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/clickhouse/apply/apply.d.ts.map +1 -1
- package/dist/clickhouse/apply/statements.d.ts +77 -1
- package/dist/clickhouse/apply/statements.d.ts.map +1 -1
- package/dist/clickhouse/plan/commands.d.ts +6 -1
- package/dist/clickhouse/plan/commands.d.ts.map +1 -1
- package/dist/clickhouse/topology.d.ts +100 -0
- package/dist/clickhouse/topology.d.ts.map +1 -0
- package/dist/codegen/classifier-page.d.ts.map +1 -1
- package/dist/codegen/postgres-classifier-page.d.ts.map +1 -1
- package/dist/core/statements.d.ts +104 -0
- package/dist/core/statements.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/dist/migration-statements.d.ts +55 -0
- package/dist/migration-statements.d.ts.map +1 -0
- package/dist/postgres/apply/apply.d.ts.map +1 -1
- package/dist/postgres/apply/statements.d.ts +91 -1
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/clickhouse/apply/apply.ts +25 -53
- package/src/clickhouse/apply/statements.ts +149 -31
- package/src/clickhouse/plan/commands.ts +30 -6
- package/src/clickhouse/topology.e2e.test.ts +100 -0
- package/src/clickhouse/topology.test.ts +276 -0
- package/src/clickhouse/topology.ts +451 -0
- package/src/codegen/classifier-page.ts +17 -0
- package/src/codegen/postgres-classifier-page.ts +17 -0
- package/src/core/statements.ts +127 -0
- package/src/index.ts +15 -0
- package/src/migration-statements.test.ts +265 -0
- package/src/migration-statements.ts +252 -0
- package/src/op/__snapshots__/watch-pipeline.test.ts.snap +93 -0
- package/src/op/watch-drift.e2e.test.ts +302 -0
- package/src/op/watch-pipeline.test.ts +113 -0
- package/src/postgres/apply/apply.ts +38 -96
- package/src/postgres/apply/statements.ts +145 -2
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { canonicalObject } from "./plan/normalize";
|
|
3
|
+
import type { Change } from "./plan/diff";
|
|
4
|
+
import { alterSteps, createStatement, dropStatement, type DeclaredObject } from "./apply/statements";
|
|
5
|
+
import { CLICKHOUSE_ENTITY_TYPES, type ClickHouseEntityType } from "./entities";
|
|
6
|
+
import { parseTopology, renderEngine, renderStatement, renderSteps, type Topology } from "./topology";
|
|
7
|
+
|
|
8
|
+
const SINGLE: Topology = { kind: "single" };
|
|
9
|
+
const CLUSTER: Topology = { kind: "cluster", cluster: "main" };
|
|
10
|
+
const REPLICATED: Topology = { kind: "replicated" };
|
|
11
|
+
const CLOUD: Topology = { kind: "cloud" };
|
|
12
|
+
|
|
13
|
+
const EVENTS = `CREATE TABLE analytics.events (
|
|
14
|
+
id UInt64,
|
|
15
|
+
ts DateTime,
|
|
16
|
+
kind LowCardinality(String)
|
|
17
|
+
)
|
|
18
|
+
ENGINE = ReplacingMergeTree(ts)
|
|
19
|
+
ORDER BY id
|
|
20
|
+
COMMENT 'events'`;
|
|
21
|
+
|
|
22
|
+
describe("CREATE TABLE per topology", () => {
|
|
23
|
+
test("a single node takes the declaration as written", () => {
|
|
24
|
+
expect(renderStatement(EVENTS, SINGLE)).toBe(EVENTS);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("a cluster adds ON CLUSTER after the name and replicates the engine with its Keeper path and replica", () => {
|
|
28
|
+
expect(renderStatement(EVENTS, CLUSTER)).toBe(`CREATE TABLE analytics.events ON CLUSTER \`main\` (
|
|
29
|
+
id UInt64,
|
|
30
|
+
ts DateTime,
|
|
31
|
+
kind LowCardinality(String)
|
|
32
|
+
)
|
|
33
|
+
ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}', ts)
|
|
34
|
+
ORDER BY id
|
|
35
|
+
COMMENT 'events'`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("a cluster's Keeper path and replica name can be set, and a macro cluster name is a string literal", () => {
|
|
39
|
+
const t: Topology = { kind: "cluster", cluster: "{cluster}", replicaPath: "/ch/{shard}/{database}/{table}", replicaName: "{replica}" };
|
|
40
|
+
expect(renderStatement("CREATE TABLE db.t (id UInt64) ENGINE = MergeTree ORDER BY id", t)).toBe(
|
|
41
|
+
"CREATE TABLE db.t ON CLUSTER '{cluster}' (id UInt64) ENGINE = ReplicatedMergeTree('/ch/{shard}/{database}/{table}', '{replica}') ORDER BY id",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("a Replicated database gets the Replicated family with no path arguments and no ON CLUSTER", () => {
|
|
46
|
+
expect(renderStatement(EVENTS, REPLICATED)).toBe(EVENTS.replace("ReplacingMergeTree(ts)", "ReplicatedReplacingMergeTree(ts)"));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("Cloud takes the plain family, which it turns into SharedMergeTree itself", () => {
|
|
50
|
+
expect(renderStatement(EVENTS, CLOUD)).toBe(EVENTS);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("a declaration written for a cluster", () => {
|
|
54
|
+
const declared =
|
|
55
|
+
"CREATE TABLE IF NOT EXISTS db.t ON CLUSTER prod (id UInt64, v UInt32) ENGINE = ReplicatedSummingMergeTree('/p/{shard}/t', '{replica}', (v)) ORDER BY id";
|
|
56
|
+
|
|
57
|
+
test("keeps its own path on a cluster, under the topology's cluster name", () => {
|
|
58
|
+
expect(renderStatement(declared, CLUSTER)).toBe(
|
|
59
|
+
"CREATE TABLE IF NOT EXISTS db.t ON CLUSTER `main` (id UInt64, v UInt32) ENGINE = ReplicatedSummingMergeTree('/p/{shard}/t', '{replica}', (v)) ORDER BY id",
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("loses ON CLUSTER and replication on a single node", () => {
|
|
64
|
+
expect(renderStatement(declared, SINGLE)).toBe("CREATE TABLE IF NOT EXISTS db.t (id UInt64, v UInt32) ENGINE = SummingMergeTree((v)) ORDER BY id");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("loses ON CLUSTER and the path arguments in a Replicated database", () => {
|
|
68
|
+
expect(renderStatement(declared, REPLICATED)).toBe(
|
|
69
|
+
"CREATE TABLE IF NOT EXISTS db.t (id UInt64, v UInt32) ENGINE = ReplicatedSummingMergeTree((v)) ORDER BY id",
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("loses ON CLUSTER and the path arguments on Cloud", () => {
|
|
74
|
+
expect(renderStatement(declared, CLOUD)).toBe("CREATE TABLE IF NOT EXISTS db.t (id UInt64, v UInt32) ENGINE = SummingMergeTree((v)) ORDER BY id");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("ON CLUSTER goes after a UUID clause", () => {
|
|
79
|
+
expect(renderStatement("CREATE TABLE db.t UUID '0e9a7e46-0a7d-4c3a-8d47-3c1f2f9b0a11' (id UInt64) ENGINE = MergeTree ORDER BY id", CLUSTER)).toBe(
|
|
80
|
+
"CREATE TABLE db.t UUID '0e9a7e46-0a7d-4c3a-8d47-3c1f2f9b0a11' ON CLUSTER `main` (id UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}') ORDER BY id",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("an engine outside the MergeTree family is left alone; only ON CLUSTER changes", () => {
|
|
85
|
+
const log = "CREATE TABLE db.l (id UInt64) ENGINE = Log";
|
|
86
|
+
expect(renderStatement(log, REPLICATED)).toBe(log);
|
|
87
|
+
expect(renderStatement(log, CLUSTER)).toBe("CREATE TABLE db.l ON CLUSTER `main` (id UInt64) ENGINE = Log");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("comments and spacing in the declaration survive", () => {
|
|
91
|
+
const sql = "CREATE TABLE db.t ( -- the key\n id UInt64 /* id */\n)\nENGINE = MergeTree() -- engine\nORDER BY id";
|
|
92
|
+
expect(renderStatement(sql, REPLICATED)).toBe(sql.replace("MergeTree()", "ReplicatedMergeTree()"));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("views per topology", () => {
|
|
97
|
+
const mv = "CREATE MATERIALIZED VIEW db.counts ENGINE = SummingMergeTree ORDER BY kind AS SELECT kind, count() AS n FROM db.events GROUP BY kind";
|
|
98
|
+
const to = "CREATE MATERIALIZED VIEW db.feed TO db.events AS SELECT * FROM db.raw";
|
|
99
|
+
|
|
100
|
+
test("a materialized view's inner engine is replicated like a table's", () => {
|
|
101
|
+
expect(renderStatement(mv, CLUSTER)).toBe(
|
|
102
|
+
"CREATE MATERIALIZED VIEW db.counts ON CLUSTER `main` ENGINE = ReplicatedSummingMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}') ORDER BY kind AS SELECT kind, count() AS n FROM db.events GROUP BY kind",
|
|
103
|
+
);
|
|
104
|
+
expect(renderStatement(mv, REPLICATED)).toBe(mv.replace("SummingMergeTree", "ReplicatedSummingMergeTree"));
|
|
105
|
+
expect(renderStatement(mv, SINGLE)).toBe(mv);
|
|
106
|
+
expect(renderStatement(mv, CLOUD)).toBe(mv);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("a view writing TO a table only gains ON CLUSTER", () => {
|
|
110
|
+
expect(renderStatement(to, CLUSTER)).toBe("CREATE MATERIALIZED VIEW db.feed ON CLUSTER `main` TO db.events AS SELECT * FROM db.raw");
|
|
111
|
+
for (const t of [SINGLE, REPLICATED, CLOUD]) expect(renderStatement(to, t)).toBe(to);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a SELECT inside a view is not mistaken for the view's own clauses", () => {
|
|
115
|
+
const v = "CREATE OR REPLACE VIEW db.v AS SELECT * FROM db.t ON CLUSTER_X JOIN db.u USING id";
|
|
116
|
+
expect(renderStatement(v, SINGLE)).toBe(v);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("CREATE DATABASE per topology", () => {
|
|
121
|
+
const db = "CREATE DATABASE IF NOT EXISTS analytics COMMENT 'the warehouse'";
|
|
122
|
+
|
|
123
|
+
test("single node and Cloud take it as written", () => {
|
|
124
|
+
expect(renderStatement(db, SINGLE)).toBe(db);
|
|
125
|
+
expect(renderStatement(db, CLOUD)).toBe(db);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a cluster adds ON CLUSTER", () => {
|
|
129
|
+
expect(renderStatement(db, CLUSTER)).toBe("CREATE DATABASE IF NOT EXISTS analytics ON CLUSTER `main` COMMENT 'the warehouse'");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("the replicated topology makes it a Replicated database, on its cluster when it names one", () => {
|
|
133
|
+
expect(renderStatement(db, REPLICATED)).toBe(
|
|
134
|
+
"CREATE DATABASE IF NOT EXISTS analytics ENGINE = Replicated('/clickhouse/databases/analytics', '{shard}', '{replica}') COMMENT 'the warehouse'",
|
|
135
|
+
);
|
|
136
|
+
expect(renderStatement(db, { kind: "replicated", cluster: "all" })).toBe(
|
|
137
|
+
"CREATE DATABASE IF NOT EXISTS analytics ON CLUSTER `all` ENGINE = Replicated('/clickhouse/databases/analytics', '{shard}', '{replica}') COMMENT 'the warehouse'",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("a declared Atomic engine becomes Replicated in the replicated topology, and a declared Replicated one keeps its arguments", () => {
|
|
142
|
+
expect(renderStatement("CREATE DATABASE `a` ENGINE = Atomic", REPLICATED)).toBe("CREATE DATABASE `a` ENGINE = Replicated('/clickhouse/databases/a', '{shard}', '{replica}')");
|
|
143
|
+
const r = "CREATE DATABASE a ENGINE = Replicated('/x/a', 's', 'r')";
|
|
144
|
+
expect(renderStatement(r, REPLICATED)).toBe(r);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("a declared Replicated engine is dropped everywhere else", () => {
|
|
148
|
+
const r = "CREATE DATABASE a ENGINE = Replicated('/x/a', '{shard}', '{replica}') COMMENT 'c'";
|
|
149
|
+
expect(renderStatement(r, SINGLE)).toBe("CREATE DATABASE a COMMENT 'c'");
|
|
150
|
+
expect(renderStatement(r, CLOUD)).toBe("CREATE DATABASE a COMMENT 'c'");
|
|
151
|
+
expect(renderStatement(r, CLUSTER)).toBe("CREATE DATABASE a ON CLUSTER `main` COMMENT 'c'");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("the other DDL per topology", () => {
|
|
156
|
+
const cases: Array<[string, string]> = [
|
|
157
|
+
["ALTER TABLE `db`.`t` ADD COLUMN `x` UInt8 AFTER `id`", "ALTER TABLE `db`.`t` ON CLUSTER `main` ADD COLUMN `x` UInt8 AFTER `id`"],
|
|
158
|
+
["ALTER TABLE `db`.`t` MODIFY COMMENT 'c'", "ALTER TABLE `db`.`t` ON CLUSTER `main` MODIFY COMMENT 'c'"],
|
|
159
|
+
["DROP TABLE `db`.`t` SYNC", "DROP TABLE `db`.`t` ON CLUSTER `main` SYNC"],
|
|
160
|
+
["DROP VIEW IF EXISTS `db`.`v` SYNC", "DROP VIEW IF EXISTS `db`.`v` ON CLUSTER `main` SYNC"],
|
|
161
|
+
["TRUNCATE TABLE db.t", "TRUNCATE TABLE db.t ON CLUSTER `main`"],
|
|
162
|
+
["DETACH VIEW db.v", "DETACH VIEW db.v ON CLUSTER `main`"],
|
|
163
|
+
["ATTACH TABLE db.v", "ATTACH TABLE db.v ON CLUSTER `main`"],
|
|
164
|
+
["OPTIMIZE TABLE db.t FINAL", "OPTIMIZE TABLE db.t ON CLUSTER `main` FINAL"],
|
|
165
|
+
["RENAME TABLE `db`.`a` TO `db`.`b`", "RENAME TABLE `db`.`a` TO `db`.`b` ON CLUSTER `main`"],
|
|
166
|
+
["RENAME TABLE db.a TO db.b, db.c TO db.d;", "RENAME TABLE db.a TO db.b, db.c TO db.d ON CLUSTER `main`;"],
|
|
167
|
+
["EXCHANGE TABLES db.a AND db.b", "EXCHANGE TABLES db.a AND db.b ON CLUSTER `main`"],
|
|
168
|
+
["EXCHANGE TABLES db.a AND db.b SETTINGS x = 1", "EXCHANGE TABLES db.a AND db.b ON CLUSTER `main` SETTINGS x = 1"],
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
test.each(cases)("%s gains ON CLUSTER on a cluster", (sql, onCluster) => {
|
|
172
|
+
expect(renderStatement(sql, CLUSTER)).toBe(onCluster);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test.each(cases)("%s is written without ON CLUSTER on every other topology", (sql, onCluster) => {
|
|
176
|
+
for (const t of [SINGLE, REPLICATED, CLOUD]) {
|
|
177
|
+
expect(renderStatement(sql, t)).toBe(sql);
|
|
178
|
+
expect(renderStatement(onCluster, t)).toBe(sql);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("ALTER, RENAME and DROP DATABASE take ON CLUSTER on a cluster", () => {
|
|
183
|
+
expect(renderStatement("ALTER DATABASE `db` MODIFY COMMENT 'c'", CLUSTER)).toBe("ALTER DATABASE `db` ON CLUSTER `main` MODIFY COMMENT 'c'");
|
|
184
|
+
expect(renderStatement("RENAME DATABASE `a` TO `b`", CLUSTER)).toBe("RENAME DATABASE `a` TO `b` ON CLUSTER `main`");
|
|
185
|
+
expect(renderStatement("DROP DATABASE `db` SYNC", CLUSTER)).toBe("DROP DATABASE `db` ON CLUSTER `main` SYNC");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("in the replicated topology only dropping a database carries its cluster", () => {
|
|
189
|
+
const t: Topology = { kind: "replicated", cluster: "all" };
|
|
190
|
+
expect(renderStatement("DROP DATABASE `db` SYNC", t)).toBe("DROP DATABASE `db` ON CLUSTER `all` SYNC");
|
|
191
|
+
expect(renderStatement("ALTER DATABASE `db` MODIFY COMMENT 'c'", t)).toBe("ALTER DATABASE `db` MODIFY COMMENT 'c'");
|
|
192
|
+
expect(renderStatement("ALTER TABLE db.t DROP COLUMN x", t)).toBe("ALTER TABLE db.t DROP COLUMN x");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("a declared ON CLUSTER is replaced with the topology's cluster", () => {
|
|
196
|
+
expect(renderStatement("ALTER TABLE db.t ON CLUSTER old DROP COLUMN x", CLUSTER)).toBe("ALTER TABLE db.t ON CLUSTER `main` DROP COLUMN x");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("KILL gains ON CLUSTER on a cluster, and is kept as written elsewhere", () => {
|
|
200
|
+
expect(renderStatement("KILL QUERY WHERE query_id = 'q' SYNC", CLUSTER)).toBe("KILL QUERY ON CLUSTER `main` WHERE query_id = 'q' SYNC");
|
|
201
|
+
const replicatedKill = "KILL QUERY ON CLUSTER 'db' WHERE query_id = 'q' SYNC";
|
|
202
|
+
expect(renderStatement(replicatedKill, REPLICATED)).toBe(replicatedKill);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("statements that are not DDL come back unchanged", () => {
|
|
206
|
+
for (const sql of ["INSERT INTO db.t SELECT * FROM db.s", "SELECT 1", "SYSTEM SYNC REPLICA db.t LIGHTWEIGHT"]) {
|
|
207
|
+
for (const t of [SINGLE, CLUSTER, REPLICATED, CLOUD]) expect(renderStatement(sql, t)).toBe(sql);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("a CREATE the parser does not take still gains ON CLUSTER", () => {
|
|
212
|
+
expect(renderStatement("CREATE TABLE db.t2 AS db.t", CLUSTER)).toBe("CREATE TABLE db.t2 ON CLUSTER `main` AS db.t");
|
|
213
|
+
expect(renderStatement("CREATE TABLE db.t2 ON CLUSTER x AS db.t", SINGLE)).toBe("CREATE TABLE db.t2 AS db.t");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("renderEngine", () => {
|
|
218
|
+
test("splits a Replicated engine's path and replica from the family's own arguments", () => {
|
|
219
|
+
expect(renderEngine({ name: "ReplicatedReplacingMergeTree", args: ["'/p'", "'{replica}'", "ver"] }, SINGLE)).toEqual({ name: "ReplacingMergeTree", args: ["ver"] });
|
|
220
|
+
expect(renderEngine({ name: "ReplicatedReplacingMergeTree", args: ["ver"] }, SINGLE)).toEqual({ name: "ReplacingMergeTree", args: ["ver"] });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("leaves a Shared engine and the non-MergeTree engines alone", () => {
|
|
224
|
+
for (const name of ["SharedMergeTree", "Distributed", "Memory"]) {
|
|
225
|
+
const e = { name, args: ["a"] };
|
|
226
|
+
for (const t of [SINGLE, CLUSTER, REPLICATED, CLOUD]) expect(renderEngine(e, t)).toBe(e);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("keeps an engine written without parentheses without them where it can", () => {
|
|
231
|
+
expect(renderEngine({ name: "MergeTree" }, REPLICATED)).toEqual({ name: "ReplicatedMergeTree" });
|
|
232
|
+
expect(renderEngine({ name: "ReplicatedMergeTree" }, CLOUD)).toEqual({ name: "MergeTree" });
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("parseTopology", () => {
|
|
237
|
+
test("reads each topology's string form", () => {
|
|
238
|
+
expect(parseTopology("single")).toEqual(SINGLE);
|
|
239
|
+
expect(parseTopology("cluster:main")).toEqual(CLUSTER);
|
|
240
|
+
expect(parseTopology("cluster:{cluster}")).toEqual({ kind: "cluster", cluster: "{cluster}" });
|
|
241
|
+
expect(parseTopology("replicated")).toEqual(REPLICATED);
|
|
242
|
+
expect(parseTopology("replicated:all")).toEqual({ kind: "replicated", cluster: "all" });
|
|
243
|
+
expect(parseTopology("cloud")).toEqual(CLOUD);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("refuses a cluster without a name, and anything unknown", () => {
|
|
247
|
+
expect(() => parseTopology("cluster")).toThrow(/needs the cluster's name/);
|
|
248
|
+
expect(() => parseTopology("single:x")).toThrow(/takes no argument/);
|
|
249
|
+
expect(() => parseTopology("sharded")).toThrow(/unknown topology/);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("the applier's own statements", () => {
|
|
254
|
+
const declared = (type: ClickHouseEntityType, exportName: string, ddl: string): DeclaredObject => {
|
|
255
|
+
const canonical = canonicalObject(ddl);
|
|
256
|
+
return { exportName, type, key: canonical.database ? `${canonical.database}.${canonical.name}` : canonical.name, ddl, canonical, dependsOn: [] };
|
|
257
|
+
};
|
|
258
|
+
const table = declared(CLICKHOUSE_ENTITY_TYPES.table, "events", "CREATE TABLE db.events (id UInt64, x UInt8) ENGINE = MergeTree ORDER BY id");
|
|
259
|
+
const marker = { stack: "s", env: "e" };
|
|
260
|
+
const added: Change = { object: "events", field: "columns.x", after: "UInt8", rule: "SQLCH201", class: "metadata" };
|
|
261
|
+
|
|
262
|
+
test("render for a cluster, with the marker comment and steps kept", () => {
|
|
263
|
+
const create = renderStatement(createStatement(table, marker), CLUSTER);
|
|
264
|
+
expect(create).toMatch(/^CREATE TABLE db\.events ON CLUSTER `main` \(id UInt64, x UInt8\) ENGINE = ReplicatedMergeTree\('\/clickhouse\/tables\/\{uuid\}\/\{shard\}', '\{replica\}'\) ORDER BY id COMMENT '/);
|
|
265
|
+
const steps = renderSteps(alterSteps(table, [added]), CLUSTER);
|
|
266
|
+
expect(steps).toEqual([{ sql: "ALTER TABLE `db`.`events` ON CLUSTER `main` ADD COLUMN x UInt8 AFTER `id`", rewrite: false, rule: "SQLCH201", class: "metadata" }]);
|
|
267
|
+
expect(renderStatement(dropStatement(CLICKHOUSE_ENTITY_TYPES.table, "db", "events"), CLUSTER)).toBe("DROP TABLE `db`.`events` ON CLUSTER `main` SYNC");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("come back unchanged for a single node", () => {
|
|
271
|
+
const create = createStatement(table, marker);
|
|
272
|
+
expect(renderStatement(create, SINGLE)).toBe(create);
|
|
273
|
+
const steps = alterSteps(table, [added]);
|
|
274
|
+
expect(renderSteps(steps, SINGLE)).toEqual(steps);
|
|
275
|
+
});
|
|
276
|
+
});
|