parse-query.ts65 lines · main
| 1 | import { parse } from 'libpg-query' |
| 2 | import { NextApiRequest, NextApiResponse } from 'next' |
| 3 | |
| 4 | const getOperation = async (sql: string) => { |
| 5 | const ast = await parse(sql) |
| 6 | const stmt = ast.stmts?.[0]?.stmt |
| 7 | |
| 8 | if (!stmt) return null |
| 9 | |
| 10 | if ('SelectStmt' in stmt) return 'SELECT' |
| 11 | if ('InsertStmt' in stmt) return 'INSERT' |
| 12 | if ('UpdateStmt' in stmt) return 'UPDATE' |
| 13 | if ('DeleteStmt' in stmt) return 'DELETE' |
| 14 | } |
| 15 | |
| 16 | const getTablesInQuery = async (sql: string) => { |
| 17 | const ast = await parse(sql) |
| 18 | const tables: string[] = [] |
| 19 | |
| 20 | function traverse(node: unknown): void { |
| 21 | if (!node || typeof node !== 'object') return |
| 22 | const obj = node as Record<string, unknown> |
| 23 | |
| 24 | if ('RangeVar' in obj) { |
| 25 | const rv = obj.RangeVar as { relname?: string; schemaname?: string } |
| 26 | if (rv.relname) tables.push(rv.schemaname ? `${rv.schemaname}.${rv.relname}` : rv.relname) |
| 27 | } |
| 28 | |
| 29 | if ('relation' in obj && obj.relation && typeof obj.relation === 'object') { |
| 30 | const rv = obj.relation as { relname?: string; schemaname?: string } |
| 31 | if (rv.relname) tables.push(rv.schemaname ? `${rv.schemaname}.${rv.relname}` : rv.relname) |
| 32 | } |
| 33 | |
| 34 | for (const value of Object.values(obj)) { |
| 35 | Array.isArray(value) ? value.forEach(traverse) : traverse(value) |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | traverse(ast) |
| 40 | return [...new Set(tables)].sort((a, b) => a.localeCompare(b)) |
| 41 | } |
| 42 | |
| 43 | export default async function handler(req: NextApiRequest, res: NextApiResponse) { |
| 44 | if (req.method !== 'POST') { |
| 45 | res.setHeader('Allow', ['POST']) |
| 46 | return res.status(405).json({ error: `Method ${req.method} Not Allowed` }) |
| 47 | } |
| 48 | |
| 49 | try { |
| 50 | const { sql } = req.body |
| 51 | |
| 52 | if (typeof sql !== 'string' || sql.trim().length === 0) { |
| 53 | return res.status(400).json({ error: 'Missing or invalid "sql" in request body' }) |
| 54 | } |
| 55 | |
| 56 | const tables = await getTablesInQuery(sql) |
| 57 | const operation = await getOperation(sql) |
| 58 | return res.status(200).json({ tables, operation }) |
| 59 | } catch (error) { |
| 60 | const message = |
| 61 | (error as { sqlDetails?: { message?: string } })?.sqlDetails?.message ?? |
| 62 | (error instanceof Error ? error.message : 'Failed to parse SQL') |
| 63 | return res.status(400).json({ error: message }) |
| 64 | } |
| 65 | } |