@quark-fw/clisma 0.1.15 → 0.1.16
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/client.js +31 -9
- package/dist/ddl.js +3 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/migrate.js +3 -1
- package/dist/migration-error.d.ts +10 -0
- package/dist/migration-error.js +24 -0
- package/package.json +4 -4
package/dist/client.js
CHANGED
|
@@ -5,6 +5,7 @@ import { buildMigrationPlan, buildSettingsPlan, parseEngineSettings, } from "./m
|
|
|
5
5
|
import { assertReplicatedSchema } from "./assert-replicated-schema.js";
|
|
6
6
|
import { ensureTablesOnNodes, } from "./ensure-tables-on-nodes.js";
|
|
7
7
|
import { ensureDictionariesOnNodes } from "./ensure-dictionaries-on-nodes.js";
|
|
8
|
+
import { MigrationError } from "./migration-error.js";
|
|
8
9
|
import { runCommand, runQuery, runQueryJson, } from "./query.js";
|
|
9
10
|
export function createClient(schema, options) {
|
|
10
11
|
const database = options.database ?? schema.database ?? "default";
|
|
@@ -14,7 +15,9 @@ export function createClient(schema, options) {
|
|
|
14
15
|
username: options.username,
|
|
15
16
|
password: options.password,
|
|
16
17
|
...(withDatabase ? { database } : {}),
|
|
17
|
-
...(options.requestTimeoutMs
|
|
18
|
+
...(options.requestTimeoutMs
|
|
19
|
+
? { request_timeout: options.requestTimeoutMs }
|
|
20
|
+
: {}),
|
|
18
21
|
});
|
|
19
22
|
const ch = connect(options.url, true);
|
|
20
23
|
// клиенты DDL-нод создаются один раз; основная нода переиспользует `ch`
|
|
@@ -23,6 +26,9 @@ export function createClient(schema, options) {
|
|
|
23
26
|
url,
|
|
24
27
|
client: url === options.url ? ch : connect(url, true),
|
|
25
28
|
}));
|
|
29
|
+
// источник истины схемы: `url` может быть балансировщиком или нодой вне
|
|
30
|
+
// ddlNodes, и план с него разошёлся бы с тем, куда пишется DDL
|
|
31
|
+
const primary = nodes[0];
|
|
26
32
|
const db = {};
|
|
27
33
|
for (const tableName in schema.tables) {
|
|
28
34
|
db[tableName] = new TableClient(database, tableName, schema.tables[tableName], ch);
|
|
@@ -69,7 +75,7 @@ export function createClient(schema, options) {
|
|
|
69
75
|
assertReplicatedSchema(nodes.length, schema.tables);
|
|
70
76
|
if (!opts?.dryRun)
|
|
71
77
|
await ensureDatabase();
|
|
72
|
-
const result = await
|
|
78
|
+
const result = await primary.client.query({
|
|
73
79
|
query: `
|
|
74
80
|
SELECT table, name, type
|
|
75
81
|
FROM system.columns
|
|
@@ -92,17 +98,33 @@ export function createClient(schema, options) {
|
|
|
92
98
|
return { ...plan, settings, executed: [] };
|
|
93
99
|
// план (CREATE недостающих, ALTER ADD/MODIFY) выполняется на первой
|
|
94
100
|
// ноде: реплицируемый ALTER сам разъезжается по репликам, а недостающие
|
|
95
|
-
// на репликах таблицы докладывает ensureTablesOnNodes копией SHOW CREATE
|
|
101
|
+
// на репликах таблицы докладывает ensureTablesOnNodes копией SHOW CREATE.
|
|
102
|
+
// Сбой посередине — MigrationError со списком уже выполненного
|
|
96
103
|
const executed = [];
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
const run = async (node, sql) => {
|
|
105
|
+
try {
|
|
106
|
+
await node.client.command({ query: sql });
|
|
107
|
+
}
|
|
108
|
+
catch (cause) {
|
|
109
|
+
throw new MigrationError({
|
|
110
|
+
executed,
|
|
111
|
+
failed: { node: node.url, sql },
|
|
112
|
+
cause,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
executed.push({ node: node.url, sql });
|
|
116
|
+
};
|
|
117
|
+
for (const statement of plan.statements)
|
|
118
|
+
await run(primary, statement);
|
|
119
|
+
try {
|
|
120
|
+
executed.push(...(await ensureEverywhere("replace")));
|
|
121
|
+
}
|
|
122
|
+
catch (cause) {
|
|
123
|
+
throw new MigrationError({ executed, cause });
|
|
100
124
|
}
|
|
101
|
-
executed.push(...(await ensureEverywhere("replace")));
|
|
102
125
|
for (const statement of settings) {
|
|
103
126
|
const node = nodes.find((n) => n.url === statement.node);
|
|
104
|
-
await node
|
|
105
|
-
executed.push(statement);
|
|
127
|
+
await run(node, statement.sql);
|
|
106
128
|
}
|
|
107
129
|
return { ...plan, settings, executed };
|
|
108
130
|
};
|
package/dist/ddl.js
CHANGED
|
@@ -5,7 +5,9 @@ import { quoteString, renderEngine, renderKeyList, renderKeyPart, } from "./ddl-
|
|
|
5
5
|
// список для `SETTINGS` / `MODIFY SETTING`: числа как есть, строки в кавычках
|
|
6
6
|
export function renderSettings(settings) {
|
|
7
7
|
return Object.entries(settings)
|
|
8
|
-
.map(([key, value]) => typeof value === "number"
|
|
8
|
+
.map(([key, value]) => typeof value === "number"
|
|
9
|
+
? `${key} = ${value}`
|
|
10
|
+
: `${key} = ${quoteString(value)}`)
|
|
9
11
|
.join(", ");
|
|
10
12
|
}
|
|
11
13
|
export function buildColumnSQL(name, col) {
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./table-client.js";
|
|
|
11
11
|
export * from "./rows.js";
|
|
12
12
|
export * from "./migrate.js";
|
|
13
13
|
export * from "./ddl.js";
|
|
14
|
+
export * from "./migration-error.js";
|
|
14
15
|
export * from "./build-create-dictionary-query.js";
|
|
15
16
|
export type { QueryOptions, QueryJsonResult, QuerySettings } from "./query.js";
|
|
16
17
|
export type { DdlNode, DdlStatement } from "./ensure-tables-on-nodes.js";
|
package/dist/index.js
CHANGED
package/dist/migrate.js
CHANGED
|
@@ -11,7 +11,9 @@ export function parseEngineSettings(engineFull) {
|
|
|
11
11
|
const re = /(\w+)\s*=\s*('(?:[^'\\]|\\.)*'|[^,]+)/g;
|
|
12
12
|
for (const m of engineFull.slice(at + " SETTINGS ".length).matchAll(re)) {
|
|
13
13
|
const raw = m[2].trim();
|
|
14
|
-
out[m[1]] = raw.startsWith("'")
|
|
14
|
+
out[m[1]] = raw.startsWith("'")
|
|
15
|
+
? raw.slice(1, -1).replace(/\\(.)/g, "$1")
|
|
16
|
+
: raw;
|
|
15
17
|
}
|
|
16
18
|
return out;
|
|
17
19
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DdlStatement } from "./ensure-tables-on-nodes.js";
|
|
2
|
+
export declare class MigrationError extends Error {
|
|
3
|
+
readonly executed: DdlStatement[];
|
|
4
|
+
readonly failed?: DdlStatement;
|
|
5
|
+
constructor(args: {
|
|
6
|
+
executed: DdlStatement[];
|
|
7
|
+
failed?: DdlStatement;
|
|
8
|
+
cause: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/migration-error.ts
|
|
2
|
+
// DDL в ClickHouse не транзакционен: упавший посередине $migrate оставляет
|
|
3
|
+
// выполненную часть на нодах. Без списка выполненного вызывающий не знает,
|
|
4
|
+
// что уже применено, поэтому ошибка несёт его вместе с упавшим оператором.
|
|
5
|
+
export class MigrationError extends Error {
|
|
6
|
+
// успешно выполненные до сбоя операторы, в порядке выполнения
|
|
7
|
+
executed;
|
|
8
|
+
// упавший оператор; нет, если сбой внутри докладки таблиц/словарей на
|
|
9
|
+
// ноды — тогда их выполненная часть видна только по самим нодам
|
|
10
|
+
failed;
|
|
11
|
+
constructor(args) {
|
|
12
|
+
const reason = args.cause instanceof Error
|
|
13
|
+
? args.cause.message
|
|
14
|
+
: String(args.cause);
|
|
15
|
+
super(`$migrate failed after ${args.executed.length} statement(s)` +
|
|
16
|
+
(args.failed
|
|
17
|
+
? ` on ${args.failed.node}: ${args.failed.sql}`
|
|
18
|
+
: "") +
|
|
19
|
+
`\n${reason}`, { cause: args.cause });
|
|
20
|
+
this.name = "MigrationError";
|
|
21
|
+
this.executed = args.executed;
|
|
22
|
+
this.failed = args.failed;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quark-fw/clisma",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Prisma-like typed client and schema migrator for ClickHouse",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"clickhouse",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsc -p tsconfig.build.json",
|
|
33
33
|
"typecheck": "tsc --noEmit",
|
|
34
|
-
"test": "tsx --env-file=./src/test/.env ./src/test/index.ts",
|
|
35
|
-
"test
|
|
36
|
-
"prepublishOnly": "npm run typecheck && npm
|
|
34
|
+
"test:integration": "tsx --env-file=./src/test/.env ./src/test/index.ts",
|
|
35
|
+
"test": "tsx --test src/test/unit.test.ts src/test/ddl.test.ts src/test/migrate-nodes.test.ts",
|
|
36
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@clickhouse/client": "^1.17.0"
|