Skip to content

Commit 963a6f6

Browse files
committed
CASSGO-43: externally-defined type registration
The new RegisterType function can be used to register externally-defined types. You'll need to define your own marshalling and unmarshalling code as well as a TypeInfo implementation. The name and id MUST not collide with existing and future native CQL types. A lot of the type handling was refactored to use the new format for native types. Performance should be slightly improved thanks to some simplification and removal of unnecessary slice copying. Benchmarks are coming soon. Pointers to empty interfaces are accepted by Scan and are used to build the maps in MapScan and SliceMap. inet columns are now unmarshaled as net.IP which is a breaking change. Patch by James Hartig for CASSGO-43
1 parent 91cbf12 commit 963a6f6

13 files changed

+1302
-748
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Added
1010

11+
- Externally-defined type registration (CASSGO-43)
12+
1113
### Changed
1214

1315
- Don't restrict server authenticator unless PasswordAuthentictor.AllowedAuthenticators is provided (CASSGO-19)
@@ -26,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2628

2729
- Keep nil slices in MapScan (CASSGO-44)
2830

31+
- inet columns are now unmarshalled as net.IP when using MapScan or SliceMap (CASSGO-43)
32+
2933
### Fixed
3034

3135
- Retry policy now takes into account query idempotency (CASSGO-27)

cassandra_test.go

