@batadata/cli 0.2.7 → 0.2.9
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/commands/powdb.d.ts +11 -3
- package/dist/commands/powdb.js +101 -20
- package/package.json +1 -1
package/dist/commands/powdb.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ interface IndexInfo {
|
|
|
37
37
|
name: string;
|
|
38
38
|
definition: string;
|
|
39
39
|
}
|
|
40
|
-
interface TableSchema {
|
|
40
|
+
export interface TableSchema {
|
|
41
41
|
schema: string;
|
|
42
42
|
name: string;
|
|
43
43
|
columns: ColumnInfo[];
|
|
@@ -62,14 +62,22 @@ export declare function mapPgType(dataType: string): TypeMapping;
|
|
|
62
62
|
/**
|
|
63
63
|
* Sanitize a Postgres identifier into a valid PowQL identifier (leading
|
|
64
64
|
* alpha/underscore, then alphanumeric/underscore). Reports whether it had to be
|
|
65
|
-
* changed and whether it collides with a PowQL keyword
|
|
66
|
-
*
|
|
65
|
+
* changed and whether it collides with a PowQL keyword. A keyword collision is
|
|
66
|
+
* NOT renamed — the name is kept and `renderPowqlIdent` backtick-quotes it at
|
|
67
|
+
* emit time (PowDB 0.10+ syntax), so exported columns keep their source names.
|
|
67
68
|
*/
|
|
68
69
|
export declare function powqlIdentifier(name: string): {
|
|
69
70
|
ident: string;
|
|
70
71
|
changed: boolean;
|
|
71
72
|
keyword: boolean;
|
|
72
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Render an identifier for emitted PowQL: backtick-quoted when it collides
|
|
76
|
+
* with a reserved word (the 0.10 lexer accepts `` `name` `` in every
|
|
77
|
+
* identifier position), bare otherwise. Identifiers are pre-sanitized by
|
|
78
|
+
* `powqlIdentifier`, so no other escaping is needed inside the backticks.
|
|
79
|
+
*/
|
|
80
|
+
export declare function renderPowqlIdent(ident: string): string;
|
|
73
81
|
/**
|
|
74
82
|
* Escape a string for a PowQL double-quoted literal. The engine lexer honors
|
|
75
83
|
* `\" \\ \n \t`; every other character passes through verbatim. Returns the
|
package/dist/commands/powdb.js
CHANGED
|
@@ -3,16 +3,29 @@ import * as path from "node:path";
|
|
|
3
3
|
import { api, apiError } from "../api.js";
|
|
4
4
|
import { requireToken, isJsonMode, loadConfig } from "../config.js";
|
|
5
5
|
import { colors, log, json, spinner, heading, table } from "../utils/logger.js";
|
|
6
|
+
import { confirmDestructive } from "../utils/prompts.js";
|
|
6
7
|
import { emitError, isRetryable } from "../utils/errors.js";
|
|
7
8
|
import { resolveProjectId, resolveBranchId } from "../link.js";
|
|
8
|
-
// PowQL reserved words a column/table name must not collide with
|
|
9
|
-
// engine lexer keyword set
|
|
10
|
-
//
|
|
9
|
+
// PowQL reserved words a column/table name must not collide with. Mirrors the
|
|
10
|
+
// FULL engine lexer keyword set (crates/query/src/lexer.rs POWQL_KEYWORDS as
|
|
11
|
+
// of PowDB 0.10.0 — which added `schema`/`describe`). A colliding name is
|
|
12
|
+
// emitted backtick-quoted (0.10+ syntax) rather than renamed, so the exported
|
|
13
|
+
// column keeps its source name. Keep this list in sync on PowDB upgrades: a
|
|
14
|
+
// missing word here means a bare identifier that fails to parse on load.
|
|
11
15
|
const POWQL_KEYWORDS = new Set([
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
+
"abs", "add", "alter", "and", "as", "asc", "auto", "avg", "begin",
|
|
17
|
+
"between", "case", "cast", "ceil", "column", "commit", "concat", "conflict",
|
|
18
|
+
"count", "cross", "date_add", "date_diff", "default", "delete",
|
|
19
|
+
"dense_rank", "desc", "describe", "distinct", "drop", "else", "end",
|
|
20
|
+
"exists", "explain", "extract", "false", "filter", "floor", "group",
|
|
21
|
+
"having", "in", "index", "inner", "insert", "is", "join", "left", "length",
|
|
22
|
+
"let", "like", "limit", "link", "lower", "match", "materialize",
|
|
23
|
+
"materialized", "max", "min", "multi", "not", "now", "null", "offset", "on",
|
|
24
|
+
"or", "order", "outer", "over", "partition", "pow", "rank", "refresh",
|
|
25
|
+
"required", "returning", "right", "rollback", "round", "row_number",
|
|
26
|
+
"schema", "select", "sqrt", "substring", "sum", "then", "transaction",
|
|
27
|
+
"trim", "true", "type", "union", "unique", "update", "upper", "upsert",
|
|
28
|
+
"view", "when",
|
|
16
29
|
]);
|
|
17
30
|
/**
|
|
18
31
|
* Map a Postgres data type (as reported by schema introspection) to the PowDB
|
|
@@ -73,8 +86,9 @@ export function mapPgType(dataType) {
|
|
|
73
86
|
/**
|
|
74
87
|
* Sanitize a Postgres identifier into a valid PowQL identifier (leading
|
|
75
88
|
* alpha/underscore, then alphanumeric/underscore). Reports whether it had to be
|
|
76
|
-
* changed and whether it collides with a PowQL keyword
|
|
77
|
-
*
|
|
89
|
+
* changed and whether it collides with a PowQL keyword. A keyword collision is
|
|
90
|
+
* NOT renamed — the name is kept and `renderPowqlIdent` backtick-quotes it at
|
|
91
|
+
* emit time (PowDB 0.10+ syntax), so exported columns keep their source names.
|
|
78
92
|
*/
|
|
79
93
|
export function powqlIdentifier(name) {
|
|
80
94
|
let ident = name.replace(/[^A-Za-z0-9_]/g, "_");
|
|
@@ -82,11 +96,17 @@ export function powqlIdentifier(name) {
|
|
|
82
96
|
ident = "_" + ident;
|
|
83
97
|
}
|
|
84
98
|
const keyword = POWQL_KEYWORDS.has(ident.toLowerCase());
|
|
85
|
-
if (keyword) {
|
|
86
|
-
ident = ident + "_";
|
|
87
|
-
}
|
|
88
99
|
return { ident, changed: ident !== name, keyword };
|
|
89
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Render an identifier for emitted PowQL: backtick-quoted when it collides
|
|
103
|
+
* with a reserved word (the 0.10 lexer accepts `` `name` `` in every
|
|
104
|
+
* identifier position), bare otherwise. Identifiers are pre-sanitized by
|
|
105
|
+
* `powqlIdentifier`, so no other escaping is needed inside the backticks.
|
|
106
|
+
*/
|
|
107
|
+
export function renderPowqlIdent(ident) {
|
|
108
|
+
return POWQL_KEYWORDS.has(ident.toLowerCase()) ? `\`${ident}\`` : ident;
|
|
109
|
+
}
|
|
90
110
|
/**
|
|
91
111
|
* Escape a string for a PowQL double-quoted literal. The engine lexer honors
|
|
92
112
|
* `\" \\ \n \t`; every other character passes through verbatim. Returns the
|
|
@@ -221,7 +241,10 @@ export function planTable(t) {
|
|
|
221
241
|
const warnings = [];
|
|
222
242
|
const { ident: tPowName, changed: nameChanged, keyword } = powqlIdentifier(t.name);
|
|
223
243
|
if (nameChanged) {
|
|
224
|
-
warnings.push(`table renamed to \`${tPowName}
|
|
244
|
+
warnings.push(`table renamed to \`${tPowName}\` (name not a valid PowQL identifier)`);
|
|
245
|
+
}
|
|
246
|
+
else if (keyword) {
|
|
247
|
+
warnings.push(`table name "${t.name}" is a PowQL reserved word — emitted backtick-quoted (requires PowDB 0.10+)`);
|
|
225
248
|
}
|
|
226
249
|
// Single-column UNIQUE constraints → the PowQL `unique` modifier.
|
|
227
250
|
const singleUniqueCols = new Set();
|
|
@@ -263,7 +286,10 @@ export function planTable(t) {
|
|
|
263
286
|
}
|
|
264
287
|
const { ident: colPowName, changed: colChanged, keyword: colKeyword } = powqlIdentifier(col.name);
|
|
265
288
|
if (colChanged) {
|
|
266
|
-
warnings.push(`column ${col.name} renamed to \`${colPowName}
|
|
289
|
+
warnings.push(`column ${col.name} renamed to \`${colPowName}\` (name not a valid PowQL identifier)`);
|
|
290
|
+
}
|
|
291
|
+
else if (colKeyword) {
|
|
292
|
+
warnings.push(`column "${col.name}" is a PowQL reserved word — emitted backtick-quoted (requires PowDB 0.10+)`);
|
|
267
293
|
}
|
|
268
294
|
const def = parsePgDefault(col.defaultValue, mapping.powType);
|
|
269
295
|
let auto = false;
|
|
@@ -297,7 +323,7 @@ export function planTable(t) {
|
|
|
297
323
|
}
|
|
298
324
|
/** Render a table plan as a PowQL `type` DDL block. */
|
|
299
325
|
export function renderTypeDdl(plan) {
|
|
300
|
-
const lines = [`type ${plan.powName} {`];
|
|
326
|
+
const lines = [`type ${renderPowqlIdent(plan.powName)} {`];
|
|
301
327
|
const fieldLines = plan.columns.map((c) => {
|
|
302
328
|
const mods = [];
|
|
303
329
|
if (c.required)
|
|
@@ -308,7 +334,7 @@ export function renderTypeDdl(plan) {
|
|
|
308
334
|
mods.push("auto");
|
|
309
335
|
const prefix = mods.length ? mods.join(" ") + " " : "";
|
|
310
336
|
const def = c.defaultLiteral ? ` default ${c.defaultLiteral}` : "";
|
|
311
|
-
return ` ${prefix}${c.powName}: ${c.powType}${def}`;
|
|
337
|
+
return ` ${prefix}${renderPowqlIdent(c.powName)}: ${c.powType}${def}`;
|
|
312
338
|
});
|
|
313
339
|
lines.push(fieldLines.join(",\n"));
|
|
314
340
|
lines.push("}");
|
|
@@ -337,14 +363,14 @@ export function buildInserts(plan, rows) {
|
|
|
337
363
|
}
|
|
338
364
|
if (res.risky)
|
|
339
365
|
loadRisk = true;
|
|
340
|
-
assigns.push(`${col.powName} := ${res.literal}`);
|
|
366
|
+
assigns.push(`${renderPowqlIdent(col.powName)} := ${res.literal}`);
|
|
341
367
|
anyField = true;
|
|
342
368
|
}
|
|
343
369
|
if (!anyField) {
|
|
344
370
|
skippedRows++;
|
|
345
371
|
continue;
|
|
346
372
|
}
|
|
347
|
-
statements.push(`insert ${plan.powName} { ${assigns.join(", ")} };`);
|
|
373
|
+
statements.push(`insert ${renderPowqlIdent(plan.powName)} { ${assigns.join(", ")} };`);
|
|
348
374
|
}
|
|
349
375
|
const warnings = [];
|
|
350
376
|
for (const [col, reason] of skipNotes) {
|
|
@@ -603,6 +629,7 @@ function buildArtifactHeader(o) {
|
|
|
603
629
|
// which lexes as two minus signs and fails to parse).
|
|
604
630
|
const lines = [
|
|
605
631
|
"# PowDB Stage A export — generated by `bata powdb pull`",
|
|
632
|
+
"# Target: PowDB 0.10+ (reserved identifiers are backtick-quoted)",
|
|
606
633
|
"#",
|
|
607
634
|
`# Source project : ${o.projectName} (${o.projectId})`,
|
|
608
635
|
`# Source branch : ${o.branchName} (${o.branchId})`,
|
|
@@ -834,7 +861,57 @@ async function powdbLifecycle(action, args) {
|
|
|
834
861
|
return;
|
|
835
862
|
}
|
|
836
863
|
log();
|
|
837
|
-
|
|
864
|
+
const ver = res.data.server_version;
|
|
865
|
+
log(` ${colors.green(">")} ${colors.cyan(projectId)} ${colors.dim("engine=powdb state=")}${res.data.state}${ver ? colors.dim(" server=") + ver : ""}`);
|
|
866
|
+
log();
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* `bata powdb upgrade --to <version>` — pin the project's server to a new
|
|
870
|
+
* engine version. Server-side this is park→backup→flip→wake→verify with
|
|
871
|
+
* automatic rollback to the pre-upgrade backup on failure. Storage-format
|
|
872
|
+
* releases (0.11+) make a verified upgrade a ONE-WAY door once new-format
|
|
873
|
+
* data is written, so this always confirms unless --yes/--json.
|
|
874
|
+
*/
|
|
875
|
+
async function powdbUpgrade(args) {
|
|
876
|
+
const token = requireToken();
|
|
877
|
+
const jsonMode = isJsonMode();
|
|
878
|
+
let toVersion;
|
|
879
|
+
const rest = [];
|
|
880
|
+
for (let i = 0; i < args.length; i++) {
|
|
881
|
+
const a = args[i];
|
|
882
|
+
if (a === "--to")
|
|
883
|
+
toVersion = args[++i];
|
|
884
|
+
else if (a.startsWith("--to="))
|
|
885
|
+
toVersion = a.slice("--to=".length);
|
|
886
|
+
else
|
|
887
|
+
rest.push(a);
|
|
888
|
+
}
|
|
889
|
+
const positional = rest.find((a) => !a.startsWith("-"));
|
|
890
|
+
const projectId = powdbProjectFlag(rest) ?? positional ?? resolvePowdbProject(rest);
|
|
891
|
+
if (!toVersion || !/^\d+\.\d+\.\d+$/.test(toVersion)) {
|
|
892
|
+
emitError("MISSING_ARG", "A target version is required.", "Usage: bata powdb upgrade --to <x.y.z> [--project <id>]");
|
|
893
|
+
}
|
|
894
|
+
const ok = await confirmDestructive(`Upgrade ${colors.cyan(projectId)} to powdb-server ${colors.cyan(toVersion)}? A format-changing release cannot be downgraded once new data is written.`);
|
|
895
|
+
if (!ok) {
|
|
896
|
+
log(" Aborted.");
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
const s = jsonMode ? null : spinner(`Upgrading ${projectId} to ${toVersion} (park -> backup -> flip -> wake -> verify)`);
|
|
900
|
+
const res = await api.post(`/v1/powdb/${encodeURIComponent(projectId)}/upgrade`, { to_version: toVersion }, token);
|
|
901
|
+
s?.stop();
|
|
902
|
+
if (!res.ok) {
|
|
903
|
+
emitError(res.status === 404 ? "NOT_FOUND"
|
|
904
|
+
: res.status === 401 || res.status === 403 ? "INVALID_KEY"
|
|
905
|
+
: res.status >= 500 || res.status === 0 ? "API_UNAVAILABLE"
|
|
906
|
+
: "CLI_ERROR", apiError(res, "PowDB upgrade failed (instance rolled back to its pre-upgrade state)"), "");
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (jsonMode) {
|
|
910
|
+
json({ project_id: projectId, ...res.data });
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
log();
|
|
914
|
+
log(` ${colors.green(">")} ${colors.cyan(projectId)} upgraded to powdb-server ${colors.cyan(res.data.server_version)} ${colors.dim("(verified)")}`);
|
|
838
915
|
log();
|
|
839
916
|
}
|
|
840
917
|
// ─── Help + dispatch ──────────────────────────────────────────────────────────
|
|
@@ -857,6 +934,8 @@ function powdbHelp() {
|
|
|
857
934
|
log(` ${colors.cyan("status")} Show the server state (running | parked) [--project <id>]`);
|
|
858
935
|
log(` ${colors.cyan("park")} Scale the server to zero (WAL is durable) [--project <id>]`);
|
|
859
936
|
log(` ${colors.cyan("wake")} Start a parked server (~tens of ms) [--project <id>]`);
|
|
937
|
+
log(` ${colors.cyan("upgrade")} Pin the server to a new engine version --to <x.y.z> [--project <id>]`);
|
|
938
|
+
log(` ${colors.dim(" park -> backup -> flip -> wake -> verify, auto-rollback on failure")}`);
|
|
860
939
|
log();
|
|
861
940
|
log(` ${colors.dim("Options (pull):")}`);
|
|
862
941
|
log(` ${colors.dim("--project <id> override the linked/default project")}`);
|
|
@@ -892,10 +971,12 @@ export async function handlePowdb(args) {
|
|
|
892
971
|
return powdbLifecycle("park", args.slice(1));
|
|
893
972
|
case "wake":
|
|
894
973
|
return powdbLifecycle("wake", args.slice(1));
|
|
974
|
+
case "upgrade":
|
|
975
|
+
return powdbUpgrade(args.slice(1));
|
|
895
976
|
case undefined:
|
|
896
977
|
powdbHelp();
|
|
897
978
|
return;
|
|
898
979
|
default:
|
|
899
|
-
emitError("INVALID_FLAG", `Unknown subcommand: powdb ${sub}`, "Available: pull, query, exec, status, park, wake. Run `bata powdb --help`.");
|
|
980
|
+
emitError("INVALID_FLAG", `Unknown subcommand: powdb ${sub}`, "Available: pull, query, exec, status, park, wake, upgrade. Run `bata powdb --help`.");
|
|
900
981
|
}
|
|
901
982
|
}
|