parse-query.ts 2.1 KB

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