Files
fn_registry/dev/issues/completed/0078-tables-joins-mbql.md
T
egutierrez 49a924bb34 chore(issues): mueve 0078/0079/0080 a completed/
3 issues cerradas movidas al directorio completed/ por convencion:

- 0078 tables playground joins MBQL (fase 9)
- 0079 tables playground drill-through extendido (fase 10)
- 0080 tables playground LLM Ask AI + TQL->SQL emit (fase 11)

0081 (promote a registry, fase 12) permanece en dev/issues/ — status
partial, 0081-A done, 0081-B..L pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 01:21:50 +02:00

4.2 KiB

id, title, status, priority, created, closed, related_components
id title status priority created closed related_components
0078 tables playground — joins MBQL-style (fase 9) done medium 2026-05-12 2026-05-12
cpp/apps/primitives_gallery/playground/tables
lua_engine
tql

Contexto

Fase 9 del roadmap del tables playground. Hoy render() acepta un solo input table. MBQL permite :joins para combinar varias tablas en una sola query. Queremos lo mismo en TQL.

Roadmap restante tras esta fase:

  • 10: drill-through extendido
  • 11: LLM API ("Ask AI")
  • 12: promote a registry + migrar apps C++

Diseño (referencia MBQL)

MBQL :joins:

:joins [{:source-table 2
         :alias "orders"
         :condition [:= [:field "user_id"] [:field "user_id" {:join-alias "orders"}]]
         :strategy :left-join
         :fields :all}]

Adaptacion a nuestro modelo:

  • Sin DB ids — usamos nombres de inputs pasados en runtime.
  • Soportar multi-key composite via vector de pares.
  • Strategies: left | inner | right | full.
  • Fields: all | none | lista.

Cambios

Tipos / API

  • struct TableInput { string name; vector<string> headers; vector<ColumnType> types; vector<const char*> cells; int rows; int cols; }
  • struct Join { string alias; string source; vector<pair<string,string>> on; JoinStrategy strategy; vector<string> fields; } en data_table_logic.h.
  • State.joins: vector<Join> (antes de stages[0]).
  • render() signature extendida:
    void render(const char* id,
                const char* const* headers, int col_count,
                const char* const* cells, int row_count,
                State& st,
                const ColumnType* declared_types,
                const std::vector<TableInput>* joinables = nullptr);
    

Logica pura

  • join_tables_cpp_core(left, right, alias, on, strategy, fields) -> StageOutput.
  • Tests: left/inner/right/full join + multi-key + NULL handling (left propaga "").
  • Pre-pipeline: si state.joins no vacio, materializar tabla joined (recorriendo joinables[]) ANTES de aplicar stage 0.
  • Headers post-join prefijados: alias.col (preserva originales del main).

UI

  • Chip row "Joins:" debajo de "Filters:". + button abre popup con:
    • Combo alias (auto-sugerido) + combo source (nombre de input)
    • Strategy combo
    • on[] list: par left-col / right-col con + para añadir mas pares (multi-key)
    • Fields radio: all / none / pick
  • Chip muestra alias <- source on left=right (left-join). Right-click edit, X borrar.
  • CONDICIONAL: la fila de joins solo se renderiza si joinables != nullptr && !joinables->empty(). Sin tablas extra → no UI de joins, no se pierde espacio en apps que solo pasan una tabla.

TQL

Nuevo bloque root-level:

return {
  version = 1,
  display = "table",
  joins = {
    {alias = "orders", source = "orders_tbl",
     on = { {"user_id", "user_id"} },
     strategy = "left", fields = "all"},
  },
  stages = { ... },
  columns = { ... },
  views = { ... },
}

emit/apply round-trip + tests.

Lua engine

YA aplicado preempt (2026-05-12): ident_cont acepta . para parsear [alias.col] post-join sin colision.

compute_stage / find_orig_col

No requieren cambios — operan sobre strings de col names. Aceptan alias.col directamente.

extra_panels

No requieren cambios — ven el StageOutput post-join automaticamente.

Pasos de implementacion

  1. join_tables_cpp_core (pure) + tests unit.
  2. TableInput struct + Join struct + State.joins.
  3. render() signature extendida (default nullptr, back-compat).
  4. Pre-pipeline materialize join cuando state.joins no vacio.
  5. UI chip row (condicional a joinables disponibles).
  6. TQL emit/apply joins + tests round-trip.
  7. Lua [alias.col] resolver test (la sintaxis ya parsea).
  8. Actualizar tests phase9: 4 join types + multi-key + NULL + TQL round-trip.

No-objetivos (esta fase)

  • Subqueries / source-query MBQL — no aplicable, las inputs ya estan materializadas.
  • Joins recursivos — flat list, sin chains internos.
  • Outer joins con conditions no-igualdad (>, <, range) — solo = por ahora.

Riesgos

  • Performance con tablas grandes — hash join sobre key cols. Para v1, nested loop con hash map sobre right table es suficiente.
  • Memoria — joined table puede ser N*M en worst case. Documentar.