@@ -80,6 +80,35 @@ export async function installTinChunkSync(tx: Sql | TransactionSql): Promise<voi
8080 $$` )
8181}
8282
83+ /**
84+ * The knowledge base trigger's body: projects or removes a whole base when its search-index marker
85+ * changes. The chunks are key-share locked as they are read, as the backfill locks its own, so a
86+ * chunk delete in flight is waited for and its chunk skipped rather than failing the adoption on
87+ * the row's foreign key.
88+ */
89+ export async function installTinMembershipSync ( tx : Sql | TransactionSql ) : Promise < void > {
90+ await tx . unsafe ( `CREATE OR REPLACE FUNCTION sync_knowledge_base_keyword_tin()
91+ RETURNS trigger LANGUAGE plpgsql AS $$
92+ BEGIN
93+ PERFORM pg_advisory_xact_lock(knowledge_tin_membership_key(NEW.id));
94+ IF NOT NEW.is_search_index THEN
95+ DELETE FROM embedding_keyword_tin t USING embedding e
96+ WHERE e.knowledge_base_id = NEW.id AND t.id = e.id;
97+ RETURN NEW;
98+ END IF;
99+ INSERT INTO embedding_keyword_tin (id, knowledge_base_id, document_id, enabled, content)
100+ SELECT id, knowledge_base_id, document_id, enabled,
101+ knowledge_tin_base_token(knowledge_base_id) || ' ' || knowledge_tin_stream(content_tsv)
102+ FROM embedding WHERE knowledge_base_id = NEW.id
103+ ORDER BY id FOR KEY SHARE
104+ ON CONFLICT (id) DO UPDATE SET
105+ knowledge_base_id = EXCLUDED.knowledge_base_id, document_id = EXCLUDED.document_id,
106+ enabled = EXCLUDED.enabled, content = EXCLUDED.content;
107+ RETURN NEW;
108+ END;
109+ $$` )
110+ }
111+
83112/**
84113 * Installs the stream and base-token functions shared by the triggers and the query path, and the
85114 * embedding and knowledge base triggers, atomically with respect to embedding writers.
@@ -123,25 +152,7 @@ export async function installProjection(sql: Sql): Promise<void> {
123152 await tx . unsafe ( `CREATE OR REPLACE TRIGGER embedding_keyword_tin_sync
124153 AFTER INSERT OR UPDATE OF knowledge_base_id, document_id, enabled, content ON embedding
125154 FOR EACH ROW WHEN (${ SYNCHRONOUS_PROJECTION_WHEN } ) EXECUTE FUNCTION sync_embedding_keyword_tin()` )
126- await tx . unsafe ( `CREATE OR REPLACE FUNCTION sync_knowledge_base_keyword_tin()
127- RETURNS trigger LANGUAGE plpgsql AS $$
128- BEGIN
129- PERFORM pg_advisory_xact_lock(knowledge_tin_membership_key(NEW.id));
130- IF NOT NEW.is_search_index THEN
131- DELETE FROM embedding_keyword_tin t USING embedding e
132- WHERE e.knowledge_base_id = NEW.id AND t.id = e.id;
133- RETURN NEW;
134- END IF;
135- INSERT INTO embedding_keyword_tin (id, knowledge_base_id, document_id, enabled, content)
136- SELECT id, knowledge_base_id, document_id, enabled,
137- knowledge_tin_base_token(knowledge_base_id) || ' ' || knowledge_tin_stream(content_tsv)
138- FROM embedding WHERE knowledge_base_id = NEW.id
139- ON CONFLICT (id) DO UPDATE SET
140- knowledge_base_id = EXCLUDED.knowledge_base_id, document_id = EXCLUDED.document_id,
141- enabled = EXCLUDED.enabled, content = EXCLUDED.content;
142- RETURN NEW;
143- END;
144- $$` )
155+ await installTinMembershipSync ( tx )
145156 await tx . unsafe ( `CREATE OR REPLACE TRIGGER knowledge_base_keyword_tin_sync
146157 AFTER UPDATE OF is_search_index ON knowledge_base
147158 FOR EACH ROW WHEN (OLD.is_search_index IS DISTINCT FROM NEW.is_search_index)
0 commit comments