@batadata/cli 0.2.7 → 0.2.8

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.
@@ -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, so the caller can flag
66
- * the rename honestly rather than emit a name that means something else.
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
@@ -5,14 +5,26 @@ import { requireToken, isJsonMode, loadConfig } from "../config.js";
5
5
  import { colors, log, json, spinner, heading, table } from "../utils/logger.js";
6
6
  import { emitError, isRetryable } from "../utils/errors.js";
7
7
  import { resolveProjectId, resolveBranchId } from "../link.js";
8
- // PowQL reserved words a column/table name must not collide with (from the
9
- // engine lexer keyword set). A colliding name is renamed and flagged rather
10
- // than silently producing a broken `type`/`insert`.
8
+ // PowQL reserved words a column/table name must not collide with. Mirrors the
9
+ // FULL engine lexer keyword set (crates/query/src/lexer.rs POWQL_KEYWORDS as
10
+ // of PowDB 0.10.0 which added `schema`/`describe`). A colliding name is
11
+ // emitted backtick-quoted (0.10+ syntax) rather than renamed, so the exported
12
+ // column keeps its source name. Keep this list in sync on PowDB upgrades: a
13
+ // missing word here means a bare identifier that fails to parse on load.
11
14
  const POWQL_KEYWORDS = new Set([
12
- "type", "filter", "order", "limit", "offset", "insert", "update", "delete",
13
- "default", "upsert", "returning", "group", "having", "distinct", "and", "or",
14
- "not", "is", "null", "true", "false", "asc", "desc", "like", "in", "between",
15
- "required", "unique", "auto", "count", "sum", "avg", "min", "max",
15
+ "abs", "add", "alter", "and", "as", "asc", "auto", "avg", "begin",
16
+ "between", "case", "cast", "ceil", "column", "commit", "concat", "conflict",
17
+ "count", "cross", "date_add", "date_diff", "default", "delete",
18
+ "dense_rank", "desc", "describe", "distinct", "drop", "else", "end",
19
+ "exists", "explain", "extract", "false", "filter", "floor", "group",
20
+ "having", "in", "index", "inner", "insert", "is", "join", "left", "length",
21
+ "let", "like", "limit", "link", "lower", "match", "materialize",
22
+ "materialized", "max", "min", "multi", "not", "now", "null", "offset", "on",
23
+ "or", "order", "outer", "over", "partition", "pow", "rank", "refresh",
24
+ "required", "returning", "right", "rollback", "round", "row_number",
25
+ "schema", "select", "sqrt", "substring", "sum", "then", "transaction",
26
+ "trim", "true", "type", "union", "unique", "update", "upper", "upsert",
27
+ "view", "when",
16
28
  ]);
17
29
  /**
18
30
  * Map a Postgres data type (as reported by schema introspection) to the PowDB
@@ -73,8 +85,9 @@ export function mapPgType(dataType) {
73
85
  /**
74
86
  * Sanitize a Postgres identifier into a valid PowQL identifier (leading
75
87
  * alpha/underscore, then alphanumeric/underscore). Reports whether it had to be
76
- * changed and whether it collides with a PowQL keyword, so the caller can flag
77
- * the rename honestly rather than emit a name that means something else.
88
+ * changed and whether it collides with a PowQL keyword. A keyword collision is
89
+ * NOT renamed the name is kept and `renderPowqlIdent` backtick-quotes it at
90
+ * emit time (PowDB 0.10+ syntax), so exported columns keep their source names.
78
91
  */
79
92
  export function powqlIdentifier(name) {
80
93
  let ident = name.replace(/[^A-Za-z0-9_]/g, "_");
@@ -82,11 +95,17 @@ export function powqlIdentifier(name) {
82
95
  ident = "_" + ident;
83
96
  }
84
97
  const keyword = POWQL_KEYWORDS.has(ident.toLowerCase());
85
- if (keyword) {
86
- ident = ident + "_";
87
- }
88
98
  return { ident, changed: ident !== name, keyword };
89
99
  }
