55# the root directory of this source tree.
66
77import os
8- from datetime import datetime
8+ from datetime import UTC , datetime
99
1010import aiosqlite
1111
@@ -23,6 +23,7 @@ class SqliteKVStoreImpl(KVStore):
2323 def __init__ (self , config : SqliteKVStoreConfig ) -> None :
2424 self .db_path = config .db_path
2525 self .table_name = "kvstore"
26+ self ._namespace = config .namespace
2627 self ._conn : aiosqlite .Connection | None = None
2728
2829 def __str__ (self ) -> str :
@@ -32,6 +33,16 @@ def _is_memory_db(self) -> bool:
3233 """Check if this is an in-memory database."""
3334 return self .db_path == ":memory:" or "mode=memory" in self .db_path
3435
36+ def _namespaced_key (self , key : str ) -> str :
37+ if not self ._namespace :
38+ return key
39+ return f"{ self ._namespace } :{ key } "
40+
41+ def _strip_namespace (self , key : str ) -> str :
42+ if self ._namespace and key .startswith (f"{ self ._namespace } :" ):
43+ return key [len (self ._namespace ) + 1 :]
44+ return key
45+
3546 async def initialize (self ) -> None :
3647 # Skip directory creation for in-memory databases and file: URIs
3748 if not self ._is_memory_db () and not self .db_path .startswith ("file:" ):
@@ -74,103 +85,90 @@ async def shutdown(self) -> None:
7485 self ._conn = None
7586
7687 async def set (self , key : str , value : str , expiration : datetime | None = None ) -> None :
88+ key = self ._namespaced_key (key )
89+ exp_str = expiration .isoformat () if expiration else None
7790 if self ._conn :
78- # In-memory database with persistent connection
7991 await self ._conn .execute (
8092 f"INSERT OR REPLACE INTO { self .table_name } (key, value, expiration) VALUES (?, ?, ?)" ,
81- (key , value , expiration ),
93+ (key , value , exp_str ),
8294 )
8395 await self ._conn .commit ()
8496 else :
85- # File-based database with connection per operation
8697 async with aiosqlite .connect (self .db_path ) as db :
8798 await db .execute (
8899 f"INSERT OR REPLACE INTO { self .table_name } (key, value, expiration) VALUES (?, ?, ?)" ,
89- (key , value , expiration ),
100+ (key , value , exp_str ),
90101 )
91102 await db .commit ()
92103
93104 async def get (self , key : str ) -> str | None :
105+ key = self ._namespaced_key (key )
106+ now = datetime .now (tz = UTC ).isoformat ()
107+ query = f"SELECT value FROM { self .table_name } WHERE key = ? AND (expiration IS NULL OR expiration > ?)"
94108 if self ._conn :
95- # In-memory database with persistent connection
96- async with self ._conn .execute (
97- f"SELECT value, expiration FROM { self .table_name } WHERE key = ?" , (key ,)
98- ) as cursor :
109+ async with self ._conn .execute (query , (key , now )) as cursor :
99110 row = await cursor .fetchone ()
100111 if row is None :
101112 return None
102- value , expiration = row
113+ value = row [ 0 ]
103114 if not isinstance (value , str ):
104115 logger .warning ("Expected string value for key, returning None" , key = key , value_type = type (value ))
105116 return None
106117 return value
107118 else :
108- # File-based database with connection per operation
109119 async with aiosqlite .connect (self .db_path ) as db :
110- async with db .execute (
111- f"SELECT value, expiration FROM { self .table_name } WHERE key = ?" , (key ,)
112- ) as cursor :
120+ async with db .execute (query , (key , now )) as cursor :
113121 row = await cursor .fetchone ()
114122 if row is None :
115123 return None
116- value , expiration = row
124+ value = row [ 0 ]
117125 if not isinstance (value , str ):
118126 logger .warning ("Expected string value for key, returning None" , key = key , value_type = type (value ))
119127 return None
120128 return value
121129
122130 async def delete (self , key : str ) -> None :
131+ key = self ._namespaced_key (key )
123132 if self ._conn :
124- # In-memory database with persistent connection
125133 await self ._conn .execute (f"DELETE FROM { self .table_name } WHERE key = ?" , (key ,))
126134 await self ._conn .commit ()
127135 else :
128- # File-based database with connection per operation
129136 async with aiosqlite .connect (self .db_path ) as db :
130137 await db .execute (f"DELETE FROM { self .table_name } WHERE key = ?" , (key ,))
131138 await db .commit ()
132139
133140 async def values_in_range (self , start_key : str , end_key : str ) -> list [str ]:
141+ start_key = self ._namespaced_key (start_key )
142+ end_key = self ._namespaced_key (end_key )
143+ now = datetime .now (tz = UTC ).isoformat ()
144+ query = (
145+ f"SELECT value FROM { self .table_name } "
146+ f"WHERE key >= ? AND key < ? AND (expiration IS NULL OR expiration > ?) "
147+ f"ORDER BY key"
148+ )
134149 if self ._conn :
135- # In-memory database with persistent connection
136- async with self ._conn .execute (
137- f"SELECT key, value, expiration FROM { self .table_name } WHERE key >= ? AND key <= ?" ,
138- (start_key , end_key ),
139- ) as cursor :
140- result = []
141- async for row in cursor :
142- _ , value , _ = row
143- result .append (value )
144- return result
150+ async with self ._conn .execute (query , (start_key , end_key , now )) as cursor :
151+ return [row [0 ] async for row in cursor ]
145152 else :
146- # File-based database with connection per operation
147153 async with aiosqlite .connect (self .db_path ) as db :
148- async with db .execute (
149- f"SELECT key, value, expiration FROM { self .table_name } WHERE key >= ? AND key <= ?" ,
150- (start_key , end_key ),
151- ) as cursor :
152- result = []
153- async for row in cursor :
154- _ , value , _ = row
155- result .append (value )
156- return result
154+ async with db .execute (query , (start_key , end_key , now )) as cursor :
155+ return [row [0 ] async for row in cursor ]
157156
158157 async def keys_in_range (self , start_key : str , end_key : str ) -> list [str ]:
159- """Get all keys in the given range."""
158+ start_key = self ._namespaced_key (start_key )
159+ end_key = self ._namespaced_key (end_key )
160+ now = datetime .now (tz = UTC ).isoformat ()
161+ query = (
162+ f"SELECT key FROM { self .table_name } "
163+ f"WHERE key >= ? AND key < ? AND (expiration IS NULL OR expiration > ?) "
164+ f"ORDER BY key"
165+ )
160166 if self ._conn :
161- # In-memory database with persistent connection
162- cursor = await self ._conn .execute (
163- f"SELECT key FROM { self .table_name } WHERE key >= ? AND key <= ?" ,
164- (start_key , end_key ),
165- )
167+ cursor = await self ._conn .execute (query , (start_key , end_key , now ))
166168 rows = await cursor .fetchall ()
167- return [row [0 ] for row in rows ]
169+ return [self . _strip_namespace ( row [0 ]) for row in rows ]
168170 else :
169- # File-based database with connection per operation
170171 async with aiosqlite .connect (self .db_path ) as db :
171- cursor = await db .execute (
172- f"SELECT key FROM { self .table_name } WHERE key >= ? AND key <= ?" ,
173- (start_key , end_key ),
174- )
172+ cursor = await db .execute (query , (start_key , end_key , now ))
175173 rows = await cursor .fetchall ()
176- return [row [0 ] for row in rows ]
174+ return [self . _strip_namespace ( row [0 ]) for row in rows ]
0 commit comments