@@ -100,24 +100,37 @@ def backup_target_files(db_path: str, segment_uuid: Optional[str]) -> str:
100100 raise RuntimeError ("Aborting recovery: Backup failed." )
101101
102102
103- def extract_raw_data (db_path : str , collection_uuid : str ) -> Dict [str , List [Any ]]:
104- """Dynamically extracts IDs, Documents, and Metadata using exact schema bindings."""
103+ def extract_raw_data (db_path : str , collection_uuid : str , brute_force : bool = False ) -> Dict [str , List [Any ]]:
104+ """
105+ Dynamically extracts IDs, Documents, and Metadata using exact schema bindings.
106+ If brute_force is True, it ignores the VECTOR scope constraint and pulls all orphaned data.
107+ """
105108 sqlite_path = os .path .join (db_path , "chroma.sqlite3" )
106109 conn = sqlite3 .connect (sqlite_path )
107110 cursor = conn .cursor ()
108111
109112 data = {'ids' : [], 'documents' : [], 'metadatas' : [], 'embeddings' : []}
110113
111- logger .info ("Extracting data based on exact DB Schema..." )
112-
113- # 1. Map internal integer IDs to the user's string IDs
114- # 1.5.8 Fix: Explicitly filter by scope = 'VECTOR' to avoid reading Sparse Vector definitions
115- cursor .execute ("""
116- SELECT e.id, e.embedding_id
117- FROM embeddings e
118- JOIN segments s ON e.segment_id = s.id
119- WHERE s.collection = ? AND s.scope = 'VECTOR'
120- """ , (collection_uuid ,))
114+ if brute_force :
115+ logger .info ("Extracting data using BRUTE FORCE (ignoring segment scope)..." )
116+ cursor .execute ("SELECT id FROM segments WHERE collection = ?" , (collection_uuid ,))
117+ segment_ids = [r [0 ] for r in cursor .fetchall ()]
118+
119+ if not segment_ids :
120+ conn .close ()
121+ return data
122+
123+ placeholders = "," .join (["?" ] * len (segment_ids ))
124+ cursor .execute (f"SELECT DISTINCT id, embedding_id FROM embeddings WHERE segment_id IN ({ placeholders } )" ,
125+ segment_ids )
126+ else :
127+ logger .info ("Extracting data based on exact DB Schema (VECTOR scope)..." )
128+ cursor .execute ("""
129+ SELECT e.id, e.embedding_id
130+ FROM embeddings e
131+ JOIN segments s ON e.segment_id = s.id
132+ WHERE s.collection = ? AND s.scope = 'VECTOR'
133+ """ , (collection_uuid ,))
121134
122135 id_map = {row [0 ]: row [1 ] for row in cursor .fetchall ()}
123136 internal_ids = list (id_map .keys ())
@@ -178,7 +191,7 @@ def extract_raw_data(db_path: str, collection_uuid: str) -> Dict[str, List[Any]]
178191
179192 meta = meta_dict .get (internal_id , {})
180193 if not meta :
181- meta = {"source" : "auto-recovered" }
194+ meta = {"source" : "brute-force-recovered" if brute_force else " auto-recovered" }
182195 data ['metadatas' ].append (meta )
183196
184197 data ['embeddings' ].append (None ) # Force Local SentenceTransformer to rebuild
@@ -198,18 +211,31 @@ def recover_collection(client: Any, db_path: str, collection_name: str, embeddin
198211 logger .info ("Safeguarding data..." )
199212 backup_target_files (db_path , seg_uuid )
200213
201- logger .info ("Extracting text and metadata from SQLite..." )
202- raw_data = extract_raw_data (db_path , col_uuid )
214+ # -------------------------------------------------------------
215+ # METHOD 1: Try standard extraction first
216+ # -------------------------------------------------------------
217+ raw_data = extract_raw_data (db_path , col_uuid , brute_force = False )
203218 total_records = len (raw_data .get ('ids' , []))
204219
220+ # -------------------------------------------------------------
221+ # METHOD 2: Fallback to brute force if VECTOR segment is corrupted
222+ # -------------------------------------------------------------
223+ if total_records == 0 :
224+ logger .warning ("No records found in standard VECTOR segment. Attempting Brute Force recovery..." )
225+ raw_data = extract_raw_data (db_path , col_uuid , brute_force = True )
226+ total_records = len (raw_data .get ('ids' , []))
227+
205228 if total_records == 0 :
206229 logger .warning ("No records found to recover in any segment." )
207230 return False
208231
209232 try :
210- logger .info (f"Deleting corrupted HNSW index ..." )
233+ logger .info (f"Deleting corrupted collection framework ..." )
211234 client .delete_collection (name = collection_name )
235+ except Exception as e :
236+ logger .warning (f"Could not cleanly delete collection (may already be missing/corrupt): { e } " )
212237
238+ try :
213239 logger .info ("Rebuilding collection framework..." )
214240 new_col = client .create_collection (
215241 name = collection_name ,
@@ -219,7 +245,7 @@ def recover_collection(client: Any, db_path: str, collection_name: str, embeddin
219245 logger .error (f"Failed to reset collection: { e } " )
220246 return False
221247
222- logger .info ("Injecting data and regenerating vectors (this may take a moment) ..." )
248+ logger .info (f "Injecting { total_records } recovered records back into the matrix ..." )
223249 batch_size = 500
224250 stats = RecoveryStats (total_items = total_records )
225251
@@ -299,4 +325,4 @@ def wrapper(self, *args, **kwargs):
299325 else :
300326 raise e
301327
302- return wrapper
328+ return wrapper
0 commit comments