// 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 }