From 20d8bbf360a5001dae3fc2b8dceda49842b5fb60 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Fri, 1 May 2026 01:14:52 +0200 Subject: [PATCH] docs(issues): rewrite 0010 con DuckDB + new 0011 (UI fase 2) 0010 cambia de modelo SQLite CONTAINS_ROW a tier DuckDB: operations.db sigue con grafo + filas promovidas, tablas grandes viven en projects//apps/graph_explorer/tables/.duckdb. 0011 separa la fase 2 (UI expandida + promote/demote + ingesta CSV). --- issues/0010-table-node.md | 119 +++++++++++++--------- issues/0011-tablenode-expanded-promote.md | 79 ++++++++++++++ 2 files changed, 150 insertions(+), 48 deletions(-) create mode 100644 issues/0011-tablenode-expanded-promote.md diff --git a/issues/0010-table-node.md b/issues/0010-table-node.md index 1883a25..b3f7b7f 100644 --- a/issues/0010-table-node.md +++ b/issues/0010-table-node.md @@ -1,71 +1,94 @@ --- id: 0010 -title: Nodo tabla — contenedor cuadrado con filas que son nodos del grafo +title: Nodo tabla — DuckDB foundation + render colapsado status: pending -priority: medium +priority: high created: 2026-04-30 +revised: 2026-05-01 depends_on: [0004, 0005, 0008] --- ## Objetivo -Un tipo especial de nodo, **Table**, que se renderiza en el viewport como -un rectangulo (no circulo) y agrupa visualmente N entidades del grafo. Cada -fila de la tabla = un nodo real del grafo (con su `type_ref`, sus fields, -sus tags). Las filas se pueden **extraer** (salen al canvas como nodos -sueltos) y **meter** (un nodo suelto entra como fila). +Tier de almacenamiento tabular para nodos `Table` que pueden contener millones +de filas sin saturar `operations.db` ni el grafo. Las "filas" viven en un +`.duckdb` por proyecto (o por tabla) y se promueven a entidades reales del +grafo solo cuando se necesita interactuar con ellas individualmente +(relaciones, edicion, etc.). -Distinto del issue 0004 (vista tabla global por tipo): ese es una **ventana -auxiliar** que tabula entidades existentes; este es un **nodo en el grafo** -que existe en `entities` y posee filas via relaciones. +Esta issue cubre **fase 1** — vendoring de DuckDB, funciones `tableview_*` +core, y render colapsado del nodo Table en el viewport. La fase 2 (UI +expandida, paginacion, promote/demote, ingesta CSV/Parquet) va en issue +0011. ## Modelo de datos -- El nodo tabla es una entidad normal con `type_ref = 'Table'` y metadata: - ```json - { - "row_type": "Person", // tipo esperado de las filas (puede ser vacio = mixto) - "columns": ["name","age","email"], // subset de fields del row_type a mostrar como columnas - "expanded": true // estado de UI persistido - } - ``` -- La pertenencia se modela con relaciones: - - `name = "CONTAINS_ROW"`, `from_entity = `, `to_entity = `, `order = N`. -- Una fila puede pertenecer a varias tablas (varias relaciones `CONTAINS_ROW` apuntando al mismo nodo). Confirmado en la conversacion. -- Las columnas pueden ser fijas (`row_type` definido → columnas = subset de los `fields` de ese tipo) o libres (definidas por el creador de la tabla en `columns`). +**Dos tiers** por proyecto: -## Render en viewport +``` +projects//apps/graph_explorer/ + operations.db # SQLite — grafo (entities + relations + filas promovidas) + tables/ + .duckdb # DuckDB — bulk tabular (millones de filas) +``` -- Forma: `square` o `rounded_square` con tamano dependiente del numero de filas (clamp a min/max). -- Cuando esta **colapsada**: caja con titulo + contador (`Table · 23 filas`). -- Cuando esta **expandida**: caja crece y dibuja un grid interno (filas x columnas) con los valores principales. Las filas son arrastrables individualmente. -- Las relaciones `CONTAINS_ROW` no se dibujan como aristas normales (serian ruido visual). En su lugar, una fila extraida muestra una arista fina punteada hacia su tabla de origen. -- Aristas entrantes/salientes del nodo tabla se dibujan al borde del rectangulo, no al centro. +El nodo Table es una entidad normal con `type_ref = 'Table'` y metadata que +apunta a su dataset DuckDB. **No contiene filas internamente** — es una vista. -## Operaciones +```json +{ + "duckdb_path": "tables/sospechosos.duckdb", + "table_name": "people", + "row_type": "Person", + "id_column": "id", + "label_column": "name", + "columns": ["name","age","email"], + "filter_sql": "", + "expanded": false +} +``` -- **Crear tabla**: en context menu del viewport, "New table here". Pide `row_type` opcional. Crea entidad `Table` y la posiciona donde el click. -- **Anadir fila** (tabla expandida o seleccionada): boton "+ row" que crea una entidad nueva con `type_ref = row_type` (si esta definido) y la engancha via `CONTAINS_ROW`. -- **Extraer fila**: borra la relacion `CONTAINS_ROW`. La fila queda como nodo libre, posicionada al lado de la tabla. -- **Extraer multiples**: shift+click en filas dentro de la tabla expandida, "Extract selected". -- **Meter fila**: drag de un nodo sobre el rectangulo de una tabla. Confirm dialog si su `type_ref` no coincide con `row_type` de la tabla. -- **Editar fila**: doble-click en fila → abre Inspector con esa entidad seleccionada. +Una fila promovida es una entidad en `operations.db` con metadata de origen: + +```json +{ + "source": { + "duckdb": "tables/sospechosos.duckdb", + "table": "people", + "row_id": "p_42" + }, + "name": "Ana Lopez", + "age": 33 +} +``` ## Cambios en codigo -- `entity_ops`: - - `bool table_create(db_path, name, row_type, columns_csv, char* out_id)`. - - `bool table_add_row(db_path, table_id, char* out_row_id)` (crea entidad + relacion CONTAINS_ROW). - - `bool table_extract_row(db_path, table_id, row_id)` (borra solo la relacion). - - `bool table_attach_row(db_path, table_id, row_id, int order)`. - - `bool table_list_rows(db_path, table_id, vector* out_row_ids)`. -- Renderer del viewport (`graph_viewport.cpp` y/o `graph_renderer`): branch para `is_table_node` (detectado por `type_ref == "Table"`) que dibuja rectangulo + grid expandido y devuelve hit-testing por filas individuales. -- `graph_load_from_operations`: filtrar las aristas `CONTAINS_ROW` para que no entren en el layout fisico (no aplican fuerzas). +- **Vendor DuckDB** en `cpp/vendor/duckdb/` (amalgamation o precompiled). Add + library en `cpp/CMakeLists.txt`. +- Nuevo paquete `cpp/functions/duck/`: + - `duck_open(path) -> duckdb_database` (con `duckdb_open` + `duckdb_connect`). + - `duck_query(conn, sql, params) -> result` wrapper. +- `entity_ops` (o `tableview.{h,cpp}` en la app) — funciones a nivel de app: + - `tableview_create(ops_db, duckdb_path, duck_table, row_type, char* out_id)` + crea entidad `Table` con metadata + commit en `operations.db`. + - `tableview_count(duckdb_path, sql_filter, int64_t* out)`. + - `tableview_page(duckdb_path, sql_filter, offset, limit, vector* out)`. + - `TablePageRow` lleva los campos del `columns[]` resueltos a string + + `promoted` (LEFT JOIN contra `ops.entities`). +- `graph_load_from_operations`: filtrar relaciones `CONTAINS_ROW` (heredado + del modelo viejo, ya no se emiten pero por si se topa con dbs antiguas). +- `views.cpp`: + - Detectar nodos `type_ref == "Table"` al renderizar etiquetas/contadores. + - Overlay con `ImGui::GetForegroundDrawList()` por cada nodo Table: + rectangulo redondeado + label "Table · N filas". ## Definicion de hecho -- Crear tabla, anadir filas, extraer y meter filas funciona round-trip via SQLite. -- Tabla colapsada y expandida se renderizan correctamente en el viewport. -- Doble-click en fila enfoca el Inspector con esa entidad. -- Una fila puede pertenecer a varias tablas sin duplicarse. -- Borrar la tabla pregunta: "borrar tabla y todas sus filas" o "extraer filas y borrar solo la tabla". +- DuckDB compila y linka en linux + windows (cmake target). +- Smoke test: abrir un `.duckdb` vacio, crear tabla con 1M filas (CTAS desde + `range`), correr `SELECT COUNT(*)` < 100 ms. +- `tableview_create` + `tableview_count` + `tableview_page` con tests. +- Un nodo `type_ref='Table'` en el grafo se renderiza con un cuadrado overlay + encima del circulo GPU, con contador de filas obtenido por `tableview_count`. +- El contador refresca al recargar el grafo o tras un INSERT en su DuckDB. diff --git a/issues/0011-tablenode-expanded-promote.md b/issues/0011-tablenode-expanded-promote.md new file mode 100644 index 0000000..9341540 --- /dev/null +++ b/issues/0011-tablenode-expanded-promote.md @@ -0,0 +1,79 @@ +--- +id: 0011 +title: Nodo tabla — UI expandida, promote/demote, ingesta CSV/Parquet +status: pending +priority: high +created: 2026-05-01 +depends_on: [0010] +--- + +## Objetivo + +Fase 2 del nodo tabla. Sobre el cimiento DuckDB de 0010, anade UI completa: +expandir nodo Table en el viewport para ver paginas de filas, promover una +fila a entidad del grafo (nodo libre + arista punteada hacia su Table) y +demover de vuelta. Ingesta de CSV / Parquet como punto de entrada para +materializar tablas grandes. + +## UI expandida + +- Click sobre nodo Table -> selecciona; doble click -> toggle `expanded` en + metadata. +- Cuando expanded: + - El cuadrado overlay crece para acomodar grid de cabecera + ~20 filas + visibles. Mas filas exigen scroll dentro del overlay. + - Cabecera con nombres de `columns[]`. Anchura proporcional al texto, max + cap a un % del overlay. + - Filas paginadas via `ImGuiListClipper` + `tableview_page(offset,limit)`. + - Indicador "promovida" (chip o color) en filas que ya estan en `entities`. + - Doble-click en fila -> abre Inspector con esa entidad si esta promovida; + si no, la promueve y abre Inspector. +- Aristas entrantes/salientes del nodo Table se siguen dibujando al centro + (mejora a "al borde" se aplaza). + +## Promote / demote + +- Context menu sobre fila visible (overlay expandido): + - "Promote to graph node": crea entidad en `operations.db` con metadata de + origen, posiciona el nodo al lado del Table y dibuja arista punteada + hacia el Table (overlay). + - "Demote": deletea la entidad. La fila sigue viva en DuckDB. +- Una fila puede estar promovida una sola vez por (duckdb_path, table, + row_id) — el helper de promocion debe checar y hacer no-op idempotente. + +## Ingesta + +- Boton/comando "Import dataset..." en menu o context menu del canvas. +- Modal con: + - Path al CSV / Parquet / JSON. + - Nombre de la tabla DuckDB destino. + - row_type (combo con los entity types del proyecto + "(none)"). + - Boton "Import" -> ejecuta: + ```sql + CREATE TABLE AS SELECT * FROM read_csv_auto(''); + ``` + sobre `tables/.duckdb` (crea el .duckdb si no existe). + - Tras import, crea automaticamente un nodo Table en el viewport apuntando + a la nueva tabla. + +## Cambios en codigo + +- `tableview.{h,cpp}`: + - `tableview_promote_row(ops_db, duckdb_path, duck_table, row_id, row_type, out_entity_id)`. + - `tableview_demote_row(ops_db, entity_id)`. + - `tableview_ingest_file(duckdb_path, file_path, dest_table, *file_kind)`. +- `views.cpp`: + - Render expandida via overlay. Hit-testing por fila (rect intersection). + - Modal "Import dataset...". +- `main.cpp`: + - Wire context menu items. Recargar grafo tras promote/demote/ingest. + +## Definicion de hecho + +- Toggle expanded persiste en `entities.metadata` (JSON write). +- Tabla con 1M filas se navega con scroll fluido (paginacion 200 filas). +- Promote: la entidad creada aparece como nodo libre adyacente al Table, + unida por arista punteada (visual solamente — no es relacion en BD). +- Demote: el nodo desaparece, la fila sigue contandose en `tableview_count`. +- Ingesta de CSV de 100k filas tarda < 5 s y deja la tabla lista para mostrar. +- Doble-click en fila no promovida la promueve y enfoca Inspector.