// Package browse is the read-only web-browsing data layer over bare Dolt // chunk stores. It is deliberately the only package in dolt.sr.ht that reaches // into low-level dolthub/dolt internals (nbs, prolly, durable, diff), so that // all version-fragile code stays contained here and a future module bump only // needs to be re-verified against this one package. // // # Bare stores have no working set // // dolt.sr.ht serves bare NBS chunk-store directories (created via // doltdb.WriteEmptyRepo and grown by pushes over the remotesapi). They have no // .dolt/ working set, so the sqle engine and the embedded driver cannot open // them. Everything here reads from committed roots only. // // # Open discipline (why not doltdb.LoadDoltDB) // // The obvious entry point, doltdb.LoadDoltDB, routes through the file // dbfactory, which wraps the store in a GenerationalNBS (newgen + an oldgen // subdir + a ghost gen). In the pinned dolt/go version // (v0.40.5-0.20260626152440-45335d44ad79), calling Close() on such a // generational store over one of our bare stores panics deep in nbs: // // panic: Close() called and reduced ref count to < 0. // store/nbs/table_index.go:532 onHeapTableIndex.Close // ... GenerationalNBS.Close -> NomsBlockStore.Close -> tableSet.close // // It reproduces on a plain open-read-only-then-Close, independent of any // reads, so the documented open-per-request + Close pattern would crash the // web process, not just tests. A single (non-generational) nbs.NewLocalStore // — the exact construction storage.Cache uses to serve pushes — closes // cleanly. We therefore build the DoltDB by hand from one NewLocalStore via // doltdb.DoltDBFromCS and never touch the generational path. // // # Freshness and concurrency // // Open is called once per request and Close releases the handle, so every // request gets a fresh read of the on-disk manifest and observes commits // landed by the push writer since the last request — no stale cache, no // refresh dance. NBS readers are manifest-based and append-only: opening a // second NewLocalStore over a directory the push writer is also serving is // safe (no exclusive lock is held for the store's lifetime; the manifest lock // is taken only briefly during atomic updates), so a reader always sees a // consistent snapshot. package browse import ( "context" "errors" "fmt" "path/filepath" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/store/nbs" "github.com/dolthub/dolt/go/store/types" ) // readStoreMemTableSize bounds the in-memory memtable of the read handle. It // matches the value storage.Cache uses for served stores; browsing never // writes, so the memtable stays empty in practice. const readStoreMemTableSize = 128 * 1024 * 1024 // ErrRefNotFound is returned when a ref string matches neither an existing // branch nor a resolvable commit hash. var ErrRefNotFound = errors.New("browse: ref not found") // ErrTableNotFound is returned when a table does not exist in the resolved // root value. var ErrTableNotFound = errors.New("browse: table not found") // DB is a read-only handle to a single bare Dolt chunk store. It is not safe // for concurrent use; open one per request and Close it when done. type DB struct { ddb *doltdb.DoltDB path string } // Open opens the bare Dolt chunk store at diskPath for read-only browsing. // diskPath is the store directory itself (the dir that holds the NBS manifest // and table files), not a parent. The caller must Close the returned DB. // // See the package doc for why this bypasses doltdb.LoadDoltDB. func Open(ctx context.Context, diskPath string) (*DB, error) { cs, err := nbs.NewLocalStore( ctx, types.Format_DOLT.VersionString(), diskPath, readStoreMemTableSize, nbs.NewUnlimitedMemQuotaProvider(), false, ) if err != nil { return nil, fmt.Errorf("browse: open store %q: %w", diskPath, err) } ddb, err := doltdb.DoltDBFromCS(cs, filepath.Base(diskPath)) if err != nil { // cs is not yet owned by a DoltDB, close it directly. _ = cs.Close() return nil, fmt.Errorf("browse: build doltdb for %q: %w", diskPath, err) } return &DB{ddb: ddb, path: diskPath}, nil } // Close releases the underlying chunk store. It closes the single // non-generational NBS store, which is safe in the pinned version (unlike the // generational store LoadDoltDB would have produced). func (db *DB) Close() error { return db.ddb.Close() }