Copilot commented on code in PR #4596: URL: https://github.com/apache/cassandra/pull/4596#discussion_r2885634449
########## test/unit/org/apache/cassandra/cql3/MispreparedStatementsTest.java: ########## @@ -0,0 +1,405 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.cql3; + +import java.net.InetSocketAddress; +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.CassandraAuthorizer; +import org.apache.cassandra.auth.PasswordAuthenticator; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.guardrails.GuardrailViolatedException; +import org.apache.cassandra.db.guardrails.Guardrails; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; + +import static org.junit.Assert.assertTrue; + +public class MispreparedStatementsTest extends CQLTester +{ + private static final ClientState state = ClientState.forExternalCalls(new InetSocketAddress("127.0.0.1", 9042)); + private static boolean originalValue; + + @BeforeClass + public static void setupGlobalConfig() + { + DatabaseDescriptor.setAuthenticator(new PasswordAuthenticator()); + DatabaseDescriptor.setAuthorizer(new CassandraAuthorizer()); + originalValue = DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled(); + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true); + } + + @AfterClass + public static void cleanUp() + { + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(originalValue); + } + + @Before + public void setUp() + { + AuthenticatedUser nonSuperUser = new AuthenticatedUser("regular-user") + { + @Override + public boolean isSuper() + { + return false; + } + + @Override + public boolean isSystem() + { + return false; + } + + @Override + public boolean isAnonymous() + { + return false; + } + + @Override + public boolean canLogin() + { + return true; + } + }; + + state.login(nonSuperUser); + state.setKeyspace(KEYSPACE); + ClientWarn.instance.captureWarnings(); + createTable("CREATE TABLE %s (id int, description text, age int, name text, PRIMARY KEY (id, name, age))"); + } + + @After + public void tearDown() + { + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true); + } + + @Test + public void testSelectWithPartitionKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id = 1", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectWithClusteringKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE name = 'v1' ALLOW FILTERING", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectWithFullPrimaryKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id = 1 AND name = 'v1'", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectWithWhereNonPrimaryKeyColumn() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE description = 'foo' ALLOW FILTERING", currentTable())); + assertNoWarnings(); + } + + + @Test + public void testSelectWithCompositeRestriction() + { + assertGuardrailViolated(String.format("SELECT sum(id) from %s WHERE (name, age) = ('a', 1) ALLOW FILTERING", currentTable())); + } + + @Test + public void testSelectWithFunction() + { + assertGuardrailViolated(String.format("SELECT sum(id) from %s where name = 'v1' ALLOW FILTERING", currentTable())); + } + + @Test + public void testSelectWithRangeRestriction() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id = 1 AND name > 'a'", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectInRestrictionOnPartitionKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id IN (1, 2, 3)", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectInRestrictionOnClusteringKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE name IN ('a', 'b') ALLOW FILTERING", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectInRestrictionOnFullPrimaryKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id IN (1, 2, 3) AND name in ('a', 'b')", currentTable())); + assertNoWarnings(); + } + + @Test + public void testDDLStatementsBypass() + { + assertGuardrailPassed("CREATE TABLE IF NOT EXISTS test_table (id INT PRIMARY KEY)"); + assertNoWarnings(); + } + + @Test + public void testWhereLiteralWithLike() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE name LIKE 'prefix%%' ALLOW FILTERING", currentTable())); + assertNoWarnings(); + } + + @Test + public void testInsertJsonGuardrail() + { + assertGuardrailViolated(String.format("INSERT INTO %s JSON '{\"id\": 1, \"name\": \"v1\"}'", currentTable())); Review Comment: `INSERT ... JSON` statements don’t have `restrictions` (the parsed insert-json path passes `null` restrictions), and the current guardrail check in `ModificationStatement.validate()` only triggers when `hasRestriction` is true. With the implementation in this PR, this test will not throw `GuardrailViolatedException` and is likely to fail (or it implies the guardrail scope should be expanded beyond WHERE/IF restrictions). Please align the test expectation with the actual guardrail behavior/scope. ```suggestion assertGuardrailPassed(String.format("INSERT INTO %s JSON '{\"id\": 1, \"name\": \"v1\"}'", currentTable())); ``` ########## test/unit/org/apache/cassandra/cql3/MispreparedStatementsTest.java: ########## @@ -0,0 +1,405 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.cql3; + +import java.net.InetSocketAddress; +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.CassandraAuthorizer; +import org.apache.cassandra.auth.PasswordAuthenticator; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.guardrails.GuardrailViolatedException; +import org.apache.cassandra.db.guardrails.Guardrails; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; + +import static org.junit.Assert.assertTrue; + +public class MispreparedStatementsTest extends CQLTester +{ + private static final ClientState state = ClientState.forExternalCalls(new InetSocketAddress("127.0.0.1", 9042)); + private static boolean originalValue; + + @BeforeClass + public static void setupGlobalConfig() + { + DatabaseDescriptor.setAuthenticator(new PasswordAuthenticator()); + DatabaseDescriptor.setAuthorizer(new CassandraAuthorizer()); + originalValue = DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled(); + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true); + } Review Comment: This test class mutates global auth configuration (`DatabaseDescriptor.setAuthenticator` / `setAuthorizer`) but doesn’t restore the previous authenticator/authorizer in `@AfterClass`. That can leak state into other unit tests in the same JVM. Prefer using `CQLTester.requireAuthentication(...)` helpers (which set up local test auth implementations) and/or save+restore the original authenticator/authorizer in `cleanUp()`. ########## test/unit/org/apache/cassandra/cql3/MispreparedStatementsTest.java: ########## @@ -0,0 +1,405 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.cql3; + +import java.net.InetSocketAddress; +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.auth.CassandraAuthorizer; +import org.apache.cassandra.auth.PasswordAuthenticator; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.guardrails.GuardrailViolatedException; +import org.apache.cassandra.db.guardrails.Guardrails; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; + +import static org.junit.Assert.assertTrue; + +public class MispreparedStatementsTest extends CQLTester +{ + private static final ClientState state = ClientState.forExternalCalls(new InetSocketAddress("127.0.0.1", 9042)); + private static boolean originalValue; + + @BeforeClass + public static void setupGlobalConfig() + { + DatabaseDescriptor.setAuthenticator(new PasswordAuthenticator()); + DatabaseDescriptor.setAuthorizer(new CassandraAuthorizer()); + originalValue = DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled(); + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true); + } + + @AfterClass + public static void cleanUp() + { + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(originalValue); + } + + @Before + public void setUp() + { + AuthenticatedUser nonSuperUser = new AuthenticatedUser("regular-user") + { + @Override + public boolean isSuper() + { + return false; + } + + @Override + public boolean isSystem() + { + return false; + } + + @Override + public boolean isAnonymous() + { + return false; + } + + @Override + public boolean canLogin() + { + return true; + } + }; + + state.login(nonSuperUser); + state.setKeyspace(KEYSPACE); + ClientWarn.instance.captureWarnings(); + createTable("CREATE TABLE %s (id int, description text, age int, name text, PRIMARY KEY (id, name, age))"); + } + + @After + public void tearDown() + { + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true); + } + + @Test + public void testSelectWithPartitionKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id = 1", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectWithClusteringKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE name = 'v1' ALLOW FILTERING", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectWithFullPrimaryKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id = 1 AND name = 'v1'", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectWithWhereNonPrimaryKeyColumn() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE description = 'foo' ALLOW FILTERING", currentTable())); + assertNoWarnings(); + } + + + @Test + public void testSelectWithCompositeRestriction() + { + assertGuardrailViolated(String.format("SELECT sum(id) from %s WHERE (name, age) = ('a', 1) ALLOW FILTERING", currentTable())); + } + + @Test + public void testSelectWithFunction() + { + assertGuardrailViolated(String.format("SELECT sum(id) from %s where name = 'v1' ALLOW FILTERING", currentTable())); + } + + @Test + public void testSelectWithRangeRestriction() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id = 1 AND name > 'a'", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectInRestrictionOnPartitionKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id IN (1, 2, 3)", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectInRestrictionOnClusteringKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE name IN ('a', 'b') ALLOW FILTERING", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectInRestrictionOnFullPrimaryKey() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE id IN (1, 2, 3) AND name in ('a', 'b')", currentTable())); + assertNoWarnings(); + } + + @Test + public void testDDLStatementsBypass() + { + assertGuardrailPassed("CREATE TABLE IF NOT EXISTS test_table (id INT PRIMARY KEY)"); + assertNoWarnings(); + } + + @Test + public void testWhereLiteralWithLike() + { + assertGuardrailViolated(String.format("SELECT * FROM %s WHERE name LIKE 'prefix%%' ALLOW FILTERING", currentTable())); + assertNoWarnings(); + } + + @Test + public void testInsertJsonGuardrail() + { + assertGuardrailViolated(String.format("INSERT INTO %s JSON '{\"id\": 1, \"name\": \"v1\"}'", currentTable())); + assertNoWarnings(); + } + + @Test + public void testUpdateWithPartitionKey() + { + assertGuardrailViolated(String.format("UPDATE %s SET description = 'new' WHERE id = 1 AND name = 'name' AND age = 1", KEYSPACE + '.' + currentTable())); + assertNoWarnings(); + } + + @Test + public void testUpdateWithIfCondition() + { + assertGuardrailViolated(String.format("UPDATE %s SET description = 'v2' WHERE id = 1 AND name = 'v1' AND age = 1 IF description = 'v0'", currentTable())); + assertNoWarnings(); + } + + @Test + public void testDeleteWithFullPrimaryKey() + { + assertGuardrailViolated(String.format("DELETE FROM %s WHERE id = 1 AND name = 'v1'", currentTable())); + assertNoWarnings(); + } + + @Test + public void testMultiTableBatchGuardrailAllLiteral() + { + String table1 = currentTable(); + String table2 = createTable("CREATE TABLE %s (id int PRIMARY KEY, val text)"); + String query = String.format("BEGIN BATCH " + + "UPDATE %s.%s SET description = 'v1' WHERE id = 1 AND name = 'n1' AND age = 1; " + + "INSERT INTO %s.%s (id, val) VALUES (2, 'v2'); " + + "APPLY BATCH", + KEYSPACE, table1, KEYSPACE, table2); + + assertGuardrailViolated(query); + assertNoWarnings(); + } + + @Test + public void testSucceedWithOnlyBindMarkers() + { + assertGuardrailPassed(String.format("SELECT * FROM %s WHERE id = ? AND name = ?", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSucceedWithOneBindMarkerOneLiteral() + { + + assertGuardrailPassed(String.format("SELECT * FROM %s WHERE id = ? AND name = 'v1'", currentTable())); + assertNoWarnings(); + } + + @Test + public void testSelectAllPasses() + { + assertGuardrailPassed(String.format("SELECT * FROM %s", currentTable())); + assertNoWarnings(); + } + + @Test + public void testLiteralInProjectionIsAllowed() + { + assertGuardrailPassed(String.format("SELECT id, (text)'const_val' FROM %s WHERE id = ?", currentTable())); + assertNoWarnings(); + } + + @Test + public void testInternalBypass() + { + assertGuardrailPassed("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE id = 1", ClientState.forInternalCalls()); + } + + @Test + public void testSuperUserBypass() + { + AuthenticatedUser superUser = new AuthenticatedUser("super-user") + { + @Override + public boolean isSuper() + { + return true; + } + + @Override + public boolean isSystem() + { + return false; + } + + @Override + public boolean isAnonymous() + { + return false; + } + + @Override + public boolean canLogin() + { + return true; + } + }; + ClientState superUserState = ClientState.forExternalCalls(new InetSocketAddress("127.0.0.1", 9042)); + superUserState.login(superUser); + assertGuardrailPassed("SELECT * FROM " + KEYSPACE + '.' + currentTable() + " WHERE id = 1", superUserState); + } + + @Test + public void testSystemKeyspaceBypassForRegularUser() + { + assertGuardrailPassed("SELECT * FROM system.local WHERE key = 'local'"); + assertNoWarnings(); + } + + @Test + public void testGuardrailDisabledAllowsLiterals() + { + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(false); + assertGuardrailPassed(String.format("SELECT * FROM %s WHERE id = 1", currentTable())); + assertWarnings(); + } + + @Test + public void testWarningIsIssuedWhenGuardrailIsAllowed() + { + DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(false); + assertGuardrailPassed(String.format("SELECT * FROM %s WHERE id = 1", currentTable())); + assertWarnings(); + + } + + @Test Review Comment: `testGuardrailDisabledAllowsLiterals` and `testWarningIsIssuedWhenGuardrailIsAllowed` are currently identical (both disable the guardrail, prepare the same query, and assert warnings). Consider removing one or changing one of them to cover a distinct behavior to avoid redundant coverage and future maintenance burden. ```suggestion ``` ########## src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java: ########## @@ -409,7 +409,21 @@ public void validate(ClientState state) throws InvalidRequestException checkFalse(isVirtual() && hasConditions(), "Conditional updates are not supported by virtual tables"); if (attrs.isTimestampSet()) + { Guardrails.userTimestampsEnabled.ensureEnabled(state); + } + // Misprepare guardrail shouldn't block system keyspaces + if (SchemaConstants.isSystemKeyspace(metadata.keyspace)) + return; + + boolean hasRestriction = restrictions != null && + (restrictions.hasPartitionKeyRestrictions() || + restrictions.hasClusteringColumnsRestrictions() || + restrictions.hasNonPrimaryKeyRestrictions()); + if (getBindVariables().isEmpty() && hasRestriction) + { + Guardrails.onMisprepared(state, metadata.keyspace, metadata.getTableName()); Review Comment: As with `SelectStatement`, adding the misprepared-statement guardrail to `validate()` means it will run for *executed* non-prepared statements too (since `QueryProcessor.processStatement` calls `statement.validate(clientState)`). That can unexpectedly reject/warn on ordinary literal UPDATE/DELETE statements that were never PREPAREd. Move this check to the prepare path (or otherwise gate it so it only runs during PREPARE). ```suggestion // Misprepare guardrail should only run during PREPARE and shouldn't block system keyspaces if (state.getSource() == StatementSource.PREPARE) { if (SchemaConstants.isSystemKeyspace(metadata.keyspace)) return; boolean hasRestriction = restrictions != null && (restrictions.hasPartitionKeyRestrictions() || restrictions.hasClusteringColumnsRestrictions() || restrictions.hasNonPrimaryKeyRestrictions()); if (getBindVariables().isEmpty() && hasRestriction) { Guardrails.onMisprepared(state, metadata.keyspace, metadata.getTableName()); } ``` ########## src/java/org/apache/cassandra/service/StorageServiceMBean.java: ########## @@ -1097,6 +1097,16 @@ default int upgradeSSTables(String keyspaceName, boolean excludeCurrentVersion, @Deprecated(since = "5.0") public void setColumnIndexCacheSize(int cacheSizeInKB); + /** + * Returns whether use of misprepared statements is enabled + */ + public boolean getMispreparedStatementsEnabled(); + + /** + * Sets whether use of misprepared statements is enabled + */ + public void setMispreparedStatementsEnabled(boolean enabled); + Review Comment: These new StorageServiceMBean methods are named `get/setMispreparedStatementsEnabled`, but they simply proxy `DatabaseDescriptor.get/setPreparedStatementsRequireParametersEnabled()`. The naming/API here is inconsistent with the actual config option name (`prepared_statements_require_parameters_enabled`) and with the GuardrailsConfig/GuardrailsOptions method names, which will be confusing for JMX consumers. Consider renaming the JMX methods to match the config key, or introducing a clear aliasing scheme. ```suggestion * Returns whether use of misprepared statements is enabled. * * @deprecated use {@link #getPreparedStatementsRequireParametersEnabled()} instead. */ @Deprecated(since = "5.0") public boolean getMispreparedStatementsEnabled(); /** * Sets whether use of misprepared statements is enabled. * * @deprecated use {@link #setPreparedStatementsRequireParametersEnabled(boolean)} instead. */ @Deprecated(since = "5.0") public void setMispreparedStatementsEnabled(boolean enabled); /** * Returns whether the prepared_statements_require_parameters_enabled guardrail is enabled. */ public boolean getPreparedStatementsRequireParametersEnabled(); /** * Sets whether the prepared_statements_require_parameters_enabled guardrail is enabled. */ public void setPreparedStatementsRequireParametersEnabled(boolean enabled); ``` ########## test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java: ########## @@ -781,6 +781,27 @@ public void testRowIndexSizeWarnEqAbort() DatabaseDescriptor.applyReadThresholdsValidations(conf); } + @Test + public void testMisreparedStatementsRequireParametersEnabled() { + boolean originalValue = DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled(); + try + { + Assert.assertFalse("Default value of misprepared_statement_enabled must be false", originalValue); Review Comment: Typo in the test name: `testMisreparedStatementsRequireParametersEnabled` should be `testMisprepared...` (or preferably renamed to match the actual option name `prepared_statements_require_parameters_enabled`). The assertion message also refers to `misprepared_statement_enabled`, which doesn’t match the config key used elsewhere in this PR. ```suggestion public void testPreparedStatementsRequireParametersEnabled() { boolean originalValue = DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled(); try { Assert.assertFalse("Default value of prepared_statements_require_parameters_enabled must be false", originalValue); ``` ########## src/java/org/apache/cassandra/cql3/statements/SelectStatement.java: ########## @@ -339,8 +339,19 @@ public void authorize(ClientState state) throws InvalidRequestException, Unautho public void validate(ClientState state) throws InvalidRequestException { - if (parameters.allowFiltering && !SchemaConstants.isSystemKeyspace(table.keyspace)) + if (SchemaConstants.isSystemKeyspace(table.keyspace)) + return; + if (parameters.allowFiltering) Guardrails.allowFilteringEnabled.ensureEnabled(state); + boolean hasRestrictions = restrictions != null && + (restrictions.hasPartitionKeyRestrictions() || + restrictions.hasClusteringColumnsRestrictions() || + restrictions.hasNonPrimaryKeyRestrictions()); + if (getBindVariables().isEmpty() + && hasRestrictions) + { + Guardrails.onMisprepared(state, table.keyspace, table.name); + } Review Comment: This misprepared-statement guardrail is implemented in `validate()`, but `validate()` is invoked for both prepared statements (`parseAndPrepare`) and for executing non-prepared CQL (`processStatement`). As a result, normal literal SELECT queries (not PREPAREd) can now be rejected/warned, which is a behavioral change beyond the PR’s stated scope. The guardrail should run only on the prepare path (e.g., in `QueryProcessor.parseAndPrepare` or a prepare-specific hook), not during general statement validation/execution. ```suggestion ``` ########## src/java/org/apache/cassandra/db/guardrails/Guardrails.java: ########## @@ -615,6 +618,17 @@ public final class Guardrails implements GuardrailsMBean format("The keyspace %s has a replication factor of %s, above the %s threshold of %s.", what, value, isWarning ? "warning" : "failure", threshold)); + /** + * Prevents cache overflow and eviction caused by the anti-pattern of preparing queries with + * hardcoded literals instead of bind markers. This prevents filling the statement cache with non-reusable entries. + */ + + public static final EnableFlag preparedStatementsRequireParametersEnabled = + new EnableFlag("prepare_statements_require_parameters_enabled", Review Comment: The new guardrail is named `"prepare_statements_require_parameters_enabled"` but the yaml/config field is `prepared_statements_require_parameters_enabled` (note the missing 'd'). This inconsistency makes messages and guardrail naming confusing and will likely break expectations around configuration naming; please align the guardrail name with the config key. ```suggestion new EnableFlag("prepared_statements_require_parameters_enabled", ``` ########## conf/cassandra.yaml: ########## @@ -2536,6 +2536,14 @@ drop_compact_storage_enabled: false # This would also apply to system keyspaces. # maximum_replication_factor_warn_threshold: -1 # maximum_replication_factor_fail_threshold: -1 +# +# Guardrail to warn or fail when a statement is prepared without bind markers (parameters). +# This prevents the Prepared Statement Cache from being flooded with unique entries caused +# by hardcoded literals. +# When true, the server rejects the preparation of any statement that does not contain at least one parameter. Review Comment: The yaml comment says that when enabled the server rejects preparation of “any statement that does not contain at least one parameter”. With the current implementation, the guardrail only triggers when `restrictions` exist (i.e., WHERE-style restrictions) and won’t apply to statements without restrictions (e.g., INSERT). Please adjust the documentation here to match the actual behavior/scope (or expand the implementation to match the documented behavior). ```suggestion # Guardrail to warn or fail when a statement with restrictions (for example, a WHERE clause) # is prepared without bind markers (parameters) in those restrictions. # This helps prevent the Prepared Statement Cache from being flooded with unique entries # caused by hardcoded literals in query restrictions. # When true, the server rejects the preparation of any such statement whose restrictions do not # contain at least one parameter; statements without restrictions are not affected. ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]

