Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/storage/src/__tests__/operational-state-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ test('new runtime databases use incremental vacuum before operational schema cre
}
});

test('an operational opener configures the empty file left by another connection', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-operational-empty-open-'));
const first = new DatabaseSync(join(root, 'runtime.sqlite'));
try {
const lease = acquireOperationalStateDatabase(root);
try {
assert.equal(lease.database.prepare('PRAGMA auto_vacuum').get()?.auto_vacuum, 2);
assert.equal(first.prepare('PRAGMA auto_vacuum').get()?.auto_vacuum, 2);
} finally {
lease.close();
}
} finally {
first.close();
await rm(root, { recursive: true, force: true });
}
});

test('workflow 12 upgrade adds wait storage atomically and preserves every released row and schema scope', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-operational-workflow-12-'));
const databasePath = join(root, 'runtime.sqlite');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ describe('SQLite recovery authority multi-process races', () => {
.user_version,
SQLITE_RUNTIME_SCHEMA_VERSION,
);
assert.equal(database.prepare('PRAGMA auto_vacuum').get()?.auto_vacuum, 2);
} finally {
database.close();
}
Expand Down
29 changes: 29 additions & 0 deletions packages/storage/src/__tests__/sqlite-runtime-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,35 @@ describe('SqliteRuntimeStore', () => {
});
});

it('configures incremental auto-vacuum when another opener created the empty file', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-sqlite-runtime-empty-open-'));
const dbPath = join(root, 'runtime.sqlite');
const first = new DatabaseSync(dbPath);
try {
const store = createSqliteRuntimeStore(dbPath);
store.close();
assert.equal(first.prepare('PRAGMA auto_vacuum').get()?.auto_vacuum, 2);
} finally {
first.close();
await rm(root, { recursive: true, force: true });
}
});

it('preserves an initialized NONE database even when it has no tables', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-sqlite-runtime-empty-none-'));
const dbPath = join(root, 'runtime.sqlite');
const first = new DatabaseSync(dbPath);
try {
first.exec('BEGIN IMMEDIATE; COMMIT');
const store = createSqliteRuntimeStore(dbPath);
store.close();
assert.equal(first.prepare('PRAGMA auto_vacuum').get()?.auto_vacuum, 0);
} finally {
first.close();
await rm(root, { recursive: true, force: true });
}
});

it('reads a complete Session from one snapshot while another connection commits its terminal', async (t) => {
await withStore(async (store, dbPath) => {
const opening = invocationOpeningEvent(1);
Expand Down
2 changes: 1 addition & 1 deletion packages/storage/src/operational-state-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ class OperationalStateDatabaseOwner {
configureSqliteRuntimeLockWait(this.database);
this.database.exec('PRAGMA foreign_keys = ON');
if (options.schemaMigration !== 'require_current') {
configureSqliteRuntimeAutoVacuumForNewDatabase(this.database, databaseExisted);
configureSqliteRuntimeAutoVacuumForNewDatabase(this.database);
}
if (options.schemaMigration === 'require_current') {
requireCurrentOperationalState(this.database);
Expand Down
17 changes: 7 additions & 10 deletions packages/storage/src/sqlite-runtime-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -954,16 +954,13 @@ export function configureSqliteRuntimeDatabase(db: DatabaseSync): void {
db.exec('PRAGMA foreign_keys = ON');
}

/** Set the reclamation mode before schema creation on a newly created file only. */
export function configureSqliteRuntimeAutoVacuumForNewDatabase(
db: DatabaseSync,
databaseExisted: boolean,
): void {
if (databaseExisted) return;
const existingSchema = db
.prepare("SELECT 1 AS present FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%' LIMIT 1")
.get() as { present?: unknown } | undefined;
if (existingSchema?.present === 1) return;
/** Set the reclamation mode before an uninitialized database gets its first page. */
export function configureSqliteRuntimeAutoVacuumForNewDatabase(db: DatabaseSync): void {
// Another opener may have created the file without initializing its header.
// File existence cannot tell us whether it still needs this configuration.
// Preserve initialized databases, including those with no application tables.
const pages = db.prepare('PRAGMA page_count').get() as { page_count: number };
if (pages.page_count !== 0) return;
db.exec('PRAGMA auto_vacuum = INCREMENTAL');
const configured = db.prepare('PRAGMA auto_vacuum').get() as
| { auto_vacuum?: unknown }
Expand Down
7 changes: 4 additions & 3 deletions packages/storage/src/sqlite-runtime-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type {
ImmutableRuntimePrefixProofReadBudget,
} from './runtime-event-store-contract.js';

import { existsSync, mkdirSync } from 'node:fs';
import { mkdirSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname } from 'node:path';
import type { DatabaseSync, SQLInputValue } from 'node:sqlite';
Expand Down Expand Up @@ -134,6 +134,7 @@ import {
import {
configureSqliteRuntimeDatabase,
configureSqliteRuntimeAutoVacuumForNewDatabase,
configureSqliteRuntimeLockWait,
migrateSqliteRuntimeDatabase,
readUserVersion,
RUNTIME_RECOVERY_AUTHORITY_CAPABILITY,
Expand Down Expand Up @@ -346,7 +347,6 @@ export class SqliteRuntimeStore
return;
}
const DatabaseSync = loadDatabaseSync();
const databaseExisted = path !== ':memory:' && existsSync(path);
this.db = options.readOnly
? new DatabaseSync(path, { readOnly: true })
: new DatabaseSync(path);
Expand All @@ -360,7 +360,8 @@ export class SqliteRuntimeStore
);
}
} else {
configureSqliteRuntimeAutoVacuumForNewDatabase(this.db, databaseExisted);
configureSqliteRuntimeLockWait(this.db);
configureSqliteRuntimeAutoVacuumForNewDatabase(this.db);
configureSqliteRuntimeDatabase(this.db);
migrateSqliteRuntimeDatabase(this.db);
}
Expand Down