Copilot commented on code in PR #4596:
URL: https://github.com/apache/cassandra/pull/4596#discussion_r2878549437


##########
test/unit/org/apache/cassandra/cql3/MispreparedStatementsTest.java:
##########
@@ -0,0 +1,396 @@
+/*
+ * 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.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));
+
+    @BeforeClass
+    public static void setupGlobalConfig()
+    {
+        DatabaseDescriptor.setAuthenticator(new PasswordAuthenticator());
+        DatabaseDescriptor.setAuthorizer(new CassandraAuthorizer());
+        DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true);
+    }
+
+    @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 testGuardrailDisabledAlowsLiterals()

Review Comment:
   Typo in test name: `Alows` → `Allows`.
   ```suggestion
       public void testGuardrailDisabledAllowsLiterals()
   ```



##########
src/java/org/apache/cassandra/db/guardrails/Guardrails.java:
##########
@@ -56,6 +56,9 @@ public final class Guardrails implements GuardrailsMBean
     public static final String MBEAN_NAME = 
"org.apache.cassandra.db:type=Guardrails";
 
     public static final GuardrailsConfigProvider CONFIG_PROVIDER = 
GuardrailsConfigProvider.instance;
+
+    public static final String MISPREPARED_STATEMENT_WARN_MESSAGE = "This 
query contains only literal values in the WHERE clause. " + "Using one or more 
'?' placeholder values (bind markers) allow a prepared statement to be reused.";

Review Comment:
   Grammar in the warning message: "Using one or more '?' ... allow" should be 
"allows" (singular subject).
   ```suggestion
       public static final String MISPREPARED_STATEMENT_WARN_MESSAGE = "This 
query contains only literal values in the WHERE clause. " + "Using one or more 
'?' placeholder values (bind markers) allows a prepared statement to be 
reused.";
   ```



##########
src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java:
##########
@@ -406,6 +406,40 @@ public interface GuardrailsConfig
      */
     boolean getVectorTypeEnabled();
 
+    /**
+     * <p>
+     * A statement is considered "mis-prepared" if it contains only hardcoded 
liter values and without any bind markers
+     * instead of bind markers. This is a performance anti-pattern because it 
prevents
+     * query plan reuse and floods the server-side Prepared Statement Cache 
with
+     * unique entries, leading to heap exhaustion and high GC pressure.
+     * <p>
+     * <b>LWT and Batch Considerations:</b>
+     * This check applies to both the {@code WHERE} clause and the {@code IF} 
conditions
+     * in Lightweight Transactions (LWT). For example, the following is 
considered
+     * mis-prepared because of the hardcoded literals:
+     * <pre>{@code
+     * UPDATE users SET description = 'v2' WHERE id = 1 AND name = 'v1' IF 
description = 'v0'
+     * }</pre>
+     * To be compliant, it should use bind markers ({@code ?}):
+     * <pre>{@code
+     * UPDATE users SET description = ? WHERE id = ? AND name = ? IF 
description = ?
+     * }</pre>
+     * For {@code BATCH} statements, if any individual statement within the 
batch is
+     * identified as mis-prepared, the entire batch triggers this guardrail.
+     * @return {@code true} if the requirement for parameters in prepared 
statements is strictly enforced (queries will fail).
+     *         {@code false} if the requirement is not strictly enforced 
(queries will only log a warning).
+     *         Defaults to {@code false}.
+     */
+
+    boolean getPreparedStatementsRequireParametersEnabled();
+
+    /**
+     * Sets whether prepared statements are strictly required to have 
parameters.
+     *
+     * @param enabled {@code true} to reject statements without parameters, 
{@code false} to only warn.
+     */
+    void setPreparedStatementsRequireParamtersEnabled(boolean enabled);

Review Comment:
   The new setter is misspelled as 
`setPreparedStatementsRequireParamtersEnabled` (missing 'e' in Parameters). 
Since this is a public interface method, the typo will leak into 
API/implementations and be hard to change later; rename it to 
`setPreparedStatementsRequireParametersEnabled` and update 
callers/implementations accordingly.
   ```suggestion
       void setPreparedStatementsRequireParametersEnabled(boolean enabled);
   ```



##########
test/unit/org/apache/cassandra/tools/nodetool/GuardrailsConfigCommandsTest.java:
##########
@@ -208,6 +208,7 @@ private Set<String> getConfigFieldNames()
     "group_by_enabled                                 true\n"  +
     "intersect_filtering_query_enabled                true\n"  +
     "intersect_filtering_query_warned                 true\n"  +
+    "misprepared_statements_enabled                   true\n"  +

