2424import static com .datastax .driver .core .ProtocolVersion .V4 ;
2525
2626import com .datastax .driver .core .policies .RetryPolicy ;
27+ import com .google .common .base .Splitter ;
2728import com .google .common .collect .ImmutableMap ;
2829import java .nio .ByteBuffer ;
2930import java .util .List ;
3031import java .util .Map ;
32+ import org .slf4j .Logger ;
33+ import org .slf4j .LoggerFactory ;
3134
3235public class DefaultPreparedStatement implements PreparedStatement {
33-
36+ private static final Logger LOGGER = LoggerFactory . getLogger ( DefaultPreparedStatement . class );
3437 private static final String SCYLLA_CDC_LOG_SUFFIX = "_scylla_cdc_log" ;
3538
3639 final PreparedId preparedId ;
@@ -50,6 +53,7 @@ public class DefaultPreparedStatement implements PreparedStatement {
5053 volatile RetryPolicy retryPolicy ;
5154 volatile ImmutableMap <String , ByteBuffer > outgoingPayload ;
5255 volatile Boolean idempotent ;
56+ volatile boolean skipMetadata ;
5357
5458 private DefaultPreparedStatement (
5559 PreparedId id ,
@@ -66,6 +70,7 @@ private DefaultPreparedStatement(
6670 this .cluster = cluster ;
6771 this .isLWT = isLWT ;
6872 this .partitioner = partitioner ;
73+ this .skipMetadata = this .calculateSkipMetadata ();
6974 }
7075
7176 static DefaultPreparedStatement fromMessage (
@@ -172,6 +177,70 @@ private static Token.Factory partitioner(ColumnDefinitions defs, Cluster cluster
172177 return null ;
173178 }
174179
180+ private boolean calculateSkipMetadata () {
181+ if (cluster .manager .protocolVersion () != ProtocolVersion .V1
182+ || preparedId .resultSetMetadata .variables == null ) {
183+ // CQL1 does not support it.
184+ // If no rows returned there is no reason to send this flag, consequently, no metadata.
185+ return false ;
186+ }
187+
188+ if (preparedId .resultSetMetadata .id .bytes .length > 0 ) {
189+ // It is CQL 5 or higher.
190+ // Prepared statement invalidation works perfectly no need to disable skip metadata
191+ return true ;
192+ }
193+
194+ switch (cluster .getConfiguration ().getQueryOptions ().getSkipCQL4MetadataResolveMethod ()) {
195+ case ALWAYS_ON :
196+ return true ;
197+ case ALWAYS_OFF :
198+ return false ;
199+ }
200+
201+ List <String > chunks = Splitter .onPattern ("\\ s+" ).splitToList (query );
202+ if (chunks .size () < 2 ) {
203+ // Weird query, assuming no result expected
204+ return false ;
205+ }
206+ if (!chunks .get (0 ).toLowerCase ().startsWith ("select" )) {
207+ // In case if non-select sneaks in, disable skip metadata for it no result expected.
208+ return false ;
209+ }
210+ if (chunks .get (1 ).equals ("*" )) {
211+ LOGGER .warn (
212+ "Prepared statement {} is a wildcard select, which can cause prepared statement invalidation issues when executed on CQL4. "
213+ + "These issues may lead to broken deserialization or data corruption. "
214+ + "To mitigate this, the driver ensures that the server returns metadata with each query for such statements, "
215+ + "though this negatively impacts performance. "
216+ + "To avoid this, consider using a targeted select instead. "
217+ + "Alternatively, you can enable the skip-cql4-metadata-resolve-method option in the execution profile by setting it to `always-on`, "
218+ + "allowing the driver to ignore this issue and proceed regardless, risking broken deserialization or data corruption." ,
219+ query );
220+ return false ;
221+ }
222+ // Disable skipping metadata if results contains udt and
223+ for (ColumnDefinitions .Definition columnDefinition : preparedId .resultSetMetadata .variables ) {
224+ if (containsUDT (columnDefinition .getType ())) {
225+ LOGGER .warn (
226+ "Prepared statement {} contains UDT in result, which can cause prepared statement invalidation issues when executed on CQL4. "
227+ + "These issues may lead to broken deserialization or data corruption. "
228+ + "To mitigate this, the driver ensures that the server returns metadata with each query for such statements, "
229+ + "though this negatively impacts performance. "
230+ + "To avoid this, consider using a targeted select instead. "
231+ + "Alternatively, you can enable the skip-cql4-metadata-resolve-method option in the execution profile by setting it to `always-on`, "
232+ + "allowing the driver to ignore this issue and proceed regardless, risking broken deserialization or data corruption." ,
233+ query );
234+ return false ;
235+ }
236+ }
237+ return true ;
238+ }
239+
240+ public boolean getSkipMetadata () {
241+ return skipMetadata ;
242+ }
243+
175244 @ Override
176245 public ColumnDefinitions getVariables () {
177246 return preparedId .boundValuesMetadata .variables ;
@@ -315,4 +384,16 @@ public Boolean isIdempotent() {
315384 public boolean isLWT () {
316385 return isLWT ;
317386 }
387+
388+ private static boolean containsUDT (DataType dataType ) {
389+ if (dataType .isCollection ()) {
390+ for (DataType elementType : dataType .getTypeArguments ()) {
391+ if (containsUDT (elementType )) {
392+ return true ;
393+ }
394+ }
395+ return false ;
396+ }
397+ return dataType instanceof UserType ;
398+ }
318399}
0 commit comments