From d5e25376382f3211b5a0887e84e859e12b0fdcd6 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Sat, 20 Jan 2024 16:30:39 -0500 Subject: [PATCH] feat: add pagefile api (#47) --- pkg/btree/pagefile.go | 31 +++++++++++++++++++ pkg/btree/pagefile_test.go | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 pkg/btree/pagefile.go create mode 100644 pkg/btree/pagefile_test.go diff --git a/pkg/btree/pagefile.go b/pkg/btree/pagefile.go new file mode 100644 index 00000000..24b0bb55 --- /dev/null +++ b/pkg/btree/pagefile.go @@ -0,0 +1,31 @@ +package btree + +import "io" + +type PageFile struct { + io.ReadWriteSeeker + PageSize int +} + +func (pf *PageFile) Seek(offset int64, whence int) (int64, error) { + if offset == 0 && whence == io.SeekEnd { + // Seek to the end of the file + offset, err := pf.ReadWriteSeeker.Seek(0, io.SeekEnd) + if err != nil { + return 0, err + } + // If the offset is not a multiple of the page size, we need to pad the file + // with zeros to the next page boundary. + if pf.PageSize > 0 && offset%int64(pf.PageSize) != 0 { + // Calculate the number of bytes to pad + pad := int64(pf.PageSize) - (offset % int64(pf.PageSize)) + // Write the padding + if _, err := pf.Write(make([]byte, pad)); err != nil { + return 0, err + } + return offset + pad, nil + } + return offset, nil + } + return pf.ReadWriteSeeker.Seek(offset, whence) +} diff --git a/pkg/btree/pagefile_test.go b/pkg/btree/pagefile_test.go new file mode 100644 index 00000000..b2a010d8 --- /dev/null +++ b/pkg/btree/pagefile_test.go @@ -0,0 +1,62 @@ +package btree + +import ( + "io" + "testing" +) + +func TestPageFile(t *testing.T) { + t.Run("no page size behaves like regular ReadWriteSeeker", func(t *testing.T) { + buf := newSeekableBuffer() + pf := &PageFile{ + ReadWriteSeeker: buf, + } + if _, err := pf.Seek(0, io.SeekStart); err != nil { + t.Fatal(err) + } + if _, err := pf.Write([]byte("hello")); err != nil { + t.Fatal(err) + } + if _, err := pf.Seek(0, io.SeekStart); err != nil { + t.Fatal(err) + } + b := make([]byte, 5) + if _, err := pf.Read(b); err != nil { + t.Fatal(err) + } + if string(b) != "hello" { + t.Fatalf("expected %q, got %q", "hello", string(b)) + } + }) + + t.Run("page size allocates pages on seek end", func(t *testing.T) { + buf := newSeekableBuffer() + pf := &PageFile{ + ReadWriteSeeker: buf, + PageSize: 16, + } + if _, err := pf.Seek(0, io.SeekEnd); err != nil { + t.Fatal(err) + } + if _, err := pf.Write([]byte("hello")); err != nil { + t.Fatal(err) + } + if _, err := pf.Seek(0, io.SeekStart); err != nil { + t.Fatal(err) + } + b := make([]byte, 5) + if _, err := pf.Read(b); err != nil { + t.Fatal(err) + } + if string(b) != "hello" { + t.Fatalf("expected %q, got %q", "hello", string(b)) + } + n, err := pf.Seek(0, io.SeekEnd) + if err != nil { + t.Fatal(err) + } + if n != 16 { + t.Fatalf("expected %d, got %d", 16, n) + } + }) +}