refactor(tables): FASE 4 - Add DataSource adapters

Add MemoryDataSource for AdvancedTable (in-memory ArrayList) and
PagedDataSource for VirtualAdvancedTable (paged DB data).

Both implement TableDataSource interface from table_core.zig,
enabling unified rendering patterns while respecting memory ownership.

New files:
- advanced_table/datasource.zig - MemoryDataSource
- virtual_advanced_table/paged_datasource.zig - PagedDataSource

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
R.Eugenio 2025-12-27 17:37:04 +01:00
parent 473bbdb648
commit cf2f91f8bc
4 changed files with 316 additions and 0 deletions

View file

@ -47,6 +47,10 @@ pub const state = @import("state.zig");
pub const AdvancedTableState = state.AdvancedTableState; pub const AdvancedTableState = state.AdvancedTableState;
pub const AdvancedTableResult = state.AdvancedTableResult; pub const AdvancedTableResult = state.AdvancedTableResult;
// Re-export datasource
pub const datasource = @import("datasource.zig");
pub const MemoryDataSource = datasource.MemoryDataSource;
// Re-export table_core types // Re-export table_core types
pub const NavigateDirection = table_core.NavigateDirection; pub const NavigateDirection = table_core.NavigateDirection;

View file

@ -0,0 +1,126 @@
//! MemoryDataSource - Adaptador de TableDataSource para datos en memoria
//!
//! Wrappea AdvancedTableState + columnas para implementar la interfaz
//! TableDataSource de table_core. Permite que AdvancedTable use el mismo
//! patrón de renderizado que VirtualAdvancedTable.
const std = @import("std");
const table_core = @import("../table_core.zig");
const state_mod = @import("state.zig");
const schema_mod = @import("schema.zig");
const types = @import("types.zig");
const AdvancedTableState = state_mod.AdvancedTableState;
const ColumnDef = schema_mod.ColumnDef;
const CellValue = types.CellValue;
const Row = types.Row;
const TableDataSource = table_core.TableDataSource;
// =============================================================================
// MemoryDataSource
// =============================================================================
/// Adaptador que implementa TableDataSource para datos en memoria (ArrayList)
/// Usa el patrón getCellValueInto para escritura segura en buffer proporcionado
pub const MemoryDataSource = struct {
/// Referencia al estado de la tabla
state: *AdvancedTableState,
/// Definiciones de columnas (para mapear índice -> nombre)
columns: []const ColumnDef,
/// Buffer interno para formateo (evita allocaciones)
format_buffer: [256]u8 = undefined,
const Self = @This();
/// Crea un nuevo MemoryDataSource
pub fn init(state: *AdvancedTableState, columns: []const ColumnDef) Self {
return .{
.state = state,
.columns = columns,
};
}
// =========================================================================
// Implementación de TableDataSource
// =========================================================================
/// Retorna el número total de filas
pub fn getRowCount(self: *Self) usize {
return self.state.getRowCount();
}
/// Escribe el valor de una celda en el buffer proporcionado
/// Retorna slice del buffer con el contenido
pub fn getCellValueInto(self: *Self, row: usize, col: usize, buf: []u8) []const u8 {
// Validar índices
if (col >= self.columns.len) return "";
// Obtener fila
const row_data = self.state.getRowConst(row) orelse return "";
// Obtener nombre de columna
const col_name = self.columns[col].name;
// Obtener valor de celda
const cell_value = row_data.get(col_name);
// Formatear en el buffer proporcionado
return cell_value.format(buf);
}
/// Retorna el ID único de una fila
/// Para datos en memoria, usamos el índice como ID
/// TODO: Si Row tuviera campo 'id', lo usaríamos
pub fn getRowId(self: *Self, row: usize) i64 {
_ = self;
// Por ahora usamos el índice como ID
// En el futuro, Row podría tener un campo 'id' explícito
return @intCast(row);
}
/// Verifica si una celda es editable
pub fn isCellEditable(self: *Self, row: usize, col: usize) bool {
_ = row;
// Verificar configuración de columna
if (col >= self.columns.len) return false;
return self.columns[col].editable;
}
/// Invalida cache (no aplica para datos en memoria)
pub fn invalidate(self: *Self) void {
_ = self;
// No hay cache que invalidar en datos en memoria
}
// =========================================================================
// Conversión a TableDataSource
// =========================================================================
/// Crea TableDataSource vtable para este adaptador
pub fn toDataSource(self: *Self) TableDataSource {
return table_core.makeTableDataSource(Self, self);
}
};
// =============================================================================
// Tests
// =============================================================================
test "MemoryDataSource basic" {
// Test básico de que compila y los tipos son correctos
const columns = [_]ColumnDef{
.{ .name = "id", .title = "ID", .width = 50 },
.{ .name = "name", .title = "Name", .width = 200 },
};
var state = AdvancedTableState.init(std.testing.allocator);
defer state.deinit();
var ds = MemoryDataSource.init(&state, &columns);
const tds = ds.toDataSource();
try std.testing.expectEqual(@as(usize, 0), tds.getRowCount());
}

