@hienlh/ppm 0.13.13 → 0.13.14
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/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.14] - 2026-04-24
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **DB CLI run multi-statement**: PostgreSQL `ppm db run` now splits SQL into individual statements and executes within `sql.begin()` transaction. Handles strings, comments, dollar-quoting. Strips user-supplied `BEGIN`/`COMMIT`/`ROLLBACK` to avoid conflicts with managed transaction
|
|
7
|
+
|
|
3
8
|
## [0.13.13] - 2026-04-24
|
|
4
9
|
|
|
5
10
|
### Added
|
|
6
|
-
- **DB CLI run command**: `ppm db run <name> <file.sql>` executes SQL files against saved connections. Supports multi-statement files and transactions (`BEGIN...COMMIT`). Respects readonly flag. Works with both SQLite (`db.exec()`) and PostgreSQL
|
|
11
|
+
- **DB CLI run command**: `ppm db run <name> <file.sql>` executes SQL files against saved connections. Supports multi-statement files and transactions (`BEGIN...COMMIT`). Respects readonly flag. Works with both SQLite (`db.exec()`) and PostgreSQL
|
|
7
12
|
|
|
8
13
|
## [0.13.12] - 2026-04-24
|
|
9
14
|
|
|
@@ -71,4 +71,4 @@ This skill covers the `ppm` CLI, its HTTP API, and its config DB. It does **not*
|
|
|
71
71
|
- Third-party extensions (inspect via `ppm ext list`).
|
|
72
72
|
- The Claude Agent SDK internals (separate skill).
|
|
73
73
|
|
|
74
|
-
<!-- Generated for PPM v0.13.
|
|
74
|
+
<!-- Generated for PPM v0.13.14 at build time. Re-run `ppm export skill --install` to refresh. -->
|
|
@@ -201,4 +201,4 @@ _Base URL: `http://localhost:8080` (default; override via `ppm config set port <
|
|
|
201
201
|
- `ws://<host>/ws/terminal` — PTY terminal multiplexer
|
|
202
202
|
- `ws://<host>/ws/extensions` — extension host channel
|
|
203
203
|
|
|
204
|
-
<!-- Generated from src/server/routes/ for PPM v0.13.
|
|
204
|
+
<!-- Generated from src/server/routes/ for PPM v0.13.14 -->
|
package/package.json
CHANGED
|
@@ -401,13 +401,9 @@ export function registerDbCommands(program: Command): void {
|
|
|
401
401
|
|
|
402
402
|
if (conn.type === "postgres") {
|
|
403
403
|
const { postgresService } = await import("../../services/postgres.service.ts");
|
|
404
|
-
const result = await postgresService.
|
|
404
|
+
const result = await postgresService.executeScript(cfg.connectionString!, sql);
|
|
405
405
|
await postgresService.closeAll();
|
|
406
|
-
|
|
407
|
-
formatRows(result.columns, result.rows);
|
|
408
|
-
} else {
|
|
409
|
-
console.log(`${C.green}OK${C.reset} — ${result.rowsAffected} row(s) affected (${result.executionTimeMs}ms)`);
|
|
410
|
-
}
|
|
406
|
+
console.log(`${C.green}OK${C.reset} — ${result.statementsRun} statement(s) executed (${result.executionTimeMs}ms)`);
|
|
411
407
|
} else {
|
|
412
408
|
const { sqliteService } = await import("../../services/sqlite.service.ts");
|
|
413
409
|
const result = sqliteService.executeScript(cfg.path!, cfg.path!, sql);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split a SQL script into individual statements, respecting:
|
|
3
|
+
* - Single-quoted strings ('hello; world')
|
|
4
|
+
* - Double-quoted identifiers ("my;table")
|
|
5
|
+
* - Dollar-quoted strings ($$...$$, $tag$...$tag$)
|
|
6
|
+
* - Single-line comments (-- ...)
|
|
7
|
+
* - Multi-line comments (/* ... */)
|
|
8
|
+
*
|
|
9
|
+
* Returns non-empty trimmed statements without trailing semicolons.
|
|
10
|
+
*/
|
|
11
|
+
export function splitSqlStatements(script: string): string[] {
|
|
12
|
+
const statements: string[] = [];
|
|
13
|
+
let current = "";
|
|
14
|
+
let i = 0;
|
|
15
|
+
const len = script.length;
|
|
16
|
+
|
|
17
|
+
while (i < len) {
|
|
18
|
+
const ch = script[i]!;
|
|
19
|
+
|
|
20
|
+
// Single-line comment
|
|
21
|
+
if (ch === "-" && script[i + 1] === "-") {
|
|
22
|
+
const end = script.indexOf("\n", i);
|
|
23
|
+
const lineEnd = end === -1 ? len : end + 1;
|
|
24
|
+
current += script.slice(i, lineEnd);
|
|
25
|
+
i = lineEnd;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Multi-line comment
|
|
30
|
+
if (ch === "/" && script[i + 1] === "*") {
|
|
31
|
+
const end = script.indexOf("*/", i + 2);
|
|
32
|
+
const blockEnd = end === -1 ? len : end + 2;
|
|
33
|
+
current += script.slice(i, blockEnd);
|
|
34
|
+
i = blockEnd;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Single-quoted string
|
|
39
|
+
if (ch === "'") {
|
|
40
|
+
let j = i + 1;
|
|
41
|
+
while (j < len) {
|
|
42
|
+
if (script[j] === "'" && script[j + 1] === "'") { j += 2; continue; }
|
|
43
|
+
if (script[j] === "'") { j++; break; }
|
|
44
|
+
j++;
|
|
45
|
+
}
|
|
46
|
+
current += script.slice(i, j);
|
|
47
|
+
i = j;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Double-quoted identifier
|
|
52
|
+
if (ch === '"') {
|
|
53
|
+
let j = i + 1;
|
|
54
|
+
while (j < len) {
|
|
55
|
+
if (script[j] === '"' && script[j + 1] === '"') { j += 2; continue; }
|
|
56
|
+
if (script[j] === '"') { j++; break; }
|
|
57
|
+
j++;
|
|
58
|
+
}
|
|
59
|
+
current += script.slice(i, j);
|
|
60
|
+
i = j;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Dollar-quoted string (PostgreSQL): $$...$$ or $tag$...$tag$
|
|
65
|
+
if (ch === "$") {
|
|
66
|
+
const tagMatch = script.slice(i).match(/^(\$[A-Za-z0-9_]*\$)/);
|
|
67
|
+
if (tagMatch) {
|
|
68
|
+
const tag = tagMatch[1]!;
|
|
69
|
+
const endIdx = script.indexOf(tag, i + tag.length);
|
|
70
|
+
const blockEnd = endIdx === -1 ? len : endIdx + tag.length;
|
|
71
|
+
current += script.slice(i, blockEnd);
|
|
72
|
+
i = blockEnd;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Statement separator
|
|
78
|
+
if (ch === ";") {
|
|
79
|
+
const trimmed = current.trim();
|
|
80
|
+
if (trimmed) statements.push(trimmed);
|
|
81
|
+
current = "";
|
|
82
|
+
i++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
current += ch;
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Last statement (no trailing semicolon)
|
|
91
|
+
const trimmed = current.trim();
|
|
92
|
+
if (trimmed) statements.push(trimmed);
|
|
93
|
+
|
|
94
|
+
return statements;
|
|
95
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import postgres from "postgres";
|
|
2
|
+
import { splitSqlStatements } from "./database/split-sql-statements.ts";
|
|
2
3
|
|
|
3
4
|
export interface PgTableInfo {
|
|
4
5
|
name: string;
|
|
@@ -180,6 +181,23 @@ class PostgresService {
|
|
|
180
181
|
return { columns: [], rows: [], rowsAffected: result.count ?? 0, changeType: "modify", executionTimeMs };
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
/** Execute multi-statement SQL script inside a transaction via sql.begin().
|
|
185
|
+
* Strips user-supplied BEGIN/COMMIT/ROLLBACK since sql.begin() manages the transaction. */
|
|
186
|
+
async executeScript(connectionString: string, scriptText: string): Promise<{ statementsRun: number; executionTimeMs: number }> {
|
|
187
|
+
const sql = this.connect(connectionString);
|
|
188
|
+
const txControl = /^(BEGIN|COMMIT|ROLLBACK|END)(;|\s|$)/i;
|
|
189
|
+
const statements = splitSqlStatements(scriptText).filter((s) => !txControl.test(s));
|
|
190
|
+
if (statements.length === 0) return { statementsRun: 0, executionTimeMs: 0 };
|
|
191
|
+
|
|
192
|
+
const start = performance.now();
|
|
193
|
+
await sql.begin(async (tx) => {
|
|
194
|
+
for (const stmt of statements) {
|
|
195
|
+
await tx.unsafe(stmt);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return { statementsRun: statements.length, executionTimeMs: Math.round(performance.now() - start) };
|
|
199
|
+
}
|
|
200
|
+
|
|
183
201
|
/** Update a single cell value */
|
|
184
202
|
async updateCell(
|
|
185
203
|
connectionString: string, table: string, schema = "public",
|