⚙️ El motor de query — cómo funciona (y su costura con Karma)
Referencia técnica del endpoint
POST /api/sql-editor/execute
(app/api/sql-editor/execute/route.ts). Para las capacidades de cara al
usuario ve los docs de bloque; esto es el "bajo el capó".
1. Clasificación de la sentencia
El modo interno (mode: 'internal') primero quita comentarios (--, /* */)
y luego clasifica, en orden:
ALTER TABLE … RENAME TO …(batch) → rename + estampado de tier medallón.CREATE [OR REPLACE] VIEW … AS …/DROP VIEW …→ objeto del catálogo.DESCRIBE …(incl.DESCRIBE HISTORY) → plano de catálogo.SHOW …→ plano de catálogo.- DML gobernado (
DELETE/MERGE) con destino declarado → puerta → motor. information_schema.columnsen un SELECT → tabla virtual (CTEjsonb_to_recordsetsobre filas servidas por Index; no la sirve el motor).- Cualquier otra cosa → gate read-only (
SELECT/WITH/EXPLAIN) → motor.
Todo lo que no sea uno de estos comandos curados y no sea lectura se rechaza: no se ejecuta escritura arbitraria en modo interno, y una escritura cuyo destino no se reconoce cae al rechazo (fail-closed).
⚠️ Los pasos 1-4 y 6 los reconoce un conjunto de expresiones regulares escritas a mano, y el paso 5/7 los clasifica una segunda heurística —duplicada en Python dentro de duck-server— que debe coincidir con la de Node. Es la deuda estructural del pilar; ver la spec enlazada en §4.
2. El plano de metadatos (los 3 pilares)
DESCRIBE y SHOW no tocan datos — leen de:
- Postgres (
datasets): schema, tier, row_count, size, service, owner, coordenada. - Lakekeeper (
iceberg_namespace): ubicación/punteros. - Ledger (
dataset_transactions): historial (DESCRIBE HISTORY).
Alcance por usuario (created_by) — igual que el resto de la consola.
3. El motor de query (SELECT / WITH / DML)
El motor es DuckDB, sobre Iceberg + Parquet en R2, por la puerta (Junction).
No hay traducción de dialecto ni reconstrucción de filas desde JSONB: el cuerpo de
la query del usuario llega al motor prácticamente verbatim. El plan
(buildDuckReadPlan) hace exactamente dos cosas:
| Paso | Qué hace | Función |
|---|---|---|
| Nombre → identidad física | FROM ventas → FROM lake."main.default".ds_<uuid>; colapsa el FQN estilo Databricks (workspace.public.ventas) antes de resolver | rewriteDottedFromRefs + replaceTableToken |
| Vista → CTE | una vista del catálogo se expande a "<vista>" AS (<cuerpo>), recursivamente, fusionándose con el WITH del usuario | mergeViewCtes |
Restricciones vigentes: funciones de tabla limitadas a
generate_series/range/unnest/repeat (el Warehouse es la única fuente), y
DML acotado a DELETE y MERGE, gateado por el canary del puerto
sql-editor.dml.
Lo que ESTE DOC decía antes, y ya no es cierto
Hasta el 2026-07-10 el motor era un shim sobre Postgres: traducía cada dataset
a una CTE que extraía columnas de dataset_rows.data con operadores JSONB, más un
traductor de dialecto Spark→PG (RLIKE→~, DATEDIFF, …). Nada de eso existe:
el shim se retiró en el F3-RETIRO y la tabla dataset_rows se soltó el 2026-07-31
(mig. 20261262). Se deja constancia aquí porque este documento describió ese
diseño como presente durante semanas después de morir — y un doc rancio sobre el
motor cuesta una tarde de depuración.
4. La frontera — y por qué no es el motor
El diseño anterior anticipaba a Karma como el reemplazo del ejecutor. La tesis resultó correcta y ya se ejerció una vez: el ejecutor cambió (PG → DuckDB) sin tocar la clasificación (§1), el plano de catálogo (§2), el DDL/rename, el tiering ni la UI. Los shims de dialecto se borraron, no se migraron.
La frontera es Junction + Index, no el motor:
- Index resuelve identidad, tenencia y gobernanza; el motor recibe identificadores físicos ya resueltos y queda ciego.
- El motor es intercambiable por política de la puerta (
getSqlEngine): DuckDB hoy; DataFusion o Karma sin cambiar esta superficie.
Regla de disciplina (vigente y reforzada): nunca un parser SQL completo escrito en expresiones regulares. Hoy seguimos teniendo dos clasificadores heurísticos —uno en Node y otro en Python dentro de duck-server— que deben coincidir, y esa es la deuda abierta del pilar. El contrato de dialecto/formato/parser se especifica en warehouse-sql-dialect-spec.md.