100
+ /**
101
+ * Render an identifier for emitted PowQL: backtick-quoted when it collides
102
+ * with a reserved word (the 0.10 lexer accepts `` `name` `` in every
103
+ * identifier position), bare otherwise. Identifiers are pre-sanitized by
104
+ * `powqlIdentifier`, so no other escaping is needed inside the backticks.
105
+ */
106
+ export function renderPowqlIdent(ident) {
107
+ return POWQL_KEYWORDS.has(ident.toLowerCase()) ? `\`${ident}\`` : ident;
108
+ }
90
109
  /**
91
110
  * Escape a string for a PowQL double-quoted literal. The engine lexer honors
92
111
  * `\" \\ \n \t`; every other character passes through verbatim. Returns the
@@ -221,7 +240,10 @@ export function planTable(t) {
221
240
  const warnings = [];
222
241
  const { ident: tPowName, changed: nameChanged, keyword } = powqlIdentifier(t.name);
223
242
  if (nameChanged) {
224
- warnings.push(`table renamed to \`${tPowName}\`${keyword ? " (collides with a PowQL keyword)" : " (name not a valid PowQL identifier)"}`);
243
+ warnings.push(`table renamed to \`${tPowName}\` (name not a valid PowQL identifier)`);
244
+ }
245
+ else if (keyword) {
246
+ warnings.push(`table name "${t.name}" is a PowQL reserved word — emitted backtick-quoted (requires PowDB 0.10+)`);
225
247
  }
226
248
  // Single-column UNIQUE constraints → the PowQL `unique` modifier.
227
249
  const singleUniqueCols = new Set();
@@ -263,7 +285,10 @@ export function planTable(t) {
263
285
  }
264
286
  const { ident: colPowName, changed: colChanged, keyword: colKeyword } = powqlIdentifier(col.name);
265
287
  if (colChanged) {
266
- warnings.push(`column ${col.name} renamed to \`${colPowName}\`${colKeyword ? " (collides with a PowQL keyword)" : ""}`);
288
+ warnings.push(`column ${col.name} renamed to \`${colPowName}\` (name not a valid PowQL identifier)`);
289
+ }
290
+ else if (colKeyword) {
291
+ warnings.push(`column "${col.name}" is a PowQL reserved word — emitted backtick-quoted (requires PowDB 0.10+)`);
267
292
  }
268
293
  const def = parsePgDefault(col.defaultValue, mapping.powType);
269
294
  let auto = false;
@@ -297,7 +322,7 @@ export function planTable(t) {
297
322
  }
298
323
  /** Render a table plan as a PowQL `type` DDL block. */
299
324
  export function renderTypeDdl(plan) {
300
- const lines = [`type ${plan.powName} {`];
325
+ const lines = [`type ${renderPowqlIdent(plan.powName)} {`];
301
326
  const fieldLines = plan.columns.map((c) => {
302
327
  const mods = [];
303
328
  if (c.required)
@@ -308,7 +333,7 @@ export function renderTypeDdl(plan) {
308
333
  mods.push("auto");
309
334
  const prefix = mods.length ? mods.join(" ") + " " : "";
310
335
  const def = c.defaultLiteral ? ` default ${c.defaultLiteral}` : "";
311
- return ` ${prefix}${c.powName}: ${c.powType}${def}`;
336
+ return ` ${prefix}${renderPowqlIdent(c.powName)}: ${c.powType}${def}`;
312
337
  });
313
338
  lines.push(fieldLines.join(",\n"));
314
339
  lines.push("}");
@@ -337,14 +362,14 @@ export function buildInserts(plan, rows) {
337
362
  }
338
363
  if (res.risky)
339
364
  loadRisk = true;
340
- assigns.push(`${col.powName} := ${res.literal}`);
365
+ assigns.push(`${renderPowqlIdent(col.powName)} := ${res.literal}`);
341
366
  anyField = true;
342
367
  }
343
368
  if (!anyField) {
344
369
  skippedRows++;
345
370
  continue;
346
371
  }
347
- statements.push(`insert ${plan.powName} { ${assigns.join(", ")} };`);
372
+ statements.push(`insert ${renderPowqlIdent(plan.powName)} { ${assigns.join(", ")} };`);
348
373
  }
349
374
  const warnings = [];
350
375
  for (const [col, reason] of skipNotes) {
@@ -603,6 +628,7 @@ function buildArtifactHeader(o) {
603
628
  // which lexes as two minus signs and fails to parse).
604
629
  const lines = [
605
630
  "# PowDB Stage A export — generated by `bata powdb pull`",
631
+ "# Target: PowDB 0.10+ (reserved identifiers are backtick-quoted)",
606
632
  "#",
607
633
  `# Source project : ${o.projectName} (${o.projectId})`,
608
634
  `# Source branch : ${o.branchName} (${o.branchId})`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@batadata/cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "CLI for BataDB — serverless Postgres platform",
5
5
  "bin": {
6
6
  "bata": "./dist/index.js"