David · Data Architect
Consultas seguras
Las validaciones que pasa cada consulta antes de tocar tus datos: solo lectura, filtros obligatorios, límites, idempotencia y registro.
Antes de que una consulta toque tus datos pasa por varias capas de control. La primera y más importante no es de software: el usuario con el que David se conecta solo puede leer.
#Las capas de control
| Capa | Qué hace |
|---|---|
| Usuario de solo lectura | Tu base de datos rechaza cualquier escritura, aunque todo lo demás fallara. Lo crea tu equipo con los permisos mínimos. |
Solo SELECT | Se rechaza cualquier instrucción que modifique datos o estructura: INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE. |
| Filtros obligatorios | Cada consulta lleva el filtro de empresa o de unidad que corresponda al usuario que pregunta. |
| Reglas de la capa semántica | Uniones solo por relaciones definidas y agregación en el grano correcto. Ver duplicación silenciosa. |
| Límites | Tiempo máximo de ejecución y cantidad máxima de filas devueltas, para no afectar a tus sistemas. |
| Registro | Quién preguntó, cuándo, qué consulta se ejecutó y qué devolvió. |
#Validación de la consulta
El diseño de David valida cada consulta como un árbol sintáctico (AST): en vez de buscar palabras en un texto, analiza la estructura de la consulta y puede reescribirla, por ejemplo para agregar el filtro obligatorio. Este es un ejemplo de referencia de esa validación:
import sqlglot
from sqlglot import exp
PROHIBIDAS = (exp.Insert, exp.Update, exp.Delete, exp.Drop, exp.Alter)
def validar(sql: str, columna_tenant: str, tenant_id: str, dialecto: str = "postgres") -> str:
arbol = sqlglot.parse_one(sql, read=dialecto)
# 1. Solo lectura: cualquier nodo que modifique datos detiene la ejecución
if not isinstance(arbol, exp.Select) or any(arbol.find_all(*PROHIBIDAS)):
raise PermissionError("Solo se permiten consultas de lectura")
# 2. Uniones con condición explícita (sin productos cartesianos)
for union in arbol.find_all(exp.Join):
if not union.args.get("on") and not union.args.get("using"):
raise ValueError("Unión sin condición: rechazada")
# 3. Filtro obligatorio de empresa
arbol = arbol.where(f"{columna_tenant} = '{tenant_id}'")
return arbol.sql(dialect=dialecto)#Idempotencia
Cuando una operación registra algo (por ejemplo, marcar una partida como conciliada en un sistema intermedio), lleva una clave de idempotencia. Si la red falla y la solicitud se reintenta, la operación no se duplica.
POST https://guren.cl/api/david/simulate
Content-Type: application/json
Idempotency-Key: 6f1c2a8e-ejemplo
{
"system": "SAP_S4HANA",
"table": "BKPF_ACCDOC",
"query": "SELECT doc_id, account, debit, credit FROM BKPF_ACCDOC WHERE BUKRS = 'CL01'"
}Si la consulta intenta modificar datos, la demo responde 422 con el motivo del rechazo. Ver Para desarrolladores.
#Qué ve cada persona
Los permisos se definen por rol: un gerente de sucursal ve su sucursal; finanzas ve la empresa completa. Si tu empresa ya administra roles en su proveedor de identidad (Google Workspace, Microsoft Entra ID u otro), los usamos como base en el diseño.