2626import com .datastax .oss .driver .api .core .ConsistencyLevel ;
2727import com .datastax .oss .driver .api .core .CqlIdentifier ;
2828import com .datastax .oss .driver .api .core .ProtocolVersion ;
29+ import com .datastax .oss .driver .api .core .config .DefaultDriverOption ;
2930import com .datastax .oss .driver .api .core .config .DriverExecutionProfile ;
3031import com .datastax .oss .driver .api .core .cql .BoundStatement ;
3132import com .datastax .oss .driver .api .core .cql .BoundStatementBuilder ;
33+ import com .datastax .oss .driver .api .core .cql .ColumnDefinition ;
3234import com .datastax .oss .driver .api .core .cql .ColumnDefinitions ;
3335import com .datastax .oss .driver .api .core .cql .PreparedStatement ;
3436import com .datastax .oss .driver .api .core .cql .Statement ;
3537import com .datastax .oss .driver .api .core .metadata .token .Partitioner ;
3638import com .datastax .oss .driver .api .core .metadata .token .Token ;
39+ import com .datastax .oss .driver .api .core .type .ContainerType ;
40+ import com .datastax .oss .driver .api .core .type .DataType ;
41+ import com .datastax .oss .driver .api .core .type .MapType ;
42+ import com .datastax .oss .driver .api .core .type .TupleType ;
43+ import com .datastax .oss .driver .api .core .type .UserDefinedType ;
3744import com .datastax .oss .driver .api .core .type .codec .registry .CodecRegistry ;
3845import com .datastax .oss .driver .internal .core .data .ValuesHelper ;
3946import com .datastax .oss .driver .internal .core .session .RepreparePayload ;
47+ import com .datastax .oss .driver .shaded .guava .common .base .Splitter ;
4048import edu .umd .cs .findbugs .annotations .NonNull ;
4149import java .nio .ByteBuffer ;
4250import java .time .Duration ;
4351import java .util .List ;
4452import java .util .Map ;
4553import net .jcip .annotations .ThreadSafe ;
54+ import org .slf4j .Logger ;
55+ import org .slf4j .LoggerFactory ;
4656
4757@ ThreadSafe
4858public class DefaultPreparedStatement implements PreparedStatement {
59+ private static final Logger LOGGER = LoggerFactory .getLogger (DefaultPreparedStatement .class );
4960
5061 private final ByteBuffer id ;
5162 private final RepreparePayload repreparePayload ;
@@ -69,6 +80,8 @@ public class DefaultPreparedStatement implements PreparedStatement {
6980 private final Duration timeoutForBoundStatements ;
7081 private final Partitioner partitioner ;
7182 private final boolean isLWT ;
83+ private volatile boolean skipMetadata ;
84+ private final DriverExecutionProfile defaultExecutionProfile ;
7285
7386 public DefaultPreparedStatement (
7487 ByteBuffer id ,
@@ -95,7 +108,8 @@ public DefaultPreparedStatement(
95108 boolean areBoundStatementsTracing ,
96109 CodecRegistry codecRegistry ,
97110 ProtocolVersion protocolVersion ,
98- boolean isLWT ) {
111+ boolean isLWT ,
112+ DriverExecutionProfile defaultExecutionProfile ) {
99113 this .id = id ;
100114 this .partitionKeyIndices = partitionKeyIndices ;
101115 // It's important that we keep a reference to this object, so that it only gets evicted from
@@ -122,6 +136,15 @@ public DefaultPreparedStatement(
122136 this .codecRegistry = codecRegistry ;
123137 this .protocolVersion = protocolVersion ;
124138 this .isLWT = isLWT ;
139+ this .defaultExecutionProfile = defaultExecutionProfile ;
140+ this .skipMetadata =
141+ resolveSkipMetadata (
142+ query ,
143+ resultMetadataId ,
144+ resultSetDefinitions ,
145+ executionProfileForBoundStatements != null
146+ ? executionProfileForBoundStatements
147+ : this .defaultExecutionProfile );
125148 }
126149
127150 @ NonNull
@@ -147,6 +170,10 @@ public Partitioner getPartitioner() {
147170 return partitioner ;
148171 }
149172
173+ public boolean isSkipMetadata () {
174+ return skipMetadata ;
175+ }
176+
150177 @ NonNull
151178 @ Override
152179 public List <Integer > getPartitionKeyIndices () {
@@ -172,6 +199,15 @@ public boolean isLWT() {
172199 @ Override
173200 public void setResultMetadata (
174201 @ NonNull ByteBuffer newResultMetadataId , @ NonNull ColumnDefinitions newResultSetDefinitions ) {
202+ this .skipMetadata =
203+ resolveSkipMetadata (
204+ this .getQuery (),
205+ newResultMetadataId ,
206+ newResultSetDefinitions ,
207+ executionProfileForBoundStatements != null
208+ ? executionProfileForBoundStatements
209+ : this .defaultExecutionProfile );
210+
175211 this .resultMetadata = new ResultMetadata (newResultMetadataId , newResultSetDefinitions );
176212 }
177213
@@ -242,4 +278,88 @@ private ResultMetadata(ByteBuffer resultMetadataId, ColumnDefinitions resultSetD
242278 this .resultSetDefinitions = resultSetDefinitions ;
243279 }
244280 }
281+
282+ private static boolean resolveSkipMetadata (
283+ String query ,
284+ ByteBuffer resultMetadataId ,
285+ ColumnDefinitions resultSet ,
286+ DriverExecutionProfile executionProfileForBoundStatements ) {
287+ String resolveMethod =
288+ (executionProfileForBoundStatements != null )
289+ ? executionProfileForBoundStatements .getString (
290+ DefaultDriverOption .PREPARE_SKIP_CQL4_METADATA_RESOLVE_METHOD )
291+ : "smart" ;
292+
293+ if (resultSet == null || resultSet .size () == 0 ) {
294+ // there is no reason to send this flag, there will be no rows in the response and,
295+ // consequently, no metadata.
296+ return false ;
297+ }
298+ if (resultMetadataId != null && resultMetadataId .capacity () > 0 ) {
299+ // It is CQL 5 or higher.
300+ // Prepared statement invalidation works perfectly no need to disable skip metadata
301+ return true ;
302+ }
303+ // It is CQL 4 or lower.
304+ switch (resolveMethod ) {
305+ case "always-on" :
306+ return true ;
307+ case "always-off" :
308+ return false ;
309+ }
310+
311+ if (!resolveMethod .equals ("smart" )) {
312+ LOGGER .warn (
313+ "Property advanced.prepared-statements.skip-cql4-metadata-resolve-method is incorrectly set to `{}`, available options: smart, always-on, always-off. Defaulting to `smart`" ,
314+ resolveMethod );
315+ }
316+ List <String > chunks = Splitter .onPattern ("\\ s+" ).splitToList (query );
317+ if (chunks .size () < 2 ) {
318+ // Weird query, assuming no result expected
319+ return false ;
320+ }
321+ if (!chunks .get (0 ).toLowerCase ().startsWith ("select" )) {
322+ // In case if non-select sneaks in, disable skip metadata for it no result expected.
323+ return false ;
324+ }
325+ if (chunks .get (1 ).equals ("*" )) {
326+ LOGGER .warn (
327+ "Prepared statement `{}` is a wildcard select. Such statements have prepared statement "
328+ + "invalidation problem when run on CQL4, this problems can lead to broken deserialization or data corruption. "
329+ + "Consider using targeted select instead, or alternatively, you can set `skip-cql4-metadata-resolve-method` "
330+ + "to `always-on` in execution profile to make driver ignore this problem and proceed anyway." ,
331+ query );
332+ return false ;
333+ }
334+ // Disable skipping metadata if results contains udt and
335+ for (ColumnDefinition columnDefinition : resultSet ) {
336+ if (contains_udt (columnDefinition .getType ())) {
337+ LOGGER .warn (
338+ "Prepared statement `{}` contains UDTs in result set. Such statements have prepared statement "
339+ + "invalidation problem when run on CQL4, this problems can lead to broken deserialization or data corruption. "
340+ + "Consider using targeted select instead, or alternatively, you can set `skip-cql4-metadata-resolve-method` "
341+ + "to `always-on` in execution profile to make driver ignore this problem and proceed anyway." ,
342+ query );
343+ return false ;
344+ }
345+ }
346+ return true ;
347+ }
348+
349+ private static boolean contains_udt (DataType dataType ) {
350+ if (dataType instanceof ContainerType ) {
351+ return contains_udt (((ContainerType ) dataType ).getElementType ());
352+ } else if (dataType instanceof TupleType ) {
353+ for (DataType elementType : ((TupleType ) dataType ).getComponentTypes ()) {
354+ if (contains_udt (elementType )) {
355+ return true ;
356+ }
357+ }
358+ return false ;
359+ } else if (dataType instanceof MapType ) {
360+ return contains_udt (((MapType ) dataType ).getKeyType ())
361+ || contains_udt (((MapType ) dataType ).getValueType ());
362+ }
363+ return dataType instanceof UserDefinedType ;
364+ }
245365}
0 commit comments