Files
fn_registry/cpp/functions/viz/graph_sources.md
T
egutierrez 474c2822bc feat(viz): graph_sources lector operations.db + streaming (issue 0049g)
- graph_load_from_operations: SQLite read-only, schema-detect (type_ref/type,
  from_entity/source, to_entity/target, name/type, weight, updated_at).
- 16-color indigo palette por hash FNV1a32 del nombre de tipo. user_data
  por nodo es FNV1a64(entity.id) — deterministico entre cargas.
- Label pool interno: metadata.name (JSON simple) > entities.name > id.
- graph_free libera nodes/edges/types/rel_types/labels/strdup'd names via
  arena_map (GraphData* -> arena).
- Streaming pull-based con tiebreak (updated_at, id) y crecimiento x2 de
  capacidad. Tipos nuevos descubiertos en stream se anaden a types.
- Tests: fixture in-memory (3 entity types, 2 rel types, 10 entities,
  15 relations) + smoke contra apps/script_navegador/operations.db.
- Issue movido a completed/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:12:31 +02:00

6.3 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, framework, params, output
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path framework params output
graph_sources function cpp viz 1.0.0 impure bool graph_load_from_operations(const char* db_path, GraphData* out, GraphLoadStats* stats) Lectores de grafos para GraphData con firma uniforme GraphLoadFn. Primera implementacion: operations.db (entities + relations) con variante streaming pull-based
graph
sources
sqlite
operations
streaming
loader
viz
GraphData_cpp_viz
EntityType_cpp_viz
RelationType_cpp_viz
false error_go_core
true
test_graph_sources
cpp/tests/test_graph_sources.cpp cpp/functions/viz/graph_sources.cpp imgui
name desc
db_path Ruta a un fichero SQLite operations.db (schema con entities + relations). Solo se abre en modo READONLY
name desc
out GraphData destino. La funcion aloca nodes/edges/types/rel_types y un string pool interno; el caller debe liberar con graph_free()
name desc
stats GraphLoadStats opcional con conteos y un buffer error_msg de 256 bytes; si la carga falla, errors > 0 y error_msg describe el motivo (BD ausente, tabla faltante, columna desconocida)
true si la carga fue correcta. false con stats->errors > 0 y error_msg poblado en error. Tras un retorno true: out->nodes/edges apuntan a memoria interna; out->types/rel_types listan los tipos descubiertos con color por hash del nombre y SHAPE_CIRCLE/EDGE_SOLID por defecto. user_data por nodo es FNV1a64 del entity.id (deterministico). label_idx apunta a metadata.name si existe, else entity.name, else entity.id. Streaming: graph_stream_operations_open captura MAX(updated_at, id) actuales; graph_stream_pull devuelve cuantas filas append y crece capacity en x2 si hace falta

graph_sources

Lectores de grafos para GraphData (issue 0049g). Disenado como un set de funciones con la misma firma GraphLoadFn para que anadir un backend nuevo (JSON, JSONL, GraphML, Neo4j export, etc.) sea declarar otra funcion compatible — el resto del codigo (apps, viewport, force layout, renderer) no cambia.

API

typedef bool (*GraphLoadFn)(const char* uri, GraphData* out, GraphLoadStats* stats);

bool graph_load_from_operations(const char* db_path, GraphData* out, GraphLoadStats* stats);
void graph_free(GraphData* graph);
const char* graph_label(const GraphData* graph, uint32_t label_idx);

// Streaming pull-based (poll cada N ms desde el caller).
GraphStreamSource* graph_stream_operations_open(const char* db_path, int poll_ms);
int                graph_stream_pull(GraphStreamSource*, GraphData*);
void               graph_stream_close(GraphStreamSource*);

Mapeo operations.db → GraphData

operations.db es la BD de cada app del registry. La funcion detecta el schema via PRAGMA table_info:

Concepto Columna preferida Fallback
Tipo de entidad entities.type_ref entities.type
Source / target de relacion relations.from_entity / relations.to_entity relations.source / relations.target
Tipo de relacion relations.type relations.name
Etiqueta de nodo metadata.name (JSON) entities.name o entities.id
Tiebreak streaming (updated_at, id) id solo

Cada valor distinto de type_ref produce un EntityType con color tomado de un palette de 16 (FNV1a32 sobre el nombre → palette[h & 0xF]), shape = SHAPE_CIRCLE, default_size = 6.0, icon_id = 0. La app consumidora puede sobreescribir esa apariencia via types.yaml (issue 0049k).

user_data por nodo es FNV1a64(entity.id) — deterministico entre cargas y util como handle estable para joins con metadata externa.

Errores

La funcion no usa excepciones. En error:

  • Retorna false.
  • stats->errors >= 1.
  • stats->error_msg contiene un texto corto ("open: ...", "missing table: entities", "entities: missing type_ref/type column", ...).

Relaciones con from_entity / to_entity que apunten a entities inexistentes se cuentan en stats->errors y se descartan — el grafo resultante es siempre consistente.

Streaming

El streaming es pull-based: el caller decide la cadencia y llama graph_stream_pull cuando quiere chequear cambios. La fuente guarda (MAX(updated_at), MAX(id)) al abrir y avanza el cursor con tiebreak (updated_at, id):

WHERE (updated_at > ?) OR (updated_at = ? AND id > ?)
ORDER BY updated_at, id

graph_stream_pull crece la capacidad de nodes/edges en x2 si hace falta — el caller no necesita pre-allocar. Tipos nuevos descubiertos durante el stream se anaden al final de types. Operaciones inversas (deletes) no se propagan — para reset, el caller debe graph_free y recargar.

Memoria

graph_load_from_operations aloca toda la memoria que cuelga del GraphData devuelto (incluido el string pool de labels y los nombres de tipos via strdup). El caller libera todo con graph_free(graph). La asociacion GraphData* → arena se mantiene en un mapa estatico interno; graph_label y graph_stream_pull lo consultan para acceder al pool.

Ejemplo

GraphData g{};
graph::GraphLoadStats s{};
if (!graph::graph_load_from_operations("apps/script_navegador/operations.db", &g, &s)) {
    fprintf(stderr, "load failed: %s\n", s.error_msg);
    return 1;
}
printf("nodes=%d edges=%d types=%d rel_types=%d\n",
       s.nodes_loaded, s.edges_loaded, s.types_discovered, s.rel_types_discovered);

// Render con graph_renderer + force layout via graph_force_layout.
// ...

// Watcher en otro hilo:
auto* src = graph::graph_stream_operations_open("apps/script_navegador/operations.db", 500);
while (running) {
    sleep_ms(500);
    int n = graph::graph_stream_pull(src, &g);
    if (n > 0) printf("appended %d new rows\n", n);
}
graph::graph_stream_close(src);
graph::graph_free(&g);

Notas

  • v1.0 (2026-04-29, issue 0049g): primera version. Lector sincrono + streaming poll-based. Tests con fixture in-memory: 10 entities + 15 relations, 3 entity types, 2 relation types. Determinismo del user_data y resolucion de aristas verificados.
  • La firma GraphLoadFn esta diseniada para futuros backends. Anadir uno (ej. graph_load_from_jsonl) consiste en declarar una funcion con la misma firma; nada en el resto del pipeline (renderer, layout, viewport) cambia.