#pragma once // data_table_internal — contrato compartido entre las 6 sub-funciones del modulo. // NO publico para apps: las apps incluyen `data_table/data_table.h` y nada mas. // Este header lo incluyen SOLO los .cpp del modulo (data_table.cpp + sus 6 sub-funciones). // // Issue 0107c — split de data_table.cpp (4777 LOC) en 6 sub-funciones del registry. // Issue 0133 — columnar snapshot + string interning (Changes 1+2). // // Provee: // 1. `UiState` agregador (composicion de sub-states declarados en los .h de // cada sub-funcion + estado compartido por >1 sub-funcion). // 2. `ui()` accessor para singleton thread_local — mismo lifetime que el // original UiState del playground. // 3. Helpers compartidos como `inline` puros: `ops_for_type`, `op_label`, // `effective_type`, `view_mode_label`, `join_strategy_label`, etc. // 4. Forward refs de funciones internas que cruzan sub-funciones (ej. el // draw_header_menu de chips llama draw_color_rule_menu de color_rules). // 5. `ColumnSnapshot` / `SnapshotCache` — snapshot columnar interno (issue 0133). // // Politica: // - Si un helper se usa SOLO dentro de UNA sub-funcion -> queda `static` en su .cpp. // - Si se usa en >1 sub-funcion -> aqui como `inline`. // - Si manipula estado -> miembro de UiState (o sub-state). // // API publica externa = data_table/data_table.h (intacta tras refactor). // API interna del modulo = este header. // // --------------------------------------------------------------------------- // DISEÑO: Snapshot columnar + String Interning (issue 0133) // --------------------------------------------------------------------------- // // MOTIVACION // El layout de datos de entrada es row-major: `cells[row * cols + col]` // (punteros a C-strings en el `TableInput` del caller). Acceder a una // columna entera (para filtrar, ordenar, colorear, calcular stats) requiere // saltar `cols` posiciones en memoria por fila — mal para cache a 10M+ filas. // El snapshot convierte a column-major una sola vez por frame y amortiza el // coste entre filter / sort / color_rules / stats. // // SNAPSHOT COLUMNAR — `ColumnSnapshot` / `SnapshotCache` // - `SnapshotCache` vive en `UiState` (singleton thread_local de este modulo). // - Contiene un vector de `ColumnSnapshot`, uno por columna efectiva de la // tabla DESPUES de joins pero ANTES de stages (= stage-0 input). // - `SnapshotCache::last_cells_ptr` guarda el puntero de `cells` del ultimo // rebuild. Si el puntero cambia (o es la primera llamada), se reconstruye // el snapshot completo. Esto sigue exactamente el patron de `stats_last_cells` // en `State` (data_table_types.h:399). // - String columns (ColumnType::String / Auto sin numero) almacenan indices // uint32_t al `StringPool` del `State` correspondiente. // - Int columns almacenan int64_t parseados una sola vez via `parse_number`. // - Float columns almacenan double parseados una sola vez via `parse_number`. // - Si `parse_number` falla en una celda que deberia ser Int o Float, la celda // se trata como 0 / 0.0. Este comportamiento es consistente con `compare()` // y el sort actual que ya llaman `parse_number` per-compare. // // STRING INTERNING — `StringPool` en `State` // - `StringPool` vive en `State` (NOT global, NOT singleton) para que cada // instancia de tabla tenga su propio pool sin interferencia. // - `intern(sv)` inserta la cadena si no esta y devuelve su indice uint32_t. // Usa `unordered_map` con la `string_view` apuntando // al `strings[i]` del vector (el vector se reserva antes del rebuild para // evitar reallocs que invaliden los string_views del mapa). // - En datasets tipicos (60-70% de strings repetidos) la reduccion de RAM // es de 60-70% en la columna interned vs copias planas. // // INVARIANTES // 1. Pointer-identity: si `cells == last_cells_ptr`, el snapshot es valido // para este frame. Cambio de puntero => rebuild completo. // 2. row→snapshot_row: el indice de fila en el snapshot es IDENTICO al indice // de fila del `TableInput` original (no hay reordenacion en el snapshot). // `TableEvent.row` del caller sigue siendo indice en `TableInput`. // 3. El snapshot es input de stage-0. Los stages sucesivos (compute_stage) // operan sobre `StageOutput` materializado y NO consultan el snapshot // directamente — el snapshot solo alimenta la ruta stage-0 de render(). // 4. `stats_last_cells` y snapshot son independientes: `stats_last_cells` // ya existia antes de issue 0133 y permanece como sentinel propio del // cache de stats. El snapshot tiene su propio `last_cells_ptr`. // Ambos pueden diferir temporalmente si `stats_cache` invalida por // filtro (hash de filtros) pero el snapshot sigue valido por puntero. // 5. `StringPool` se limpia (clear()) en cada rebuild del snapshot para // mantener coherencia: los indices del snapshot siempre corresponden // al pool del mismo frame. // --------------------------------------------------------------------------- #include "core/data_table_types.h" #include "core/auto_detect_type.h" // Sub-states declarados en los .h de cada sub-funcion. Se incluyen aqui para // que `UiState` los pueda componer por valor. #include "viz/data_table_ai_panel.h" // AskAiState #include "viz/data_table_chips.h" // TqlBarState #include "viz/data_table_color_rules.h" // ColorRuleEditorState #include "imgui.h" #include #include #include namespace data_table { // --------------------------------------------------------------------------- // ColumnSnapshot — snapshot de una columna en memoria columnar. // Creado una vez por frame al detectar cambio de puntero en `cells`. // --------------------------------------------------------------------------- struct ColumnSnapshot { ColumnType type; // tipo efectivo inferido (post auto_detect) std::vector str_ids; // para String/Auto: indices al StringPool std::vector i64; // para Int: valores parseados std::vector f64; // para Float: valores parseados }; // --------------------------------------------------------------------------- // SnapshotCache — vive en UiState (thread_local singleton). // Un snapshot cubre TODAS las columnas efectivas de la tabla activa // (post-join, pre-stages). Se invalida por pointer-identity de `cells`. // --------------------------------------------------------------------------- struct SnapshotCache { const char* const* last_cells_ptr = nullptr; // sentinel de invalidacion por ptr uint32_t pool_size_built = 0; // strings.size() cuando se construyo std::vector cols; // un entry por columna efectiva }; // --------------------------------------------------------------------------- // UiState — singleton thread_local del modulo. Agrupa: // (a) Sub-states declarados en headers de sub-funciones (AskAiState etc.). // (b) Estado compartido por >1 sub-funcion que no encaja en ningun sub-state // individual. // El entrypoint thin (data_table.cpp) mantiene el UiState via `ui()`. // Cada sub-funcion recibe `UiState&` o solo el sub-state que necesita. // --------------------------------------------------------------------------- struct UiState { // ----- Sub-states de sub-funciones (composicion) ----- AskAiState ask_ai; // data_table_ai_panel TqlBarState tql_bar; // data_table_chips ColorRuleEditorState color_rules; // data_table_color_rules // ----- Cell popup (grid + chips, drill popup en cell ctx menu) ----- int pending_col = -1; std::string pending_value; bool open_cell_popup = false; // ----- Header popup (chips draw_header_menu + color_rules + grid header) ----- int header_popup_col = -1; std::unordered_map filter_inputs; // color_value_inputs + color_picker_vals viven en color_rules (ColorRuleEditorState). // ----- Add-filter popup state (chips draw_add_filter_popup) ----- int addf_col = 0; std::string addf_val; bool addf_range = false; std::string addf_lo; std::string addf_hi; // ----- Custom column modal (formula editor — vive en data_table.cpp entrypoint) ----- bool cf_open = false; bool cf_editing = false; int cf_edit_idx = -1; int cf_target_stage = 0; std::string cf_formula; std::string cf_name; ColumnType cf_type = ColumnType::String; std::string cf_error; bool cf_ac_open = false; int cf_ac_start = -1; int cf_ac_cursor = -1; std::string cf_ac_filter; bool cf_force_cursor = false; int cf_target_cursor = -1; // ----- Add-breakout / add-aggregation / edit chip popups (chips) ----- int brk_picker_col = 0; int agg_picker_fn = (int)AggFn::Count; int agg_picker_col = 0; double agg_picker_arg = 0.95; int edit_chip_kind = 0; // 0=none, 1=filter, 2=breakout, 3=agg, 4=sort int edit_chip_idx = -1; int edit_col_idx = 0; int edit_op = (int)Op::Eq; int edit_agg_fn = (int)AggFn::Count; double edit_agg_arg = 0.5; bool edit_sort_desc = false; std::string edit_value; int sort_picker_col = 0; bool sort_picker_desc = false; // ----- Snapshot del active stage output/input (viz_panels config popup) ----- std::vector active_headers; std::vector active_types; std::vector input_headers_active; std::vector input_types_active; // ----- Re-fit triggers para viz panels ----- ViewMode prev_viz_display = ViewMode::Table; int prev_viz_stage = 0; std::size_t prev_viz_cfg_h = 0; // ----- Toggle Table <-> View (viz_panels) ----- ViewMode last_non_table_main = ViewMode::Bar; // ----- Export path (chips export action) ----- std::string last_export_path; // ----- Columnar snapshot (issue 0133, Change 1) ----- // Invalida cuando cells pointer cambia entre frames. // Usado en render() stage-0 path para filter/sort/color_rules/stats. SnapshotCache snapshot; }; // Singleton accessor. Definido en data_table.cpp (entrypoint). UiState& ui(); // --------------------------------------------------------------------------- // ViewMode table — fuente de verdad para label/needs_agg/all_modes. // Compartida por: data_table.cpp (re-fit trigger) y data_table_viz_panels.cpp. // --------------------------------------------------------------------------- struct ViewModeInfo { ViewMode m; const char* token; const char* label; int min_cols; bool needs_num; bool needs_cat; bool needs_agg; }; static const ViewModeInfo kViewModes[] = { { ViewMode::Table, "table", "Table", 1, false, false, false }, { ViewMode::Bar, "bar", "Bar (horizontal)", 2, true, true, true }, { ViewMode::Column, "column", "Column (vertical)", 2, true, true, true }, { ViewMode::GroupedBar, "grouped_bar", "Grouped bar", 2, true, true, true }, { ViewMode::StackedBar, "stacked_bar", "Stacked bar", 2, true, true, true }, { ViewMode::Line, "line", "Line", 1, true, false, false }, { ViewMode::Area, "area", "Area", 1, true, false, false }, { ViewMode::Stairs, "stairs", "Stairs", 1, true, false, false }, { ViewMode::Scatter, "scatter", "Scatter", 2, true, false, false }, { ViewMode::Bubble, "bubble", "Bubble", 3, true, false, false }, { ViewMode::Histogram, "histogram", "Histogram", 1, true, false, false }, { ViewMode::Histogram2D, "hist2d", "Histogram 2D", 2, true, false, false }, { ViewMode::Heatmap, "heatmap", "Heatmap", 1, true, false, false }, { ViewMode::BoxPlot, "boxplot", "Box plot", 2, true, true, false }, { ViewMode::Stem, "stem", "Stem", 1, true, false, false }, { ViewMode::ErrorBars, "errorbars", "Error bars", 2, true, false, false }, { ViewMode::Pie, "pie", "Pie", 2, true, true, true }, { ViewMode::Donut, "donut", "Donut", 2, true, true, true }, { ViewMode::Funnel, "funnel", "Funnel", 2, true, true, true }, { ViewMode::Waterfall, "waterfall", "Waterfall", 1, true, false, true }, { ViewMode::KPI, "kpi", "KPI (single)", 1, true, false, true }, { ViewMode::KPIGrid, "kpi_grid", "KPI grid", 1, true, false, true }, { ViewMode::Candlestick, "candlestick", "Candlestick (OHLC)", 4, true, false, false }, { ViewMode::Radar, "radar", "Radar", 2, true, true, false }, }; static const int kViewModesN = (int)(sizeof(kViewModes) / sizeof(kViewModes[0])); inline const char* view_mode_label(ViewMode m) { for (int i = 0; i < kViewModesN; ++i) if (kViewModes[i].m == m) return kViewModes[i].label; return "Table"; } inline bool view_mode_needs_aggregation(ViewMode m) { for (int i = 0; i < kViewModesN; ++i) if (kViewModes[i].m == m) return kViewModes[i].needs_agg; return false; } inline const ViewMode* all_view_modes(int* n_out) { static ViewMode arr[64]; static bool init = false; if (!init) { for (int i = 0; i < kViewModesN; ++i) arr[i] = kViewModes[i].m; init = true; } if (n_out) *n_out = kViewModesN; return arr; } // filters_hash: FNV-1a hash de filtros activos para detectar cambios en maybe_recompute_stats. inline size_t filters_hash(const std::vector& f) { size_t h = 0xcbf29ce484222325ULL; for (const auto& x : f) { h ^= (size_t)x.col; h *= 0x100000001b3ULL; h ^= (size_t)x.op; h *= 0x100000001b3ULL; for (char ch : x.value) { h ^= (size_t)(unsigned char)ch; h *= 0x100000001b3ULL; } } return h; } // ColInfo: nombre + tipo de columna. Usado por draw_viz_config_popup para // construir las listas de seleccion X/Y/Cat. struct ColInfo { std::string name; ColumnType type; }; // collect_active_col_info: devuelve snapshot de columnas activas del active_stage // (pobladas por render() en UiState::active_headers / active_types). // Compartida por: data_table.cpp (forward decl) y data_table_viz_panels.cpp. inline std::vector collect_active_col_info(const State& /*st*/) { auto& U = ui(); std::vector r; int n = (int)std::min(U.active_headers.size(), U.active_types.size()); r.reserve(n); for (int i = 0; i < n; ++i) r.push_back({U.active_headers[i], U.active_types[i]}); return r; } // auto_promote_aggregated: si user en stage 0 elige una viz que necesita // agrupacion, crea stage 1 con breakout=primera cat + agg=sum(primera num) o count. // Compartida por: data_table.cpp y data_table_viz_panels.cpp. inline void auto_promote_aggregated(State& st) { auto& U = ui(); if (st.active_stage != 0) return; if (st.stages.size() != 1) return; std::string cat_name; std::string num_name; for (size_t i = 0; i < U.active_headers.size() && i < U.active_types.size(); ++i) { ColumnType t = U.active_types[i]; if (cat_name.empty() && (t == ColumnType::String || t == ColumnType::Date || t == ColumnType::Bool || t == ColumnType::Json)) { cat_name = U.active_headers[i]; } if (num_name.empty() && (t == ColumnType::Int || t == ColumnType::Float)) { num_name = U.active_headers[i]; } } Stage s1; if (!cat_name.empty()) s1.breakouts.push_back(cat_name); Aggregation a; if (!num_name.empty()) { a.fn = AggFn::Sum; a.col = num_name; } else { a.fn = AggFn::Count; } s1.aggregations.push_back(a); st.stages.push_back(std::move(s1)); st.active_stage = (int)st.stages.size() - 1; } // --------------------------------------------------------------------------- // Helpers compartidos (inline, puros). Usados por >1 sub-funcion. // --------------------------------------------------------------------------- // effective_type: si declared==Auto, llama auto_detect_type sobre el rango. // Usado por: chips (filter popups), grid (cell render), viz_panels (stats). inline ColumnType effective_type(ColumnType declared, const char* const* cells, int rows, int cols, int col) { if (declared != ColumnType::Auto) return declared; return auto_detect_type(cells, rows, cols, col); } // ops_for_type: lista de operadores validos para un ColumnType. // Usado por: chips (filter popups, edit popups), grid (cell ctx menu). inline std::vector ops_for_type(ColumnType t) { switch (t) { case ColumnType::Int: case ColumnType::Float: case ColumnType::Date: return {Op::Eq, Op::Neq, Op::Gt, Op::Gte, Op::Lt, Op::Lte}; case ColumnType::Bool: return {Op::Eq, Op::Neq}; case ColumnType::Json: return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains}; case ColumnType::String: return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains, Op::StartsWith, Op::EndsWith}; case ColumnType::Auto: default: return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains}; } } // op_label: nombre humano del operador para chips + cell ctx menu. // Usado por: chips, grid. // NOTE: `static inline` para evitar clash de simbolos con la definicion // canonica de `op_label` en tql_helpers.cpp (issue 0107c follow-up). MinGW // linker es estricto con inline-no-static vs out-of-line definition. static inline const char* op_label(Op op) { switch (op) { case Op::Eq: return "="; case Op::Neq: return "!="; case Op::Gt: return ">"; case Op::Gte: return ">="; case Op::Lt: return "<"; case Op::Lte: return "<="; case Op::Contains: return "contains"; case Op::NotContains: return "!contains"; case Op::StartsWith: return "starts"; case Op::EndsWith: return "ends"; } return "?"; } // join_strategy_label: nombre humano de la estrategia de join. // Usado por: chips (joins chips), data_table.cpp (render setup). inline const char* join_strategy_label(JoinStrategy s) { switch (s) { case JoinStrategy::Left: return "left-join"; case JoinStrategy::Inner: return "inner-join"; case JoinStrategy::Right: return "right-join"; case JoinStrategy::Full: return "full-join"; } return "left-join"; } // resolve_main_idx: encuentra el indice de tables[] que coincide con main_source. // Usado por: data_table.cpp render entrypoint. inline int resolve_main_idx(const std::vector& tables, const std::string& main_source) { if (tables.empty()) return -1; if (main_source.empty()) return 0; for (std::size_t i = 0; i < tables.size(); ++i) { if (tables[i].name == main_source) return (int)i; } return 0; } // --------------------------------------------------------------------------- // Forward refs de funciones internas que cruzan sub-funciones. // Cada cual definida en SU .cpp; aqui solo declarada para que otros .cpp del // modulo la puedan llamar sin doble include. // --------------------------------------------------------------------------- // draw_color_rule_menu: llamado desde draw_header_menu (submenu "Conditional color"). // Definido en data_table_color_rules.cpp. // Retorna true si el usuario hizo click en "Apply". bool draw_color_rule_menu(State& st, int col, ColumnType col_type, ColorRuleEditorState& editor_st); } // namespace data_table