e3c8979e8d
- cmd/fn/doctor.go - cmd/fn/main.go - cpp/apps/primitives_gallery/playground/tables/CMakeLists.txt - cpp/apps/primitives_gallery/playground/tables/data_table.cpp - cpp/apps/primitives_gallery/playground/tables/data_table_logic.cpp - cpp/apps/primitives_gallery/playground/tables/data_table_logic.h - cpp/apps/primitives_gallery/playground/tables/self_test.cpp - cpp/apps/primitives_gallery/playground/tables/tql.cpp - cpp/apps/primitives_gallery/playground/tables/viz.cpp - cpp/apps/primitives_gallery/playground/tables/viz.h - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
130 lines
4.2 KiB
Markdown
130 lines
4.2 KiB
Markdown
---
|
|
id: 0078
|
|
title: tables playground — joins MBQL-style (fase 9)
|
|
status: done
|
|
priority: medium
|
|
created: 2026-05-12
|
|
closed: 2026-05-12
|
|
related_components: [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`:
|
|
|
|
```clojure
|
|
: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:
|
|
```cpp
|
|
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:
|
|
|
|
```lua
|
|
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.
|