import type { BunMariaDBConfig } from "../../types"; import dbHandler from "../db-handler"; type SchemaRunState = { /** Record statements instead of executing them */ dryRun: boolean; statements: string[]; /** * Destructive statements that may run. `true` allows all of them; * otherwise only exact matches are allowed. */ allowDestructive: true | Set; }; let schemaRunState: SchemaRunState | null = null; /** * Statements that discard stored data (tables or column values). */ export function isDestructiveSchemaStatement(query: string): boolean { return /^\s*DROP\s+TABLE\b/i.test(query) || /\bDROP\s+COLUMN\b/i.test(query); } /** * Run `fn` with schema statements recorded but not executed. * Reads (information_schema) still run, so the plan is a close approximation: * steps that depend on earlier changes (e.g. after a table rename) may differ. */ export async function planSchemaQueries( fn: () => Promise, ): Promise { const previous = schemaRunState; schemaRunState = { dryRun: true, statements: [], allowDestructive: true }; try { await fn(); return schemaRunState.statements; } finally { schemaRunState = previous; } } /** * Run `fn`, refusing any destructive statement that was not approved. */ export async function runSchemaQueriesGuarded( fn: () => Promise, allowDestructive: true | Set, ): Promise { const previous = schemaRunState; schemaRunState = { dryRun: false, statements: [], allowDestructive }; try { await fn(); } finally { schemaRunState = previous; } } export default async function runSchemaQuery({ query, values, config, }: { query: string; values?: any[]; config?: BunMariaDBConfig; }): Promise { if (schemaRunState) { schemaRunState.statements.push(query); if (schemaRunState.dryRun) { return; } const allowed = schemaRunState.allowDestructive; if ( allowed !== true && isDestructiveSchemaStatement(query) && !allowed.has(query) ) { throw new Error( `Refusing unapproved destructive statement: ${query}. Re-run with --dry-run to review, or --yes to allow it.`, ); } } const res = await dbHandler({ query, values, config, }); if (!res.success) { throw new Error( `Database query failed: ${query} ... ERROR: ${res.error || res.msg}`, ); } } export async function querySchemaRows>({ query, values, config, }: { query: string; values?: any[]; config?: BunMariaDBConfig; }): Promise { const res = await dbHandler({ query, values, config, }); if (!res.success) { throw new Error( `Database query failed: ${query} ... ERROR: ${res.error || res.msg}`, ); } return (res.payload || []) as T[]; }