Published

⚙️ El motor de query — cómo funciona (y su costura con Karma)

Connect any source, model it as an ontology, transform it, and operationalize it, analytics, automation and machine learning, under one governed, self-hostable roof. --- Most teams stitch the...

⚙️ 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:

  1. ALTER TABLE … RENAME TO … (batch) → rename + estampado de tier medallón.
  2. CREATE [OR REPLACE] VIEW … AS … / DROP VIEW … → objeto del catálogo.
  3. DESCRIBE … (incl. DESCRIBE HISTORY) → plano de catálogo.
  4. SHOW … → plano de catálogo.
  5. DML gobernado (DELETE / MERGE) con destino declarado → puerta → motor.
  6. information_schema.columns en un SELECT → tabla virtual (CTE jsonb_to_recordset sobre filas servidas por Index; no la sirve el motor).
  7. 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:

PasoQué haceFunción
Nombre → identidad físicaFROM ventasFROM lake."main.default".ds_<uuid>; colapsa el FQN estilo Databricks (workspace.public.ventas) antes de resolverrewriteDottedFromRefs + replaceTableToken
Vista → CTEuna vista del catálogo se expande a "<vista>" AS (<cuerpo>), recursivamente, fusionándose con el WITH del usuariomergeViewCtes

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.