Skip to content

Commit eabd2a2

Browse files
AshenScribesmiklosovic
authored andcommitted
Add a guardrail for misprepared statements
patch by Rishabh Saraswat; reviewed by Stefan Miklosovic, Brad Schoening for CASSANDRA-21139
1 parent cd75b9c commit eabd2a2

18 files changed

Lines changed: 489 additions & 0 deletions

File tree

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
7.0
2+
* Add a guardrail for misprepared statements (CASSANDRA-21139)
23
Merged from 6.0:
34
* Ensure schema created before 2.1 without tableId in folder name can be loaded in SnapshotLoader (CASSANDRA-21246)
45
* Differentiate between legitimate cases where the first entry is the same as the last entry and empty bounds in SSTableCursorWriter#addIndexBlock() (CASSANDRA-21255)

conf/cassandra.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2565,6 +2565,13 @@ drop_compact_storage_enabled: false
25652565
# This would also apply to system keyspaces.
25662566
# maximum_replication_factor_warn_threshold: -1
25672567
# maximum_replication_factor_fail_threshold: -1
2568+
#
2569+
# Guardrail to warn or fail when a statement is prepared without bind markers (parameters).
2570+
# This prevents the Prepared Statement Cache from being flooded with unique entries caused
2571+
# by hardcoded literals. When warned is true, logs a warning on the usage of misprepared statements.
2572+
# When enabled is true, rejects misprepared statements with an error.
2573+
# prepared_statements_require_parameters_warned: true
2574+
# prepared_statements_require_parameters_enabled: false
25682575

25692576
#role_name_policy:
25702577
# # Implementation class of a validator. When not in form of FQCN, the

conf/cassandra_latest.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2343,6 +2343,13 @@ drop_compact_storage_enabled: false
23432343
# This would also apply to system keyspaces.
23442344
# maximum_replication_factor_warn_threshold: -1
23452345
# maximum_replication_factor_fail_threshold: -1
2346+
#
2347+
# Guardrail to warn or fail when a statement is prepared without bind markers (parameters).
2348+
# This prevents the Prepared Statement Cache from being flooded with unique entries caused
2349+
# by hardcoded literals. When warned is true, logs a warning on the usage of misprepared statements.
2350+
# When enabled is true, rejects misprepared statements with an error.
2351+
# prepared_statements_require_parameters_warned: true
2352+
# prepared_statements_require_parameters_enabled: false
23462353

23472354
#role_name_policy:
23482355
# # Implementation class of a validator. When not in form of FQCN, the

src/java/org/apache/cassandra/config/Config.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,8 @@ public static void setClientMode(boolean clientMode)
938938
public volatile boolean user_timestamps_enabled = true;
939939
public volatile boolean alter_table_enabled = true;
940940
public volatile boolean group_by_enabled = true;
941+
public volatile boolean prepared_statements_require_parameters_warned = true;
942+
public volatile boolean prepared_statements_require_parameters_enabled = false;
941943
public volatile boolean bulk_load_enabled = true;
942944
public volatile boolean drop_truncate_table_enabled = true;
943945
public volatile boolean drop_keyspace_enabled = true;

src/java/org/apache/cassandra/config/GuardrailsOptions.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,36 @@ public void setMinimumClientDriverVersionsDisallowed(Map<String, String> version
14031403
x -> config.minimum_client_driver_versions_disallowed = x);
14041404
}
14051405

1406+
@Override
1407+
public boolean getPreparedStatementsRequireParametersWarned()
1408+
{
1409+
return config.prepared_statements_require_parameters_warned;
1410+
}
1411+
1412+
@Override
1413+
public boolean getPreparedStatementsRequireParametersEnabled()
1414+
{
1415+
return config.prepared_statements_require_parameters_enabled;
1416+
}
1417+
1418+
@Override
1419+
public void setPreparedStatementsRequireParametersWarned(boolean warned)
1420+
{
1421+
updatePropertyWithLogging("prepared_statements_require_parameters_warned",
1422+
warned,
1423+
() -> config.prepared_statements_require_parameters_warned,
1424+
x -> config.prepared_statements_require_parameters_warned = x);
1425+
}
1426+
1427+
@Override
1428+
public void setPreparedStatementsRequireParametersEnabled(boolean enabled)
1429+
{
1430+
updatePropertyWithLogging("prepared_statements_require_parameters_enabled",
1431+
enabled,
1432+
() -> config.prepared_statements_require_parameters_enabled,
1433+
x -> config.prepared_statements_require_parameters_enabled = x);
1434+
}
1435+
14061436
private static <T> void updatePropertyWithLogging(String propertyName, T newValue, Supplier<T> getter, Consumer<T> setter)
14071437
{
14081438
T oldValue = getter.get();

src/java/org/apache/cassandra/cql3/CQLStatement.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ default Iterable<Function> getFunctions()
7373
*/
7474
void validate(ClientState state);
7575

76+
/**
77+
* Performs validation specific to the preparation phase of a CQL statement.
78+
* <p>
79+
* Unlike {@link #validate(ClientState)}, which is invoked during normal execution,
80+
* this method is explicitly triggered only when a statement is being prepared by a client.
81+
* By default, this is a no-op. Subclasses can override this to enforce preparation-specific
82+
* rules or guardrails (e.g., ensuring prepared statements contain bind markers).
83+
*
84+
* @param state the current client state
85+
*/
86+
default void validatePrepare(ClientState state) {}
87+
7688
/**
7789
* Execute the statement and return the resulting result or null if there is no result.
7890
*

src/java/org/apache/cassandra/cql3/QueryProcessor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ public static Prepared parseAndPrepare(String query, ClientState clientState, bo
494494
// Note: if 2 threads prepare the same query, we'll live so don't bother synchronizing
495495
CQLStatement statement = raw.prepare(clientState);
496496
statement.validate(clientState);
497+
statement.validatePrepare(clientState);
497498

498499
// Set CQL string for AlterSchemaStatement as this is used to serialize the transformation
499500
// in the cluster metadata log

src/java/org/apache/cassandra/cql3/statements/BatchStatement.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,15 @@ public void validate(ClientState state) throws InvalidRequestException
338338
statement.validate(state);
339339
}
340340

341+
@Override
342+
public void validatePrepare(ClientState state)
343+
{
344+
for (ModificationStatement statement : statements)
345+
{
346+
statement.validatePrepare(state);
347+
}
348+
}
349+
341350
@Override
342351
public List<ModificationStatement> getStatements()
343352
{

src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,12 @@ public void validate(ClientState state) throws InvalidRequestException
414414
Guardrails.userTimestampsEnabled.ensureEnabled(state);
415415
}
416416

417+
@Override
418+
public void validatePrepare(ClientState state)
419+
{
420+
Guardrails.preparedStatementsRequireParameters.guard(this, restrictions, state, metadata.keyspace, metadata.getTableName());
421+
}
422+
417423
public void validateDiskUsage(QueryOptions options, ClientState state)
418424
{
419425

src/java/org/apache/cassandra/cql3/statements/SelectStatement.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,12 @@ public void validate(ClientState state) throws InvalidRequestException
344344
Guardrails.allowFilteringEnabled.ensureEnabled(state);
345345
}
346346

347+
@Override
348+
public void validatePrepare(ClientState state)
349+
{
350+
Guardrails.preparedStatementsRequireParameters.guard(this, restrictions, state, table.keyspace, table.getTableName());
351+
}
352+
347353
@Override
348354
public ResultMessage.Rows execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime)
349355
{

0 commit comments

Comments
 (0)