Skip to content

Commit b30d6a7

Browse files
committed
chore(search): retire legacy search knowledge base projection rows in place
1 parent 09ed990 commit b30d6a7

22 files changed

Lines changed: 32536 additions & 2657 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { readFileSync } from 'node:fs'
2+
import { SYNC_EMBEDDING_SEARCH_FUNCTION } from '@sim/db/knowledge-projection'
3+
import { describe, expect, it } from 'vitest'
4+
5+
const normalized = (text: string) => text.replace(/\s+/g, ' ').trim()
6+
7+
describe('sync_embedding_search installers', () => {
8+
it('keeps migration 0448 and the provisioning paths on the same function', () => {
9+
const migration = readFileSync(
10+
new URL('./migrations/0448_retire_search_projection_writes.sql', import.meta.url),
11+
'utf8'
12+
)
13+
const statement = migration.slice(migration.indexOf('CREATE OR REPLACE FUNCTION'))
14+
expect(
15+
normalized(statement.replace('"public"."sync_embedding_search"', 'sync_embedding_search'))
16+
).toBe(normalized(`${SYNC_EMBEDDING_SEARCH_FUNCTION};`))
17+
})
18+
})

‎packages/db/knowledge-projection.ts‎

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,16 @@ const SEARCH_VECTOR_COLUMNS = SEARCH_VECTOR_WIDTHS.map((width) => widthColumn('v
6868
/** The bit columns of `embedding_search`, whose width check requires exactly one. */
6969
const SEARCH_BINARY_COLUMNS = ['"binary"', 'binary_384', 'binary_768', 'binary_1024', 'binary_3072']
7070

71+
/** The `ON CONFLICT DO UPDATE` assignments that take each column from the proposed row. */
72+
function excludedAssignments(columns: readonly string[]): string {
73+
return columns.map((column) => `${column} = EXCLUDED.${column}`).join(', ')
74+
}
75+
7176
/**
7277
* The halfvec projections of an embedding row, in {@link SEARCH_VECTOR_COLUMNS} order. Shortening
7378
* is valid only for the two OpenAI models trained for prefix retrieval. These and the bit
74-
* projections match what `sync_embedding_search()` from `0016_backfill_search_vectors` writes, so
75-
* a pass over rows a synchronous writer wrote finds nothing to rewrite.
79+
* projections are what {@link SYNC_EMBEDDING_SEARCH_FUNCTION} writes, so a pass over rows a
80+
* synchronous writer wrote finds nothing to rewrite.
7681
*/
7782
function searchVectorProjections(prefix: string, shortened: string): string {
7883
return SEARCH_VECTOR_WIDTHS.map((width) =>
@@ -94,6 +99,39 @@ function searchVectorShortened(model: string, prefix: string): string {
9499
return `${model} IN ${SHORTENED_EMBEDDING_MODELS} AND ${prefix}.embedding_384 IS NULL`
95100
}
96101

102+
/**
103+
* The `embedding_search_sync` trigger function: projects an ordinary-KB chunk and deletes the row
104+
* of a Search-KB chunk. Migration 0448 installs this exact body; fresh databases and pushes install
105+
* it through `0016_backfill_search_vectors`.
106+
*
107+
* contract-pending(after Search KB candidate rows are purged and Search KBs are unwritable): drop
108+
* the `search_index` delete branch — no Search-KB chunk write can reach the trigger then.
109+
*/
110+
export const SYNC_EMBEDDING_SEARCH_FUNCTION = `CREATE OR REPLACE FUNCTION sync_embedding_search()
111+
RETURNS trigger LANGUAGE plpgsql AS $$
112+
DECLARE
113+
search_index boolean;
114+
shortened boolean;
115+
BEGIN
116+
SELECT is_search_index, ${searchVectorShortened('embedding_model', 'NEW')}
117+
INTO STRICT search_index, shortened
118+
FROM knowledge_base WHERE id = NEW.knowledge_base_id;
119+
IF search_index THEN
120+
DELETE FROM embedding_search WHERE id = NEW.id;
121+
RETURN NEW;
122+
END IF;
123+
INSERT INTO embedding_search
124+
(id, knowledge_base_id, document_id, enabled, ${SEARCH_BINARY_COLUMNS.join(', ')},
125+
${SEARCH_VECTOR_COLUMNS.join(', ')})
126+
VALUES (NEW.id, NEW.knowledge_base_id, NEW.document_id, NEW.enabled,
127+
${searchBinaryProjections('NEW')},
128+
${searchVectorProjections('NEW', 'shortened')})
129+
ON CONFLICT (id) DO UPDATE SET
130+
${excludedAssignments(['knowledge_base_id', 'document_id', 'enabled', ...SEARCH_BINARY_COLUMNS, ...SEARCH_VECTOR_COLUMNS])};
131+
RETURN NEW;
132+
END;
133+
$$`
134+
97135
export type KnowledgeProjection = 'embedding_search'
98136

99137
/** Active vector projections that mirror document access for legacy synchronous writers. */
@@ -138,7 +176,7 @@ function contentPageStatement(): string {
138176
${searchVectorProjections('source', 'shortened')}
139177
FROM source
140178
ON CONFLICT (id) DO UPDATE SET
141-
${[...compared, ...SEARCH_BINARY_COLUMNS].map((column) => `${column} = EXCLUDED.${column}`).join(', ')}
179+
${excludedAssignments([...compared, ...SEARCH_BINARY_COLUMNS])}
142180
WHERE (${compared.map((column) => `s.${column}`).join(', ')})
143181
IS DISTINCT FROM (${compared.map((column) => `EXCLUDED.${column}`).join(', ')})
144182
RETURNING s.id

‎packages/db/maintenance/index.ts‎

Lines changed: 0 additions & 18 deletions
This file was deleted.

‎packages/db/maintenance/search-retirement-health.test.ts‎

Lines changed: 0 additions & 266 deletions
This file was deleted.

0 commit comments

Comments
 (0)