# 0032 — C++ sql_workbench ## APP Metadata | Campo | Valor | |-------|-------| | **ID** | 0032 | | **Estado** | pendiente | | **Prioridad** | media | | **Tipo** | feature — C++ core (cpp/functions/core) | ## Dependencias `text_editor_cpp_core` (issue 0025) — recomendado pero no bloqueante: si no esta listo, fallback a `ImGui::InputTextMultiline`. `table_view_cpp_viz`, `tokens_cpp_core`. SQLite ya esta en uso por `layout_storage_sqlite`. **Desbloquea:** explorar `registry.db` y `operations.db` desde una app C++ sin salir al CLI; debugging visual de assertions/executions. --- ## Objetivo Componente ImGui que combina: - Editor SQL (con highlight si `text_editor` esta disponible). - Botón "Run" que ejecuta sobre una `sqlite3*` proporcionada por el caller. - Tabla de resultados (`table_view`) con sorting y scrolling. - Lista de tablas/views del schema (sidebar) clickable que insertan `SELECT * FROM LIMIT 100;`. - Historial de queries en memoria + boton "Save" a un archivo `.sql.history`. Demo en `primitives_gallery` apuntando a `registry.db` (read-only). ## Contexto Manualmente uso `sqlite3` CLI o DBeaver para inspeccionar `registry.db` mientras desarrollo. Tener un workbench in-app abre la posibilidad de apps C++ que sean dashboards-cum-SQL-explorers contra `operations.db` (entities, executions, assertions). ## Arquitectura ``` cpp/functions/core/ ├── sql_workbench.h # NEW ├── sql_workbench.cpp # NEW └── sql_workbench.md # NEW cpp/apps/primitives_gallery/ ├── demos_sql.cpp # NEW ├── demos.h # MOD ├── main.cpp # MOD └── CMakeLists.txt # MOD ``` ### Pure core / impure shell `sql_workbench_cpp_core`: `kind: component`, `purity: impure` (ejecuta SQL contra una DB). ### API propuesta ```cpp namespace fn { struct SqlWorkbenchState { std::string query = "SELECT name FROM sqlite_master WHERE type='table';"; std::string last_error; std::vector columns; std::vector> rows; std::vector history; // queries ejecutadas bool readonly = false; }; // db: caller-owned sqlite3*. NO se cierra desde el componente. void sql_workbench(const char* id, sqlite3* db, SqlWorkbenchState& state, ImVec2 size = {-1, -1}); } ``` ## Tareas ### Fase 1 — Backend de ejecucion - 1.1 Implementar `sql_workbench_run_query(sqlite3*, const char* sql, SqlWorkbenchState&)`: prepara, step, lee columnas y filas como strings. Limpia errores anteriores. - 1.2 Si `state.readonly=true`, rechazar comandos que no sean `SELECT`/`PRAGMA`/`EXPLAIN`. - 1.3 Cap rows a 10000 para no congelar UI; mostrar warning si se trunca. ### Fase 2 — Schema sidebar - 2.1 `sql_workbench_list_schema(sqlite3*) -> {tables: [], views: [], indices: []}`. Query a `sqlite_master`. - 2.2 Sidebar collapsable con tablas. Click → insertar `SELECT * FROM LIMIT 100;` en el editor. ### Fase 3 — UI - 3.1 Layout: panel izquierdo schema (200px), panel derecho dividido vertical (editor arriba, tabla resultado abajo). - 3.2 Boton Run + atajo Ctrl+Enter. - 3.3 Barra inferior: status (`5 rows in 12.3 ms`), error en rojo si hubo. - 3.4 Editor: usar `text_editor` con `CodeLang::SQL` si disponible (compile-time `#ifdef FN_HAS_TEXT_EDITOR` o detectar via include guard). - 3.5 Resultado: usar `table_view_cpp_viz` con sorting por columnas. ### Fase 4 — Historial - 4.1 Cada Run exitoso aparea la query a `state.history`. - 4.2 Popup "History" con las ultimas 50; click → recargar al editor. ### Fase 5 — Gallery demo - 5.1 `demos_sql.cpp` con `demo_sql_workbench()`: abre `registry.db` (path resuelto via env var `FN_REGISTRY_ROOT` o cwd), modo readonly. La sidebar muestra `functions`, `types`, etc. - 5.2 Registrar. ### Fase 6 — Tests + docs - 6.1 Test `sql_workbench_run_query` con DB en memoria: crea tabla, insert, select, verifica columns/rows. - 6.2 Test rechazo de DML en modo readonly. - 6.3 `./fn index` + `./fn show sql_workbench_cpp_core`. ## Ejemplo de uso ```cpp sqlite3* db; sqlite3_open_v2("registry.db", &db, SQLITE_OPEN_READONLY, nullptr); fn::SqlWorkbenchState st; st.readonly = true; fn::run_app("sql", [&]{ fn::sql_workbench("##sql", db, st); }); sqlite3_close(db); ``` ## Decisiones de diseño - **Caller-owned DB**: el workbench no abre/cierra. Permite reusar conexiones del host (un app que ya tiene `operations.db` abierto la pasa). - **Rows como strings**: SQLite ya las da convertibles. La `table_view` espera strings, sin conversion extra. - **Readonly opt-in**: por defecto permite escribir; el caller decide. ## Riesgos - **Queries que cuelgan UI**: documentar; en MVP, ejecucion sincrona en main thread. Si bloquea, considerar `std::thread` (ya disponible via `process_runner_cpp_core` patrón). - **Memoria con resultados grandes**: cap a 10000 filas. Documentar.