Review Comment:
   `ALL_FLAGS_GETTER_OUTPUT` expects `misprepared_statements_enabled`, but the 
corresponding `Config` field added in this PR is 
`prepared_statements_require_parameters_enabled`. Unless the MBean/config 
naming is changed to match, this test (and 
`testParsedGuardrailNamesFromMBeanExistInCassandraYaml`) will fail because 
`misprepared_statements_enabled` is not a valid config key.
   ```suggestion
       "prepared_statements_require_parameters_enabled   true\n"  +
   ```



##########
src/java/org/apache/cassandra/config/Config.java:
##########
@@ -933,6 +933,7 @@ public static void setClientMode(boolean clientMode)
     public volatile boolean user_timestamps_enabled = true;
     public volatile boolean alter_table_enabled = true;
     public volatile boolean group_by_enabled = true;
+    public volatile boolean prepared_statements_require_parameters_enabled = 
false;
     public volatile boolean bulk_load_enabled = true;

Review Comment:
   PR description says the new flag’s default is `true` for backward 
compatibility, but the introduced config field 
`prepared_statements_require_parameters_enabled` defaults to `false`. Please 
align the implementation or update the PR description so operators understand 
the default behavior (warn vs reject).



##########
src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java:
##########
@@ -406,6 +406,40 @@ public interface GuardrailsConfig
      */
     boolean getVectorTypeEnabled();
 
+    /**
+     * <p>
+     * A statement is considered "mis-prepared" if it contains only hardcoded 
liter values and without any bind markers
+     * instead of bind markers. This is a performance anti-pattern because it 
prevents

Review Comment:
   Javadoc has a few typos/grammar issues: "liter values" should be "literal 
values", and the phrase "without any bind markers instead of bind markers" is 
redundant/unclear. Cleaning this up will make the guardrail definition easier 
to understand for operators.
   ```suggestion
        * A statement is considered "mis-prepared" if it contains only 
hardcoded literal values and no bind markers.
        * This is a performance anti-pattern because it prevents
   ```



##########
conf/cassandra.yaml:
##########
@@ -2536,6 +2536,15 @@ 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 
contains atleast one parameter.
+# When false, the server only emits a warning.
+# Defaults to false.
+# prepare_statements_require_parameters_enabled: false

Review Comment:
   The documented yaml key is `prepare_statements_require_parameters_enabled`, 
but the actual `Config` field introduced is 
`prepared_statements_require_parameters_enabled` (with 'prepared'). If 
operators copy/paste the commented key, it won’t take effect. Also the comment 
has typos ("does not contains", "atleast").
   ```suggestion
   # When true, the server rejects the preparation of any statement that does 
not contain at least one parameter.
   # When false, the server only emits a warning.
   # Defaults to false.
   # prepared_statements_require_parameters_enabled: false
   ```



##########
src/java/org/apache/cassandra/config/DatabaseDescriptor.java:
##########
@@ -5522,6 +5522,15 @@ public static void setUseStatementsEnabled(boolean 
enabled)
         }
     }
 
+    public static boolean getPreparedStatementsRequireParametersEnabled()
+    {
+        return conf.prepared_statements_require_parameters_enabled;
+    }
+
+    public static void setPreparedStatementsRequireParametersEnabled(boolean 
enabled)
+    {
+        conf.prepared_statements_require_parameters_enabled = enabled;

Review Comment:
   `setPreparedStatementsRequireParametersEnabled` sets the config flag without 
any logging or change check, unlike nearby dynamic setters (e.g. 
`setUseStatementsEnabled`). Consider logging when the value changes to help 
operators audit runtime config changes via JMX/tests.
   ```suggestion
           if (enabled != conf.prepared_statements_require_parameters_enabled)
           {
               logger.info("Setting 
prepared_statements_require_parameters_enabled to {}", enabled);
               conf.prepared_statements_require_parameters_enabled = enabled;
           }
   ```



##########
src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java:
##########
@@ -54,6 +54,13 @@ public interface GuardrailsMBean
      */
     void setKeyspacesThreshold(int warn, int fail);
 
+    /**
+     * @return the use of misprepared statement is enabled
+     */
+    boolean getMispreparedStatementsEnabled();
+
+    void setMispreparedStatementsEnabled(boolean enabled);

Review Comment:
   The new MBean attribute name (`getMispreparedStatementsEnabled` → 
`misprepared_statements_enabled`) doesn’t correspond to any `Config` field / 
cassandra.yaml key (the added config is 
`prepared_statements_require_parameters_enabled`). This will break 
`GuardrailsConfigCommand` name parsing and runtime configuration via 
nodetool/JMX unless the names are aligned (either rename the MBean methods or 
rename the config key).
   ```suggestion
        * @return whether the guardrail enforcing that prepared statements 
require parameters is enabled
        */
       boolean getPreparedStatementsRequireParametersEnabled();
   
       void setPreparedStatementsRequireParametersEnabled(boolean enabled);
   ```



