open static method
Implementation
static Future<Database> open(String dirPath) async {
final db = Database._(dirPath);
await Directory(dirPath).create(recursive: true);
db._catalogFile = File('$dirPath/catalog.json');
// ── Step 1: Open storage layers ─────────────────────────────────────
db.pager = await Pager.open('$dirPath/data.ndb');
db.cache = PageCache(db.pager, capacity: 256);
db.wal = await Wal.open('$dirPath/wal.log');
db.pager.setWalFlushedLsn(db.wal.flushedLsn);
// ── Step 2: Init transaction manager V2 ─────────────────────────────
//
// FIX Flaw 10: real monotonic txnIds, never 0, restored across restarts.
db.txnManager = TransactionManagerV2(
lockManager: LockManagerV2(),
dataDir: dirPath,
);
// ── Step 3: Load catalog ─────────────────────────────────────────────
await db._loadCatalog();
// ── Step 4: WAL recovery (REDO committed records only) ──────────────
//
// FIX Flaw 3: _applyWalRecord now reads the explicit rowId from the WAL
// payload, so UPDATE/DELETE replay is idempotent.
final walMaxTxnId = await db._recover();
// ── Step 5: Restore txn state from persistence ───────────────────────
//
// Must happen AFTER WAL recovery so walMaxTxnId is known and we never
// re-allocate a txnId that appeared in the WAL.
await db.txnManager.restoreFromPersistence(walMaxTxnId: walMaxTxnId);
// ── Step 6: Recalculate row IDs from page data ───────────────────────
for (final t in db.tables.values) {
await t.recalcNextRowId();
}
// ── Step 7: Populate in-memory MVCC stores from page data ───────────
await db._populateMvccStores();
// ── Step 8: Load persisted statistics ───────────────────────────────
//
// FIX Flaw 8: stats survive restarts.
final statsPath = '$dirPath/stats.json';
final loadedRegistry = await StatsPersistence.load(statsPath);
for (final ts in loadedRegistry.allStats.values) {
db.statsRegistry.update(ts);
}
// ── Step 9: Wire up incremental checkpoint manager ───────────────────
//
// FIX Flaw 9 + Flaw 13:
// • Only dirty pages flushed → O(D) not O(N)
// • Data pages flushed BEFORE indexes and catalog
// • WAL truncation AFTER checkpoint_end fsync
db._checkpointMgr = CheckpointManager(
cache: db.cache,
pager: db.pager,
wal: db.wal,
dataDir: dirPath,
getTables: () => db.pageTables,
persistTxnState: (lsn) => db.txnManager.persistStateAt(lsn),
persistStats: () => StatsPersistence.save(
db.statsRegistry, '$dirPath/stats.json'),
persistCatalog: () => db._saveCatalog(),
persistIndexes: (pts) => db._persistAllIndexes(),
);
print('[NebulaDB] Database opened at $dirPath '
'(tables=${db.tables.length})');
return db;
}