Files
ad/molodetz/database/sql_lint.py
T

59 lines
1.7 KiB
Python

# retoor <retoor@molodetz.nl>
import ast
from pathlib import Path
import sqlglot
from sqlglot.errors import ParseError
from molodetz import config
SQL_PREFIXES = ("SELECT ", "INSERT ", "UPDATE ", "DELETE ", "CREATE ", "PRAGMA ", "WITH ", "ANALYZE")
def _joined_text(node):
parts = []
for value in node.values:
if isinstance(value, ast.Constant):
parts.append(str(value.value))
else:
parts.append("lint_placeholder")
return "".join(parts)
def _literal_sql(node):
if isinstance(node, ast.JoinedStr):
raw = _joined_text(node)
elif isinstance(node, ast.Constant) and isinstance(node.value, str):
raw = node.value
else:
return None
text = " ".join(raw.split())
return text if text.upper().startswith(SQL_PREFIXES) else None
def collect_sql(root=None):
root = Path(root or config.PKG_ROOT)
found = []
for path in sorted(root.rglob("*.py")):
tree = ast.parse(path.read_text(encoding="utf-8"))
inner = {id(value) for node in ast.walk(tree) if isinstance(node, ast.JoinedStr) for value in node.values}
for node in ast.walk(tree):
if id(node) in inner:
continue
sql = _literal_sql(node)
if sql:
found.append((path, node.lineno, sql))
return found
def lint(root=None):
problems = []
for path, line, sql in collect_sql(root):
if sql.upper().startswith(("PRAGMA", "ANALYZE")):
continue
try:
sqlglot.parse_one(sql, read="sqlite")
except ParseError as exc:
problems.append(f"{path}:{line}: {str(exc).splitlines()[0]}")
return problems