@@ -3397,8 +3397,6 @@ func TestLargeSizeQuery(t *testing.T) {
33973397 t .Fatal (err )
33983398 }
33993399
3400- defer session .Close ()
3401-
34023400 longString := strings .Repeat ("a" , 500_000 )
34033401
34043402 err := session .Query ("INSERT INTO gocql_test.large_size_query (id, text_col) VALUES (?, ?)" , "1" , longString ).Exec ()
@@ -3430,8 +3428,6 @@ func TestQueryCompressionNotWorthIt(t *testing.T) {
34303428 t .Fatal (err )
34313429 }
34323430
3433- defer session .Close ()
3434-
34353431 str := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+"
34363432 err := session .Query ("INSERT INTO gocql_test.large_size_query (id, text_col) VALUES (?, ?)" , "1" , str ).Exec ()
34373433 if err != nil {
@@ -3446,3 +3442,101 @@ func TestQueryCompressionNotWorthIt(t *testing.T) {
34463442
34473443 require .Equal (t , str , result )
34483444}
3445+
3446+ func TestPrepareExecuteMetadataChangedFlag (t * testing.T ) {
3447+ // This test ensures that the whole Metadata_changed flow
3448+ // is handled properly.
3449+ //
3450+ // To trigger C* to return Metadata_changed we should do:
3451+ // 1. Create a table
3452+ // 2. Prepare stmt which uses the created table
3453+ // 3. Change the table schema in order to affect prepared stmt (e.g. add a column)
3454+ // 4. Execute prepared stmt. As a result C* should return RESULT/ROWS response with
3455+ // Metadata_changed flag, new metadata id and updated metadata resultset.
3456+ //
3457+ // The driver should handle this by updating its prepared statement inside the cache
3458+ // when it receives RESULT/ROWS with Metadata_changed flag
3459+ session := createSession (t )
3460+ defer session .Close ()
3461+
3462+ if session .cfg .ProtoVersion < protoVersion5 {
3463+ t .Skip ("Metadata_changed mechanism is only available in proto > 4" )
3464+ }
3465+
3466+ if err := createTable (session , "CREATE TABLE IF NOT EXISTS gocql_test.metadata_changed(id int, PRIMARY KEY (id))" ); err != nil {
3467+ t .Fatal (err )
3468+ }
3469+
3470+ err := session .Query ("INSERT INTO gocql_test.metadata_changed (id) VALUES (?)" , 1 ).Exec ()
3471+ if err != nil {
3472+ t .Fatal (err )
3473+ }
3474+
3475+ // We have to specify conn for all queries to ensure that
3476+ // all queries are running on the same node
3477+ conn := session .getConn ()
3478+
3479+ const selectStmt = "SELECT * FROM gocql_test.metadata_changed"
3480+ queryBeforeTableAltering := session .Query (selectStmt )
3481+ queryBeforeTableAltering .conn = conn
3482+ row := make (map [string ]interface {})
3483+ err = queryBeforeTableAltering .MapScan (row )
3484+ if err != nil {
3485+ t .Fatal (err )
3486+ }
3487+
3488+ require .Len (t , row , 1 , "Expected to retrieve a single column" )
3489+ stmtCacheKey := session .stmtsLRU .keyFor (conn .host .HostID (), conn .currentKeyspace , queryBeforeTableAltering .stmt )
3490+ inflight , _ := session .stmtsLRU .get (stmtCacheKey )
3491+ preparedStatementBeforeTableAltering := inflight .preparedStatment
3492+
3493+ // Changing table schema in order to cause C* to return RESULT/ROWS Metadata_changed
3494+ alteringTableQuery := session .Query ("ALTER TABLE gocql_test.metadata_changed ADD new_col int" )
3495+ alteringTableQuery .conn = conn
3496+ err = alteringTableQuery .Exec ()
3497+ if err != nil {
3498+ t .Fatal (err )
3499+ }
3500+
3501+ // Expecting C* will return RESULT/ROWS Metadata_changed
3502+ // and it will be properly handled
3503+ queryAfterTableAltering := session .Query (selectStmt )
3504+ queryAfterTableAltering .conn = conn
3505+ row = make (map [string ]interface {})
3506+ err = queryAfterTableAltering .MapScan (row )
3507+ if err != nil {
3508+ t .Fatal (err )
3509+ }
3510+
3511+ // Ensuring if cache contains updated prepared statement
3512+ require .Len (t , row , 2 , "Expected to retrieve both columns" )
3513+ inflight , _ = session .stmtsLRU .get (stmtCacheKey )
3514+ preparedStatementAfterTableAltering := inflight .preparedStatment
3515+ require .NotEqual (t , preparedStatementBeforeTableAltering .resultMetadataID , preparedStatementAfterTableAltering .resultMetadataID )
3516+ require .NotEqual (t , preparedStatementBeforeTableAltering .response , preparedStatementAfterTableAltering .response )
3517+
3518+ // Executing prepared stmt and expecting that C* won't return
3519+ // Metadata_changed because the table is not being changed.
3520+ // Running query with timeout to ensure there is no deadlocks.
3521+ // However, it doesn't 100% proves that there is a deadlock...
3522+ ctx , cancel := context .WithTimeout (context .Background (), time .Second * 30000 )
3523+ defer cancel ()
3524+
3525+ queryAfterTableAltering2 := session .Query (selectStmt ).WithContext (ctx )
3526+ queryAfterTableAltering2 .conn = conn
3527+ row = make (map [string ]interface {})
3528+ err = queryAfterTableAltering2 .MapScan (row )
3529+ if err != nil {
3530+ if errors .Is (err , context .DeadlineExceeded ) {
3531+ t .Fatal ("It is likely failed due deadlock" )
3532+ }
3533+ t .Fatal (err )
3534+ }
3535+
3536+ // Ensuring metadata of prepared stmt is not changed
3537+ require .Len (t , row , 2 , "Expected to retrieve both columns" )
3538+ inflight , _ = session .stmtsLRU .get (stmtCacheKey )
3539+ preparedStatementAfterTableAltering2 := inflight .preparedStatment
3540+ require .Equal (t , preparedStatementAfterTableAltering .resultMetadataID , preparedStatementAfterTableAltering2 .resultMetadataID )
3541+ require .Equal (t , preparedStatementAfterTableAltering .response , preparedStatementAfterTableAltering2 .response )
3542+ }
0 commit comments