Spaces:
Running
Running
Download src/lib/postgresql/read_query.py from Leon4gr45/brain: direct link, hf CLI and curl.
- Browser
- Download file 4.4 kB
-
https://huggingface.co/spaces/Leon4gr45/brain/resolve/main/src/lib/postgresql/read_query.py
- Command line
-
hf download hf://spaces/Leon4gr45/brain/src/lib/postgresql/read_query.py
-
curl -L -o read_query.py https://huggingface.co/spaces/Leon4gr45/brain/resolve/main/src/lib/postgresql/read_query.py
4.4 kB
| from __future__ import annotations | |
| import re | |
| MAX_READ_QUERY_ROWS = 100 | |
| READ_QUERY_TIMEOUT_MS = 10000 | |
| _READ_QUERY_START = re.compile( | |
| r"^\s*(SELECT|WITH|EXPLAIN|TABLE)\b", | |
| re.IGNORECASE | re.DOTALL, | |
| ) | |
| _FORBIDDEN_SQL = re.compile( | |
| r"\b(" | |
| r"INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|GRANT|REVOKE|" | |
| r"COPY|MERGE|CALL|DO|VACUUM|REINDEX|CLUSTER|" | |
| r"LISTEN|NOTIFY|LOAD|LOCK|DISCARD|RESET" | |
| r")\b", | |
| re.IGNORECASE, | |
| ) | |
| _CYPHER_MARKERS = re.compile( | |
| r"\b(MATCH|RETURN|OPTIONAL\s+MATCH|CREATE\s*\(|DETACH\s+DELETE|SET\s+n\.|REMOVE\s+n\.)\b", | |
| re.IGNORECASE, | |
| ) | |
| _LEGACY_TABLE_NAMES = re.compile( | |
| r"\b(FROM|JOIN|TABLE|INTO|UPDATE)\s+(nodes|relationships)\b", | |
| re.IGNORECASE, | |
| ) | |
| _WITH_RECURSIVE = re.compile(r"\bWITH\s+RECURSIVE\b", re.IGNORECASE) | |
| class ReadQueryValidationError(Exception): | |
| pass | |
| def _paren_balance(sql: str) -> int: | |
| """Return open-paren count minus close-paren count, ignoring quoted strings.""" | |
| depth = 0 | |
| in_single = False | |
| in_double = False | |
| i = 0 | |
| while i < len(sql): | |
| ch = sql[i] | |
| if in_single: | |
| if ch == "'" and i + 1 < len(sql) and sql[i + 1] == "'": | |
| i += 2 | |
| continue | |
| if ch == "'": | |
| in_single = False | |
| i += 1 | |
| continue | |
| if in_double: | |
| if ch == '"': | |
| in_double = False | |
| i += 1 | |
| continue | |
| if ch == "'": | |
| in_single = True | |
| elif ch == '"': | |
| in_double = True | |
| elif ch == "(": | |
| depth += 1 | |
| elif ch == ")": | |
| depth -= 1 | |
| i += 1 | |
| return depth | |
| def validate_read_only_sql(query: str) -> str: | |
| normalized = query.strip() | |
| if not normalized: | |
| raise ReadQueryValidationError("Empty query") | |
| parts = [part.strip() for part in normalized.split(";") if part.strip()] | |
| if len(parts) != 1: | |
| raise ReadQueryValidationError("Multiple statements are not allowed") | |
| normalized = parts[0] | |
| if _CYPHER_MARKERS.search(normalized): | |
| raise ReadQueryValidationError( | |
| "Cypher is not supported on PostgreSQL. Use SQL against kg_nodes " | |
| "and kg_relationships (for example: SELECT uuid, data->>'name' AS name " | |
| "FROM kg_nodes WHERE data->>'name' ILIKE '%alice%' LIMIT 50)." | |
| ) | |
| if _LEGACY_TABLE_NAMES.search(normalized): | |
| raise ReadQueryValidationError( | |
| "Unknown table. Use kg_nodes and kg_relationships " | |
| "(not nodes/relationships)." | |
| ) | |
| if _FORBIDDEN_SQL.search(normalized): | |
| raise ReadQueryValidationError("Only read-only SELECT queries are allowed") | |
| if not _READ_QUERY_START.match(normalized): | |
| raise ReadQueryValidationError( | |
| "Query must start with SELECT, WITH, EXPLAIN, or TABLE" | |
| ) | |
| # EXPLAIN of Cypher still starts with EXPLAIN; catch residual Cypher body. | |
| if normalized.upper().startswith("EXPLAIN") and _CYPHER_MARKERS.search( | |
| normalized[7:] | |
| ): | |
| raise ReadQueryValidationError( | |
| "Cypher is not supported on PostgreSQL. Use SQL EXPLAIN SELECT ..." | |
| ) | |
| balance = _paren_balance(normalized) | |
| if balance != 0: | |
| raise ReadQueryValidationError( | |
| "Incomplete SQL (unbalanced parentheses). Send one complete statement " | |
| "in a single tool call — do not split WITH RECURSIVE CTEs across calls. " | |
| "Example: WITH RECURSIVE walk AS (" | |
| "SELECT source_uuid, target_uuid, rel_type, 1 AS depth " | |
| "FROM kg_relationships WHERE source_uuid = '<uuid>' " | |
| "UNION ALL " | |
| "SELECT r.source_uuid, r.target_uuid, r.rel_type, w.depth + 1 " | |
| "FROM walk w JOIN kg_relationships r ON r.source_uuid = w.target_uuid " | |
| "WHERE w.depth < 3" | |
| ") SELECT * FROM walk LIMIT 50" | |
| ) | |
| if _WITH_RECURSIVE.search(normalized): | |
| # A recursive CTE must both define the CTE and select from it. | |
| if not re.search(r"\)\s*SELECT\b", normalized, re.IGNORECASE): | |
| raise ReadQueryValidationError( | |
| "Incomplete WITH RECURSIVE query. Include the outer SELECT after " | |
| "the CTE, e.g. ') SELECT * FROM walk LIMIT 50'. Do not send only " | |
| "the CTE body or a fragment of the UNION ALL branch." | |
| ) | |
| return normalized | |