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,71 @@ 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 != null
189+ && preparedId .resultSetMetadata .id .bytes .length > 0 ) {
190+ // It is CQL 5 or higher.
191+ // Prepared statement invalidation works perfectly no need to disable skip metadata
192+ return true ;
193+ }
194+
195+ switch (cluster .getConfiguration ().getQueryOptions ().getSkipCQL4MetadataResolveMethod ()) {
196+ case ALWAYS_ON :
197+ return true ;
198+ case ALWAYS_OFF :
199+ return false ;
200+ }
201+
202+ List <String > chunks = Splitter .onPattern ("\\ s+" ).splitToList (query );
203+ if (chunks .size () < 2 ) {
204+ // Weird query, assuming no result expected
205+ return false ;
206+ }
207+ if (!chunks .get (0 ).toLowerCase ().startsWith ("select" )) {
208+ // In case if non-select sneaks in, disable skip metadata for it no result expected.
209+ return false ;
210+ }
211+ if (chunks .get (1 ).equals ("*" )) {
212+ LOGGER .warn (
213+ "Prepared statement {} is a wildcard select, which can cause prepared statement invalidation issues when executed on CQL4. "
214+ + "These issues may lead to broken deserialization or data corruption. "
215+ + "To mitigate this, the driver ensures that the server returns metadata with each query for such statements, "
216+ + "though this negatively impacts performance. "
217+ + "To avoid this, consider using a targeted select instead. "
218+ + "Alternatively, you can enable the skip-cql4-metadata-resolve-method option in the execution profile by setting it to `always-on`, "
219+ + "allowing the driver to ignore this issue and proceed regardless, risking broken deserialization or data corruption." ,
220+ query );
221+ return false ;
222+ }
223+ // Disable skipping metadata if results contains udt and
224+ for (ColumnDefinitions .Definition columnDefinition : preparedId .resultSetMetadata .variables ) {
225+ if (containsUDT (columnDefinition .getType ())) {
226+ LOGGER .warn (
227+ "Prepared statement {} contains UDT in result, which can cause prepared statement invalidation issues when executed on CQL4. "
228+ + "These issues may lead to broken deserialization or data corruption. "
229+ + "To mitigate this, the driver ensures that the server returns metadata with each query for such statements, "
230+ + "though this negatively impacts performance. "
231+ + "To avoid this, consider using a targeted select instead. "
232+ + "Alternatively, you can enable the skip-cql4-metadata-resolve-method option in the execution profile by setting it to `always-on`, "
233+ + "allowing the driver to ignore this issue and proceed regardless, risking broken deserialization or data corruption." ,
234+ query );
235+ return false ;
236+ }
237+ }
238+ return true ;
239+ }
240+
241+ public boolean isSkipMetadata () {
242+ return skipMetadata ;
243+ }
244+
175245 @ Override
176246 public ColumnDefinitions getVariables () {
177247 return preparedId .boundValuesMetadata .variables ;
@@ -315,4 +385,16 @@ public Boolean isIdempotent() {
315385 public boolean isLWT () {
316386 return isLWT ;
317387 }
388+
389+ private static boolean containsUDT (DataType dataType ) {
390+ if (dataType .isCollection ()) {
391+ for (DataType elementType : dataType .getTypeArguments ()) {
392+ if (containsUDT (elementType )) {
393+ return true ;
394+ }
395+ }
396+ return false ;
397+ }
398+ return dataType instanceof UserType ;
399+ }
318400}
0 commit comments