diff options
Diffstat (limited to 'rwtransaction.go')
-rw-r--r-- | rwtransaction.go | 228 |
1 files changed, 87 insertions, 141 deletions
diff --git a/rwtransaction.go b/rwtransaction.go index b0123f5..a0fd5ff 100644 --- a/rwtransaction.go +++ b/rwtransaction.go @@ -1,19 +1,13 @@ package bolt +import ( + "unsafe" +) + // RWTransaction represents a transaction that can read and write data. // Only one read/write transaction can be active for a DB at a time. type RWTransaction struct { Transaction - - dirtyPages map[pgid]*page - freelist []pgid -} - -// init initializes the transaction and associates it with a database. -func (t *RWTransaction) init(db *DB, meta *meta) { - t.dirtyPages = make(map[pgid]*page) - t.freelist = make([]pgid) - t.Transaction.init(db, meta) } // TODO: Allocate scratch meta page. @@ -50,150 +44,86 @@ func (t *RWTransaction) close() error { } // CreateBucket creates a new bucket. -func (t *RWTransaction) CreateBucket(name string, dupsort bool) (*Bucket, error) { +func (t *RWTransaction) CreateBucket(name string) error { if t.db == nil { - return nil, InvalidTransactionError + return InvalidTransactionError } // Check if bucket already exists. - if b := t.buckets[name]; b != nil { - return nil, &Error{"bucket already exists", nil} + if b, err := t.Bucket(name); err != nil { + return err + } else if b != nil { + return &Error{"bucket already exists", nil} } // Create a new bucket entry. var buf [unsafe.Sizeof(bucket{})]byte var raw = (*bucket)(unsafe.Pointer(&buf[0])) - raw.root = p_invalid - // TODO: Set dupsort flag. + raw.root = 0 // Open cursor to system bucket. - c, err := t.Cursor(&t.sysbuckets) - if err != nil { - return nil, err + c := t.sys.cursor() + if c.Goto([]byte(name)) { + // TODO: Delete node first. } - // Put new entry into system bucket. - if err := c.Put([]byte(name), buf[:]); err != nil { - return nil, err + // Insert new node. + if err := t.insert([]byte(name), buf[:]); err != nil { + return err } - // Save reference to bucket. - b := &Bucket{name: name, bucket: raw, isNew: true} - t.buckets[name] = b - - // TODO: dbflag |= DB_DIRTY; - - return b, nil + return nil } // DropBucket deletes a bucket. func (t *RWTransaction) DeleteBucket(b *Bucket) error { - // TODO: Find bucket. - // TODO: Remove entry from system bucket. + // TODO: Remove from main DB. + // TODO: Delete entry from system bucket. + // TODO: Free all pages. + // TODO: Remove cursor. return nil } -// Put sets the value for a key in a given bucket. -func (t *Transaction) Put(name string, key []byte, value []byte) error { - c, err := t.Cursor(name) - if err != nil { - return nil, err - } - return c.Put(key, value) -} - -// page returns a reference to the page with a given id. -// If page has been written to then a temporary bufferred page is returned. -func (t *Transaction) page(id int) *page { - // Check the dirty pages first. - if p, ok := t.pages[id]; ok { - return p - } - - // Otherwise return directly from the mmap. - return t.Transaction.page(id) -} - // Flush (some) dirty pages to the map, after clearing their dirty flag. // @param[in] txn the transaction that's being committed // @param[in] keep number of initial pages in dirty_list to keep dirty. // @return 0 on success, non-zero on failure. -func (t *Transaction) flush(keep bool) error { +func (t *RWTransaction) flush(keep bool) error { // TODO(benbjohnson): Use vectorized I/O to write out dirty pages. // TODO: Loop over each dirty page and write it to disk. return nil } -func (t *RWTransaction) DeleteBucket(name string) error { - // TODO: Remove from main DB. - // TODO: Delete entry from system bucket. - // TODO: Free all pages. - // TODO: Remove cursor. - - return nil -} - -func (c *RWCursor) Put(key []byte, value []byte) error { - // Make sure this cursor was created by a transaction. - if c.transaction == nil { - return &Error{"invalid cursor", nil} +func (t *RWTransaction) Put(name string, key []byte, value []byte) error { + b := t.Bucket(name) + if b == nil { + return BucketNotFoundError } - db := c.transaction.db - // Validate the key we're using. - if key == nil { + // Validate the key and data size. + if len(key) == 0 { return &Error{"key required", nil} - } else if len(key) > db.maxKeySize { + } else if len(key) > MaxKeySize { return &Error{"key too large", nil} - } - - // TODO: Validate data size based on MaxKeySize if DUPSORT. - - // Validate the size of our data. - if len(data) > MaxDataSize { + } else if len(value) > MaxDataSize { return &Error{"data too large", nil} } - // If we don't have a root page then add one. - if c.bucket.root == p_invalid { - p, err := c.newLeafPage() - if err != nil { - return err - } - c.push(p) - c.bucket.root = p.id - c.bucket.root++ - // TODO: *mc->mc_dbflag |= DB_DIRTY; - // TODO? mc->mc_flags |= C_INITIALIZED; - } - - // TODO: Move to key. - exists, err := c.moveTo(key) - if err != nil { - return err - } - - // TODO: spill? - if err := c.spill(key, data); err != nil { - return err - } + // Move cursor to insertion position. + c := b.cursor() + replace := c.Goto() + p, index := c.current() - // Make sure all cursor pages are writable - if err := c.touch(); err != nil { + // Insert a new node. + if err := t.insert(p, index, key, value, replace); err != nil { return err } - // If key does not exist the - if exists { - node := c.currentNode() - - } - return nil } -func (c *Cursor) Delete(key []byte) error { +func (t *RWTransaction) Delete(key []byte) error { // TODO: Traverse to the correct node. // TODO: If missing, exit. // TODO: Remove node from page. @@ -201,50 +131,66 @@ func (c *Cursor) Delete(key []byte) error { return nil } -// newLeafPage allocates and initialize new a new leaf page. -func (c *RWCursor) newLeafPage() (*page, error) { - // Allocate page. - p, err := c.allocatePage(1) - if err != nil { - return nil, err - } +// allocate returns a contiguous block of memory starting at a given page. +func (t *RWTransaction) allocate(count int) (*page, error) { + // TODO: Find a continuous block of free pages. + // TODO: If no free pages are available, resize the mmap to allocate more. + return nil, nil +} - // Set flags and bounds. - p.flags = p_leaf | p_dirty - p.lower = pageHeaderSize - p.upper = c.transaction.db.pageSize +func (t *RWTransaction) insert(p *page, index int, key []byte, data []byte, replace bool) error { + nodes := copy(p.lnodes()) + if replace { + nodes = nodes.replace(index, key, data) + } else { + nodes = nodes.insert(index, key, data) + } - return p, nil -} + // If our page fits in the same size page then just write it. + if pageHeaderSize + nodes.size() < p.size() { + // TODO: Write new page. + // TODO: Update parent branches. + } -// newBranchPage allocates and initialize new a new branch page. -func (b *RWCursor) newBranchPage() (*page, error) { - // Allocate page. - p, err := c.allocatePage(1) - if err != nil { - return nil, err + // Calculate total page size. + size := pageHeaderSize + for _, n := range nodes { + size += lnodeSize + n.ksize + n.vsize } - // Set flags and bounds. - p.flags = p_branch | p_dirty - p.lower = pageHeaderSize - p.upper = c.transaction.db.pageSize + // If our new page fits in our current page size then just write it. + if size < t.db.pageSize { - return p, nil -} + return t.writeLeafPage(p.id, nodes) + } -// allocate returns a contiguous block of memory starting at a given page. -func (t *RWTransaction) allocate(count int) (*page, error) { - // TODO: Find a continuous block of free pages. - // TODO: If no free pages are available, resize the mmap to allocate more. - return nil, nil -} + var nodesets [][]lnodes + if size < t.db.pageSize { + nodesets = [][]lnodes{nodes} + } + nodesets := t.split(nodes) -func (t *RWTransaction) insert(key []byte, data []byte) error { - // TODO: If there is not enough space on page for key+data then split. // TODO: Move remaining data on page forward. // TODO: Write leaf node to current location. // TODO: Adjust available page size. return nil } + +// split takes a list of nodes and returns multiple sets of nodes if a +// page split is required. +func (t *RWTransaction) split(nodes []lnodes) [][]lnodes { + + // If the size is less than the page size then just return the current set. + if size < t.db.pageSize { + return [][]lnodes{nodes} + } + + // Otherwise loop over nodes and split up into multiple pages. + var nodeset []lnodes + var nodesets [][]lnodes + for _, n := range nodes { + + } + +} |