perf(sqlite): skip no-op automatic schema repair (#115193)

This commit is contained in:
Peter Steinberger
2026-07-28 11:18:56 -04:00
committed by GitHub
parent 0f41b401fd
commit 2cbbb00fba
4 changed files with 89 additions and 4 deletions

View File

@@ -36,6 +36,7 @@ import {
import {
detectOpenClawStateDatabaseSchemaMigrations,
repairOpenClawStateDatabaseSchema,
repairOpenClawStateDatabaseSchemaIfNeeded,
type OpenClawStateDatabaseSchemaMigration,
} from "../state/openclaw-state-db.js";
import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js";
@@ -956,7 +957,7 @@ export async function autoMigrateLegacyPluginDoctorState(params: {
});
const stateDir = resolveStateDir(env, params.homedir ?? os.homedir);
const oauthDir = resolveOAuthDir(env, stateDir);
const stateSchema = repairOpenClawStateDatabaseSchema({
const stateSchema = repairOpenClawStateDatabaseSchemaIfNeeded({
env: { ...env, OPENCLAW_STATE_DIR: stateDir },
});
const changes = [...stateDirResult.changes, ...stateSchema.changes];
@@ -1347,9 +1348,11 @@ export async function autoMigrateLegacyState(params: {
});
const stateDir = resolveStateDir(env, homedir);
autoMigrateChecked.add(`${path.resolve(stateDir)}\0${migrationMode}`);
const stateSchema = repairOpenClawStateDatabaseSchema({
env: { ...env, OPENCLAW_STATE_DIR: stateDir },
});
const stateSchemaOptions = { env: { ...env, OPENCLAW_STATE_DIR: stateDir } };
const stateSchema =
params.doctorOnlyStateMigrations === true
? repairOpenClawStateDatabaseSchema(stateSchemaOptions)
: repairOpenClawStateDatabaseSchemaIfNeeded(stateSchemaOptions);
if (stateSchema.warnings.length > 0) {
return {
migrated: stateDirResult.migrated || stateSchema.changes.length > 0,

View File

@@ -512,6 +512,25 @@ describe("state migrations", () => {
detectionCase = { ...detected, stateDir, env };
});
it("keeps automatic migration read-only when the shared schema is current", async () => {
const root = await createTempDir();
const stateDir = path.join(root, ".openclaw");
const env = createEnv(stateDir);
const databasePath = openOpenClawStateDatabase({ env }).path;
closeOpenClawStateDatabaseForTest();
const writer = new DatabaseSync(databasePath);
writer.exec("PRAGMA journal_mode = WAL; BEGIN IMMEDIATE;");
try {
await expect(
autoMigrateLegacyState({ cfg: createConfig(), env, homedir: () => root }),
).resolves.toMatchObject({ changes: [], warnings: [] });
} finally {
writer.exec("ROLLBACK;");
writer.close();
}
});
it("uses the requested environment for plugin migration refresh and writes", async () => {
const root = await createTempDir();
const stateDir = path.join(root, "custom-state");

View File

@@ -30,6 +30,7 @@ import {
openOpenClawStateDatabase,
OPENCLAW_STATE_SCHEMA_VERSION,
repairOpenClawStateDatabaseSchema,
repairOpenClawStateDatabaseSchemaIfNeeded,
runOpenClawStateWriteTransaction,
withOpenClawStateStartupMigrationCheckpointDatabase,
} from "./openclaw-state-db.js";
@@ -1068,6 +1069,32 @@ describe("openclaw state database", () => {
).toThrow();
});
it("skips exclusive repair when the automatic schema gate is already current", () => {
const stateDir = createTempStateDir();
const options = { env: { OPENCLAW_STATE_DIR: stateDir } };
const databasePath = openOpenClawStateDatabase(options).path;
closeOpenClawStateDatabaseForTest();
const { DatabaseSync } = requireNodeSqlite();
const before = new DatabaseSync(databasePath);
before.prepare("UPDATE schema_meta SET updated_at = 123 WHERE meta_key = 'primary'").run();
before.close();
expect(repairOpenClawStateDatabaseSchemaIfNeeded(options)).toEqual({
changes: [],
warnings: [],
});
const after = new DatabaseSync(databasePath, { readOnly: true });
try {
expect(
after.prepare("SELECT updated_at FROM schema_meta WHERE meta_key = 'primary'").get(),
).toEqual({ updated_at: 123 });
} finally {
after.close();
}
});
it("drops unreleased transient verification history on open", () => {
const stateDir = createTempStateDir();
const options = { env: { OPENCLAW_STATE_DIR: stateDir } };

View File

@@ -64,6 +64,7 @@ import { ensureAdditiveStateColumns } from "./openclaw-state-db-schema-additive.
import { tableExists } from "./openclaw-state-db-schema-helpers.js";
import {
assertCanonicalStateSchemaShape,
detectOpenClawStateDatabaseSchemaMigrationsFromDatabase,
dropLegacyStateTables,
markCurrentStateSchemaVersion,
repairAgentDatabasesCompositePrimaryKey,
@@ -292,6 +293,41 @@ export function repairOpenClawStateDatabaseSchema(options: OpenClawStateDatabase
}
}
/** Skip the exclusive doctor repair when automatic migration sees a canonical current schema. */
export function repairOpenClawStateDatabaseSchemaIfNeeded(
options: OpenClawStateDatabaseOptions = {},
): {
changes: string[];
warnings: string[];
} {
const pathname = resolveDatabasePath(options);
if (!existsSync(pathname)) {
return { changes: [], warnings: [] };
}
let needsRepair = true;
let database: DatabaseSync | undefined;
try {
database = openNodeSqliteDatabase(pathname, { readOnly: true });
assertSupportedSchemaVersion(database, pathname);
needsRepair =
readSqliteUserVersion(database) !== OPENCLAW_STATE_SCHEMA_VERSION ||
detectOpenClawStateDatabaseSchemaMigrationsFromDatabase(database, pathname).length > 0;
if (!needsRepair) {
assertCurrentStateRuntimeSchema(database, pathname);
}
} catch {
// Preserve the repair path's existing diagnostics for unreadable or noncanonical databases.
needsRepair = true;
} finally {
if (database?.isOpen) {
database.close();
}
}
return needsRepair ? repairOpenClawStateDatabaseSchema(options) : { changes: [], warnings: [] };
}
function ensureSchema(db: DatabaseSync, pathname: string): void {
const now = Date.now();
const kysely = getNodeSqliteKysely<OpenClawStateMetadataDatabase>(db);