This is an automated email from the ASF dual-hosted git repository.

maedhroz pushed a commit to branch cassandra-4.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-4.0 by this push:
     new f375a3914f Support null column value tombstones in FQL batch statements
f375a3914f is described below

commit f375a3914f9e322e11ac30b2ea2999ff14bb65d4
Author: Caleb Rackliffe <calebrackli...@gmail.com>
AuthorDate: Tue Mar 4 18:04:08 2025 -0600

    Support null column value tombstones in FQL batch statements
    
    patch by Caleb Rackliffe; reviewed by Abe Ratnofsky for CASSANDRA-20397
---
 CHANGES.txt                                        |   1 +
 .../org/apache/cassandra/fql/FullQueryLogger.java  |   4 +-
 .../test/{ => fql}/FqlReplayDDLExclusionTest.java  |   3 +-
 .../test/fql/FqlTombstoneHandlingTest.java         | 115 +++++++++++++++++++++
 .../apache/cassandra/fqltool/FQLQueryReader.java   |   5 +-
 .../apache/cassandra/fqltool/commands/Dump.java    |   9 +-
 6 files changed, 129 insertions(+), 8 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index fff1039d33..30d253d29b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.0.18
+ * Support null column value tombstones in FQL batch statements 
(CASSANDRA-20397)
  * Update Zstd library to 1.5.7-1 (CASSANDRA-20367)
  * Fix premature auto-failing of long-running repairs (CASSANDRA-20312)
 
diff --git a/src/java/org/apache/cassandra/fql/FullQueryLogger.java 
b/src/java/org/apache/cassandra/fql/FullQueryLogger.java
index 0604df67fb..34c9641e77 100644
--- a/src/java/org/apache/cassandra/fql/FullQueryLogger.java
+++ b/src/java/org/apache/cassandra/fql/FullQueryLogger.java
@@ -424,9 +424,7 @@ public class FullQueryLogger implements QueryEvents.Listener
             {
                 valueOut.int32(subValues.size());
                 for (ByteBuffer value : subValues)
-                {
-                    valueOut.bytes(BytesStore.wrap(value));
-                }
+                    valueOut.bytes(value == null ? null : 
BytesStore.wrap(value));
             }
         }
 
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/FqlReplayDDLExclusionTest.java
 
b/test/distributed/org/apache/cassandra/distributed/test/fql/FqlReplayDDLExclusionTest.java
similarity index 97%
rename from 
test/distributed/org/apache/cassandra/distributed/test/FqlReplayDDLExclusionTest.java
rename to 
test/distributed/org/apache/cassandra/distributed/test/fql/FqlReplayDDLExclusionTest.java
index 3dbef35c78..daa1ae25d6 100644
--- 
a/test/distributed/org/apache/cassandra/distributed/test/FqlReplayDDLExclusionTest.java
+++ 
b/test/distributed/org/apache/cassandra/distributed/test/fql/FqlReplayDDLExclusionTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.distributed.test;
+package org.apache.cassandra.distributed.test.fql;
 
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -27,6 +27,7 @@ import com.datastax.driver.core.Session;
 import org.apache.cassandra.distributed.Cluster;
 import org.apache.cassandra.distributed.api.IInvokableInstance;
 import org.apache.cassandra.distributed.api.QueryResults;
+import org.apache.cassandra.distributed.test.TestBaseImpl;
 import org.apache.cassandra.tools.ToolRunner;
 import org.apache.cassandra.tools.ToolRunner.ToolResult;
 
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/fql/FqlTombstoneHandlingTest.java
 
