Bug 1968020 - Fix schema check of the semantic history database. r=cgopal

The healthy database schema check cannot use table_info as it is not
supported by the sqlite-vec extension.

Differential Revision: https://phabricator.services.mozilla.com/D250794
This commit is contained in:
Marco Bonardo
2025-05-22 18:56:15 +00:00
committed by mak77@bonardo.net
parent 9b2092feba
commit fd60166ce8
2 changed files with 37 additions and 8 deletions

View File

@@ -97,7 +97,7 @@ export class PlacesSemanticHistoryDatabase {
lazy.logger.info("Initializing schema"); lazy.logger.info("Initializing schema");
await this.#initializeSchema(); await this.#initializeSchema();
} catch (e) { } catch (e) {
lazy.logger.warn("Schema initialization failed"); lazy.logger.warn(`Schema initialization failed: ${e}`);
// If the schema cannot be initialized close the connection and create // If the schema cannot be initialized close the connection and create
// a new database file. // a new database file.
await this.closeConnection(); await this.closeConnection();
@@ -153,12 +153,15 @@ export class PlacesSemanticHistoryDatabase {
*/ */
async #initializeSchema() { async #initializeSchema() {
let version = await this.#conn.getSchemaVersion(); let version = await this.#conn.getSchemaVersion();
lazy.logger.debug(`Database schema version: ${version}`);
if (version > CURRENT_SCHEMA_VERSION) { if (version > CURRENT_SCHEMA_VERSION) {
lazy.logger.warn(`Database schema downgrade`);
throw new Error("Downgrade of the schema is not supported"); throw new Error("Downgrade of the schema is not supported");
} }
if (version == CURRENT_SCHEMA_VERSION) { if (version == CURRENT_SCHEMA_VERSION) {
let healthy = await this.#checkDatabaseEntities(this.#embeddingSize); let healthy = await this.#checkDatabaseEntities(this.#embeddingSize);
if (!healthy) { if (!healthy) {
lazy.logger.error(`Database schema is not healthy`);
throw new Error("Database schema is not healthy"); throw new Error("Database schema is not healthy");
} }
return; return;
@@ -228,18 +231,21 @@ export class PlacesSemanticHistoryDatabase {
!tableNames.includes("vec_history") || !tableNames.includes("vec_history") ||
!tableNames.includes("vec_history_mapping") !tableNames.includes("vec_history_mapping")
) { ) {
lazy.logger.error(`Missing tables in the database`);
return false; return false;
} }
// If the embedding size changed the database should be recreated. This // If the embedding size changed the database should be recreated. This
// should be handled by a migration, but we check to be overly safe. // should be handled by a migration, but we check to be overly safe.
let embeddingSizeCheck = await this.#conn.execute( let embeddingSizeMatches = (
`PRAGMA table_info(vec_history)` await this.#conn.execute(
); `SELECT INSTR(sql, :needle) > 0
let embeddingSizeRow = embeddingSizeCheck.find( FROM sqlite_master WHERE name = 'vec_history'`,
row => row.getResultByName("name") == "embedding" { needle: `FLOAT[${embeddingSize}]` }
); )
if (embeddingSizeRow.getResultByName("type") != `FLOAT[${embeddingSize}]`) { )[0].getResultByIndex(0);
if (!embeddingSizeMatches) {
lazy.logger.error(`Embeddings size doesn't match`);
return false; return false;
} }

View File

@@ -94,3 +94,26 @@ add_task(async function test_corruptdb() {
await db.closeConnection(); await db.closeConnection();
await db.removeDatabaseFiles(); await db.removeDatabaseFiles();
}); });
add_task(async function test_healthydb() {
let db = new PlacesSemanticHistoryDatabase({
embeddingSize: 4,
fileName: "places_semantic.sqlite",
});
await db.getConnection();
await db.closeConnection();
// Check database creation time won't change when reopening, as that would
// indicate the database file was replaced.
let creationTime = (await IOUtils.stat(db.databaseFilePath)).creationTime;
db = new PlacesSemanticHistoryDatabase({
embeddingSize: 4,
fileName: "places_semantic.sqlite",
});
await db.getConnection();
await db.closeConnection();
Assert.equal(
creationTime,
(await IOUtils.stat(db.databaseFilePath)).creationTime,
"Database creation time should not change."
);
});