diff --git a/haiku_rag_slim/haiku/rag/ingester/queue/migrations.py b/haiku_rag_slim/haiku/rag/ingester/queue/migrations.py index 6cb01525..c7fde7e7 100644 --- a/haiku_rag_slim/haiku/rag/ingester/queue/migrations.py +++ b/haiku_rag_slim/haiku/rag/ingester/queue/migrations.py @@ -1,5 +1,5 @@ import sqlalchemy as sa -from sqlalchemy.engine import make_url +from sqlalchemy.engine import URL, make_url from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine from haiku.rag.config.models import QueueConfig @@ -25,7 +25,9 @@ def make_engine(config: QueueConfig) -> AsyncEngine: else: path = config.path.expanduser().resolve() path.parent.mkdir(parents=True, exist_ok=True) - url = make_url(f"sqlite+aiosqlite:///{path}") + # URL.create keeps the path literal — building a string and reparsing + # would treat `?`/`#` in the filename as query/fragment. + url = URL.create("sqlite+aiosqlite", database=str(path)) if url.get_backend_name() == "sqlite": engine = create_async_engine(url, pool_size=1, max_overflow=0) diff --git a/tests/ingester/test_queue.py b/tests/ingester/test_queue.py index c7d9529b..058b4f77 100644 --- a/tests/ingester/test_queue.py +++ b/tests/ingester/test_queue.py @@ -44,6 +44,19 @@ async def test_open_queue_creates_file_and_schema(tmp_path): await eng.dispose() +@pytest.mark.asyncio +async def test_open_queue_handles_path_with_url_chars(tmp_path): + """A `?` (or `#`) is a valid POSIX filename char but has URL meaning. + The queue must open the literal file, not a truncated one.""" + path = tmp_path / "queue?weird.db" + eng = await open_queue(QueueConfig(path=path)) + try: + assert path.exists() + assert not (tmp_path / "queue").exists() + finally: + await eng.dispose() + + # --- enqueue ---