---
id: "0032"
title: "C++ sql_workbench"
status: completado
type: feature
domain:
  - cpp-stack
scope: multi-app
priority: media
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 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 <name> 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<std::string>              columns;
    std::vector<std::vector<std::string>> rows;
    std::vector<std::string>              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 <t> 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.
