1919import param
2020import semchunk
2121
22- from panel import cache as pn_cache
22+ from panel import cache as pn_cache , state as pn_state
2323from tqdm .auto import tqdm
2424
2525from .actor import PROMPTS_DIR , LLMUser
@@ -1116,10 +1116,22 @@ class DuckDBVectorStore(VectorStore):
11161116 doc = "Embeddings object for text processing. If None and a URI is provided, loads from the database; else NumpyEmbeddings." ,
11171117 )
11181118
1119+ # Class-level registry of connections by URI. Used to close stale
1120+ # connections on reload (e.g. Panel hot-reload) so the file handle is
1121+ # released and a fresh ATTACH can succeed.
1122+ _uri_connections : t .ClassVar [dict [str , duckdb .DuckDBPyConnection ]] = {}
1123+
11191124 def __init__ (self , ** params ):
11201125 super ().__init__ (** params )
11211126 self ._add_items_lock = asyncio .Lock ()
11221127
1128+ # Reuse existing connection for this URI if still open
1129+ # (e.g. on Panel hot-reload within the same process).
1130+ if self .uri != ":memory:" and self .uri in DuckDBVectorStore ._uri_connections :
1131+ self .connection = DuckDBVectorStore ._uri_connections [self .uri ]
1132+ self ._resolve_state ()
1133+ return
1134+
11231135 connection = duckdb .connect (":memory:" )
11241136 # following the instructions from
11251137 # https://duckdb.org/docs/stable/extensions/vss.html#persistence
@@ -1133,43 +1145,23 @@ def __init__(self, **params):
11331145 if self .embeddings is None :
11341146 self .embeddings = NumpyEmbeddings ()
11351147 return
1136- uri_exists = Path (self .uri ).exists ()
1137- direct_connection = False
1138- try :
1139- attach_mode = "READ_ONLY" if self .read_only else "READ_WRITE"
1140- connection .execute (f"ATTACH DATABASE '{ self .uri } ' AS embedded ({ attach_mode } );" )
1141- except (duckdb .BinderException , duckdb .IOException ) as e :
1142- err_msg = str (e ).lower ()
1143- if "already attached" in err_msg or "unique file handle" in err_msg :
1144- # File already held by another DuckDB connection in this process
1145- # (e.g., during Panel hot-reload). Connect directly to the file instead.
1146- connection .close ()
1147- connection = duckdb .connect (self .uri , read_only = self .read_only )
1148- connection .execute ("LOAD 'vss';" )
1149- connection .execute ("SET hnsw_enable_experimental_persistence = true;" )
1150- direct_connection = True
1151- else :
1152- raise
1153- except duckdb .CatalogException :
1154- # handle "Failure while replaying WAL file"
1155- # remove .wal uri on corruption
1156- wal_path = Path (str (self .uri ) + ".wal" )
1157- if wal_path .exists ():
1158- wal_path .unlink ()
1159- attach_mode = "READ_ONLY" if self .read_only else "READ_WRITE"
1160- connection .execute (f"ATTACH DATABASE '{ self .uri } ' AS embedded ({ attach_mode } );" )
1161- if not direct_connection :
1162- connection .execute ("USE embedded;" )
1163- self .connection = connection
1164- has_documents = (
1165- connection .execute (
1148+
1149+ attach_mode = "READ_ONLY" if self .read_only else "READ_WRITE"
1150+ connection .execute (f"ATTACH DATABASE '{ self .uri } ' AS embedded ({ attach_mode } );" )
1151+ connection .execute ("USE embedded;" )
1152+ DuckDBVectorStore ._uri_connections [self .uri ] = self .connection = connection
1153+ pn_state .on_session_destroyed (lambda session_context : self .close ())
1154+ self ._resolve_state ()
1155+
1156+ def _resolve_state (self ):
1157+ """Set _initialized and resolve embeddings from the active connection."""
1158+ self ._initialized = (
1159+ self .connection .execute (
11661160 "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'documents';"
11671161 ).fetchone ()[0 ]
11681162 > 0
1169- )
1170- self ._initialized = uri_exists and has_documents
1171-
1172- if self .uri != ":memory:" and self ._initialized :
1163+ ) and Path (self .uri ).exists ()
1164+ if self ._initialized :
11731165 config = self ._get_embeddings_config ()
11741166 if config and self .embeddings is None :
11751167 module_name , class_name = config ["class" ].rsplit ("." , 1 )
@@ -1178,7 +1170,6 @@ def __init__(self, **params):
11781170 self .embeddings = embedding_class (** config ["params" ])
11791171 log_debug (f"Loaded embeddings { class_name } from database." )
11801172 self ._check_embeddings_consistency ()
1181-
11821173 if self .embeddings is None :
11831174 self .embeddings = NumpyEmbeddings ()
11841175
@@ -1674,6 +1665,7 @@ def close(self) -> None:
16741665 if self .connection :
16751666 self .connection .close ()
16761667 self .connection = None
1668+ DuckDBVectorStore ._uri_connections .pop (self .uri , None )
16771669
16781670
16791671class ChromaDBVectorStore (VectorStore ):
0 commit comments