131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
// Package gobplustree implements a B+ tree data structure and related utilities.
|
|
package gobplustree
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
)
|
|
|
|
var (
|
|
// ErrPageIdOutOfRange is returned when trying to access a page that doesn't exist in the file.
|
|
ErrPageIdOutOfRange = errors.New("page id out of range")
|
|
)
|
|
|
|
// BlockFile represents a file divided into fixed-size blocks or pages.
|
|
// It provides methods for reading and writing these pages.
|
|
type BlockFile struct {
|
|
PageCount int64
|
|
FileName string
|
|
file *os.File
|
|
}
|
|
|
|
// Constants defining the structure of the block file.
|
|
const (
|
|
// BlockFileHeaderSize is the size of the file header in bytes.
|
|
BlockFileHeaderSize = 8
|
|
|
|
// BlockFilePageSize is the size of each page in bytes.
|
|
BlockFilePageSize = 4096
|
|
|
|
// InitPageCount is the initial number of pages when creating a new file.
|
|
InitPageCount = 1024
|
|
)
|
|
|
|
// NewBlockFile creates a new BlockFile instance.
|
|
// fileName: the name of the file to be created or opened.
|
|
// initialPageCount: the initial number of pages for the file.
|
|
func NewBlockFile(fileName string, initialPageCount int64) (*BlockFile, error) {
|
|
return &BlockFile{
|
|
PageCount: initialPageCount,
|
|
FileName: fileName,
|
|
file: nil,
|
|
}, nil
|
|
}
|
|
|
|
// Open opens the file and initializes it if it's empty.
|
|
// Returns an error if the file cannot be opened or initialized.
|
|
func (bf *BlockFile) Open() error {
|
|
file, err := os.OpenFile(bf.FileName, os.O_RDWR|os.O_CREATE, 0666)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bf.file = file
|
|
fileInfo, err := file.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// If file is empty, initialize it with default pages
|
|
if fileInfo.Size() == 0 {
|
|
// bf.PageCount = InitPageCount
|
|
_, err := bf.file.Write(make([]byte, BlockFileHeaderSize))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = bf.file.Write(make([]byte, BlockFilePageSize*InitPageCount))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
bf.PageCount = fileInfo.Size() / BlockFilePageSize
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close closes the underlying file.
|
|
// Returns an error if the file cannot be closed.
|
|
func (bf *BlockFile) Close() error {
|
|
return bf.file.Close()
|
|
}
|
|
|
|
// Read reads a page from the file by its page ID.
|
|
// pageId: the ID of the page to read.
|
|
// Returns the page data and an error if the operation fails.
|
|
func (bf *BlockFile) Read(pageId int64) ([]byte, error) {
|
|
if pageId >= bf.PageCount || pageId < 0 {
|
|
return nil, ErrPageIdOutOfRange
|
|
}
|
|
|
|
data := make([]byte, BlockFilePageSize)
|
|
n, err := bf.file.ReadAt(data, pageId*BlockFilePageSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check if we read the full page
|
|
if n != BlockFilePageSize {
|
|
return nil, ErrPageIdOutOfRange
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// enlarge increases the file size by adding more pages.
|
|
// count: the number of pages to add.
|
|
// Returns an error if the operation fails.
|
|
func (bf *BlockFile) enlarge(count int64) error {
|
|
_, err := bf.file.WriteAt(make([]byte, BlockFilePageSize*count), bf.PageCount*BlockFilePageSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bf.PageCount += count
|
|
return nil
|
|
}
|
|
|
|
// Write writes data to a specific page in the file.
|
|
// pageId: the ID of the page to write to.
|
|
// data: the data to write.
|
|
// Returns an error if the operation fails.
|
|
func (bf *BlockFile) Write(pageId int64, data []byte) error {
|
|
if pageId >= bf.PageCount {
|
|
err := bf.enlarge(pageId - bf.PageCount + 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err := bf.file.WriteAt(data, pageId*BlockFilePageSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|