-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Update TimeKeyEncoder to Preserve Lexicographical Order #21
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
package collections | ||
|
||
import ( | ||
"sort" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
@@ -53,3 +55,221 @@ func TestKeysetIterator(t *testing.T) { | |
iter.Next() | ||
assert.False(t, iter.Valid()) | ||
} | ||
|
||
func TestTimeKeySet(t *testing.T) { | ||
sk, ctx, _ := deps() | ||
keyset := NewKeySet[time.Time](sk, 0, TimeKeyEncoder) | ||
|
||
// Use a fixed time | ||
now := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) | ||
keyset.Insert(ctx, now) | ||
require.True(t, keyset.Has(ctx, now)) | ||
|
||
// Test delete and get | ||
keyset.Delete(ctx, now) | ||
require.False(t, keyset.Has(ctx, now)) | ||
} | ||
|
||
func TestTimeKeySet_IterateAscending(t *testing.T) { | ||
sk, ctx, _ := deps() | ||
keyset := NewKeySet[time.Time](sk, 0, TimeKeyEncoder) | ||
|
||
now := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) | ||
times := []time.Time{ | ||
now.Add(2 * time.Second), | ||
now.Add(1 * time.Second), | ||
now.Add(3 * time.Second), | ||
now, | ||
} | ||
|
||
// Insert times into the keyset | ||
for _, t := range times { | ||
keyset.Insert(ctx, t) | ||
} | ||
|
||
// Sort times in ascending order | ||
sort.Slice(times, func(i, j int) bool { | ||
return times[i].Before(times[j]) | ||
}) | ||
|
||
Comment on lines
+91
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor duplicate sorting logic into helper functions The sorting logic for time slices is repeated across multiple test functions. Refactoring this code into helper functions will reduce duplication and enhance maintainability. Apply this refactor by adding helper functions: // Helper function to sort times in ascending order
func sortTimesAsc(times []time.Time) []time.Time {
sortedTimes := make([]time.Time, len(times))
copy(sortedTimes, times)
sort.Slice(sortedTimes, func(i, j int) bool {
return sortedTimes[i].Before(sortedTimes[j])
})
return sortedTimes
}
// Helper function to sort times in descending order
func sortTimesDesc(times []time.Time) []time.Time {
sortedTimes := make([]time.Time, len(times))
copy(sortedTimes, times)
sort.Slice(sortedTimes, func(i, j int) bool {
return sortedTimes[i].After(sortedTimes[j])
})
return sortedTimes
} Then, replace the sorting code in your test functions with these helper functions: -// Sort times in ascending order
-sortedTimesAsc := make([]time.Time, len(times))
-copy(sortedTimesAsc, times)
-sort.Slice(sortedTimesAsc, func(i, j int) bool {
- return sortedTimesAsc[i].Before(sortedTimesAsc[j])
-})
+sortedTimesAsc := sortTimesAsc(times) -// Sort times in descending order
-sortedTimesDesc := make([]time.Time, len(times))
-copy(sortedTimesDesc, times)
-sort.Slice(sortedTimesDesc, func(i, j int) bool {
- return sortedTimesDesc[i].After(sortedTimesDesc[j])
-})
+sortedTimesDesc := sortTimesDesc(times) Also applies to: 130-133, 176-181, 196-201 |
||
// Iterate over the keyset in ascending order | ||
iter := keyset.Iterate(ctx, Range[time.Time]{}) | ||
defer iter.Close() | ||
|
||
keys := iter.Keys() | ||
require.Equal(t, len(times), len(keys)) | ||
|
||
for i, k := range keys { | ||
// Strip monotonic clock readings | ||
expectedTime := times[i].Round(0) | ||
actualTime := k.Round(0) | ||
|
||
// Compare UnixNano timestamps | ||
require.Equal(t, expectedTime.UnixNano(), actualTime.UnixNano()) | ||
} | ||
} | ||
|
||
func TestTimeKeySet_IterateDescending(t *testing.T) { | ||
sk, ctx, _ := deps() | ||
keyset := NewKeySet[time.Time](sk, 0, TimeKeyEncoder) | ||
|
||
now := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) | ||
times := []time.Time{ | ||
now.Add(2 * time.Second), | ||
now.Add(1 * time.Second), | ||
now.Add(3 * time.Second), | ||
now, | ||
} | ||
|
||
// Insert times into the keyset | ||
for _, t := range times { | ||
keyset.Insert(ctx, t) | ||
} | ||
|
||
// Sort times in descending order | ||
sort.Slice(times, func(i, j int) bool { | ||
return times[i].After(times[j]) | ||
}) | ||
|
||
// Iterate over the keyset in descending order | ||
iter := keyset.Iterate(ctx, Range[time.Time]{}.Descending()) | ||
defer iter.Close() | ||
|
||
keys := iter.Keys() | ||
require.Equal(t, len(times), len(keys)) | ||
|
||
for i, k := range keys { | ||
expectedTime := times[i].Round(0) | ||
actualTime := k.Round(0) | ||
require.Equal(t, expectedTime.UnixNano(), actualTime.UnixNano()) | ||
} | ||
} | ||
|
||
func TestTimeKeyEncoder_EncodeDecode(t *testing.T) { | ||
now := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) | ||
encoded := TimeKeyEncoder.Encode(now) | ||
_, decoded := TimeKeyEncoder.Decode(encoded) | ||
|
||
// Compare UnixNano timestamps | ||
require.Equal(t, now.UnixNano(), decoded.UnixNano()) | ||
} | ||
|
||
func TestTimeKeySet_OrderConsistency(t *testing.T) { | ||
sk, ctx, _ := deps() | ||
keyset := NewKeySet[time.Time](sk, 0, TimeKeyEncoder) | ||
|
||
now := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) | ||
times := []time.Time{ | ||
now.Add(-1 * time.Hour), | ||
now, | ||
now.Add(1 * time.Hour), | ||
now.Add(2 * time.Hour), | ||
now.Add(-2 * time.Hour), | ||
} | ||
|
||
// Insert times into the keyset | ||
for _, t := range times { | ||
keyset.Insert(ctx, t) | ||
} | ||
|
||
// Sort times in ascending order | ||
sortedTimesAsc := make([]time.Time, len(times)) | ||
copy(sortedTimesAsc, times) | ||
sort.Slice(sortedTimesAsc, func(i, j int) bool { | ||
return sortedTimesAsc[i].Before(sortedTimesAsc[j]) | ||
}) | ||
|
||
// Iterate over the keyset in ascending order | ||
iterAsc := keyset.Iterate(ctx, Range[time.Time]{}) | ||
defer iterAsc.Close() | ||
|
||
keysAsc := iterAsc.Keys() | ||
require.Equal(t, len(times), len(keysAsc)) | ||
|
||
for i, k := range keysAsc { | ||
expectedTime := sortedTimesAsc[i].Round(0) | ||
actualTime := k.Round(0) | ||
require.Equal(t, expectedTime.UnixNano(), actualTime.UnixNano()) | ||
} | ||
|
||
// Sort times in descending order | ||
sortedTimesDesc := make([]time.Time, len(times)) | ||
copy(sortedTimesDesc, times) | ||
sort.Slice(sortedTimesDesc, func(i, j int) bool { | ||
return sortedTimesDesc[i].After(sortedTimesDesc[j]) | ||
}) | ||
|
||
// Iterate over the keyset in descending order | ||
iterDesc := keyset.Iterate(ctx, Range[time.Time]{}.Descending()) | ||
defer iterDesc.Close() | ||
|
||
keysDesc := iterDesc.Keys() | ||
require.Equal(t, len(times), len(keysDesc)) | ||
|
||
for i, k := range keysDesc { | ||
expectedTime := sortedTimesDesc[i].Round(0) | ||
actualTime := k.Round(0) | ||
require.Equal(t, expectedTime.UnixNano(), actualTime.UnixNano()) | ||
} | ||
} | ||
|
||
func TestTimeKeySet_IterateRange(t *testing.T) { | ||
sk, ctx, _ := deps() | ||
keyset := NewKeySet[time.Time](sk, 0, TimeKeyEncoder) | ||
|
||
now := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) | ||
times := []time.Time{ | ||
now.Add(1 * time.Second), | ||
now.Add(2 * time.Second), | ||
now.Add(3 * time.Second), | ||
now.Add(4 * time.Second), | ||
now.Add(5 * time.Second), | ||
} | ||
|
||
// Insert times into the keyset | ||
for _, t := range times { | ||
keyset.Insert(ctx, t) | ||
} | ||
|
||
// Define range from now.Add(2s) inclusive to now.Add(4s) exclusive | ||
iter := keyset.Iterate(ctx, Range[time.Time]{}. | ||
StartInclusive(now.Add(2*time.Second)). | ||
EndExclusive(now.Add(4*time.Second))) | ||
defer iter.Close() | ||
|
||
expectedTimes := []time.Time{ | ||
now.Add(2 * time.Second), | ||
now.Add(3 * time.Second), | ||
} | ||
|
||
keys := iter.Keys() | ||
require.Equal(t, len(expectedTimes), len(keys)) | ||
|
||
for i, k := range keys { | ||
expectedTime := expectedTimes[i].Round(0) | ||
actualTime := k.Round(0) | ||
require.Equal(t, expectedTime.UnixNano(), actualTime.UnixNano()) | ||
} | ||
} | ||
|
||
func TestTimeKeySet_SameTimeKeys(t *testing.T) { | ||
sk, ctx, _ := deps() | ||
keyset := NewKeySet[time.Time](sk, 0, TimeKeyEncoder) | ||
|
||
now := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) | ||
|
||
// Insert the same time multiple times (should only be stored once in a set) | ||
keyset.Insert(ctx, now) | ||
keyset.Insert(ctx, now) | ||
keyset.Insert(ctx, now) | ||
|
||
iter := keyset.Iterate(ctx, Range[time.Time]{}) | ||
defer iter.Close() | ||
|
||
keys := iter.Keys() | ||
require.Equal(t, 1, len(keys)) | ||
|
||
expectedTime := now.Round(0) | ||
actualTime := keys[0].Round(0) | ||
require.Equal(t, expectedTime.UnixNano(), actualTime.UnixNano()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Time range limitation needs documentation and testing
The implementation's use of UnixNano() introduces time range constraints (years 1678 to 2262), but this limitation is neither documented nor tested. Consider:
keys.go
explaining the valid time range🔗 Analysis chain
Implementation ensures lexicographical ordering but has time range limitations.
The new implementation correctly uses binary encoding of Unix nanoseconds to ensure lexicographical ordering matches chronological ordering. However, there are some important considerations:
UnixNano()
limits the valid time range to years 1678 to 2262 due to the int64 constraints.Let's verify the time range limitation:
The search results show that all tests use timestamps from 2021, with relatively small time offsets (hours/seconds). There's no evidence of testing the time range limitations. Let's search for any documentation or comments about time range constraints.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 3537
Script:
Length of output: 117