##########
src/java/org/apache/cassandra/config/GuardrailsOptions.java:
##########
@@ -1329,6 +1329,21 @@ public boolean getVectorTypeEnabled()
         return config.vector_type_enabled;
     }
 
+    @Override
+    public boolean getPreparedStatementsRequireParametersEnabled()
+    {
+        return config.prepared_statements_require_parameters_enabled;
+    }
+
+
+    public void setPreparedStatementsRequireParamtersEnabled(boolean enabled)
+    {
+        
updatePropertyWithLogging("prepared_statements_require_parameters_enabled",
+                                  enabled,
+                                  () -> 
config.prepared_statements_require_parameters_enabled,
+                                  x -> 
config.prepared_statements_require_parameters_enabled = x);
+    }

Review Comment:
   `setPreparedStatementsRequireParamtersEnabled` is misspelled and also lacks 
`@Override` even though `GuardrailsOptions` implements `GuardrailsConfig`. Once 
the interface method is corrected, this will stop compiling; please rename the 
method to match the corrected interface signature and add `@Override` for 
compile-time safety.



##########
test/unit/org/apache/cassandra/cql3/MispreparedStatementsTest.java:
##########
@@ -0,0 +1,396 @@
+/*
+ * 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.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));
+
+    @BeforeClass
+    public static void setupGlobalConfig()
+    {
+        DatabaseDescriptor.setAuthenticator(new PasswordAuthenticator());
+        DatabaseDescriptor.setAuthorizer(new CassandraAuthorizer());
+        DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true);
+    }
+
+    @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 testGuardrailDisabledAlowsLiterals()
+    {
+        
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();
+

Review Comment:
   `testWarningIsIssuedWhenGuardrailIsAllowed` duplicates the same 
steps/assertions as `testGuardrailDisabledAlowsLiterals`. Consider removing one 
or changing it to cover a distinct scenario (e.g., verifying the warning text 
includes keyspace/table, or that the strict mode rejects).
   ```suggestion
   
           // Capture warnings for this operation
           ClientWarn.instance.captureWarnings();
   
           String tableName = currentTable();
           assertGuardrailPassed(String.format("SELECT * FROM %s WHERE id = 1", 
tableName));
   
           List<String> warnings = ClientWarn.instance.getWarnings();
           Assert.assertNotNull("Warnings list should not be null", warnings);
           Assert.assertFalse("Expected at least one warning to be issued", 
warnings.isEmpty());
   
           String firstWarning = warnings.get(0);
           Assert.assertTrue("Warning should mention keyspace", 
firstWarning.contains(KEYSPACE));
           Assert.assertTrue("Warning should mention table name", 
firstWarning.contains(tableName));
   ```



##########
src/java/org/apache/cassandra/service/StorageService.java:
##########
@@ -4839,6 +4839,18 @@ public void setColumnIndexCacheSize(int cacheSizeInKB)
         logger.info("Updated column_index_cache_size to {}", cacheSizeInKB);
     }
 
+    @Override
+    public boolean getMispreparedStatementsEnabled()
+    {
+        return 
DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled();
+    }
+
+    @Override
+    public void setMispreparedStatementsEnabled(boolean enabled)
+    {
+        
DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(enabled);
+    }
+

Review Comment:
   The JMX methods are named `get/setMispreparedStatementsEnabled`, but they 
actually proxy 
`DatabaseDescriptor.get/setPreparedStatementsRequireParametersEnabled`. The 
names imply enabling misprepared statements (allowing the anti-pattern), while 
the underlying flag indicates enforcing parameters. Consider renaming these 
methods to match the underlying config semantics/key to avoid operator 
confusion (and to stay consistent with the GuardrailsMBean naming you choose).
   ```suggestion
   
       /**
        * Returns the value of the {@code 
prepared_statements_require_parameters_enabled} flag.
        *
        * This method is misnamed: the underlying configuration controls 
whether prepared statements
        * are required to use parameters (i.e. disallowing "misprepared" 
statements that inline
        * literal values), not whether such misprepared statements are enabled.
        *
        * @deprecated Use {@link 
#getPreparedStatementsRequireParametersEnabled()} instead.
        */
       @Deprecated(since = "5.0")
       @Override
       public boolean getMispreparedStatementsEnabled()
       {
           return 
DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled();
       }
   
       /**
        * Sets the value of the {@code 
prepared_statements_require_parameters_enabled} flag.
        *
        * This method is misnamed: the underlying configuration controls 
whether prepared statements
        * are required to use parameters (i.e. disallowing "misprepared" 
statements that inline
        * literal values), not whether such misprepared statements are enabled.
        *
        * @deprecated Use {@link 
#setPreparedStatementsRequireParametersEnabled(boolean)} instead.
        */
       @Deprecated(since = "5.0")
       @Override
       public void setMispreparedStatementsEnabled(boolean enabled)
       {
           
DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(enabled);
       }
   
       /**
        * JMX accessor for the {@code 
prepared_statements_require_parameters_enabled} configuration flag.
        *
        * When {@code true}, prepared statements must use bind parameters 
instead of inlining literals.
        */
       public boolean getPreparedStatementsRequireParametersEnabled()
       {
           return 
DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled();
       }
   
       /**
        * JMX mutator for the {@code 
prepared_statements_require_parameters_enabled} configuration flag.
        *
        * @param enabled {@code true} to require parameters for prepared 
statements, {@code false} to allow
        *                prepared statements that inline literal values.
        */
       public void setPreparedStatementsRequireParametersEnabled(boolean 
enabled)
       {
           
DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(enabled);
       }
   ```



##########
test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java:
##########
@@ -781,6 +781,27 @@ public void testRowIndexSizeWarnEqAbort()
         DatabaseDescriptor.applyReadThresholdsValidations(conf);
     }
 
+    @Test
+    public void testMispreparedStatementsEnabled() {
+        boolean originalValue = 
DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled();
+        try
+        {
+            
DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true);
+            Assert.assertTrue("Value should be true after setting to true",
+                              
DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled());
+
+            
DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(false);
+            Assert.assertFalse("Value should be false after setting to false",
+                               
DatabaseDescriptor.getPreparedStatementsRequireParametersEnabled());
+
+            Assert.assertTrue("Default value of misprepared_statement_enabled 
must be true", originalValue);

Review Comment:
   This test asserts the default value is `true` (`Assert.assertTrue(... 
originalValue)`), but the new config field is initialized to `false` in 
`Config` and the yaml comment also says default is false. This assertion will 
be unstable/failing depending on test initialization; the test should assert 
the actual default (or avoid asserting a hard-coded default and only validate 
the setter/getter round-trip).
   ```suggestion
   
   ```



##########
test/unit/org/apache/cassandra/cql3/MispreparedStatementsTest.java:
##########
@@ -0,0 +1,396 @@
+/*
+ * 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.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));
+
+    @BeforeClass
+    public static void setupGlobalConfig()
+    {
+        DatabaseDescriptor.setAuthenticator(new PasswordAuthenticator());
+        DatabaseDescriptor.setAuthorizer(new CassandraAuthorizer());
+        DatabaseDescriptor.setPreparedStatementsRequireParametersEnabled(true);
+    }
+
+    @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);
+    }

Review Comment:
   `tearDown()` unconditionally sets 
`prepared_statements_require_parameters_enabled` back to `true`, which can leak 
global state into other tests (especially since the default appears to be 
`false`). Capture the original value and restore it in `@After`/`@AfterClass` 
instead of hard-coding `true`.



##########
src/java/org/apache/cassandra/db/guardrails/Guardrails.java:
##########
@@ -1925,4 +1951,16 @@ private static long 
minimumTimestampAsRelativeMicros(@Nullable DurationSpec.Long
                ? Long.MIN_VALUE
                : (ClientState.getLastTimestampMicros() - 
duration.toMicroseconds());
     }
+
+    public static void onMisprepared(ClientState state, String keyspace, 
String tableName)
+    {
+        if (preparedStatementsRequireParametersEnabled.isEnabled(state) && 
preparedStatementsRequireParametersEnabled.enabled(state))
+        {
+            
preparedStatementsRequireParametersEnabled.fail(preparedStatementsRequireParametersEnabled.name
 + " is not allowed", state);
+        }
+        else
+        {
+            
preparedStatementsRequireParametersEnabled.warn(MISPREPARED_STATEMENT_WARN_MESSAGE
 + "\n Keyspace: " + keyspace + "\n table: " + tableName);
+        }

Review Comment:
   `onMisprepared` falls into the warning path whenever the guardrail is 
bypassed (internal calls, superusers, daemon not initialized) because the 
`else` runs when `enabled(state)` is false. This defeats the guardrail bypass 
semantics and can emit warnings for internal/superuser queries. Consider 
returning early when 
`!preparedStatementsRequireParametersEnabled.enabled(state)`, then using the 
config predicate to choose fail vs warn.



-- 
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]


Reply via email to