Skip to content

Commit c171403

Browse files
Add support for cassandra 4.0 table options
In the PR implemented backward compatibility with previous versions, and added new types support. To make metadata table support easier for future Cassandra versions, hardcode scan from Cassandra were replaced with new "parseSystemSchemaViews" method which is much easier to expand, even if some fields were added in the middle of the table it wouldn`t be an issue anymore. patch by Mykyta Oleksiienko; reviewed by Joao Reis CASSGO-13
1 parent 37030fb commit c171403

File tree

3 files changed

+208
-57
lines changed

3 files changed

+208
-57
lines changed

CHANGELOG.md

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

1919
- Change Batch API to be consistent with Query() (CASSGO-7)
2020

21+
- Added Cassandra 4.0 table options support(CASSGO-13)
22+
2123
### Fixed
2224

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

cassandra_test.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2314,7 +2314,7 @@ func TestViewMetadata(t *testing.T) {
23142314

23152315
func TestMaterializedViewMetadata(t *testing.T) {
23162316
if flagCassVersion.Before(3, 0, 0) {
2317-
return
2317+
t.Skip("The Cassandra version is too old")
23182318
}
23192319
session := createSession(t)
23202320
defer session.Close()
@@ -2333,14 +2333,19 @@ func TestMaterializedViewMetadata(t *testing.T) {
23332333
expectedChunkLengthInKB := "16"
23342334
expectedDCLocalReadRepairChance := float64(0)
23352335
expectedSpeculativeRetry := "99p"
2336+
expectedAdditionalWritePolicy := "99p"
2337+
expectedReadRepair := "BLOCKING"
23362338
if flagCassVersion.Before(4, 0, 0) {
23372339
expectedChunkLengthInKB = "64"
23382340
expectedDCLocalReadRepairChance = 0.1
23392341
expectedSpeculativeRetry = "99PERCENTILE"
2342+
expectedReadRepair = ""
2343+
expectedAdditionalWritePolicy = ""
23402344
}
23412345
expectedView1 := MaterializedViewMetadata{
23422346
Keyspace: "gocql_test",
23432347
Name: "view_view",
2348+
AdditionalWritePolicy: expectedAdditionalWritePolicy,
23442349
baseTableName: "view_table",
23452350
BloomFilterFpChance: 0.01,
23462351
Caching: map[string]string{"keys": "ALL", "rows_per_partition": "NONE"},
@@ -2352,12 +2357,17 @@ func TestMaterializedViewMetadata(t *testing.T) {
23522357
DefaultTimeToLive: 0,
23532358
Extensions: map[string]string{},
23542359
GcGraceSeconds: 864000,
2355-
IncludeAllColumns: false, MaxIndexInterval: 2048, MemtableFlushPeriodInMs: 0, MinIndexInterval: 128, ReadRepairChance: 0,
2356-
SpeculativeRetry: expectedSpeculativeRetry,
2360+
IncludeAllColumns: false, MaxIndexInterval: 2048,
2361+
MemtableFlushPeriodInMs: 0,
2362+
MinIndexInterval: 128,
2363+
ReadRepair: expectedReadRepair,
2364+
ReadRepairChance: 0,
2365+
SpeculativeRetry: expectedSpeculativeRetry,
23572366
}
23582367
expectedView2 := MaterializedViewMetadata{
23592368
Keyspace: "gocql_test",
23602369
Name: "view_view2",
2370+
AdditionalWritePolicy: expectedAdditionalWritePolicy,
23612371
baseTableName: "view_table2",
23622372
BloomFilterFpChance: 0.01,
23632373
Caching: map[string]string{"keys": "ALL", "rows_per_partition": "NONE"},
@@ -2369,8 +2379,13 @@ func TestMaterializedViewMetadata(t *testing.T) {
23692379
DefaultTimeToLive: 0,
23702380
Extensions: map[string]string{},
23712381
GcGraceSeconds: 864000,
2372-
IncludeAllColumns: false, MaxIndexInterval: 2048, MemtableFlushPeriodInMs: 0, MinIndexInterval: 128, ReadRepairChance: 0,
2373-
SpeculativeRetry: expectedSpeculativeRetry,
2382+
IncludeAllColumns: false,
2383+
MaxIndexInterval: 2048,
2384+
MemtableFlushPeriodInMs: 0,
2385+
MinIndexInterval: 128,
2386+
ReadRepair: expectedReadRepair,
2387+
ReadRepairChance: 0,
2388+
SpeculativeRetry: expectedSpeculativeRetry,
23742389
}
23752390

23762391
expectedView1.BaseTableId = materializedViews[0].BaseTableId

metadata.go

Lines changed: 186 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ type ViewMetadata struct {
122122
type MaterializedViewMetadata struct {
123123
Keyspace string
124124
Name string
125+
AdditionalWritePolicy string
125126
BaseTableId UUID
126127
BaseTable *TableMetadata
127128
BloomFilterFpChance float64
@@ -139,7 +140,8 @@ type MaterializedViewMetadata struct {
139140
MaxIndexInterval int
140141
MemtableFlushPeriodInMs int
141142
MinIndexInterval int
142-
ReadRepairChance float64
143+
ReadRepair string // Only present in Cassandra 4.0+
144+
ReadRepairChance float64 // Note: Cassandra 4.0 removed ReadRepairChance and added ReadRepair instead
143145
SpeculativeRetry string
144146

145147
baseTableName string
@@ -999,69 +1001,201 @@ func getViewsMetadata(session *Session, keyspaceName string) ([]ViewMetadata, er
9991001
return views, nil
10001002
}
10011003

1004+
func bytesMapToStringsMap(byteData map[string][]byte) map[string]string {
1005+
extensions := make(map[string]string, len(byteData))
1006+
for key, rowByte := range byteData {
1007+
extensions[key] = string(rowByte)
1008+
}
1009+
1010+
return extensions
1011+
}
1012+
1013+
func materializedViewMetadataFromMap(currentObject map[string]interface{}, materializedView *MaterializedViewMetadata) error {
1014+
const errorMessage = "gocql.materializedViewMetadataFromMap failed to read column %s"
1015+
var ok bool
1016+
for key, value := range currentObject {
1017+
switch key {
1018+
case "keyspace_name":
1019+
materializedView.Keyspace, ok = value.(string)
1020+
if !ok {
1021+
return fmt.Errorf(errorMessage, key)
1022+
}
1023+
1024+
case "view_name":
1025+
materializedView.Name, ok = value.(string)
1026+
if !ok {
1027+
return fmt.Errorf(errorMessage, key)
1028+
}
1029+
1030+
case "additional_write_policy":
1031+
materializedView.AdditionalWritePolicy, ok = value.(string)
1032+
if !ok {
1033+
return fmt.Errorf(errorMessage, key)
1034+
}
1035+
1036+
case "base_table_id":
1037+
materializedView.BaseTableId, ok = value.(UUID)
1038+
if !ok {
1039+
return fmt.Errorf(errorMessage, key)
1040+
}
1041+
1042+
case "base_table_name":
1043+
materializedView.baseTableName, ok = value.(string)
1044+
if !ok {
1045+
return fmt.Errorf(errorMessage, key)
1046+
}
1047+
1048+
case "bloom_filter_fp_chance":
1049+
materializedView.BloomFilterFpChance, ok = value.(float64)
1050+
if !ok {
1051+
return fmt.Errorf(errorMessage, key)
1052+
}
1053+
1054+
case "caching":
1055+
materializedView.Caching, ok = value.(map[string]string)
1056+
if !ok {
1057+
return fmt.Errorf(errorMessage, key)
1058+
}
1059+
1060+
case "comment":
1061+
materializedView.Comment, ok = value.(string)
1062+
if !ok {
1063+
return fmt.Errorf(errorMessage, key)
1064+
}
1065+
1066+
case "compaction":
1067+
materializedView.Compaction, ok = value.(map[string]string)
1068+
if !ok {
1069+
return fmt.Errorf(errorMessage, key)
1070+
}
1071+
1072+
case "compression":
1073+
materializedView.Compression, ok = value.(map[string]string)
1074+
if !ok {
1075+
return fmt.Errorf(errorMessage, key)
1076+
}
1077+
1078+
case "crc_check_chance":
1079+
materializedView.CrcCheckChance, ok = value.(float64)
1080+
if !ok {
1081+
return fmt.Errorf(errorMessage, key)
1082+
}
1083+
1084+
case "dclocal_read_repair_chance":
1085+
materializedView.DcLocalReadRepairChance, ok = value.(float64)
1086+
if !ok {
1087+
return fmt.Errorf(errorMessage, key)
1088+
}
1089+
1090+
case "default_time_to_live":
1091+
materializedView.DefaultTimeToLive, ok = value.(int)
1092+
if !ok {
1093+
return fmt.Errorf(errorMessage, key)
1094+
}
1095+
1096+
case "extensions":
1097+
byteData, ok := value.(map[string][]byte)
1098+
if !ok {
1099+
return fmt.Errorf(errorMessage, key)
1100+
}
1101+
1102+
materializedView.Extensions = bytesMapToStringsMap(byteData)
1103+
1104+
case "gc_grace_seconds":
1105+
materializedView.GcGraceSeconds, ok = value.(int)
1106+
if !ok {
1107+
return fmt.Errorf(errorMessage, key)
1108+
}
1109+
1110+
case "id":
1111+
materializedView.Id, ok = value.(UUID)
1112+
if !ok {
1113+
return fmt.Errorf(errorMessage, key)
1114+
}
1115+
1116+
case "include_all_columns":
1117+
materializedView.IncludeAllColumns, ok = value.(bool)
1118+
if !ok {
1119+
return fmt.Errorf(errorMessage, key)
1120+
}
1121+
1122+
case "max_index_interval":
1123+
materializedView.MaxIndexInterval, ok = value.(int)
1124+
if !ok {
1125+
return fmt.Errorf(errorMessage, key)
1126+
}
1127+
1128+
case "memtable_flush_period_in_ms":
1129+
materializedView.MemtableFlushPeriodInMs, ok = value.(int)
1130+
if !ok {
1131+
return fmt.Errorf(errorMessage, key)
1132+
}
1133+
1134+
case "min_index_interval":
1135+
materializedView.MinIndexInterval, ok = value.(int)
1136+
if !ok {
1137+
return fmt.Errorf(errorMessage, key)
1138+
}
1139+
1140+
case "read_repair":
1141+
materializedView.ReadRepair, ok = value.(string)
1142+
if !ok {
1143+
return fmt.Errorf(errorMessage, key)
1144+
}
1145+
1146+
case "read_repair_chance":
1147+
materializedView.ReadRepairChance, ok = value.(float64)
1148+
if !ok {
1149+
return fmt.Errorf(errorMessage, key)
1150+
}
1151+
1152+
case "speculative_retry":
1153+
materializedView.SpeculativeRetry, ok = value.(string)
1154+
if !ok {
1155+
return fmt.Errorf(errorMessage, key)
1156+
}
1157+
1158+
}
1159+
}
1160+
return nil
1161+
}
1162+
1163+
func parseSystemSchemaViews(iter *Iter) ([]MaterializedViewMetadata, error) {
1164+
var materializedViews []MaterializedViewMetadata
1165+
s, err := iter.SliceMap()
1166+
if err != nil {
1167+
return nil, err
1168+
}
1169+
1170+
for _, row := range s {
1171+
var materializedView MaterializedViewMetadata
1172+
err = materializedViewMetadataFromMap(row, &materializedView)
1173+
if err != nil {
1174+
return nil, err
1175+
}
1176+
1177+
materializedViews = append(materializedViews, materializedView)
1178+
}
1179+
1180+
return materializedViews, nil
1181+
}
1182+
10021183
func getMaterializedViewsMetadata(session *Session, keyspaceName string) ([]MaterializedViewMetadata, error) {
10031184
if !session.useSystemSchema {
10041185
return nil, nil
10051186
}
10061187
var tableName = "system_schema.views"
10071188
stmt := fmt.Sprintf(`
1008-
SELECT
1009-
view_name,
1010-
base_table_id,
1011-
base_table_name,
1012-
bloom_filter_fp_chance,
1013-
caching,
1014-
comment,
1015-
compaction,
1016-
compression,
1017-
crc_check_chance,
1018-
dclocal_read_repair_chance,
1019-
default_time_to_live,
1020-
extensions,
1021-
gc_grace_seconds,
1022-
id,
1023-
include_all_columns,
1024-
max_index_interval,
1025-
memtable_flush_period_in_ms,
1026-
min_index_interval,
1027-
read_repair_chance,
1028-
speculative_retry
1189+
SELECT *
10291190
FROM %s
10301191
WHERE keyspace_name = ?`, tableName)
10311192

10321193
var materializedViews []MaterializedViewMetadata
10331194

1034-
rows := session.control.query(stmt, keyspaceName).Scanner()
1035-
for rows.Next() {
1036-
materializedView := MaterializedViewMetadata{Keyspace: keyspaceName}
1037-
err := rows.Scan(&materializedView.Name,
1038-
&materializedView.BaseTableId,
1039-
&materializedView.baseTableName,
1040-
&materializedView.BloomFilterFpChance,
1041-
&materializedView.Caching,
1042-
&materializedView.Comment,
1043-
&materializedView.Compaction,
1044-
&materializedView.Compression,
1045-
&materializedView.CrcCheckChance,
1046-
&materializedView.DcLocalReadRepairChance,
1047-
&materializedView.DefaultTimeToLive,
1048-
&materializedView.Extensions,
1049-
&materializedView.GcGraceSeconds,
1050-
&materializedView.Id,
1051-
&materializedView.IncludeAllColumns,
1052-
&materializedView.MaxIndexInterval,
1053-
&materializedView.MemtableFlushPeriodInMs,
1054-
&materializedView.MinIndexInterval,
1055-
&materializedView.ReadRepairChance,
1056-
&materializedView.SpeculativeRetry,
1057-
)
1058-
if err != nil {
1059-
return nil, err
1060-
}
1061-
materializedViews = append(materializedViews, materializedView)
1062-
}
1195+
iter := session.control.query(stmt, keyspaceName)
10631196

1064-
if err := rows.Err(); err != nil {
1197+
materializedViews, err := parseSystemSchemaViews(iter)
1198+
if err != nil {
10651199
return nil, err
10661200
}
10671201

0 commit comments

Comments
 (0)