Skip to content

Commit 3eeb283

Browse files
authored
fix(fts): try local LOAD before INSTALL to avoid network failures (#726)
1 parent 2d7b15f commit 3eeb283

3 files changed

Lines changed: 57 additions & 24 deletions

File tree

gitnexus/src/core/lbug/lbug-adapter.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,34 +1144,51 @@ export const getEmbeddingTableName = (): string => EMBEDDING_TABLE_NAME;
11441144

11451145
/**
11461146
* Load the FTS extension (required before using FTS functions).
1147-
* Safe to call multiple times — tracks loaded state via module-level ftsLoaded.
1147+
*
1148+
* Safe to call multiple times — when invoked without arguments, tracks loaded
1149+
* state via module-level `ftsLoaded`. When invoked with an explicit
1150+
* connection, loads on that connection and returns whether the load
1151+
* succeeded — letting callers (e.g. the pool adapter) track their own state.
1152+
*
1153+
* Tries `LOAD EXTENSION fts` first so previously-cached installs skip the
1154+
* network entirely; falls back to `INSTALL` + `LOAD` only when the extension
1155+
* hasn't been cached yet.
11481156
*/
1149-
export const loadFTSExtension = async (): Promise<void> => {
1150-
if (ftsLoaded) return;
1151-
if (!conn) {
1157+
export const loadFTSExtension = async (targetConn?: lbug.Connection): Promise<boolean> => {
1158+
const useModuleState = targetConn === undefined;
1159+
if (useModuleState && ftsLoaded) return true;
1160+
1161+
const c: lbug.Connection | null = targetConn ?? conn;
1162+
if (!c) {
11521163
throw new Error('LadybugDB not initialized. Call initLbug first.');
11531164
}
1165+
1166+
const markLoaded = (): true => {
1167+
if (useModuleState) ftsLoaded = true;
1168+
return true;
1169+
};
1170+
11541171
try {
11551172
// Try loading locally first (no network required)
1156-
await conn.query('LOAD EXTENSION fts');
1157-
ftsLoaded = true;
1173+
await c.query('LOAD EXTENSION fts');
1174+
return markLoaded();
11581175
} catch {
11591176
// Fall back to install + load (requires network)
11601177
try {
1161-
await conn.query('INSTALL fts');
1162-
await conn.query('LOAD EXTENSION fts');
1163-
ftsLoaded = true;
1178+
await c.query('INSTALL fts');
1179+
await c.query('LOAD EXTENSION fts');
1180+
return markLoaded();
11641181
} catch (err: any) {
11651182
const msg = err?.message || '';
11661183
if (
11671184
msg.includes('already loaded') ||
11681185
msg.includes('already installed') ||
11691186
msg.includes('already exists')
11701187
) {
1171-
ftsLoaded = true;
1172-
} else {
1173-
console.error('GitNexus: FTS extension load failed:', msg);
1188+
return markLoaded();
11741189
}
1190+
console.error('GitNexus: FTS extension load failed:', msg);
1191+
return false;
11751192
}
11761193
}
11771194
};

gitnexus/src/core/lbug/pool-adapter.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import fs from 'fs/promises';
1919
import lbug from '@ladybugdb/core';
20+
import { loadFTSExtension } from './lbug-adapter.js';
2021

2122
/** Per-repo pool: one Database, many Connections */
2223
interface PoolEntry {
@@ -354,12 +355,7 @@ async function doInitLbug(repoId: string, dbPath: string): Promise<void> {
354355
// Done BEFORE pool registration so no concurrent checkout can grab
355356
// the connection while the async FTS load is in progress.
356357
if (!shared.ftsLoaded) {
357-
try {
358-
await available[0].query('LOAD EXTENSION fts');
359-
shared.ftsLoaded = true;
360-
} catch {
361-
// Extension may not be installed — FTS queries will fail gracefully
362-
}
358+
shared.ftsLoaded = await loadFTSExtension(available[0]);
363359
}
364360

365361
// Load VECTOR extension once per shared Database for semantic search support.
@@ -433,12 +429,7 @@ export async function initLbugWithDb(
433429

434430
// Load FTS extension if not already loaded on this Database
435431
if (!shared.ftsLoaded) {
436-
try {
437-
await available[0].query('LOAD EXTENSION fts');
438-
shared.ftsLoaded = true;
439-
} catch {
440-
// Extension may already be loaded or not installed
441-
}
432+
shared.ftsLoaded = await loadFTSExtension(available[0]);
442433
}
443434

444435
// Load VECTOR extension for semantic search support

gitnexus/test/integration/lbug-core-adapter.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,31 @@ withTestLbugDB(
4545
).resolves.toBeUndefined();
4646
});
4747

48+
it('loadFTSExtension(conn): loads on an explicit connection and returns true', async () => {
49+
const lbug = (await import('@ladybugdb/core')).default;
50+
const { loadFTSExtension, getDatabase } =
51+
await import('../../src/core/lbug/lbug-adapter.js');
52+
53+
const db = getDatabase();
54+
expect(db).not.toBeNull();
55+
56+
// Fresh Connection on the same Database — simulates the pool adapter's
57+
// path where loadFTSExtension is called with an explicit connection
58+
// rather than the module-level singleton.
59+
const freshConn = new lbug.Connection(db!);
60+
try {
61+
const loaded = await loadFTSExtension(freshConn);
62+
expect(loaded).toBe(true);
63+
64+
// Idempotent on the same connection — calling again still returns true
65+
// (exercises the "already loaded" catch branch in the fallback path).
66+
const loadedAgain = await loadFTSExtension(freshConn);
67+
expect(loadedAgain).toBe(true);
68+
} finally {
69+
await freshConn.close().catch(() => {});
70+
}
71+
});
72+
4873
it('getLbugStats: returns correct node and edge counts for seeded data', async () => {
4974
const { getLbugStats } = await import('../../src/core/lbug/lbug-adapter.js');
5075

0 commit comments

Comments
 (0)