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.
@@ -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 table is recreated.
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
- await self.repository.execute_query(text("DROP TABLE IF EXISTS search_index"), params={})
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.dev20
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=Kf4-_uBKe28MbpbKPsxZEKQzCYpIxv79iWxbKUZI91w,2857
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=U6okrNaxDqEHYIwLNYyZPPtDimiiqmZO1U6CPrXgrHQ,9555
321
- basic_memory/repository/search_repository_base.py,sha256=GUEI39CYuAai87m6woEhCyYKrMZ9THuTdEqhoLB02DU,119986
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=OcT2wMn7rN1GQ_0l8OH8KKcoTG4D7-EvfqPulss01NA,39618
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.dev20.dist-info/METADATA,sha256=kS21fqv6k0zvFoXQ3KaKUgv6UWpoDs_APp8C7FjTpqs,26848
395
- basic_memory-0.23.1.dev20.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
396
- basic_memory-0.23.1.dev20.dist-info/entry_points.txt,sha256=wvE2mRF6-Pg4weIYcfQ-86NOLZD4WJg7F7TIsRVFLb8,90
397
- basic_memory-0.23.1.dev20.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
398
- basic_memory-0.23.1.dev20.dist-info/RECORD,,
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,,