Skip to content

Commit f088353

Browse files
committed
[PYTHON-1124] Fail fast if prepared id mismatch. See CASSANDRA-15252
1 parent 0374390 commit f088353

File tree

4 files changed

+31
-1
lines changed

4 files changed

+31
-1
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Features
88
* ssl context and cloud support for Eventlet (PYTHON-1162)
99
* Cloud Twisted support (PYTHON-1163)
1010
* Add additional_write_policy and read_repair to system schema parsing (PYTHON-1048)
11+
* Handle prepared id mismatch when repreparing on the fly (PYTHON-1124)
1112
* Remove *read_repair_chance table options (PYTHON-1140)
1213
* Flexible version parsing (PYTHON-1174)
1314
* [GRAPH] Ability to execute Fluent Graph queries asynchronously (PYTHON-1129)

cassandra/cluster.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from __future__ import absolute_import
2020

2121
import atexit
22+
from binascii import hexlify
2223
from collections import defaultdict
2324
from concurrent.futures import ThreadPoolExecutor, FIRST_COMPLETED, wait as wait_futures
2425
from copy import copy
@@ -4559,6 +4560,15 @@ def _execute_after_prepare(self, host, connection, pool, response):
45594560
if isinstance(response, ResultMessage):
45604561
if response.kind == RESULT_KIND_PREPARED:
45614562
if self.prepared_statement:
4563+
if self.prepared_statement.query_id != response.query_id:
4564+
self._set_final_exception(DriverException(
4565+
"ID mismatch while trying to reprepare (expected {expected}, got {got}). "
4566+
"This prepared statement won't work anymore. "
4567+
"This usually happens when you run a 'USE...' "
4568+
"query after the statement was prepared.".format(
4569+
expected=hexlify(self.prepared_statement.query_id), got=hexlify(response.query_id)
4570+
)
4571+
))
45624572
self.prepared_statement.result_metadata = response.column_metadata
45634573
new_metadata_id = response.result_metadata_id
45644574
if new_metadata_id is not None:

tests/integration/standard/test_prepared_statements.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import unittest2 as unittest
2020
except ImportError:
2121
import unittest # noqa
22-
from cassandra import InvalidRequest
22+
from cassandra import InvalidRequest, DriverException
2323

2424
from cassandra import ConsistencyLevel, ProtocolVersion
2525
from cassandra.cluster import Cluster
@@ -397,6 +397,22 @@ def test_raise_error_on_prepared_statement_execution_dropped_table(self):
397397
with self.assertRaises(InvalidRequest):
398398
self.session.execute(prepared, [0])
399399

400+
def test_fail_if_different_query_id_on_reprepare(self):
401+
""" PYTHON-1124 and CASSANDRA-15252 """
402+
keyspace = "test_fail_if_different_query_id_on_reprepare"
403+
self.session.execute(
404+
"CREATE KEYSPACE IF NOT EXISTS {} WITH replication = "
405+
"{{'class': 'SimpleStrategy', 'replication_factor': 1}}".format(keyspace)
406+
)
407+
self.session.execute("CREATE TABLE IF NOT EXISTS {}.foo(k int PRIMARY KEY)".format(keyspace))
408+
prepared = self.session.prepare("SELECT * FROM {}.foo WHERE k=?".format(keyspace))
409+
self.session.execute("DROP TABLE {}.foo".format(keyspace))
410+
self.session.execute("CREATE TABLE {}.foo(k int PRIMARY KEY)".format(keyspace))
411+
self.session.execute("USE {}".format(keyspace))
412+
with self.assertRaises(DriverException) as e:
413+
self.session.execute(prepared, [0])
414+
self.assertIn("ID mismatch", str(e.exception))
415+
400416

401417
@greaterthanorequalcass40
402418
class PreparedStatementInvalidationTest(BasicSharedKeyspaceUnitTestCase):

tests/unit/test_response_future.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,19 +563,22 @@ def test_prepared_query_not_found_bad_keyspace(self):
563563
self.assertRaises(ValueError, rf.result)
564564

565565
def test_repeat_orig_query_after_succesful_reprepare(self):
566+
query_id = b'abc123' # Just a random binary string so we don't hit id mismatch exception
566567
session = self.make_session()
567568
rf = self.make_response_future(session)
568569

569570
response = Mock(spec=ResultMessage,
570571
kind=RESULT_KIND_PREPARED,
571572
result_metadata_id='foo')
572573
response.results = (None, None, None, None, None)
574+
response.query_id = query_id
573575

574576
rf._query = Mock(return_value=True)
575577
rf._execute_after_prepare('host', None, None, response)
576578
rf._query.assert_called_once_with('host')
577579

578580
rf.prepared_statement = Mock()
581+
rf.prepared_statement.query_id = query_id
579582
rf._query = Mock(return_value=True)
580583
rf._execute_after_prepare('host', None, None, response)
581584
rf._query.assert_called_once_with('host')

0 commit comments

Comments
 (0)