@@ -2696,6 +2696,36 @@ int cbm_deduplicate_hops(const cbm_node_hop_t *hops, int hop_count, cbm_node_hop
26962696
26972697/* ── Schema ─────────────────────────────────────────────────────── */
26982698
2699+ enum { SCHEMA_MAX_JSON_KEYS = 50 };
2700+
2701+ /* Discover distinct JSON property keys for a table/column via json_each().
2702+ * Prepends base_cols, then appends up to SCHEMA_MAX_JSON_KEYS from the query.
2703+ * Caller must free the returned array and each string in it. */
2704+ static void schema_discover_props (sqlite3 * db , const char * sql , const char * project ,
2705+ const char * filter , const char * * base_cols , int base_col_count ,
2706+ char * * * out_props , int * out_count ) {
2707+ int pcap = base_col_count + SCHEMA_MAX_JSON_KEYS ;
2708+ char * * props = malloc (pcap * sizeof (char * ));
2709+ int pn = 0 ;
2710+
2711+ for (int b = 0 ; b < base_col_count ; b ++ ) {
2712+ props [pn ++ ] = heap_strdup (base_cols [b ]);
2713+ }
2714+
2715+ sqlite3_stmt * pstmt = NULL ;
2716+ if (sqlite3_prepare_v2 (db , sql , CBM_NOT_FOUND , & pstmt , NULL ) == SQLITE_OK ) {
2717+ bind_text (pstmt , SKIP_ONE , project );
2718+ bind_text (pstmt , PAIR_LEN , filter );
2719+ while (sqlite3_step (pstmt ) == SQLITE_ROW && pn < pcap ) {
2720+ props [pn ++ ] = heap_strdup ((const char * )sqlite3_column_text (pstmt , 0 ));
2721+ }
2722+ sqlite3_finalize (pstmt );
2723+ }
2724+
2725+ * out_props = props ;
2726+ * out_count = pn ;
2727+ }
2728+
26992729int cbm_store_get_schema (cbm_store_t * s , const char * project , cbm_schema_info_t * out ) {
27002730 memset (out , 0 , sizeof (* out ));
27012731 if (!s || !s -> db ) {
@@ -2720,13 +2750,34 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t
27202750 }
27212751 arr [n ].label = heap_strdup ((const char * )sqlite3_column_text (stmt , 0 ));
27222752 arr [n ].count = sqlite3_column_int (stmt , SKIP_ONE );
2753+ arr [n ].properties = NULL ;
2754+ arr [n ].property_count = 0 ;
27232755 n ++ ;
27242756 }
27252757 sqlite3_finalize (stmt );
27262758 out -> node_labels = arr ;
27272759 out -> node_label_count = n ;
27282760 }
27292761
2762+ /* Node label property keys: base columns + distinct JSON property keys per label */
2763+ {
2764+ static const char * node_base_cols [] = {"name" , "qualified_name" , "file_path" , "start_line" ,
2765+ "end_line" };
2766+ const char * prop_sql = "SELECT DISTINCT je.key "
2767+ "FROM nodes, json_each(nodes.properties) AS je "
2768+ "WHERE nodes.project = ?1 AND nodes.label = ?2 "
2769+ " AND nodes.properties != '{}' "
2770+ "ORDER BY je.key "
2771+ "LIMIT 50;" ;
2772+
2773+ for (int i = 0 ; i < out -> node_label_count ; i ++ ) {
2774+ schema_discover_props (
2775+ s -> db , prop_sql , project , out -> node_labels [i ].label , node_base_cols ,
2776+ (int )(sizeof (node_base_cols ) / sizeof (node_base_cols [0 ])),
2777+ & out -> node_labels [i ].properties , & out -> node_labels [i ].property_count );
2778+ }
2779+ }
2780+
27302781 /* Edge types */
27312782 {
27322783 const char * sql = "SELECT type, COUNT(*) FROM edges WHERE project = ?1 GROUP BY type ORDER "
@@ -2745,13 +2796,33 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t
27452796 }
27462797 arr [n ].type = heap_strdup ((const char * )sqlite3_column_text (stmt , 0 ));
27472798 arr [n ].count = sqlite3_column_int (stmt , SKIP_ONE );
2799+ arr [n ].properties = NULL ;
2800+ arr [n ].property_count = 0 ;
27482801 n ++ ;
27492802 }
27502803 sqlite3_finalize (stmt );
27512804 out -> edge_types = arr ;
27522805 out -> edge_type_count = n ;
27532806 }
27542807
2808+ /* Edge type property keys: base columns + distinct JSON property keys per type */
2809+ {
2810+ static const char * edge_base_cols [] = {"source_id" , "target_id" };
2811+ const char * prop_sql = "SELECT DISTINCT je.key "
2812+ "FROM edges, json_each(edges.properties) AS je "
2813+ "WHERE edges.project = ?1 AND edges.type = ?2 "
2814+ " AND edges.properties != '{}' "
2815+ "ORDER BY je.key "
2816+ "LIMIT 50;" ;
2817+
2818+ for (int i = 0 ; i < out -> edge_type_count ; i ++ ) {
2819+ schema_discover_props (s -> db , prop_sql , project , out -> edge_types [i ].type , edge_base_cols ,
2820+ (int )(sizeof (edge_base_cols ) / sizeof (edge_base_cols [0 ])),
2821+ & out -> edge_types [i ].properties ,
2822+ & out -> edge_types [i ].property_count );
2823+ }
2824+ }
2825+
27552826 return CBM_STORE_OK ;
27562827}
27572828
@@ -2761,11 +2832,19 @@ void cbm_store_schema_free(cbm_schema_info_t *out) {
27612832 }
27622833 for (int i = 0 ; i < out -> node_label_count ; i ++ ) {
27632834 free ((void * )out -> node_labels [i ].label );
2835+ for (int j = 0 ; j < out -> node_labels [i ].property_count ; j ++ ) {
2836+ free (out -> node_labels [i ].properties [j ]);
2837+ }
2838+ free (out -> node_labels [i ].properties );
27642839 }
27652840 free (out -> node_labels );
27662841
27672842 for (int i = 0 ; i < out -> edge_type_count ; i ++ ) {
27682843 free ((void * )out -> edge_types [i ].type );
2844+ for (int j = 0 ; j < out -> edge_types [i ].property_count ; j ++ ) {
2845+ free (out -> edge_types [i ].properties [j ]);
2846+ }
2847+ free (out -> edge_types [i ].properties );
27692848 }
27702849 free (out -> edge_types );
27712850
0 commit comments