Skip to content

Investigate whether PREPARE can return a different statement ID for the same query and keyspace #1148

Open

Description

@dkropachev

Investigation

Determine whether a server can return a different prepared-statement query_id for exactly the same query bytes and preparation keyspace, and what the Python driver should do if this is observed. This is an investigation request, not a confirmed server defect or a claim that such ID changes are valid.

Related: PR #725 and the discussion questioning how IDs could change:
#725 (comment)

PR #725's agreed ID acceptance boundary is recovery from an unexpected ID in an UNPREPARED error by re-PREPARING and retrying the original EXECUTE, with a stable identity in the subsequent PREPARED response. Supporting a changed ID returned by PREPARE is a separate question. This issue does not settle the pending scope of initial or recovery PREPARE errors in that PR.

Existing evidence

In the inspected ScyllaDB source, the prepared ID is computed from the query bytes and raw keyspace. Cache eviction, schema invalidation and restart state are not inputs to that computation. Thus those events alone do not establish that a different ID can be returned for unchanged inputs.

https://github.com/scylladb/scylladb/blob/bee5f63cb62a22930efb8f2520cd9e7d03559082/cql3/query_processor.cc#L716-L727

A changed keyspace, including a PREPARE racing with USE, is a different input and must be distinguished from a changed identity for the same query/keyspace. Changes to result_metadata_id must also be distinguished from changes to query_id.

The driver currently detects an ID mismatch after repreparation and sets a DriverException. It retains the original EXECUTE message's ID. Removing the check alone would not establish correct support for replacement identities.

def _execute_after_prepare(self, host, connection, pool, response):
"""
Handle the response to our attempt to prepare a statement.
If it succeeded, run the original query again against the same host.
"""
if pool:
pool.return_connection(connection)
if self._final_exception:
return
if isinstance(response, ResultMessage):
if response.kind == RESULT_KIND_PREPARED:
if self.prepared_statement:
if self.prepared_statement.query_id != response.query_id:
self._set_final_exception(DriverException(
"ID mismatch while trying to reprepare (expected {expected}, got {got}). "
"This prepared statement won't work anymore. "
"This usually happens when you run a 'USE...' "
"query after the statement was prepared.".format(
expected=hexlify(self.prepared_statement.query_id), got=hexlify(response.query_id)
)
))
self.prepared_statement.result_metadata = response.column_metadata
new_metadata_id = response.result_metadata_id
if new_metadata_id is not None:
self.prepared_statement.result_metadata_id = new_metadata_id
# use self._query to re-use the same host and
# at the same time properly borrow the connection
request_id = self._query(host)
if request_id is None:
# this host errored out, move on to the next

No live-server reproducer or matched wire capture establishing a changed query_id for identical query/keyspace was obtained during the PR investigation. Injecting a changed PREPARED response demonstrates the driver's reaction, not that a server naturally generates it.

Evidence requested

  • A minimal reproducer, exact server and driver versions, protocol version, and relevant configuration, including proxies or mixed-version nodes.
  • Exact query bytes, effective preparation keyspace, target host, and matched PREPARE/PREPARED frames before and after the change. Record stream IDs and concurrent USE, schema operations, restarts or evictions.
  • Confirmation that the changed value is query_id, not result_metadata_id, and that the query/keyspace inputs really are identical.
  • A server source path explaining the change, or evidence of a server/proxy defect. If no supported path exists, document that result rather than assuming identity replacement is valid.

Driver implications to investigate if reproduced

  • Whether adopting a replacement ID preserves the original statement's keyspace, bound values and result-decoding metadata.
  • How cluster prepared-statement caches should handle old and new IDs, and whether identities differ by coordinator or server version.
  • How existing bound statements and prepared batch members retain or update their IDs without executing a different statement.
  • Races involving shared PreparedStatement objects, in-flight requests, retries, speculative executions, concurrent PREPAREs and batch repreparation.
  • Compatibility and bounded failure behavior when an ID changes repeatedly or different coordinators disagree.

Choose any repair only after establishing the server behavior and required contract. Cover the supported outcome with focused regression tests; do not treat an injected response as proof of the server-side premise.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions