(args: string[])
| 565 | } |
| 566 | |
| 567 | export default async function dbExec(args: string[]): Promise<void> { |
| 568 | const parsed = parseArgs(args); |
| 569 | |
| 570 | if (parsed.help === "true") { |
| 571 | console.log(`Usage: pnpm action db-exec --sql "<statement>" [options] |
| 572 | pnpm action db-exec --statements '[{"sql":"UPDATE ...","args":[...]}]' [options] |
| 573 | |
| 574 | Options: |
| 575 | --sql <stmt> Single INSERT / UPDATE / DELETE / REPLACE statement |
| 576 | --args <json> JSON array of positional SQL bind parameters for --sql |
| 577 | --statements <json> JSON array of {sql, args?}; runs in one transaction |
| 578 | --db <path> Path to SQLite database (default: data/app.db) |
| 579 | --format json Output as JSON |
| 580 | --help Show this help message`); |
| 581 | return; |
| 582 | } |
| 583 | |
| 584 | const statements = parseStatements(parsed).map((statement, index) => ({ |
| 585 | sql: validateWriteSql(statement.sql, index + 1), |
| 586 | args: statement.args, |
| 587 | })); |
| 588 | |
| 589 | // Resolve database URL: --db flag → DATABASE_URL env → default file path |
| 590 | let url: string; |
| 591 | if (parsed.db) { |
| 592 | url = "file:" + path.resolve(parsed.db); |
| 593 | } else if (getDatabaseUrl()) { |
| 594 | url = getDatabaseUrl(); |
| 595 | } else { |
| 596 | url = "file:" + path.resolve(process.cwd(), "data", "app.db"); |
| 597 | } |
| 598 | |
| 599 | // Postgres path |
| 600 | if (isPostgresUrl(url)) { |
| 601 | const { default: pg } = await import("postgres"); |
| 602 | const pgSql = pg(url); |
| 603 | try { |
| 604 | // Set up user-scoped temp views in production |
| 605 | const scoping = await buildScopingPostgres(pgSql); |
| 606 | |
| 607 | const results: DbExecResult[] = []; |
| 608 | await pgSql.begin(async (tx: any) => { |
| 609 | try { |
| 610 | // For UPDATE/DELETE: temp views scope to current user's rows. |
| 611 | // Creating and dropping them inside the same transaction keeps |
| 612 | // pooled Postgres backends from retaining session-local views. |
| 613 | for (const stmt of scoping.setup) { |
| 614 | await tx.unsafe(stmt); |
| 615 | } |
| 616 | |
| 617 | for (let i = 0; i < statements.length; i++) { |
| 618 | const statement = statements[i]; |
| 619 | const hasReturning = /\bRETURNING\b/i.test(statement.sql); |
| 620 | const finalSql = normalizePostgresSql( |
| 621 | injectOwnership(statement.sql, scoping), |
| 622 | statement.args, |
| 623 | ); |
| 624 | try { |
no test coverage detected