@@ -551,65 +551,67 @@ fn ambiguous_json(candidates: &[CandidateRow]) -> String {
551551// Frontier computation helper (for session_context)
552552// ---------------------------------------------------------------------------
553553
554- fn compute_frontier_entries (
554+ /// Runs `{sql_prefix} (?, ?, ...) AND from_path IS NOT NULL` in chunks of ≤999
555+ /// to stay within SQLite's SQLITE_LIMIT_VARIABLE_NUMBER, accumulating distinct
556+ /// `from_path` values into `out`.
557+ fn query_paths_chunked (
555558 conn : & rusqlite:: Connection ,
556- explored_files : & [ String ] ,
557- explored_symbols : & [ String ] ,
558- ) -> Vec < FrontierEntry > {
559- use std:: collections:: HashSet ;
560-
561- let explored_set: HashSet < & str > = explored_files. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
562-
563- // Set A: files that import any explored file
564- let mut set_a: HashSet < String > = HashSet :: new ( ) ;
565- if !explored_files. is_empty ( ) {
566- let placeholders: String = explored_files
559+ sql_prefix : & str ,
560+ params : & [ String ] ,
561+ out : & mut std:: collections:: HashSet < String > ,
562+ ) {
563+ const CHUNK : usize = 999 ;
564+ for chunk in params. chunks ( CHUNK ) {
565+ let placeholders = chunk
567566 . iter ( )
568567 . enumerate ( )
569568 . map ( |( i, _) | format ! ( "?{}" , i + 1 ) )
570569 . collect :: < Vec < _ > > ( )
571570 . join ( ", " ) ;
572- let sql = format ! (
573- "SELECT DISTINCT from_path FROM import_edges \
574- WHERE to_path IN ({placeholders}) AND from_path IS NOT NULL"
575- ) ;
571+ let sql = format ! ( "{sql_prefix} ({placeholders}) AND from_path IS NOT NULL" ) ;
576572 if let Ok ( mut stmt) = conn. prepare ( & sql) {
577573 let _ = stmt
578- . query_map ( rusqlite:: params_from_iter ( explored_files . iter ( ) ) , |row| {
574+ . query_map ( rusqlite:: params_from_iter ( chunk . iter ( ) ) , |row| {
579575 row. get :: < _ , String > ( 0 )
580576 } )
581577 . map ( |rows| {
582578 for r in rows. flatten ( ) {
583- set_a . insert ( r) ;
579+ out . insert ( r) ;
584580 }
585581 } ) ;
586582 }
587583 }
584+ }
585+
586+ fn compute_frontier_entries (
587+ conn : & rusqlite:: Connection ,
588+ explored_files : & [ String ] ,
589+ explored_symbols : & [ String ] ,
590+ ) -> Vec < FrontierEntry > {
591+ use std:: collections:: HashSet ;
592+
593+ let explored_set: HashSet < & str > = explored_files. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
594+
595+ // Set A: files that import any explored file
596+ let mut set_a: HashSet < String > = HashSet :: new ( ) ;
597+ if !explored_files. is_empty ( ) {
598+ query_paths_chunked (
599+ conn,
600+ "SELECT DISTINCT from_path FROM import_edges WHERE to_path IN" ,
601+ explored_files,
602+ & mut set_a,
603+ ) ;
604+ }
588605
589606 // Set B: files containing callers of explored symbols
590607 let mut set_b: HashSet < String > = HashSet :: new ( ) ;
591608 if !explored_symbols. is_empty ( ) {
592- let placeholders: String = explored_symbols
593- . iter ( )
594- . enumerate ( )
595- . map ( |( i, _) | format ! ( "?{}" , i + 1 ) )
596- . collect :: < Vec < _ > > ( )
597- . join ( ", " ) ;
598- let sql = format ! (
599- "SELECT DISTINCT from_path FROM call_edges \
600- WHERE to_symbol IN ({placeholders}) AND from_path IS NOT NULL"
609+ query_paths_chunked (
610+ conn,
611+ "SELECT DISTINCT from_path FROM call_edges WHERE to_symbol IN" ,
612+ explored_symbols,
613+ & mut set_b,
601614 ) ;
602- if let Ok ( mut stmt) = conn. prepare ( & sql) {
603- let _ = stmt
604- . query_map ( rusqlite:: params_from_iter ( explored_symbols. iter ( ) ) , |row| {
605- row. get :: < _ , String > ( 0 )
606- } )
607- . map ( |rows| {
608- for r in rows. flatten ( ) {
609- set_b. insert ( r) ;
610- }
611- } ) ;
612- }
613615 }
614616
615617 // Union minus already-explored; tag each with reason
@@ -3102,6 +3104,46 @@ mod tests {
31023104 let _ = std:: fs:: remove_dir_all ( & dir) ;
31033105 }
31043106
3107+ #[ test]
3108+ fn frontier_chunking_handles_over_999_params ( ) {
3109+ let dir = std:: env:: temp_dir ( ) . join ( format ! ( "ci_frontier_chunk_{}" , std:: process:: id( ) ) ) ;
3110+ let _ = std:: fs:: remove_dir_all ( & dir) ;
3111+ std:: fs:: create_dir_all ( & dir) . unwrap ( ) ;
3112+ let server = CodeIntelligenceServer :: new ( dir. clone ( ) , dir. join ( "index.db" ) ) . unwrap ( ) ;
3113+
3114+ // Seed 1001 import_edges rows: result.rs imports 1001 distinct dep files.
3115+ // Without chunking, querying all 1001 paths as IN-clause params exceeds SQLite's
3116+ // 999-variable limit and silently returns empty; with chunking the result is non-empty.
3117+ {
3118+ let conn = rusqlite:: Connection :: open ( dir. join ( "index.db" ) ) . unwrap ( ) ;
3119+ for i in 0 ..1001usize {
3120+ conn. execute (
3121+ "INSERT INTO import_edges (from_path, to_path, module_name) VALUES (?1, ?2, ?3)" ,
3122+ rusqlite:: params![ "src/result.rs" , format!( "src/dep_{i}.rs" ) , format!( "dep_{i}" ) ] ,
3123+ )
3124+ . unwrap ( ) ;
3125+ }
3126+ }
3127+
3128+ let explored_files: Vec < String > =
3129+ ( 0 ..1001usize ) . map ( |i| format ! ( "src/dep_{i}.rs" ) ) . collect ( ) ;
3130+ let mut out = std:: collections:: HashSet :: new ( ) ;
3131+ let conn = server. make_read_conn ( ) . unwrap ( ) ;
3132+ query_paths_chunked (
3133+ & conn,
3134+ "SELECT DISTINCT from_path FROM import_edges WHERE to_path IN" ,
3135+ & explored_files,
3136+ & mut out,
3137+ ) ;
3138+
3139+ assert ! (
3140+ out. contains( "src/result.rs" ) ,
3141+ "src/result.rs must appear across 999-var chunk boundary, got: {out:?}"
3142+ ) ;
3143+
3144+ let _ = std:: fs:: remove_dir_all ( & dir) ;
3145+ }
3146+
31053147 #[ test]
31063148 fn symbol_info_stays_ambiguous_when_path_does_not_uniquely_resolve ( ) {
31073149 let dir = std:: env:: temp_dir ( ) . join ( format ! ( "ci_ambig_path_{}" , std:: process:: id( ) ) ) ;
0 commit comments