From 08115fdfb58bb3931dd72dac1c8d7e183eaee2b7 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 9 Jun 2026 22:59:32 +0200 Subject: [PATCH] get_architecture: skip JSON property-key discovery (counts-only schema) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handle_get_architecture called cbm_store_get_schema, whose per-label/per-type property-key discovery runs json_each() over EVERY node and edge row (~10s of the 23s on a 4.8M-node kernel graph) — yet the handler only renders label/type counts. Added cbm_store_get_schema_counts (same impl, with_props=false) and switched the handler to it. The full get_graph_schema tool is unchanged. A partial expression index on is_entry_point was tried and reverted: json_extract in an index WHERE aborts store open on rows with malformed properties JSON (pre-existing in older DBs; see next commit). --- src/mcp/mcp.c | 5 ++++- src/store/store.c | 24 +++++++++++++++++++++--- src/store/store.h | 5 +++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index 26b232d20..d3aa3bf3d 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -1875,7 +1875,10 @@ static char *handle_get_architecture(cbm_mcp_server_t *srv, const char *args) { } cbm_schema_info_t schema = {0}; - cbm_store_get_schema(store, project, &schema); + /* Counts-only: this handler renders label/type counts but never property + * keys, and full key discovery json_each-scans every row (seconds-to- + * minutes on multi-million-node graphs). */ + cbm_store_get_schema_counts(store, project, &schema); cbm_architecture_info_t arch = {0}; cbm_store_get_architecture(store, project, aspects_strs_count > 0 ? aspects_strs : NULL, diff --git a/src/store/store.c b/src/store/store.c index 09bb76028..c237332e2 100644 --- a/src/store/store.c +++ b/src/store/store.c @@ -295,6 +295,12 @@ static int create_user_indexes(cbm_store_t *s) { "CREATE INDEX IF NOT EXISTS idx_edges_target_type ON edges(project, target_id, type);" "CREATE INDEX IF NOT EXISTS idx_edges_source_type ON edges(project, source_id, type);" "CREATE INDEX IF NOT EXISTS idx_edges_url_path ON edges(project, url_path_gen);"; + /* NOTE: a partial expression index on json_extract(properties,'$.is_entry_point') + * was tried for arch_entry_points and REVERTED: json_extract in an index WHERE + * aborts CREATE INDEX (and thus store open) on any row whose properties JSON is + * malformed — and pre-fix databases contain such rows (see + * pipeline_def_props_valid_json_when_oversized). Revisit only with a + * json_valid()-guarded expression once legacy DBs have aged out. */ return exec_sql(s, sql); } @@ -2897,7 +2903,11 @@ static void schema_discover_props(sqlite3 *db, const char *sql, const char *proj *out_count = pn; } -int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t *out) { +/* with_props=false skips the per-label/per-type JSON property-key discovery: + * those json_each() scans walk EVERY row of each label/type (minutes-scale on + * multi-million-node graphs) and get_architecture only needs the counts. */ +static int get_schema_impl(cbm_store_t *s, const char *project, cbm_schema_info_t *out, + bool with_props) { memset(out, 0, sizeof(*out)); if (!s || !s->db) { return CBM_NOT_FOUND; @@ -2950,7 +2960,7 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t } /* Node label property keys: base columns + distinct JSON property keys per label */ - { + if (with_props) { static const char *node_base_cols[] = {"name", "qualified_name", "file_path", "start_line", "end_line"}; const char *prop_sql = "SELECT DISTINCT je.key " @@ -3018,7 +3028,7 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t } /* Edge type property keys: base columns + distinct JSON property keys per type */ - { + if (with_props) { static const char *edge_base_cols[] = {"source_id", "target_id"}; const char *prop_sql = "SELECT DISTINCT je.key " "FROM edges, json_each(edges.properties) AS je " @@ -3038,6 +3048,14 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t return CBM_STORE_OK; } +int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t *out) { + return get_schema_impl(s, project, out, true); +} + +int cbm_store_get_schema_counts(cbm_store_t *s, const char *project, cbm_schema_info_t *out) { + return get_schema_impl(s, project, out, false); +} + void cbm_store_schema_free(cbm_schema_info_t *out) { if (!out) { return; diff --git a/src/store/store.h b/src/store/store.h index a20f432de..26b09a5c2 100644 --- a/src/store/store.h +++ b/src/store/store.h @@ -425,6 +425,11 @@ int cbm_deduplicate_hops(const cbm_node_hop_t *hops, int hop_count, cbm_node_hop int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t *out); +/* Like cbm_store_get_schema but skips per-label/per-type JSON property-key + * discovery (json_each scans over every row) — for callers that only need + * label/type counts, e.g. get_architecture. */ +int cbm_store_get_schema_counts(cbm_store_t *s, const char *project, cbm_schema_info_t *out); + /* Free a schema info's allocated memory. */ void cbm_store_schema_free(cbm_schema_info_t *out);