Skip to content

Commit 71ccb42

Browse files
committed
skip metadata only if prepared result included metadata
If the prepare result doesn't include metadata we shouldn't skip metadata when executing the query. Fixes CASSGO-40. Patch by James Hartig; reviewed by João Reis for CASSGO-40
1 parent c63468d commit 71ccb42

File tree

4 files changed

+143
-21
lines changed

4 files changed

+143
-21
lines changed

CHANGELOG.md

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

3333
- Don't return error to caller with RetryType Ignore (CASSGO-28)
3434

35+
- Skip metadata only if the prepared result includes metadata (CASSGO-40)
36+
3537
- Don't panic in MapExecuteBatchCAS if no `[applied]` column is returned (CASSGO-42)
3638

3739
## [1.7.0] - 2024-09-23

cassandra_test.go

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2415,18 +2415,19 @@ func TestAggregateMetadata(t *testing.T) {
24152415
t.Fatal("expected two aggregates")
24162416
}
24172417

2418+
protoVer := byte(session.cfg.ProtoVersion)
24182419
expectedAggregrate := AggregateMetadata{
24192420
Keyspace: "gocql_test",
24202421
Name: "average",
2421-
ArgumentTypes: []TypeInfo{NativeType{typ: TypeInt}},
2422+
ArgumentTypes: []TypeInfo{NativeType{proto: protoVer, typ: TypeInt}},
24222423
InitCond: "(0, 0)",
2423-
ReturnType: NativeType{typ: TypeDouble},
2424+
ReturnType: NativeType{proto: protoVer, typ: TypeDouble},
24242425
StateType: TupleTypeInfo{
2425-
NativeType: NativeType{typ: TypeTuple},
2426+
NativeType: NativeType{proto: protoVer, typ: TypeTuple},
24262427

24272428
Elems: []TypeInfo{
2428-
NativeType{typ: TypeInt},
2429-
NativeType{typ: TypeBigInt},
2429+
NativeType{proto: protoVer, typ: TypeInt},
2430+
NativeType{proto: protoVer, typ: TypeBigInt},
24302431
},
24312432
},
24322433
stateFunc: "avgstate",
@@ -2465,28 +2466,29 @@ func TestFunctionMetadata(t *testing.T) {
24652466
avgState := functions[1]
24662467
avgFinal := functions[0]
24672468

2469+
protoVer := byte(session.cfg.ProtoVersion)
24682470
avgStateBody := "if (val !=null) {state.setInt(0, state.getInt(0)+1); state.setLong(1, state.getLong(1)+val.intValue());}return state;"
24692471
expectedAvgState := FunctionMetadata{
24702472
Keyspace: "gocql_test",
24712473
Name: "avgstate",
24722474
ArgumentTypes: []TypeInfo{
24732475
TupleTypeInfo{
2474-
NativeType: NativeType{typ: TypeTuple},
2476+
NativeType: NativeType{proto: protoVer, typ: TypeTuple},
24752477

24762478
Elems: []TypeInfo{
2477-
NativeType{typ: TypeInt},
2478-
NativeType{typ: TypeBigInt},
2479+
NativeType{proto: protoVer, typ: TypeInt},
2480+
NativeType{proto: protoVer, typ: TypeBigInt},
24792481
},
24802482
},
2481-
NativeType{typ: TypeInt},
2483+
NativeType{proto: protoVer, typ: TypeInt},
24822484
},
24832485
ArgumentNames: []string{"state", "val"},
24842486
ReturnType: TupleTypeInfo{
2485-
NativeType: NativeType{typ: TypeTuple},
2487+
NativeType: NativeType{proto: protoVer, typ: TypeTuple},
24862488

24872489
Elems: []TypeInfo{
2488-
NativeType{typ: TypeInt},
2489-
NativeType{typ: TypeBigInt},
2490+
NativeType{proto: protoVer, typ: TypeInt},
2491+
NativeType{proto: protoVer, typ: TypeBigInt},
24902492
},
24912493
},
24922494
CalledOnNullInput: true,
@@ -2503,16 +2505,16 @@ func TestFunctionMetadata(t *testing.T) {
25032505
Name: "avgfinal",
25042506
ArgumentTypes: []TypeInfo{
25052507
TupleTypeInfo{
2506-
NativeType: NativeType{typ: TypeTuple},
2508+
NativeType: NativeType{proto: protoVer, typ: TypeTuple},
25072509

25082510
Elems: []TypeInfo{
2509-
NativeType{typ: TypeInt},
2510-
NativeType{typ: TypeBigInt},
2511+
NativeType{proto: protoVer, typ: TypeInt},
2512+
NativeType{proto: protoVer, typ: TypeBigInt},
25112513
},
25122514
},
25132515
},
25142516
ArgumentNames: []string{"state"},
2515-
ReturnType: NativeType{typ: TypeDouble},
2517+
ReturnType: NativeType{proto: protoVer, typ: TypeDouble},
25162518
CalledOnNullInput: true,
25172519
Language: "java",
25182520
Body: finalStateBody,
@@ -2616,15 +2618,16 @@ func TestKeyspaceMetadata(t *testing.T) {
26162618
if flagCassVersion.Before(3, 0, 0) {
26172619
textType = TypeVarchar
26182620
}
2621+
protoVer := byte(session.cfg.ProtoVersion)
26192622
expectedType := UserTypeMetadata{
26202623
Keyspace: "gocql_test",
26212624
Name: "basicview",
26222625
FieldNames: []string{"birthday", "nationality", "weight", "height"},
26232626
FieldTypes: []TypeInfo{
2624-
NativeType{typ: TypeTimestamp},
2625-
NativeType{typ: textType},
2626-
NativeType{typ: textType},
2627-
NativeType{typ: textType},
2627+
NativeType{proto: protoVer, typ: TypeTimestamp},
2628+
NativeType{proto: protoVer, typ: textType},
2629+
NativeType{proto: protoVer, typ: textType},
2630+
NativeType{proto: protoVer, typ: textType},
26282631
},
26292632
}
26302633
if !reflect.DeepEqual(*keyspaceMetadata.UserTypes["basicview"], expectedType) {

conn.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1375,7 +1375,8 @@ func (c *Conn) executeQuery(ctx context.Context, qry *Query) *Iter {
13751375
}
13761376
}
13771377

1378-
params.skipMeta = !(c.session.cfg.DisableSkipMetadata || qry.disableSkipMetadata)
1378+
// if the metadata was not present in the response then we should not skip it
1379+
params.skipMeta = !(c.session.cfg.DisableSkipMetadata || qry.disableSkipMetadata) && info != nil && info.response.flags&flagNoMetaData == 0
13791380

13801381
frame = &writeExecuteFrame{
13811382
preparedID: info.id,

conn_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"context"
3434
"crypto/tls"
3535
"crypto/x509"
36+
"encoding/binary"
3637
"errors"
3738
"fmt"
3839
"io"
@@ -952,6 +953,28 @@ func TestWriteCoalescing_WriteAfterClose(t *testing.T) {
952953
}
953954
}
954955

956+
func TestSkipMetadata(t *testing.T) {
957+
ctx, cancel := context.WithCancel(context.Background())
958+
defer cancel()
959+
960+
srv := NewTestServer(t, protoVersion4, ctx)
961+
defer srv.Stop()
962+
963+
db, err := newTestSession(protoVersion4, srv.Address)
964+
if err != nil {
965+
t.Fatalf("NewCluster: %v", err)
966+
}
967+
defer db.Close()
968+
969+
if err := db.Query("select nometadata").Exec(); err != nil {
970+
t.Fatalf("expected no error got: %v", err)
971+
}
972+
973+
if err := db.Query("select metadata").Exec(); err != nil {
974+
t.Fatalf("expected no error got: %v", err)
975+
}
976+
}
977+
955978
type recordingFrameHeaderObserver struct {
956979
t *testing.T
957980
mu sync.Mutex
@@ -1264,6 +1287,99 @@ func (srv *TestServer) process(conn net.Conn, reqFrame *framer) {
12641287
case opError:
12651288
respFrame.writeHeader(0, opError, head.stream)
12661289
respFrame.buf = append(respFrame.buf, reqFrame.buf...)
1290+
case opPrepare:
1291+
query := reqFrame.readLongString()
1292+
name := strings.TrimPrefix(query, "select ")
1293+
if n := strings.Index(name, " "); n > 0 {
1294+
name = name[:n]
1295+
}
1296+
switch strings.ToLower(name) {
1297+
case "nometadata":
1298+
respFrame.writeHeader(0, opResult, head.stream)
1299+
respFrame.writeInt(resultKindPrepared)
1300+
// <id>
1301+
respFrame.writeShortBytes(binary.BigEndian.AppendUint64(nil, 1))
1302+
// <metadata>
1303+
respFrame.writeInt(0) // <flags>
1304+
respFrame.writeInt(0) // <columns_count>
1305+
if srv.protocol >= protoVersion4 {
1306+
respFrame.writeInt(0) // <pk_count>
1307+
}
1308+
// <result_metadata>
1309+
respFrame.writeInt(int32(flagNoMetaData)) // <flags>
1310+
respFrame.writeInt(0)
1311+
case "metadata":
1312+
respFrame.writeHeader(0, opResult, head.stream)
1313+
respFrame.writeInt(resultKindPrepared)
1314+
// <id>
1315+
respFrame.writeShortBytes(binary.BigEndian.AppendUint64(nil, 2))
1316+
// <metadata>
1317+
respFrame.writeInt(0) // <flags>
1318+
respFrame.writeInt(0) // <columns_count>
1319+
if srv.protocol >= protoVersion4 {
1320+
respFrame.writeInt(0) // <pk_count>
1321+
}
1322+
// <result_metadata>
1323+
respFrame.writeInt(int32(flagGlobalTableSpec)) // <flags>
1324+
respFrame.writeInt(1) // <columns_count>
1325+
// <global_table_spec>
1326+
respFrame.writeString("keyspace")
1327+
respFrame.writeString("table")
1328+
// <col_spec_0>
1329+
respFrame.writeString("col0") // <name>
1330+
respFrame.writeShort(uint16(TypeBoolean)) // <type>
1331+
default:
1332+
respFrame.writeHeader(0, opError, head.stream)
1333+
respFrame.writeInt(0)
1334+
respFrame.writeString("unsupported query: " + name)
1335+
}
1336+
case opExecute:
1337+
b := reqFrame.readShortBytes()
1338+
id := binary.BigEndian.Uint64(b)
1339+
// <query_parameters>
1340+
reqFrame.readConsistency() // <consistency>
1341+
var flags byte
1342+
if srv.protocol > protoVersion4 {
1343+
ui := reqFrame.readInt()
1344+
flags = byte(ui)
1345+
} else {
1346+
flags = reqFrame.readByte()
1347+
}
1348+
switch id {
1349+
case 1:
1350+
if flags&flagSkipMetaData != 0 {
1351+
respFrame.writeHeader(0, opError, head.stream)
1352+
respFrame.writeInt(0)
1353+
respFrame.writeString("skip metadata unexpected")
1354+
} else {
1355+
respFrame.writeHeader(0, opResult, head.stream)
1356+
respFrame.writeInt(resultKindRows)
1357+
// <metadata>
1358+
respFrame.writeInt(0) // <flags>
1359+
respFrame.writeInt(0) // <columns_count>
1360+
// <rows_count>
1361+
respFrame.writeInt(0)
1362+
}
1363+
case 2:
1364+
if flags&flagSkipMetaData != 0 {
1365+
respFrame.writeHeader(0, opResult, head.stream)
1366+
respFrame.writeInt(resultKindRows)
1367+
// <metadata>
1368+
respFrame.writeInt(0) // <flags>
1369+
respFrame.writeInt(0) // <columns_count>
1370+
// <rows_count>
1371+
respFrame.writeInt(0)
1372+
} else {
1373+
respFrame.writeHeader(0, opError, head.stream)
1374+
respFrame.writeInt(0)
1375+
respFrame.writeString("skip metadata expected")
1376+
}
1377+
default:
1378+
respFrame.writeHeader(0, opError, head.stream)
1379+
respFrame.writeInt(ErrCodeUnprepared)
1380+
respFrame.writeString("unprepared")
1381+
respFrame.writeShortBytes(binary.BigEndian.AppendUint64(nil, id))
1382+
}
12671383
default:
12681384
respFrame.writeHeader(0, opError, head.stream)
12691385
respFrame.writeInt(0)

0 commit comments

Comments
 (0)