basic-memory 0.23.1.dev20__py3-none-any.whl → 0.23.1.dev24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- basic_memory/alembic/versions/7f6a2b8c9d10_index_full_postgres_note_content.py +56 -0
- basic_memory/repository/search_repository.py +4 -0
- basic_memory/repository/search_repository_base.py +15 -0
- basic_memory/services/search_service.py +11 -3
- {basic_memory-0.23.1.dev20.dist-info → basic_memory-0.23.1.dev24.dist-info}/METADATA +1 -1
- {basic_memory-0.23.1.dev20.dist-info → basic_memory-0.23.1.dev24.dist-info}/RECORD +9 -9
- {basic_memory-0.23.1.dev20.dist-info → basic_memory-0.23.1.dev24.dist-info}/WHEEL +0 -0
- {basic_memory-0.23.1.dev20.dist-info → basic_memory-0.23.1.dev24.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.23.1.dev20.dist-info → basic_memory-0.23.1.dev24.dist-info}/licenses/LICENSE +0 -0
|
@@ -22,6 +22,62 @@ def upgrade() -> None:
|
|
|
22
22
|
"""Index complete note bodies as bounded PostgreSQL FTS chunks."""
|
|
23
23
|
connection = op.get_bind()
|
|
24
24
|
if connection.dialect.name == "postgresql":
|
|
25
|
+
# --- Repair: recreate search_index if a destructive reindex removed it ---
|
|
26
|
+
# Trigger: search_index does not exist. Through v0.23.0, the per-project
|
|
27
|
+
# search reindex ran DROP TABLE IF EXISTS search_index, and on Postgres
|
|
28
|
+
# nothing outside migrations recreates that table.
|
|
29
|
+
# Why: the chunk backfill below reads search_index and the chunks table
|
|
30
|
+
# declares a foreign key to it, so without repair this migration fails
|
|
31
|
+
# permanently and blocks the database from ever reaching head.
|
|
32
|
+
# Outcome: healthy databases no-op on IF NOT EXISTS; a repaired database
|
|
33
|
+
# gets an empty index that the next full reindex repopulates. The DDL
|
|
34
|
+
# mirrors the current schema in models/search.py.
|
|
35
|
+
op.execute("""
|
|
36
|
+
CREATE TABLE IF NOT EXISTS search_index (
|
|
37
|
+
id INTEGER NOT NULL,
|
|
38
|
+
project_id INTEGER NOT NULL,
|
|
39
|
+
title TEXT,
|
|
40
|
+
content_stems TEXT,
|
|
41
|
+
content_snippet TEXT,
|
|
42
|
+
permalink VARCHAR,
|
|
43
|
+
file_path VARCHAR,
|
|
44
|
+
type VARCHAR,
|
|
45
|
+
from_id INTEGER,
|
|
46
|
+
to_id INTEGER,
|
|
47
|
+
relation_type VARCHAR,
|
|
48
|
+
entity_id INTEGER,
|
|
49
|
+
category VARCHAR,
|
|
50
|
+
metadata JSONB,
|
|
51
|
+
created_at TIMESTAMP WITH TIME ZONE,
|
|
52
|
+
updated_at TIMESTAMP WITH TIME ZONE,
|
|
53
|
+
textsearchable_index_col tsvector GENERATED ALWAYS AS (
|
|
54
|
+
to_tsvector(
|
|
55
|
+
'english',
|
|
56
|
+
coalesce(title, '') || ' ' ||
|
|
57
|
+
coalesce(content_stems, '')
|
|
58
|
+
)
|
|
59
|
+
) STORED,
|
|
60
|
+
PRIMARY KEY (id, type, project_id),
|
|
61
|
+
FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
|
|
62
|
+
)
|
|
63
|
+
""")
|
|
64
|
+
op.execute("""
|
|
65
|
+
CREATE INDEX IF NOT EXISTS ix_search_index_project_id
|
|
66
|
+
ON search_index (project_id)
|
|
67
|
+
""")
|
|
68
|
+
op.execute("""
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_search_index_fts
|
|
70
|
+
ON search_index USING gin(textsearchable_index_col)
|
|
71
|
+
""")
|
|
72
|
+
op.execute("""
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_search_index_metadata_gin
|
|
74
|
+
ON search_index USING gin(metadata jsonb_path_ops)
|
|
75
|
+
""")
|
|
76
|
+
op.execute("""
|
|
77
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uix_search_index_permalink_project
|
|
78
|
+
ON search_index (permalink, project_id)
|
|
79
|
+
WHERE permalink IS NOT NULL
|
|
80
|
+
""")
|
|
25
81
|
op.execute("""
|
|
26
82
|
CREATE TABLE search_index_fts_chunks (
|
|
27
83
|
project_id INTEGER NOT NULL,
|
|
@@ -129,6 +129,10 @@ class SearchRepository(Protocol):
|
|
|
129
129
|
"""Delete item by permalink."""
|
|
130
130
|
...
|
|
131
131
|
|
|
132
|
+
async def delete_project_search_rows(self) -> None:
|
|
133
|
+
"""Delete every full-text search row owned by this project."""
|
|
134
|
+
...
|
|
135
|
+
|
|
132
136
|
async def delete_by_entity_id(self, entity_id: int) -> None:
|
|
133
137
|
"""Delete items by entity ID."""
|
|
134
138
|
...
|
|
@@ -1143,6 +1143,21 @@ class SearchRepositoryBase(ABC):
|
|
|
1143
1143
|
)
|
|
1144
1144
|
await session.commit()
|
|
1145
1145
|
|
|
1146
|
+
async def delete_project_search_rows(self) -> None:
|
|
1147
|
+
"""Delete every full-text search row owned by this repository's project.
|
|
1148
|
+
|
|
1149
|
+
Shared across backends: both store rows in `search_index` keyed by
|
|
1150
|
+
project_id, and a project-scoped DELETE leaves sibling projects in the
|
|
1151
|
+
same database untouched. On Postgres the bounded FTS chunk rows follow
|
|
1152
|
+
via their ON DELETE CASCADE foreign key.
|
|
1153
|
+
"""
|
|
1154
|
+
async with db.scoped_session(self.session_maker) as session:
|
|
1155
|
+
await session.execute(
|
|
1156
|
+
text("DELETE FROM search_index WHERE project_id = :project_id"),
|
|
1157
|
+
{"project_id": self.project_id},
|
|
1158
|
+
)
|
|
1159
|
+
await session.commit()
|
|
1160
|
+
|
|
1146
1161
|
async def execute_query(
|
|
1147
1162
|
self,
|
|
1148
1163
|
query: Executable,
|
|
@@ -11,7 +11,6 @@ from typing import Any, List, Optional, Set, Dict
|
|
|
11
11
|
from dateparser import parse
|
|
12
12
|
from fastapi import BackgroundTasks
|
|
13
13
|
from loguru import logger
|
|
14
|
-
from sqlalchemy import text
|
|
15
14
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|
16
15
|
|
|
17
16
|
import logfire
|
|
@@ -152,11 +151,20 @@ class SearchService:
|
|
|
152
151
|
logger.info("Starting full reindex")
|
|
153
152
|
# Trigger: a full search rebuild removes every derived row.
|
|
154
153
|
# Why: vector storage may live outside SQL, so cleanup must cross the
|
|
155
|
-
# repository boundary before the full-text
|
|
154
|
+
# repository boundary before the full-text rows are rebuilt.
|
|
156
155
|
# Outcome: built-in and extension indexes follow the same lifecycle.
|
|
157
156
|
await self.repository.delete_project_vector_rows()
|
|
158
|
-
|
|
157
|
+
# Trigger: this service reindexes exactly one project, but search_index
|
|
158
|
+
# is shared by every project in the database (local SQLite consolidated
|
|
159
|
+
# into one memory.db; cloud tenants share one Postgres DB across
|
|
160
|
+
# projects). The historical DROP TABLE here dated from the one-database-
|
|
161
|
+
# per-project era and destroyed sibling projects' rows — on Postgres it
|
|
162
|
+
# also left nothing behind, since only migrations recreate the table.
|
|
163
|
+
# Why: a project-scoped DELETE clears only rows this rebuild will
|
|
164
|
+
# repopulate, and on Postgres the FTS chunk rows cascade with them.
|
|
165
|
+
# Outcome: sibling projects keep serving search while this one rebuilds.
|
|
159
166
|
await self.init_search_index()
|
|
167
|
+
await self.repository.delete_project_search_rows()
|
|
160
168
|
|
|
161
169
|
# Reindex all entities
|
|
162
170
|
logger.debug("Indexing entities")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: basic-memory
|
|
3
|
-
Version: 0.23.1.
|
|
3
|
+
Version: 0.23.1.dev24
|
|
4
4
|
Summary: Local-first knowledge management combining Zettelkasten with knowledge graphs
|
|
5
5
|
Project-URL: Homepage, https://github.com/basicmachines-co/basic-memory
|
|
6
6
|
Project-URL: Repository, https://github.com/basicmachines-co/basic-memory
|
|
@@ -23,7 +23,7 @@ basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink
|
|
|
23
23
|
basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py,sha256=xTSVvB-caYl_CdVfzhj-eqdQEOxQ12NIHOc8dBSqCMQ,4779
|
|
24
24
|
basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py,sha256=oFtjzJd37HUMezuCUonK-yRc867_9L5zxd4CnLhFIi4,4655
|
|
25
25
|
basic_memory/alembic/versions/6830751f5fb6_merge_multiple_heads.py,sha256=8bO4yg_v6klCJz0E15sDI2pDtsmAL2nNO5ACpKGLYQo,499
|
|
26
|
-
basic_memory/alembic/versions/7f6a2b8c9d10_index_full_postgres_note_content.py,sha256=
|
|
26
|
+
basic_memory/alembic/versions/7f6a2b8c9d10_index_full_postgres_note_content.py,sha256=0Ipk-T-7LiA8-ExBr-mz-zqKHvH9QI0G33zdWtnREHo,5414
|
|
27
27
|
basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py,sha256=DYlXlRYO3Ms6x9GbrFGWgj1QEzsdKHx-4AA4lSVM7y0,1587
|
|
28
28
|
basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py,sha256=i42eQyO08RWf4exnH1q1PKw_EJFsHAG-T4pdLTMSpiA,1818
|
|
29
29
|
basic_memory/alembic/versions/a2b3c4d5e6f7_add_search_index_entity_cascade.py,sha256=Oh_J5Jw85J3LvknfizFP7_1SoelZM_A7IjBau8lRpfk,1671
|
|
@@ -317,8 +317,8 @@ basic_memory/repository/rerank_provider.py,sha256=JnMXexhMOv93gJ3UQkkUE6tQyJWdph
|
|
|
317
317
|
basic_memory/repository/rerank_provider_factory.py,sha256=JlPHD0YdAWSima7gGkheiTnGhEn2j5JPB09MZDuhoj0,4735
|
|
318
318
|
basic_memory/repository/search_index_row.py,sha256=bTF7-tiz91HySchZfdP1dZbUNW7WU2KNgG1s1mnRBWc,5335
|
|
319
319
|
basic_memory/repository/search_query.py,sha256=0Kh1IDMXCUza-uTKej5wCu-TjHKhA8j-MCzQ0-abvpA,13949
|
|
320
|
-
basic_memory/repository/search_repository.py,sha256=
|
|
321
|
-
basic_memory/repository/search_repository_base.py,sha256=
|
|
320
|
+
basic_memory/repository/search_repository.py,sha256=Cpgn1feacMtxbm7BhaWf_FMS__Gn7WiIAnovYJX7-Ao,9695
|
|
321
|
+
basic_memory/repository/search_repository_base.py,sha256=7olsUTL0nI-4vkspTViJZbvqIfDPxGho34D3vmILpdo,120702
|
|
322
322
|
basic_memory/repository/search_trace.py,sha256=OLcUJ-4GBva1zgNPBlNA1BThpUJo77YsW5H-0j3zqy4,20613
|
|
323
323
|
basic_memory/repository/semantic_chunking.py,sha256=7xTmL7vt_6vBSYJYeio6s7nTkvW8AdQ9sok1HIY5GWE,8427
|
|
324
324
|
basic_memory/repository/semantic_errors.py,sha256=1v0jz9aBkephpVc4FcX3xIQvgk5jnkO9VpZhPM7AdIc,996
|
|
@@ -387,12 +387,12 @@ basic_memory/services/note_preparation.py,sha256=POGmk0L_9a-eLIMmXmuKtN81QRabF0J
|
|
|
387
387
|
basic_memory/services/project_deletes.py,sha256=bnq8la1ZKsZEkcAg3Ew2onXHku9CWChr-BWDZA6lvuI,6010
|
|
388
388
|
basic_memory/services/project_service.py,sha256=JB-pNRZfRhVGIRUlSt7IiI-BllxgFs8A7xtGK3klFcA,61803
|
|
389
389
|
basic_memory/services/retrieval_inspect.py,sha256=gJq94oYV6ISeh-JeUAB8srMw8K3oGeMgzVXEOWAEzxA,23844
|
|
390
|
-
basic_memory/services/search_service.py,sha256=
|
|
390
|
+
basic_memory/services/search_service.py,sha256=8A1vXo5IIpKXL2gxEsCEP_tcKyRcnz4R5xb6zijRuW0,40254
|
|
391
391
|
basic_memory/services/service.py,sha256=V-d_8gOV07zGIQDpL-Ksqs3ZN9l3qf3HZOK1f_YNTag,336
|
|
392
392
|
basic_memory/templates/prompts/continue_conversation.hbs,sha256=trrDHSXA5S0JCbInMoUJL04xvCGRB_ku1RHNQHtl6ZI,3076
|
|
393
393
|
basic_memory/templates/prompts/search.hbs,sha256=H1cCIsHKp4VC1GrH2KeUB8pGe5vXFPqb2VPotypmeCA,3098
|
|
394
|
-
basic_memory-0.23.1.
|
|
395
|
-
basic_memory-0.23.1.
|
|
396
|
-
basic_memory-0.23.1.
|
|
397
|
-
basic_memory-0.23.1.
|
|
398
|
-
basic_memory-0.23.1.
|
|
394
|
+
basic_memory-0.23.1.dev24.dist-info/METADATA,sha256=0aAtGPFW1HeEVmf3nfY_ctjaRL_KmF8iw_601_PGvZM,26848
|
|
395
|
+
basic_memory-0.23.1.dev24.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
396
|
+
basic_memory-0.23.1.dev24.dist-info/entry_points.txt,sha256=wvE2mRF6-Pg4weIYcfQ-86NOLZD4WJg7F7TIsRVFLb8,90
|
|
397
|
+
basic_memory-0.23.1.dev24.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
398
|
+
basic_memory-0.23.1.dev24.dist-info/RECORD,,
|
|
File without changes
|
{basic_memory-0.23.1.dev20.dist-info → basic_memory-0.23.1.dev24.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{basic_memory-0.23.1.dev20.dist-info → basic_memory-0.23.1.dev24.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|