b/test/distributed/org/apache/cassandra/distributed/test/fql/FqlTombstoneHandlingTest.java
new file mode 100644
index 0000000000..45e10ab1b0
--- /dev/null
+++ 
b/test/distributed/org/apache/cassandra/distributed/test/fql/FqlTombstoneHandlingTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.distributed.test.fql;
+
+import com.google.common.collect.Sets;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import com.datastax.driver.core.BatchStatement;
+import com.datastax.driver.core.PreparedStatement;
+import com.datastax.driver.core.Session;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.test.TestBaseImpl;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
+import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL;
+import static org.apache.cassandra.distributed.api.Feature.NETWORK;
+import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
+import static org.apache.cassandra.distributed.shared.AssertUtils.row;
+
+public class FqlTombstoneHandlingTest extends TestBaseImpl
+{
+    private static Cluster CLUSTER;
+
+    @Rule
+    public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    @BeforeClass
+    public static void beforeClass() throws Throwable
+    {
+        CLUSTER = init(Cluster.build(1).withConfig(updater -> 
updater.with(NETWORK, GOSSIP, NATIVE_PROTOCOL)).start());
+    }
+    
+    @Test
+    public void testNullCellBindingInBatch()
+    {
+        String tableName = "null_as_tombstone_in_batch";
+        CLUSTER.schemaChange(withKeyspace("CREATE TABLE %s." + tableName + " 
(k int, c int, s set<int>, primary key (k, c))"));
+        CLUSTER.get(1).nodetool("enablefullquerylog", "--path", 
temporaryFolder.getRoot().getAbsolutePath());
+        String insertTemplate = withKeyspace("INSERT INTO %s." + tableName + " 
(k, c, s) VALUES ( ?, ?, ?) USING TIMESTAMP 2");
+        String select = withKeyspace("SELECT * FROM %s." + tableName + " WHERE 
k = 0 AND c = 0");
+
+        com.datastax.driver.core.Cluster.Builder builder1 
=com.datastax.driver.core.Cluster.builder().addContactPoint("127.0.0.1");
+
+        // Use the driver to write this initial row, since otherwise we won't 
hit the dispatcher
+        try (com.datastax.driver.core.Cluster cluster1 = builder1.build(); 
Session session1 = cluster1.connect())
+        {
+            BatchStatement batch = new 
BatchStatement(BatchStatement.Type.UNLOGGED);
+            PreparedStatement preparedWrite = session1.prepare(insertTemplate);
+            batch.add(preparedWrite.bind(0, 0, null));
+            session1.execute(batch);
+        }
+
+        CLUSTER.get(1).nodetool("disablefullquerylog");
+
+        // The dump should contain a null entry for our tombstone
+        ToolRunner.ToolResult runner = 
ToolRunner.invokeClass("org.apache.cassandra.fqltool.FullQueryLogTool", 
+                                                              "dump",
+                                                              "--",
+                                                              
temporaryFolder.getRoot().getAbsolutePath());
+        assertTrue(runner.getStdout().contains(insertTemplate));
+        assertEquals(0, runner.getExitCode());
+
+        Object[][] preReplayResult = CLUSTER.get(1).executeInternal(select);
+        assertRows(preReplayResult, row(0, 0, null));
+
+        // Make sure the row no longer exists after truncate...
+        CLUSTER.get(1).executeInternal(withKeyspace("TRUNCATE %s." + 
tableName));
+        assertRows(CLUSTER.get(1).executeInternal(select));
+
+        // ...insert a new row with an actual value for the set at an earlier 
timestamp...
+        CLUSTER.get(1).executeInternal(withKeyspace("INSERT INTO %s." + 
tableName + " (k, c, s) VALUES ( ?, ?, ?) USING TIMESTAMP 1"), 0, 0, 
Sets.newHashSet(1));
+        assertRows(CLUSTER.get(1).executeInternal(select), row(0, 0, 
Sets.newHashSet(1)));
+
+        runner = 
ToolRunner.invokeClass("org.apache.cassandra.fqltool.FullQueryLogTool",
+                                        "replay",
+                                        "--keyspace", KEYSPACE,
+                                        "--target", "127.0.0.1",
+                                        "--", 
temporaryFolder.getRoot().getAbsolutePath());
+        assertEquals(0, runner.getExitCode());
+
+        // ...then ensure the replayed row deletes the one we wrote before 
replay.
+        Object[][] postReplayResult = CLUSTER.get(1).executeInternal(select);
+        assertRows(postReplayResult, preReplayResult);
+    }
+
+    @AfterClass
+    public static void afterClass()
+    {
+        if (CLUSTER != null)
+            CLUSTER.close();
+    }
+}
diff --git a/tools/fqltool/src/org/apache/cassandra/fqltool/FQLQueryReader.java 
b/tools/fqltool/src/org/apache/cassandra/fqltool/FQLQueryReader.java
index 20f362b2a6..c717a6e5ce 100644
--- a/tools/fqltool/src/org/apache/cassandra/fqltool/FQLQueryReader.java
+++ b/tools/fqltool/src/org/apache/cassandra/fqltool/FQLQueryReader.java
@@ -94,7 +94,10 @@ public class FQLQueryReader implements ReadMarshallable
                     values.add(subValues);
                     int numSubValues = in.int32();
                     for (int zz = 0; zz < numSubValues; zz++)
-                        subValues.add(ByteBuffer.wrap(in.bytes()));
+                    {
+                        byte[] valueBytes = in.bytes();
+                        subValues.add(valueBytes == null ? null : 
ByteBuffer.wrap(valueBytes));
+                    }
                 }
                 query = new FQLQuery.Batch(keyspace,
                                            protocolVersion,
diff --git a/tools/fqltool/src/org/apache/cassandra/fqltool/commands/Dump.java 
b/tools/fqltool/src/org/apache/cassandra/fqltool/commands/Dump.java
index e954f81933..e383dbc7ba 100644
--- a/tools/fqltool/src/org/apache/cassandra/fqltool/commands/Dump.java
+++ b/tools/fqltool/src/org/apache/cassandra/fqltool/commands/Dump.java
@@ -126,7 +126,7 @@ public class Dump implements Runnable
                     break;
 
                 case (FullQueryLogger.BATCH):
-                    dumpBatch(options, wireIn, sb);
+                    dumpBatch(wireIn, sb);
                     break;
 
                 default:
@@ -183,7 +183,7 @@ public class Dump implements Runnable
         sb.append(System.lineSeparator());
     }
 
-    private static void dumpBatch(QueryOptions options, WireIn wireIn, 
StringBuilder sb)
+    private static void dumpBatch(WireIn wireIn, StringBuilder sb)
     {
         sb.append("Batch type: ")
           .append(wireIn.read(FullQueryLogger.BATCH_TYPE).text())
@@ -203,7 +203,10 @@ public class Dump implements Runnable
             int numSubValues = in.int32();
             List<ByteBuffer> subValues = new ArrayList<>(numSubValues);
             for (int j = 0; j < numSubValues; j++)
-                subValues.add(ByteBuffer.wrap(in.bytes()));
+            {
+                byte[] valueBytes = in.bytes();
+                subValues.add(valueBytes == null ? null : 
ByteBuffer.wrap(valueBytes));
+            }
 
             sb.append("Query: ")
               .append(queries.get(i))


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to