gobplustree/file.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
}