---
name: sql_workbench
kind: component
lang: cpp
domain: core
version: "1.0.0"
purity: impure
signature: "void fn::sql_workbench(const char* id, sqlite3* db, fn::SqlWorkbenchState& state, ImVec2 size); bool fn::sql_workbench_run_query(sqlite3*, const char*, fn::SqlWorkbenchState&); void fn::sql_workbench_load_schema(sqlite3*, fn::SqlWorkbenchState&); void fn::sql_workbench_destroy(fn::SqlWorkbenchState&)"
description: "Workbench SQL embebido en ImGui: editor con highlighting (text_editor + CodeLang::SQL), tabla de resultados (table_view), sidebar de schema (sqlite_master) e historial. Ejecuta queries contra una sqlite3* del caller (no abre/cierra la DB)."
tags: [imgui, sql, sqlite, editor, table, dashboard, registry, debug]
uses_functions: [text_editor_cpp_core, table_view_cpp_viz, button_cpp_core, tokens_cpp_core]
uses_types: []
returns: []
returns_optional: false
error_type: error_go_core
imports: [imgui, sqlite3]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/sql_workbench.cpp"
framework: imgui
params:
  - name: id
    desc: "ImGui id estable para el contenedor (debe ser unico en el frame)"
  - name: db
    desc: "sqlite3* del caller. NO se cierra desde el componente. Si es nullptr, se renderiza un empty state."
  - name: state
    desc: "Estado mutable: query, columnas, filas, historial, last_error, readonly, truncated_at, last_ms"
  - name: size
    desc: "Tamano del contenedor (ImVec2). {-1,-1} = ocupar todo el espacio disponible"
output: "Renderiza el workbench dentro de un BeginChild. Modifica state.columns/rows/last_error/last_ms al ejecutar queries y state.history al apilar queries exitosas. sql_workbench_run_query devuelve true en caso de exito."
---

# sql_workbench

Componente C++/ImGui para explorar bases SQLite desde una app del registry sin alt-tab al CLI ni a DBeaver.

## Capacidades

- **Editor SQL**: `text_editor` con `CodeLang::SQL` (syntax highlighting via ImGuiColorTextEdit).
- **Run + atajo**: boton `Run` y atajo `Ctrl+Enter` cuando el editor tiene foco.
- **Schema sidebar**: lista `tables` y `views` desde `sqlite_master`. Click → `SELECT * FROM <name> LIMIT 100;`.
- **Resultados**: tabla via `table_view_cpp_viz` (sortable/scrollable/resizable).
- **Historial**: cada query exitosa se apila en `state.history`. Boton `History` abre popup con las ultimas 50.
- **Status bar**: `N rows in X.XX ms` o mensaje de error en rojo.
- **Cap de filas**: `state.truncated_at` (default 10000); marca `state.truncated = true` y muestra "(truncated)".
- **Readonly opt-in**: si `state.readonly = true`, rechaza statements que no comiencen (case-insensitive trim) por `SELECT`, `PRAGMA`, `EXPLAIN` o `WITH`.

## API

```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;
        bool   readonly      = false;
        int    truncated_at  = 10000;
        bool   truncated     = false;
        double last_ms       = 0.0;
        // ...internals (editor handle, schema cache, popup flag).
    };

    bool sql_workbench_run_query(sqlite3* db, const char* sql, SqlWorkbenchState&);
    void sql_workbench_load_schema(sqlite3* db, SqlWorkbenchState&);
    void sql_workbench(const char* id, sqlite3* db, SqlWorkbenchState&, ImVec2 size = {-1, -1});
    void sql_workbench_destroy(SqlWorkbenchState&);
}
```

## Ejemplo

```cpp
sqlite3* db = nullptr;
sqlite3_open_v2("registry.db", &db, SQLITE_OPEN_READONLY, nullptr);

fn::SqlWorkbenchState st;
st.readonly = true;

fn::run_app({.title="sql"}, [&]{
    fn::sql_workbench("##sql", db, st, ImVec2(-1, -1));
});

fn::sql_workbench_destroy(st);
sqlite3_close(db);
```

## Decisiones de diseno

- **Caller-owned DB**: el componente recibe `sqlite3*` y NO la cierra. Permite reusar la conexion ya abierta por la app host (ej: `registry_dashboard` con `registry.db`, app con `operations.db`).
- **Rows como strings**: `sqlite3_column_text` ya da convertibles. NULL se renderiza como literal `NULL` para distinguirlo del string vacio.
- **Backend separado de UI**: `sql_workbench_run_query` es testeable contra una DB en memoria (`:memory:`) sin frame ImGui.
- **Editor lazy**: el `TextEditorState` se crea en el primer render. `sql_workbench_destroy` lo libera.
- **Schema cache**: `sql_workbench_load_schema` se llama una vez al abrir; el caller puede forzar reload poniendo `state._schema_loaded = false`.
- **Sin sorting interno**: `table_view` muestra los indicadores pero NO reordena. Para ordenar, el usuario reescribe el `ORDER BY` en SQL — coherente con la filosofia de un workbench.
- **Sin queries async**: ejecucion sincrona en main thread. Para queries lentas, considerar wrapper con `process_runner` (deja la primitiva minima).

## Riesgos / limites

- Queries que cuelgan UI: cap de 10k filas + ejecucion sincrona. Documentado.
- Memoria: cada celda es un `std::string` propio (sin `string_view` sobre `sqlite3_column_text`, que es invalidado por `sqlite3_step`).
- Historial: cap suave a 200 entries en memoria; popup muestra las ultimas 50.

## Combinaciones sugeridas

`sql_workbench` + `app_menubar` + `dashboard_panel` = panel de exploracion en cualquier app del registry. Demo en `apps/primitives_gallery` apuntando a `registry.db` readonly.
