From bc973ba18ee95edc5ab5c58dd7fc762e1acfdbbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Rold=C3=A1n=20Betancort?= Date: Fri, 8 Nov 2024 10:40:51 +0000 Subject: [PATCH] introduces ByteSortable method in Revision This helps determine if the string representation of the revision is byte-sortable. This helps being able to determine if a revision can be serialized to databases that rely on byte-sorting. --- internal/datastore/postgres/revisions.go | 4 + .../revisions/commonrevision_test.go | 83 +++++++++++++++++++ internal/datastore/revisions/hlcrevision.go | 4 + .../datastore/revisions/timestamprevision.go | 4 + internal/datastore/revisions/txidrevision.go | 4 + pkg/datastore/datastore.go | 7 ++ 6 files changed, 106 insertions(+) diff --git a/internal/datastore/postgres/revisions.go b/internal/datastore/postgres/revisions.go index e811971afb..2329d1cbaa 100644 --- a/internal/datastore/postgres/revisions.go +++ b/internal/datastore/postgres/revisions.go @@ -279,6 +279,10 @@ type postgresRevision struct { optionalMetadata map[string]any } +func (pr postgresRevision) ByteSortable() bool { + return false +} + func (pr postgresRevision) Equal(rhsRaw datastore.Revision) bool { rhs, ok := rhsRaw.(postgresRevision) return ok && pr.snapshot.Equal(rhs.snapshot) diff --git a/internal/datastore/revisions/commonrevision_test.go b/internal/datastore/revisions/commonrevision_test.go index a8607b59e5..ff8e4302f3 100644 --- a/internal/datastore/revisions/commonrevision_test.go +++ b/internal/datastore/revisions/commonrevision_test.go @@ -1,6 +1,8 @@ package revisions import ( + "bytes" + "sort" "strings" "testing" @@ -246,3 +248,84 @@ func TestHLCRevisionParsing(t *testing.T) { }) } } + +func TestRevisionByteSortable(t *testing.T) { + tcs := []struct { + left string + right string + leftFirst bool + }{ + { + "1", + "2", + true, + }, + { + "2", + "1", + false, + }, + { + "1", + "1", + true, + }, + { + "1.0000000004", + "1", + false, + }, + { + "1", + "1.0000000004", + true, + }, + { + "1.0000000004", + "1.0000000004", + true, + }, + { + "1.1000000000", + "1.0000000001", + false, + }, + } + + for _, tc := range tcs { + t.Run(tc.left+"_"+tc.right, func(t *testing.T) { + for kind, supportsDecimals := range kinds { + t.Run(string(kind), func(t *testing.T) { + if !supportsDecimals && strings.Contains(tc.left, ".") { + t.Skip("does not support decimals") + } + + if !supportsDecimals && strings.Contains(tc.right, ".") { + t.Skip("does not support decimals") + } + parser := RevisionParser(kind) + + leftRev, err := parser(tc.left) + require.NoError(t, err) + + rightRev, err := parser(tc.right) + require.NoError(t, err) + + if !leftRev.ByteSortable() || !rightRev.ByteSortable() { + t.Skip("does not support byt sorting") + } + + toSort := []string{leftRev.String(), rightRev.String()} + sort.Strings(toSort) + if tc.leftFirst { + require.Equal(t, leftRev.String(), toSort[0]) + require.Equal(t, 0, bytes.Compare([]byte(leftRev.String()), []byte(toSort[0]))) + } else { + require.Equal(t, rightRev.String(), toSort[0]) + require.Equal(t, 0, bytes.Compare([]byte(rightRev.String()), []byte(toSort[0]))) + } + }) + } + }) + } +} diff --git a/internal/datastore/revisions/hlcrevision.go b/internal/datastore/revisions/hlcrevision.go index 62f1687c7b..e4f7fc6502 100644 --- a/internal/datastore/revisions/hlcrevision.go +++ b/internal/datastore/revisions/hlcrevision.go @@ -98,6 +98,10 @@ func NewHLCForTime(time time.Time) HLCRevision { return HLCRevision{time.UnixNano(), logicalClockOffset} } +func (hlc HLCRevision) ByteSortable() bool { + return true +} + func (hlc HLCRevision) Equal(rhs datastore.Revision) bool { if rhs == datastore.NoRevision { rhs = zeroHLC diff --git a/internal/datastore/revisions/timestamprevision.go b/internal/datastore/revisions/timestamprevision.go index d08ca22e68..fc2a250133 100644 --- a/internal/datastore/revisions/timestamprevision.go +++ b/internal/datastore/revisions/timestamprevision.go @@ -33,6 +33,10 @@ func parseTimestampRevisionString(revisionStr string) (rev datastore.Revision, e return TimestampRevision(parsed), nil } +func (ir TimestampRevision) ByteSortable() bool { + return true +} + func (ir TimestampRevision) Equal(rhs datastore.Revision) bool { if rhs == datastore.NoRevision { rhs = zeroTimestampRevision diff --git a/internal/datastore/revisions/txidrevision.go b/internal/datastore/revisions/txidrevision.go index 26deb64031..31d837ff20 100644 --- a/internal/datastore/revisions/txidrevision.go +++ b/internal/datastore/revisions/txidrevision.go @@ -27,6 +27,10 @@ func parseTransactionIDRevisionString(revisionStr string) (rev datastore.Revisio return TransactionIDRevision(parsed), nil } +func (ir TransactionIDRevision) ByteSortable() bool { + return true +} + func (ir TransactionIDRevision) Equal(rhs datastore.Revision) bool { if rhs == datastore.NoRevision { rhs = zeroTransactionIDRevision diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go index b85bb9ce0b..9e53e4a4dc 100644 --- a/pkg/datastore/datastore.go +++ b/pkg/datastore/datastore.go @@ -817,10 +817,17 @@ type Revision interface { // LessThan returns whether the receiver is probably less than the right hand side. LessThan(Revision) bool + + // ByteSortable returns true if the string representation of the Revision is byte sortable, false otherwise. + ByteSortable() bool } type nilRevision struct{} +func (nilRevision) ByteSortable() bool { + return false +} + func (nilRevision) Equal(rhs Revision) bool { return rhs == NoRevision }