clickhouse-migrations 1.3.2 → 1.4.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/README.md +36 -1
- package/lib/migrate.js +12 -2
- package/lib/sql-parse.d.ts +2 -1
- package/lib/sql-parse.js +51 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -63,6 +63,10 @@ For TLS/HTTPS connections, you can provide a custom CA certificate and optional
|
|
|
63
63
|
(optional) Skip database creation,
|
|
64
64
|
set to 'true' to enable
|
|
65
65
|
(--skip-db-creation)
|
|
66
|
+
CH_MIGRATIONS_SUBSTITUTE_ENV
|
|
67
|
+
(optional) Substitute ${VAR} in migration
|
|
68
|
+
files from the environment at apply time,
|
|
69
|
+
set to 'true' to enable
|
|
66
70
|
|
|
67
71
|
|
|
68
72
|
CLI executions examples
|
|
@@ -99,7 +103,7 @@ For TLS/HTTPS connections, you can provide a custom CA certificate and optional
|
|
|
99
103
|
Migration file example:
|
|
100
104
|
(e.g., located at /app/clickhouse/migrations/1_init.sql)
|
|
101
105
|
|
|
102
|
-
```
|
|
106
|
+
```sql
|
|
103
107
|
-- an example of migration file 1_init.sql
|
|
104
108
|
|
|
105
109
|
SET allow_experimental_json_type = 1;
|
|
@@ -115,3 +119,34 @@ SAMPLE BY session_id
|
|
|
115
119
|
ORDER BY (session_id)
|
|
116
120
|
SETTINGS index_granularity = 8192;
|
|
117
121
|
```
|
|
122
|
+
|
|
123
|
+
### Environment variable substitution (optional)
|
|
124
|
+
|
|
125
|
+
Some migrations need a value that differs per environment, for example: the host of a Postgres instance behind a `DICTIONARY`, a cluster name, a bucket, and so on.
|
|
126
|
+
|
|
127
|
+
Enable `CH_MIGRATIONS_SUBSTITUTE_ENV=true` and `${VAR}` placeholders in migration files are replaced with the matching environment variables when the migration is applied. It's off by default, so existing migrations behave exactly as before.
|
|
128
|
+
|
|
129
|
+
Rules:
|
|
130
|
+
|
|
131
|
+
- `${NAME}` is replaced with the value of the `NAME` environment variable.
|
|
132
|
+
- If `NAME` is unset, the migration fails - the placeholder is never left behind in the SQL.
|
|
133
|
+
- To keep a literal `${NAME}` (for example in a comment or a string), escape it as `$${NAME}`.
|
|
134
|
+
- A malformed (`${PG-HOST}`, `${}`) or unterminated (`${PG_HOST`) placeholder also fails, so nothing is ever silently passed through.
|
|
135
|
+
|
|
136
|
+
Substitution is textual (like `envsubst`): it doesn't parse SQL and doesn't escape values, so anything placed inside quotes must be quoting-safe.
|
|
137
|
+
|
|
138
|
+
A typical use is an idempotent dictionary pointing at a per-environment source:
|
|
139
|
+
|
|
140
|
+
```sql
|
|
141
|
+
CREATE OR REPLACE DICTIONARY my_db.dict_offers
|
|
142
|
+
(
|
|
143
|
+
`id` UUID,
|
|
144
|
+
`name` String DEFAULT ''
|
|
145
|
+
)
|
|
146
|
+
PRIMARY KEY id
|
|
147
|
+
SOURCE(POSTGRESQL(HOST '${PG_HOST}' PORT ${PG_PORT} USER '${PG_USER}' PASSWORD '${PG_PASSWORD}' DB '${PG_DB}' TABLE 'offers'))
|
|
148
|
+
LIFETIME(MIN 0 MAX 300)
|
|
149
|
+
LAYOUT(COMPLEX_KEY_HASHED());
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
_Please note:_ When substitution is enabled, the checksum is taken from the **raw file**, before substitution. This keeps a migration stable across environments and keeps secrets out of `_migrations`. Two consequences follow: an already-applied migration is not re-run when a variable changes (ship a new migration instead), and the substituted SQL still reaches ClickHouse, so it may appear in `system.query_log` or server logs.
|
package/lib/migrate.js
CHANGED
|
@@ -201,9 +201,19 @@ const apply_migrations = async (client, migrations, migrations_home) => {
|
|
|
201
201
|
// Skip if a migration is already applied.
|
|
202
202
|
continue;
|
|
203
203
|
}
|
|
204
|
+
// Substitute ${VAR} from the environment (opt-in) at apply time only. The
|
|
205
|
+
// checksum above is over the raw file, so it stays stable across environments.
|
|
206
|
+
let sql;
|
|
207
|
+
try {
|
|
208
|
+
sql = (0, sql_parse_1.substitute_env)(content);
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
log('error', `the migration ${migration.file} has an error.`, e instanceof Error ? e.message : String(e));
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
204
214
|
// Extract sql from the migration.
|
|
205
|
-
const queries = (0, sql_parse_1.sql_queries)(
|
|
206
|
-
const sets = (0, sql_parse_1.sql_sets)(
|
|
215
|
+
const queries = (0, sql_parse_1.sql_queries)(sql);
|
|
216
|
+
const sets = (0, sql_parse_1.sql_sets)(sql);
|
|
207
217
|
for (const query of queries) {
|
|
208
218
|
try {
|
|
209
219
|
await client.command({
|
package/lib/sql-parse.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
declare const substitute_env: (content: string) => string;
|
|
1
2
|
declare const sql_queries: (content: string) => string[];
|
|
2
3
|
declare const sql_sets: (content: string) => {
|
|
3
4
|
[key: string]: string;
|
|
4
5
|
};
|
|
5
|
-
export { sql_queries, sql_sets };
|
|
6
|
+
export { sql_queries, sql_sets, substitute_env };
|
package/lib/sql-parse.js
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sql_sets = exports.sql_queries = void 0;
|
|
3
|
+
exports.substitute_env = exports.sql_sets = exports.sql_queries = void 0;
|
|
4
|
+
// Replace ${NAME} in migration content with env vars. Opt-in via
|
|
5
|
+
// CH_MIGRATIONS_SUBSTITUTE_ENV=true; otherwise returns content unchanged.
|
|
6
|
+
// Use $${NAME} to keep a literal ${NAME}. Throws on an unset variable or a
|
|
7
|
+
// malformed/unterminated placeholder, so nothing is ever silently left as-is.
|
|
8
|
+
// Note: the caller checksums the raw file, so substitution only affects the
|
|
9
|
+
// SQL sent to ClickHouse, not what's stored in _migrations.
|
|
10
|
+
const substitute_env = (content) => {
|
|
11
|
+
if (process.env.CH_MIGRATIONS_SUBSTITUTE_ENV !== 'true') {
|
|
12
|
+
return content;
|
|
13
|
+
}
|
|
14
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
15
|
+
let result = '';
|
|
16
|
+
let position = 0;
|
|
17
|
+
while (position < content.length) {
|
|
18
|
+
// Escaped placeholder: $${NAME} -> ${NAME} (kept literal, not substituted).
|
|
19
|
+
if (content.startsWith('$${', position)) {
|
|
20
|
+
const end = content.indexOf('}', position + 3);
|
|
21
|
+
if (end === -1) {
|
|
22
|
+
throw new Error(`unterminated escaped placeholder at position ${position}`);
|
|
23
|
+
}
|
|
24
|
+
// Drop only the leading "$" and keep the rest as a literal.
|
|
25
|
+
result += content.slice(position + 1, end + 1);
|
|
26
|
+
position = end + 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Environment placeholder: ${NAME}.
|
|
30
|
+
if (content.startsWith('${', position)) {
|
|
31
|
+
const end = content.indexOf('}', position + 2);
|
|
32
|
+
if (end === -1) {
|
|
33
|
+
throw new Error(`unterminated environment placeholder at position ${position}`);
|
|
34
|
+
}
|
|
35
|
+
const placeholder = content.slice(position, end + 1);
|
|
36
|
+
const name = content.slice(position + 2, end);
|
|
37
|
+
if (!ENV_NAME.test(name)) {
|
|
38
|
+
throw new Error(`invalid environment placeholder "${placeholder}"; expected \${NAME} or escape it as $${placeholder}`);
|
|
39
|
+
}
|
|
40
|
+
const value = process.env[name];
|
|
41
|
+
if (value === undefined) {
|
|
42
|
+
throw new Error(`migration references ${placeholder}, but environment variable ${name} is not set`);
|
|
43
|
+
}
|
|
44
|
+
result += value;
|
|
45
|
+
position = end + 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
result += content[position];
|
|
49
|
+
position += 1;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
exports.substitute_env = substitute_env;
|
|
4
54
|
// Extract sql queries from migrations.
|
|
5
55
|
const sql_queries = (content) => {
|
|
6
56
|
const queries = content
|
package/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.4.0";
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clickhouse-migrations",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "ClickHouse Migrations",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clickhouse-migrations": "lib/cli.js"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"postversion": "git push && git push --tags"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@clickhouse/client": "^1.
|
|
31
|
+
"@clickhouse/client": "^1.23.1",
|
|
32
32
|
"commander": "^14.0.3"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|