View file

@ -0,0 +1,184 @@
//! PagedDataSource - Adaptador de TableDataSource para datos paginados
//!
//! Wrappea VirtualAdvancedTableState + DataProvider para implementar la interfaz
//! TableDataSource de table_core. Permite unificar el pattern de renderizado
//! entre AdvancedTable (memoria) y VirtualAdvancedTable (paginado).
const std = @import("std");
const table_core = @import("../table_core.zig");
const state_mod = @import("state.zig");
const types = @import("types.zig");
const data_provider_mod = @import("data_provider.zig");
const VirtualAdvancedTableState = state_mod.VirtualAdvancedTableState;
const ColumnDef = types.ColumnDef;
const RowData = types.RowData;
const DataProvider = data_provider_mod.DataProvider;
const TableDataSource = table_core.TableDataSource;
// =============================================================================
// PagedDataSource
// =============================================================================
/// Adaptador que implementa TableDataSource para datos paginados (ventana virtual).
/// Lee valores desde current_window en el state, que es propiedad del DataProvider.
pub const PagedDataSource = struct {
/// Referencia al estado de la tabla (contiene current_window)
state: *VirtualAdvancedTableState,
/// DataProvider subyacente (para getRowId cuando fuera de ventana)
provider: ?DataProvider,
/// Definiciones de columnas (para validar índices)
columns: []const ColumnDef,
const Self = @This();
/// Crea un nuevo PagedDataSource
pub fn init(
state: *VirtualAdvancedTableState,
columns: []const ColumnDef,
provider: ?DataProvider,
) Self {
return .{
.state = state,
.columns = columns,
.provider = provider,
};
}
// =========================================================================
// Implementación de TableDataSource
// =========================================================================
/// Retorna el número total de filas (filtered count)
pub fn getRowCount(self: *Self) usize {
// Usar conteo filtrado si está disponible
const count_info = self.state.getDisplayCount();
return count_info.value;
}
/// Escribe el valor de una celda en el buffer proporcionado.
/// El row es índice global, se convierte a índice de ventana.
/// Retorna slice del buffer con el contenido.
pub fn getCellValueInto(self: *Self, row: usize, col: usize, buf: []u8) []const u8 {
// Validar columna
if (col >= self.columns.len) return "";
// Convertir índice global a índice de ventana
const window_idx = self.state.globalToWindowIndex(row) orelse {
// Fila fuera de ventana actual - retornar vacío
// (el caller debería asegurarse de pedir solo filas visibles)
return "";
};
// Obtener fila de la ventana
if (window_idx >= self.state.current_window.len) return "";
const row_data = self.state.current_window[window_idx];
// Obtener valor de la columna
if (col >= row_data.values.len) return "";
const value = row_data.values[col];
// Copiar al buffer proporcionado
const copy_len = @min(value.len, buf.len);
@memcpy(buf[0..copy_len], value[0..copy_len]);
return buf[0..copy_len];
}
/// Retorna el ID único de una fila.
/// Si está en ventana, usa window data. Si no, consulta al provider.
pub fn getRowId(self: *Self, row: usize) i64 {
// Intentar obtener de la ventana
if (self.state.globalToWindowIndex(row)) |window_idx| {
if (window_idx < self.state.current_window.len) {
return self.state.current_window[window_idx].id;
}
}
// Fallback al provider si está disponible
if (self.provider) |provider| {
return provider.getRowId(row) orelse -1;
}
// Si no hay provider, usar índice como fallback
return @intCast(row);
}
/// Verifica si una celda es editable.
/// TODO: Podría usar metadata de columnas cuando esté disponible.
pub fn isCellEditable(self: *Self, row: usize, col: usize) bool {
_ = row;
// Por ahora, todas las columnas son no editables por defecto
// La edición se maneja a nivel de VirtualAdvancedTable
if (col >= self.columns.len) return false;
return false; // VirtualAdvancedTable maneja edición de forma específica
}
/// Invalida cache.
/// Delega al state para forzar refetch.
pub fn invalidate(self: *Self) void {
self.state.invalidateWindow();
}
// =========================================================================
// Conversión a TableDataSource
// =========================================================================
/// Crea TableDataSource vtable para este adaptador
pub fn toDataSource(self: *Self) TableDataSource {
return table_core.makeTableDataSource(Self, self);
}
};
// =============================================================================
// Tests
// =============================================================================
test "PagedDataSource basic" {
// Test básico de que compila y los tipos son correctos
const columns = [_]ColumnDef{
.{ .name = "id", .title = "ID", .width = 50 },
.{ .name = "name", .title = "Name", .width = 200 },
};
var state = VirtualAdvancedTableState{};
var ds = PagedDataSource.init(&state, &columns, null);
const tds = ds.toDataSource();
// Sin datos, row count es 0
try std.testing.expectEqual(@as(usize, 0), tds.getRowCount());
}
test "PagedDataSource getCellValueInto" {
const columns = [_]ColumnDef{
.{ .name = "id", .title = "ID", .width = 50 },
.{ .name = "name", .title = "Name", .width = 200 },
};
// Crear datos de prueba
const values1 = [_][]const u8{ "1", "Alice" };
const values2 = [_][]const u8{ "2", "Bob" };
const rows = [_]RowData{
.{ .id = 100, .values = &values1 },
.{ .id = 101, .values = &values2 },
};
var state = VirtualAdvancedTableState{};
state.current_window = &rows;
state.window_start = 10; // Ventana empieza en índice global 10
state.filtered_count = .{ .value = 100, .state = .ready };
var ds = PagedDataSource.init(&state, &columns, null);
// Buffer para valores
var buf: [256]u8 = undefined;
// Fila en ventana (global 10 = window 0)
const val1 = ds.getCellValueInto(10, 1, &buf);
try std.testing.expectEqualStrings("Alice", val1);
// Fila fuera de ventana
const val2 = ds.getCellValueInto(0, 0, &buf);
try std.testing.expectEqualStrings("", val2);
}

View file

@ -26,6 +26,7 @@ pub const types = @import("types.zig");
pub const data_provider = @import("data_provider.zig"); pub const data_provider = @import("data_provider.zig");
pub const state_mod = @import("state.zig"); pub const state_mod = @import("state.zig");
pub const cell_editor = @import("cell_editor.zig"); pub const cell_editor = @import("cell_editor.zig");
pub const paged_datasource = @import("paged_datasource.zig");
// Tipos principales // Tipos principales
pub const RowData = types.RowData; pub const RowData = types.RowData;
@ -41,6 +42,7 @@ pub const CellId = types.CellId;
pub const CellGeometry = types.CellGeometry; pub const CellGeometry = types.CellGeometry;
pub const DataProvider = data_provider.DataProvider; pub const DataProvider = data_provider.DataProvider;
pub const PagedDataSource = paged_datasource.PagedDataSource;
pub const CellEditorColors = cell_editor.CellEditorColors; pub const CellEditorColors = cell_editor.CellEditorColors;
pub const CellEditorResult = cell_editor.CellEditorResult; pub const CellEditorResult = cell_editor.CellEditorResult;
pub const NavigateDirection = cell_editor.NavigateDirection; pub const NavigateDirection = cell_editor.NavigateDirection;