Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Add support for timestamp in Sentinel support #72

Merged
merged 2 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add support for timestamp in Sentinel support
  • Loading branch information
suhailpatel committed Sep 30, 2021
commit f0a1525db573838a7d33af43ba9bbc468f05f117
2 changes: 1 addition & 1 deletion scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func removeSentinelValues(ptrs []interface{}) {
}

elem := reflect.ValueOf(ptr).Elem()
if isSentinel, nonSentinelValue := isClusteringSentinelValue(elem.Interface()); isSentinel {
if isSentinel, nonSentinelValue := IsClusteringSentinelValue(elem.Interface()); isSentinel {
elem.Set(reflect.ValueOf(nonSentinelValue))
}
}
Expand Down
31 changes: 25 additions & 6 deletions statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ var (
// where a value needs to be present (ie: a stand-in representing a
// clustering key that is empty)
ClusteringSentinel = "<gocassa.ClusteringSentinel>"

// ClusteringSentinelTimestamp represents a placeholder time value for
// cases where we have a null timestamp column. We've chosen the time
// 1753-01-01 which ironically is the minimum date in SQL Server
ClusteringSentinelTimestamp = time.Unix(-6847804725, 0)
)

// SelectStatement represents a read (SELECT) query for some data in C*
Expand Down Expand Up @@ -236,7 +241,7 @@ func (s InsertStatement) QueryAndValues() (string, []interface{}) {
fieldNames = append(fieldNames, strings.ToLower(field))
placeholders = append(placeholders, "?")
if isClusteringKeyField(field, s.keys) && s.allowClusterSentinel {
values = append(values, clusteringFieldOrSentinel(fieldMap[field]))
values = append(values, ClusteringFieldOrSentinel(fieldMap[field]))
} else {
values = append(values, fieldMap[field])
}
Expand Down Expand Up @@ -569,7 +574,7 @@ func generateRelationCQL(rel Relation, keys Keys, clusteringSentinelsEnabled boo
switch rel.Comparator() {
case CmpEquality:
if isClusteringKeyField(rel.Field(), keys) && clusteringSentinelsEnabled {
return field + " = ?", clusteringFieldOrSentinel(rel.Terms()[0])
return field + " = ?", ClusteringFieldOrSentinel(rel.Terms()[0])
}
return field + " = ?", rel.Terms()[0]
case CmpIn:
Expand Down Expand Up @@ -612,9 +617,9 @@ func isClusteringKeyField(field string, keys Keys) bool {
return false
}

// clusteringFieldOrSentinel will check if we should substitute in our
// ClusteringFieldOrSentinel will check if we should substitute in our
// sentinel value for empty clustering fields
func clusteringFieldOrSentinel(term interface{}) interface{} {
func ClusteringFieldOrSentinel(term interface{}) interface{} {
switch v := term.(type) {
case string:
if len(v) == 0 {
Expand All @@ -626,14 +631,19 @@ func clusteringFieldOrSentinel(term interface{}) interface{} {
return []byte(ClusteringSentinel)
}
return v
case time.Time:
if v.IsZero() {
return ClusteringSentinelTimestamp
}
return v
leesio marked this conversation as resolved.
Show resolved Hide resolved
default:
return term
}
}

// isClusteringSentinelValue returns a boolean on whether the value passed in
// IsClusteringSentinelValue returns a boolean on whether the value passed in
// is the clustering sentinel value and what the non-sentinel value is
func isClusteringSentinelValue(term interface{}) (bool, interface{}) {
func IsClusteringSentinelValue(term interface{}) (bool, interface{}) {
val := reflect.ValueOf(term)
switch {
case val.Kind() == reflect.String:
Expand All @@ -646,6 +656,15 @@ func isClusteringSentinelValue(term interface{}) (bool, interface{}) {
return true, reflect.MakeSlice(val.Type(), 0, 0).Interface()
}
return false, term
case val.Kind() == reflect.Struct:
timeTyp := reflect.TypeOf(time.Time{})
if val.Type().ConvertibleTo(timeTyp) {
convertedTerm := val.Convert(timeTyp).Interface().(time.Time)
if convertedTerm.Equal(ClusteringSentinelTimestamp) {
return true, reflect.New(val.Type()).Elem().Interface()
}
}
return false, term
default:
return false, term
}
Expand Down
44 changes: 36 additions & 8 deletions statement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,20 +281,24 @@ func TestGenerateOrderByCQL(t *testing.T) {
}

func TestClusteringFieldOrSentinel(t *testing.T) {
assert.Equal(t, ClusteringSentinel, clusteringFieldOrSentinel(""))
assert.Equal(t, "foo", clusteringFieldOrSentinel("foo"))
assert.Equal(t, ClusteringSentinel, ClusteringFieldOrSentinel(""))
assert.Equal(t, "foo", ClusteringFieldOrSentinel("foo"))

assert.Equal(t, []byte(ClusteringSentinel), clusteringFieldOrSentinel([]byte{}))
assert.Equal(t, []byte{0x00}, clusteringFieldOrSentinel([]byte{0x00}))
assert.Equal(t, []byte(ClusteringSentinel), ClusteringFieldOrSentinel([]byte{}))
assert.Equal(t, []byte{0x00}, ClusteringFieldOrSentinel([]byte{0x00}))

assert.Equal(t, 0, clusteringFieldOrSentinel(0))
assert.Equal(t, 42, clusteringFieldOrSentinel(42))
assert.Equal(t, struct{}{}, clusteringFieldOrSentinel(struct{}{}))
assert.Equal(t, ClusteringSentinelTimestamp, ClusteringFieldOrSentinel(time.Time{}))
assert.Equal(t, time.Unix(10, 0), ClusteringFieldOrSentinel(time.Unix(10, 0)))

assert.Equal(t, 0, ClusteringFieldOrSentinel(0))
assert.Equal(t, 42, ClusteringFieldOrSentinel(42))
assert.Equal(t, struct{}{}, ClusteringFieldOrSentinel(struct{}{}))
}

func TestIsClusteringSentinelValue(t *testing.T) {
type fooString string
type fooSlice []byte
type fooTime time.Time

testCases := []struct {
desc string
Expand Down Expand Up @@ -368,6 +372,30 @@ func TestIsClusteringSentinelValue(t *testing.T) {
expectedIsSentinel: false,
expectedOutput: 42,
},
{
desc: "time struct",
input: time.Time{},
expectedIsSentinel: false,
expectedOutput: time.Time{},
},
{
desc: "sentinel time struct",
input: ClusteringSentinelTimestamp,
expectedIsSentinel: true,
expectedOutput: time.Time{},
},
{
desc: "indirect time struct",
input: fooTime{},
expectedIsSentinel: false,
expectedOutput: fooTime{},
},
{
desc: "indirect sentinel time struct",
input: fooTime(ClusteringSentinelTimestamp),
expectedIsSentinel: true,
expectedOutput: fooTime{},
},
{
desc: "empty struct",
input: struct{}{},
Expand All @@ -378,7 +406,7 @@ func TestIsClusteringSentinelValue(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
isSentinel, nonSentinelVal := isClusteringSentinelValue(tc.input)
isSentinel, nonSentinelVal := IsClusteringSentinelValue(tc.input)
assert.Equal(t, tc.expectedIsSentinel, isSentinel)
assert.Equal(t, tc.expectedOutput, nonSentinelVal)
})
Expand Down