Lines changed: 101 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ func TestDurationType(t *testing.T) {
560560
defer session.Close()
561561

562562
if session.cfg.ProtoVersion < 5 {
563-
t.Skip("Duration type is not supported. Please use protocol version >= 4 and cassandra version >= 3.11")
563+
t.Skip("Duration type is not supported. Please use protocol version > 4")
564564
}
565565

566566
if err := createTable(session, `CREATE TABLE gocql_test.duration_table (
@@ -983,7 +983,7 @@ func TestMapScan(t *testing.T) {
983983
}
984984
assertEqual(t, "fullname", "Ada Lovelace", row["fullname"])
985985
assertEqual(t, "age", 30, row["age"])
986-
assertEqual(t, "address", "10.0.0.2", row["address"])
986+
assertDeepEqual(t, "address", net.ParseIP("10.0.0.2").To4(), row["address"])
987987
assertDeepEqual(t, "data", []byte(`{"foo": "bar"}`), row["data"])
988988

989989
// Second iteration using a new map
@@ -993,7 +993,7 @@ func TestMapScan(t *testing.T) {
993993
}
994994
assertEqual(t, "fullname", "Grace Hopper", row["fullname"])
995995
assertEqual(t, "age", 31, row["age"])
996-
assertEqual(t, "address", "10.0.0.1", row["address"])
996+
assertDeepEqual(t, "address", net.ParseIP("10.0.0.1").To4(), row["address"])
997997
assertDeepEqual(t, "data", []byte(nil), row["data"])
998998
}
999999

@@ -1040,7 +1040,7 @@ func TestSliceMap(t *testing.T) {
10401040
m["testset"] = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
10411041
m["testmap"] = map[string]string{"field1": "val1", "field2": "val2", "field3": "val3"}
10421042
m["testvarint"] = bigInt
1043-
m["testinet"] = "213.212.2.19"
1043+
m["testinet"] = net.ParseIP("213.212.2.19").To4()
10441044
sliceMap := []map[string]interface{}{m}
10451045
if err := session.Query(`INSERT INTO slice_map_table (testuuid, testtimestamp, testvarchar, testbigint, testblob, testbool, testfloat, testdouble, testint, testdecimal, testlist, testset, testmap, testvarint, testinet) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
10461046
m["testuuid"], m["testtimestamp"], m["testvarchar"], m["testbigint"], m["testblob"], m["testbool"], m["testfloat"], m["testdouble"], m["testint"], m["testdecimal"], m["testlist"], m["testset"], m["testmap"], m["testvarint"], m["testinet"]).Exec(); err != nil {
@@ -1072,51 +1072,105 @@ func TestSliceMap(t *testing.T) {
10721072
}
10731073
func matchSliceMap(t *testing.T, sliceMap []map[string]interface{}, testMap map[string]interface{}) {
10741074
if sliceMap[0]["testuuid"] != testMap["testuuid"] {
1075-
t.Fatal("returned testuuid did not match")
1075+
t.Fatalf("returned testuuid %#v did not match %#v", sliceMap[0]["testuuid"], testMap["testuuid"])
10761076
}
10771077
if sliceMap[0]["testtimestamp"] != testMap["testtimestamp"] {
1078-
t.Fatal("returned testtimestamp did not match")
1078+
t.Fatalf("returned testtimestamp %#v did not match %#v", sliceMap[0]["testtimestamp"], testMap["testtimestamp"])
10791079
}
10801080
if sliceMap[0]["testvarchar"] != testMap["testvarchar"] {
1081-
t.Fatal("returned testvarchar did not match")
1081+
t.Fatalf("returned testvarchar %#v did not match %#v", sliceMap[0]["testvarchar"], testMap["testvarchar"])
10821082
}
10831083
if sliceMap[0]["testbigint"] != testMap["testbigint"] {
1084-
t.Fatal("returned testbigint did not match")
1084+
t.Fatalf("returned testbigint %#v did not match %#v", sliceMap[0]["testbigint"], testMap["testbigint"])
10851085
}
10861086
if !reflect.DeepEqual(sliceMap[0]["testblob"], testMap["testblob"]) {
1087-
t.Fatal("returned testblob did not match")
1087+
t.Fatalf("returned testblob %#v did not match %#v", sliceMap[0]["testblob"], testMap["testblob"])
10881088
}
10891089
if sliceMap[0]["testbool"] != testMap["testbool"] {
1090-
t.Fatal("returned testbool did not match")
1090+
t.Fatalf("returned testbool %#v did not match %#v", sliceMap[0]["testbool"], testMap["testbool"])
10911091
}
10921092
if sliceMap[0]["testfloat"] != testMap["testfloat"] {
1093-
t.Fatal("returned testfloat did not match")
1093+
t.Fatalf("returned testfloat %#v did not match %#v", sliceMap[0]["testfloat"], testMap["testfloat"])
10941094
}
10951095
if sliceMap[0]["testdouble"] != testMap["testdouble"] {
1096-
t.Fatal("returned testdouble did not match")
1096+
t.Fatalf("returned testdouble %#v did not match %#v", sliceMap[0]["testdouble"], testMap["testdouble"])
10971097
}
1098-
if sliceMap[0]["testinet"] != testMap["testinet"] {
1099-
t.Fatal("returned testinet did not match")
1098+
if !reflect.DeepEqual(sliceMap[0]["testinet"], testMap["testinet"]) {
1099+
t.Fatalf("returned testinet %#v did not match %#v", sliceMap[0]["testinet"], testMap["testinet"])
11001100
}
11011101

11021102
expectedDecimal := sliceMap[0]["testdecimal"].(*inf.Dec)
11031103
returnedDecimal := testMap["testdecimal"].(*inf.Dec)
11041104

11051105
if expectedDecimal.Cmp(returnedDecimal) != 0 {
1106-
t.Fatal("returned testdecimal did not match")
1106+
t.Fatalf("returned testdecimal %#v did not match %#v", sliceMap[0]["testdecimal"], testMap["testdecimal"])
11071107
}
11081108

11091109
if !reflect.DeepEqual(sliceMap[0]["testlist"], testMap["testlist"]) {
1110-
t.Fatal("returned testlist did not match")
1110+
t.Fatalf("returned testlist %#v did not match %#v", sliceMap[0]["testlist"], testMap["testlist"])
11111111
}
11121112
if !reflect.DeepEqual(sliceMap[0]["testset"], testMap["testset"]) {
1113-
t.Fatal("returned testset did not match")
1113+
t.Fatalf("returned testset %#v did not match %#v", sliceMap[0]["testset"], testMap["testset"])
11141114
}
11151115
if !reflect.DeepEqual(sliceMap[0]["testmap"], testMap["testmap"]) {
1116-
t.Fatal("returned testmap did not match")
1116+
t.Fatalf("returned testmap %#v did not match %#v", sliceMap[0]["testmap"], testMap["testmap"])
11171117
}
11181118
if sliceMap[0]["testint"] != testMap["testint"] {
1119-
t.Fatal("returned testint did not match")
1119+
t.Fatalf("returned testint %#v did not match %#v", sliceMap[0]["testint"], testMap["testint"])
1120+
}
1121+
}
1122+
1123+
func TestSliceMap_CopySlices(t *testing.T) {
1124+
session := createSession(t)
1125+
defer session.Close()
1126+
if err := createTable(session, `CREATE TABLE gocql_test.slice_map_copy_table (
1127+
t text,
1128+
u timeuuid,
1129+
l list<text>,
1130+
PRIMARY KEY (t, u)
1131+
)`); err != nil {
1132+
t.Fatal("create table:", err)
1133+
}
1134+
1135+
err := session.Query(
1136+
`INSERT INTO slice_map_copy_table (t, u, l) VALUES ('test', ?, ?)`,
1137+
TimeUUID(), []string{"1", "2"},
1138+
).Exec()
1139+
if err != nil {
1140+
t.Fatal("insert:", err)
1141+
}
1142+
1143+
err = session.Query(
1144+
`INSERT INTO slice_map_copy_table (t, u, l) VALUES ('test', ?, ?)`,
1145+
TimeUUID(), []string{"3", "4"},
1146+
).Exec()
1147+
if err != nil {
1148+
t.Fatal("insert:", err)
1149+
}
1150+
1151+
err = session.Query(
1152+
`INSERT INTO slice_map_copy_table (t, u, l) VALUES ('test', ?, ?)`,
1153+
TimeUUID(), []string{"5", "6"},
1154+
).Exec()
1155+
if err != nil {
1156+
t.Fatal("insert:", err)
1157+
}
1158+
1159+
if returned, retErr := session.Query(`SELECT * FROM slice_map_copy_table WHERE t = 'test'`).Iter().SliceMap(); retErr != nil {
1160+
t.Fatal("select:", retErr)
1161+
} else {
1162+
if len(returned) != 3 {
1163+
t.Fatal("expected 3 rows, got", len(returned))
1164+
}
1165+
if !reflect.DeepEqual(returned[0]["l"], []string{"1", "2"}) {
1166+
t.Fatal("expected [1, 2], got", returned[0]["l"])
1167+
}
1168+
if !reflect.DeepEqual(returned[1]["l"], []string{"3", "4"}) {
1169+
t.Fatal("expected [3, 4], got", returned[1]["l"])
1170+
}
1171+
if !reflect.DeepEqual(returned[2]["l"], []string{"5", "6"}) {
1172+
t.Fatal("expected [5, 6], got", returned[2]["l"])
1173+
}
11201174
}
11211175
}
11221176

@@ -1193,7 +1247,7 @@ func TestSmallInt(t *testing.T) {
11931247
t.Fatal("select:", retErr)
11941248
} else {
11951249
if sliceMap[0]["testsmallint"] != returned[0]["testsmallint"] {
1196-
t.Fatal("returned testsmallint did not match")
1250+
t.Fatalf("returned testsmallint %#v did not match %#v", returned[0]["testsmallint"], sliceMap[0]["testsmallint"])
11971251
}
11981252
}
11991253
}
@@ -2415,18 +2469,19 @@ func TestAggregateMetadata(t *testing.T) {
24152469
t.Fatal("expected two aggregates")
24162470
}
24172471

2472+
protoVer := byte(session.cfg.ProtoVersion)
24182473
expectedAggregrate := AggregateMetadata{
24192474
Keyspace: "gocql_test",
24202475
Name: "average",
2421-
ArgumentTypes: []TypeInfo{NativeType{typ: TypeInt}},
2476+
ArgumentTypes: []TypeInfo{NativeType{proto: protoVer, typ: TypeInt}},
24222477
InitCond: "(0, 0)",
2423-
ReturnType: NativeType{typ: TypeDouble},
2478+
ReturnType: NativeType{proto: protoVer, typ: TypeDouble},
24242479
StateType: TupleTypeInfo{
2425-
NativeType: NativeType{typ: TypeTuple},
2480+
NativeType: NativeType{proto: protoVer, typ: TypeTuple},
24262481

24272482
Elems: []TypeInfo{
2428-
NativeType{typ: TypeInt},
2429-
NativeType{typ: TypeBigInt},
2483+
NativeType{proto: protoVer, typ: TypeInt},
2484+
NativeType{proto: protoVer, typ: TypeBigInt},
24302485
},
24312486
},
24322487
stateFunc: "avgstate",
@@ -2439,11 +2494,11 @@ func TestAggregateMetadata(t *testing.T) {
24392494
}
24402495

24412496
if !reflect.DeepEqual(aggregates[0], expectedAggregrate) {
2442-
t.Fatalf("aggregate 'average' is %+v, but expected %+v", aggregates[0], expectedAggregrate)
2497+
t.Fatalf("aggregate 'average' is %#v, but expected %#v", aggregates[0], expectedAggregrate)
24432498
}
24442499
expectedAggregrate.Name = "average2"
24452500
if !reflect.DeepEqual(aggregates[1], expectedAggregrate) {
2446-
t.Fatalf("aggregate 'average2' is %+v, but expected %+v", aggregates[1], expectedAggregrate)
2501+
t.Fatalf("aggregate 'average2' is %#v, but expected %#v", aggregates[1], expectedAggregrate)
24472502
}
24482503
}
24492504

@@ -2465,28 +2520,29 @@ func TestFunctionMetadata(t *testing.T) {
24652520
avgState := functions[1]
24662521
avgFinal := functions[0]
24672522

2523+
protoVer := byte(session.cfg.ProtoVersion)
24682524
avgStateBody := "if (val !=null) {state.setInt(0, state.getInt(0)+1); state.setLong(1, state.getLong(1)+val.intValue());}return state;"
24692525
expectedAvgState := FunctionMetadata{
24702526
Keyspace: "gocql_test",
24712527
Name: "avgstate",
24722528
ArgumentTypes: []TypeInfo{
24732529
TupleTypeInfo{
2474-
NativeType: NativeType{typ: TypeTuple},
2530+
NativeType: NativeType{proto: protoVer, typ: TypeTuple},
24752531

24762532
Elems: []TypeInfo{
2477-
NativeType{typ: TypeInt},
2478-
NativeType{typ: TypeBigInt},
2533+
NativeType{proto: protoVer, typ: TypeInt},
2534+
NativeType{proto: protoVer, typ: TypeBigInt},
24792535
},
24802536
},
2481-
NativeType{typ: TypeInt},
2537+
NativeType{proto: protoVer, typ: TypeInt},
24822538
},
24832539
ArgumentNames: []string{"state", "val"},
24842540
ReturnType: TupleTypeInfo{
2485-
NativeType: NativeType{typ: TypeTuple},
2541+
NativeType: NativeType{proto: protoVer, typ: TypeTuple},
24862542

24872543
Elems: []TypeInfo{
2488-
NativeType{typ: TypeInt},
2489-
NativeType{typ: TypeBigInt},
2544+
NativeType{proto: protoVer, typ: TypeInt},
2545+
NativeType{proto: protoVer, typ: TypeBigInt},
24902546
},
24912547
},
24922548
CalledOnNullInput: true,
@@ -2503,22 +2559,22 @@ func TestFunctionMetadata(t *testing.T) {
25032559
Name: "avgfinal",
25042560
ArgumentTypes: []TypeInfo{
25052561
TupleTypeInfo{
2506-
NativeType: NativeType{typ: TypeTuple},
2562+
NativeType: NativeType{proto: protoVer, typ: TypeTuple},
25072563

25082564
Elems: []TypeInfo{
2509-
NativeType{typ: TypeInt},
2510-
NativeType{typ: TypeBigInt},
2565+
NativeType{proto: protoVer, typ: TypeInt},
2566+
NativeType{proto: protoVer, typ: TypeBigInt},
25112567
},
25122568
},
25132569
},
25142570
ArgumentNames: []string{"state"},
2515-
ReturnType: NativeType{typ: TypeDouble},
2571+
ReturnType: NativeType{proto: protoVer, typ: TypeDouble},
25162572
CalledOnNullInput: true,
25172573
Language: "java",
25182574
Body: finalStateBody,
25192575
}
25202576
if !reflect.DeepEqual(avgFinal, expectedAvgFinal) {
2521-
t.Fatalf("function is %+v, but expected %+v", avgFinal, expectedAvgFinal)
2577+
t.Fatalf("function is %#v, but expected %#v", avgFinal, expectedAvgFinal)
25222578
}
25232579
}
25242580

@@ -2616,19 +2672,20 @@ func TestKeyspaceMetadata(t *testing.T) {
26162672
if flagCassVersion.Before(3, 0, 0) {
26172673
textType = TypeVarchar
26182674
}
2675+
protoVer := byte(session.cfg.ProtoVersion)
26192676
expectedType := UserTypeMetadata{
26202677
Keyspace: "gocql_test",
26212678
Name: "basicview",
26222679
FieldNames: []string{"birthday", "nationality", "weight", "height"},
26232680
FieldTypes: []TypeInfo{
2624-
NativeType{typ: TypeTimestamp},
2625-
NativeType{typ: textType},
2626-
NativeType{typ: textType},
2627-
NativeType{typ: textType},
2681+
NativeType{proto: protoVer, typ: TypeTimestamp},
2682+
NativeType{proto: protoVer, typ: textType},
2683+
NativeType{proto: protoVer, typ: textType},
2684+
NativeType{proto: protoVer, typ: textType},
26282685
},
26292686
}
26302687
if !reflect.DeepEqual(*keyspaceMetadata.UserTypes["basicview"], expectedType) {
2631-
t.Fatalf("type is %+v, but expected %+v", keyspaceMetadata.UserTypes["basicview"], expectedType)
2688+
t.Fatalf("type is %#v, but expected %#v", keyspaceMetadata.UserTypes["basicview"], expectedType)
26322689
}
26332690
if flagCassVersion.Major >= 3 {
26342691
materializedView, found := keyspaceMetadata.MaterializedViews["view_view"]

common_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ func createFunctions(t *testing.T, session *Session) {
236236
CALLED ON NULL INPUT
237237
RETURNS double
238238
LANGUAGE java AS
239-
$$double r = 0; if (state.getInt(0) == 0) return null; r = state.getLong(1); r/= state.getInt(0); return Double.valueOf(r);$$
239+
$$double r = 0; if (state.getInt(0) == 0) return null; r = state.getLong(1); r/= state.getInt(0); return Double.valueOf(r);$$
240240
`).Exec(); err != nil {
241241
t.Fatalf("failed to create function with err: %v", err)
242242
}
@@ -280,20 +280,20 @@ func assertTrue(t *testing.T, description string, value bool) {
280280
func assertEqual(t *testing.T, description string, expected, actual interface{}) {
281281
t.Helper()
282282
if expected != actual {
283-
t.Fatalf("expected %s to be (%+v) but was (%+v) instead", description, expected, actual)
283+
t.Fatalf("expected %s to be (%#v) but was (%#v) instead", description, expected, actual)
284284
}
285285
}
286286

287287
func assertDeepEqual(t *testing.T, description string, expected, actual interface{}) {
288288
t.Helper()
289289
if !reflect.DeepEqual(expected, actual) {
290-
t.Fatalf("expected %s to be (%+v) but was (%+v) instead", description, expected, actual)
290+
t.Fatalf("expected %s to be (%#v) but was (%#v) instead", description, expected, actual)
291291
}
292292
}
293293

294294
func assertNil(t *testing.T, description string, actual interface{}) {
295295
t.Helper()
296296
if actual != nil {
297-
t.Fatalf("expected %s to be (nil) but was (%+v) instead", description, actual)
297+
t.Fatalf("expected %s to be (nil) but was (%#v) instead", description, actual)
298298
}
299299
}

0 commit comments

Comments
 (0)