From ce560a3588c7673fdb613d05e73cf347179a284b Mon Sep 17 00:00:00 2001 From: Al Cutter Date: Mon, 8 Jul 2024 17:58:42 +0100 Subject: [PATCH] Make paths adhere to the spec, and reason about them in tile-space (#38) --- api/layout/paths.go | 50 +++++++++++++++++++---------------- api/layout/paths_test.go | 57 +++++++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/api/layout/paths.go b/api/layout/paths.go index 7d21ce25..6aa22f4a 100644 --- a/api/layout/paths.go +++ b/api/layout/paths.go @@ -19,7 +19,6 @@ package layout import ( "fmt" - "path/filepath" ) const ( @@ -27,32 +26,39 @@ const ( CheckpointPath = "checkpoint" ) -// EntriesPath builds the local path at which the leaf with the given index lives in. +// EntriesPathForLogIndex builds the local path at which the leaf with the given index lives in. // Note that this will be an entry bundle containing up to 256 entries and thus multiple // indices can map to the same output path. // // TODO(mhutchinson): revisit to consider how partial tile suffixes should be added. -func EntriesPath(seq uint64) string { - seq = seq / 256 - frag := []string{ - "tile", - "entries", - fmt.Sprintf("x%03x", (seq>>16)&0xff), - fmt.Sprintf("x%03x", (seq>>8)&0xff), - fmt.Sprintf("%03x", seq&0xff), - } - return filepath.Join(frag...) +func EntriesPathForLogIndex(seq uint64) string { + return EntriesPath(seq / 256) +} + +// EntriesPath returns the local path for the Nth entry bundle. +func EntriesPath(N uint64) string { + return fmt.Sprintf("tile/entries%s", fmtN(N)) } -// TilePath builds the path to the subtree tile with the given level and index. -func TilePath(level, index uint64) string { - seq := index / 256 - frag := []string{ - "tile", - fmt.Sprintf("%d", level), - fmt.Sprintf("x%03x", (seq>>16)&0xff), - fmt.Sprintf("x%03x", (seq>>8)&0xff), - fmt.Sprintf("%03x", seq&0xff), +// TilePath builds the path to the subtree tile with the given level and index in tile space. +func TilePath(tileLevel, tileIndex uint64) string { + return fmt.Sprintf("tile/%d%s", tileLevel, fmtN(tileIndex)) +} + +// fmtN returns the "N" part of a Tiles-spec path. +// +// N is grouped into chunks of 3 decimal digits, starting with the most significant digit, and +// padding with zeroes as necessary. +// Digit groups are prefixed with "x", except for the least-significant group which has no prefix, +// and separated with slashes. +// +// See https://github.com/C2SP/C2SP/blob/main/tlog-tiles.md#:~:text=index%201234067%20will%20be%20encoded%20as%20x001/x234/067 +func fmtN(N uint64) string { + n := fmt.Sprintf("/%03d", N%1000) + N /= 1000 + for N > 0 { + n = fmt.Sprintf("/x%03d%s", N%1000, n) + N /= 1000 } - return filepath.Join(frag...) + return n } diff --git a/api/layout/paths_test.go b/api/layout/paths_test.go index bd4968e2..cf864712 100644 --- a/api/layout/paths_test.go +++ b/api/layout/paths_test.go @@ -19,28 +19,57 @@ import ( "testing" ) -func TestEntriesPath(t *testing.T) { +func TestEntriesPathForLogIndex(t *testing.T) { for _, test := range []struct { seq uint64 wantPath string }{ { seq: 0, - wantPath: "tile/entries/x000/x000/000", + wantPath: "tile/entries/000", }, { seq: 255, - wantPath: "tile/entries/x000/x000/000", + wantPath: "tile/entries/000", }, { seq: 256, - wantPath: "tile/entries/x000/x000/001", + wantPath: "tile/entries/001", }, { - seq: 0xffeeddccbb, - wantPath: "tile/entries/x0ee/x0dd/0cc", + seq: 123456789 * 256, + wantPath: "tile/entries/x123/x456/789", }, } { desc := fmt.Sprintf("seq %d", test.seq) t.Run(desc, func(t *testing.T) { - gotPath := EntriesPath(test.seq) + gotPath := EntriesPathForLogIndex(test.seq) + if gotPath != test.wantPath { + t.Errorf("got file %q want %q", gotPath, test.wantPath) + } + }) + } +} + +func TestEntriesPath(t *testing.T) { + for _, test := range []struct { + N uint64 + wantPath string + }{ + { + N: 0, + wantPath: "tile/entries/000", + }, { + N: 255, + wantPath: "tile/entries/255", + }, { + N: 256, + wantPath: "tile/entries/256", + }, { + N: 123456789000, + wantPath: "tile/entries/x123/x456/x789/000", + }, + } { + desc := fmt.Sprintf("N %d", test.N) + t.Run(desc, func(t *testing.T) { + gotPath := EntriesPath(test.N) if gotPath != test.wantPath { t.Errorf("got file %q want %q", gotPath, test.wantPath) } @@ -57,11 +86,19 @@ func TestTilePath(t *testing.T) { { level: 0, index: 0, - wantPath: "tile/0/x000/x000/000", + wantPath: "tile/0/000", + }, { + level: 15, + index: 455667, + wantPath: "tile/15/x455/667", + }, { + level: 3, + index: 1234567, + wantPath: "tile/3/x001/x234/567", }, { level: 15, - index: 0x455667, - wantPath: "tile/15/x000/x045/056", + index: 123456789, + wantPath: "tile/15/x123/x456/789", }, } { desc := fmt.Sprintf("level %x index %x", test.level, test.index)