...
This commit is contained in:
173
pkg/data/ourdb/db.go
Normal file
173
pkg/data/ourdb/db.go
Normal file
@@ -0,0 +1,173 @@
|
||||
// Package ourdb provides a simple key-value database implementation with history tracking
|
||||
package ourdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// OurDB represents a binary database with variable-length records
|
||||
type OurDB struct {
|
||||
lookup *LookupTable
|
||||
path string // Directory in which we will have the lookup db as well as all the backend
|
||||
incrementalMode bool
|
||||
fileSize uint32
|
||||
file *os.File
|
||||
fileNr uint16 // The file which is open
|
||||
lastUsedFileNr uint16
|
||||
}
|
||||
|
||||
const headerSize = 12
|
||||
|
||||
// OurDBSetArgs contains the parameters for the Set method
|
||||
type OurDBSetArgs struct {
|
||||
ID *uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Set stores data at the specified key position
|
||||
// The data is stored with a CRC32 checksum for integrity verification
|
||||
// and maintains a linked list of previous values for history tracking
|
||||
// Returns the ID used (either x if specified, or auto-incremented if x=0)
|
||||
func (db *OurDB) Set(args OurDBSetArgs) (uint32, error) {
|
||||
if db.incrementalMode {
|
||||
// If ID points to an empty location, return an error
|
||||
// else, overwrite data
|
||||
if args.ID != nil {
|
||||
// This is an update
|
||||
location, err := db.lookup.Get(*args.ID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if location.Position == 0 {
|
||||
return 0, errors.New("cannot set id for insertions when incremental mode is enabled")
|
||||
}
|
||||
|
||||
if err := db.set_(*args.ID, location, args.Data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return *args.ID, nil
|
||||
}
|
||||
|
||||
// This is an insert
|
||||
id, err := db.lookup.GetNextID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := db.set_(id, Location{}, args.Data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Using key-value mode
|
||||
if args.ID == nil {
|
||||
return 0, errors.New("id must be provided when incremental is disabled")
|
||||
}
|
||||
location, err := db.lookup.Get(*args.ID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := db.set_(*args.ID, location, args.Data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return *args.ID, nil
|
||||
}
|
||||
|
||||
// Get retrieves data stored at the specified key position
|
||||
// Returns error if the key doesn't exist or data is corrupted
|
||||
func (db *OurDB) Get(x uint32) ([]byte, error) {
|
||||
location, err := db.lookup.Get(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.get_(location)
|
||||
}
|
||||
|
||||
// GetHistory retrieves a list of previous values for the specified key
|
||||
// depth parameter controls how many historical values to retrieve (max)
|
||||
// Returns error if key doesn't exist or if there's an issue accessing the data
|
||||
func (db *OurDB) GetHistory(x uint32, depth uint8) ([][]byte, error) {
|
||||
result := make([][]byte, 0)
|
||||
currentLocation, err := db.lookup.Get(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Traverse the history chain up to specified depth
|
||||
for i := uint8(0); i < depth; i++ {
|
||||
// Get current value
|
||||
data, err := db.get_(currentLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, data)
|
||||
|
||||
// Try to get previous location
|
||||
prevLocation, err := db.getPrevPos_(currentLocation)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if prevLocation.Position == 0 {
|
||||
break
|
||||
}
|
||||
currentLocation = prevLocation
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Delete removes the data at the specified key position
|
||||
// This operation zeros out the record but maintains the space in the file
|
||||
// Use condense() to reclaim space from deleted records (happens in step after)
|
||||
func (db *OurDB) Delete(x uint32) error {
|
||||
location, err := db.lookup.Get(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.delete_(x, location); err != nil {
|
||||
return err
|
||||
}
|
||||
return db.lookup.Delete(x)
|
||||
}
|
||||
|
||||
// GetNextID returns the next id which will be used when storing
|
||||
func (db *OurDB) GetNextID() (uint32, error) {
|
||||
if !db.incrementalMode {
|
||||
return 0, errors.New("incremental mode is not enabled")
|
||||
}
|
||||
return db.lookup.GetNextID()
|
||||
}
|
||||
|
||||
// lookupDumpPath returns the path to the lookup dump file
|
||||
func (db *OurDB) lookupDumpPath() string {
|
||||
return filepath.Join(db.path, "lookup_dump.db")
|
||||
}
|
||||
|
||||
// Load metadata if exists
|
||||
func (db *OurDB) Load() error {
|
||||
if _, err := os.Stat(db.lookupDumpPath()); err == nil {
|
||||
return db.lookup.ImportSparse(db.lookupDumpPath())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save ensures we have the metadata stored on disk
|
||||
func (db *OurDB) Save() error {
|
||||
return db.lookup.ExportSparse(db.lookupDumpPath())
|
||||
}
|
||||
|
||||
// Close closes the database file
|
||||
func (db *OurDB) Close() error {
|
||||
if err := db.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
return db.close_()
|
||||
}
|
||||
|
||||
// Destroy closes and removes the database
|
||||
func (db *OurDB) Destroy() error {
|
||||
_ = db.Close()
|
||||
return os.RemoveAll(db.path)
|
||||
}
|
Reference in New Issue
Block a user