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

sarankk pushed a commit to branch mutation-tracking-support
in repository https://gitbox.apache.org/repos/asf/cassandra-analytics.git


The following commit(s) were added to refs/heads/mutation-tracking-support by 
this push:
     new 6064e02e CASSANALYTICS-160: Determine whether mutation tracking is 
enabled for keyspace for bulk writes (#214)
6064e02e is described below

commit 6064e02e7c896ae8f15a6cd4f0e9ad3559df1de0
Author: Saranya Krishnakumar <[email protected]>
AuthorDate: Tue Jul 7 16:13:55 2026 -0700

    CASSANALYTICS-160: Determine whether mutation tracking is enabled for 
keyspace for bulk writes (#214)
    
    patch by Saranya Krishnakumar; reviewed by Shailaja Koppu, Yifan Cai for 
CASSANALYTICS-160
---
 CHANGES.txt                                        |   1 +
 .../org/apache/cassandra/spark/utils/CqlUtils.java |  32 +++++
 .../spark/bulkwriter/CassandraClusterInfo.java     |  11 ++
 .../CassandraDirectDataTransportContext.java       |  31 +++--
 .../cassandra/spark/bulkwriter/ClusterInfo.java    |  10 ++
 .../bulkwriter/TrackedDirectStreamSession.java     |  70 ++++++++++
 .../spark/bulkwriter/CassandraClusterInfoTest.java |  81 +++++++++++
 .../CassandraDirectDataTransportContextTest.java   | 151 +++++++++++++++++++++
 .../spark/bulkwriter/MockBulkWriterContext.java    |  12 ++
 .../apache/cassandra/spark/utils/CqlUtilsTest.java |  37 +++++
 .../apache/cassandra/bridge/CassandraBridge.java   |  13 ++
 11 files changed, 441 insertions(+), 8 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 974b9cea..19a658dc 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 0.5.0
 -----
+ * Determine whether mutation tracking is enabled for keyspace for bulk writes 
(CASSANALYTICS-160)
  * Upgrade sidecar version to 0.4.0
  * Exclude IP address from RingInstance equality so node replacement does not 
fail bulk write jobs (CASSANALYTICS-175)
  * Regenerate bloom filters for CQLSSTableWriter (CASSANALYTICS-167)
diff --git 
a/cassandra-analytics-common/src/main/java/org/apache/cassandra/spark/utils/CqlUtils.java
 
b/cassandra-analytics-common/src/main/java/org/apache/cassandra/spark/utils/CqlUtils.java
index 9d989be5..8511dbaa 100644
--- 
a/cassandra-analytics-common/src/main/java/org/apache/cassandra/spark/utils/CqlUtils.java
+++ 
b/cassandra-analytics-common/src/main/java/org/apache/cassandra/spark/utils/CqlUtils.java
@@ -52,6 +52,9 @@ public final class CqlUtils
                                                                                
         "max_index_interval"
                                                                                
         );
     private static final Pattern REPLICATION_FACTOR_PATTERN = 
Pattern.compile("WITH REPLICATION = (\\{[^\\}]*\\})");
+    private static final String TRACKED_REPLICATION_TYPE = "tracked";
+    private static final Pattern REPLICATION_TYPE_PATTERN = 
Pattern.compile("replication_type\\s*=\\s*'(\\w+)'",
+                                                                             
Pattern.CASE_INSENSITIVE);
     // Initialize a mapper allowing single quotes to process the RF string 
from the CREATE KEYSPACE statement
     private static final ObjectMapper MAPPER = new 
ObjectMapper().configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
     private static final Pattern ESCAPED_WHITESPACE_PATTERN = 
Pattern.compile("(\\\\r|\\\\n|\\\\r\\n)+");
@@ -296,4 +299,33 @@ public final class CqlUtils
     {
         return compactionStrategy == null || 
compactionStrategy.endsWith("TimeWindowCompactionStrategy");
     }
+
+    /**
+     * Extracts replication type from create schema statement
+     *
+     * @param schemaStr full cluster schema string as returned by Sidecar
+     * @param keyspace  name of the keyspace to check
+     * @return {@code true} if keyspace is tracked {@code false} otherwise
+     */
+    public static String extractReplicationType(@NotNull String schemaStr, 
@NotNull String keyspace)
+    {
+        String createKeyspaceSchema = extractKeyspaceSchema(schemaStr, 
keyspace);
+        Matcher matcher = 
REPLICATION_TYPE_PATTERN.matcher(createKeyspaceSchema);
+        if (matcher.find())
+        {
+            return matcher.group(1);
+        }
+        return null;
+    }
+
+    /**
+     * Returns {@code true} if {@code replication_type = 'tracked'} in create 
statement otherwise {@code false}
+     *
+     * @param replicationType replication type extracted from create statement
+     * @return {@code true} if replication type is tracked {@code false} 
otherwise
+     */
+    public static boolean isTracked(String replicationType)
+    {
+        return TRACKED_REPLICATION_TYPE.equalsIgnoreCase(replicationType);
+    }
 }
diff --git 
a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/CassandraClusterInfo.java
 
b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/CassandraClusterInfo.java
index a9c12087..53ac9797 100644
--- 
a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/CassandraClusterInfo.java
+++ 
b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/CassandraClusterInfo.java
@@ -356,6 +356,17 @@ public class CassandraClusterInfo implements ClusterInfo, 
Closeable
         }
     }
 
+    @Override
+    public String getReplicationType()
+    {
+        String keyspaceSchema = getKeyspaceSchema(true);
+        if (keyspaceSchema == null)
+        {
+            throw new RuntimeException("Could not retrieve keyspace schema 
information for keyspace " + conf.keyspace);
+        }
+        return CqlUtils.extractReplicationType(keyspaceSchema, conf.keyspace);
+    }
+
     @Override
     public TokenRangeMapping<RingInstance> getTokenRangeMapping(boolean cached)
     {
diff --git 
a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/CassandraDirectDataTransportContext.java
 
b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/CassandraDirectDataTransportContext.java
index bed36ac4..fb82483a 100644
--- 
a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/CassandraDirectDataTransportContext.java
+++ 
b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/CassandraDirectDataTransportContext.java
@@ -46,13 +46,24 @@ public class CassandraDirectDataTransportContext implements 
TransportContext.Dir
     }
 
     @Override
-    public DirectStreamSession createStreamSession(BulkWriterContext 
writerContext,
-                                                   String sessionId,
-                                                   SortedSSTableWriter 
sstableWriter,
-                                                   Range<BigInteger> range,
-                                                   
ReplicaAwareFailureHandler<RingInstance> failureHandler,
-                                                   ExecutorService 
executorService)
+    public StreamSession<TransportContext.DirectDataBulkWriterContext> 
createStreamSession(
+            BulkWriterContext writerContext,
+            String sessionId,
+            SortedSSTableWriter sstableWriter,
+            Range<BigInteger> range,
+            ReplicaAwareFailureHandler<RingInstance> failureHandler,
+            ExecutorService executorService)
     {
+        if (bridge().isTracked(clusterInfo.getReplicationType()))
+        {
+            return new TrackedDirectStreamSession(writerContext,
+                                                  sstableWriter,
+                                                  this,
+                                                  sessionId,
+                                                  range,
+                                                  failureHandler,
+                                                  executorService);
+        }
         return new DirectStreamSession(writerContext,
                                        sstableWriter,
                                        this,
@@ -71,7 +82,11 @@ public class CassandraDirectDataTransportContext implements 
TransportContext.Dir
     // only invoke in constructor
     protected DirectDataTransferApi createDirectDataTransferApi()
     {
-        CassandraBridge bridge = 
CassandraBridgeFactory.get(clusterInfo.getLowestCassandraVersion());
-        return new SidecarDataTransferApi(clusterInfo.getCassandraContext(), 
bridge, jobInfo);
+        return new SidecarDataTransferApi(clusterInfo.getCassandraContext(), 
bridge(), jobInfo);
+    }
+
+    private CassandraBridge bridge()
+    {
+        return 
CassandraBridgeFactory.get(clusterInfo.getLowestCassandraVersion());
     }
 }
diff --git 
a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/ClusterInfo.java
 
b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/ClusterInfo.java
index 64b65444..e4d8a0a0 100644
--- 
a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/ClusterInfo.java
+++ 
b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/ClusterInfo.java
@@ -82,6 +82,16 @@ public interface ClusterInfo extends StartupValidatable
      */
     ReplicationFactor replicationFactor();
 
+    /**
+     * @return {@code replication_type} of the enclosing keyspace (e.g. {@code 
"tracked"}, {@code "untracked"}),
+     * or {@code null} if replication_type is absent
+     */
+    @Nullable
+    default String getReplicationType()
+    {
+        return null;
+    }
+
     CassandraContext getCassandraContext();
 
     /**
diff --git 
a/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/TrackedDirectStreamSession.java
 
b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/TrackedDirectStreamSession.java
new file mode 100644
index 00000000..166ef238
--- /dev/null
+++ 
b/cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/bulkwriter/TrackedDirectStreamSession.java
@@ -0,0 +1,70 @@
+/*
+ * 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.spark.bulkwriter;
+
+import java.math.BigInteger;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import com.google.common.collect.Range;
+
+import org.apache.cassandra.bridge.SSTableDescriptor;
+import org.apache.cassandra.spark.bulkwriter.token.ReplicaAwareFailureHandler;
+
+/**
+ * Stream session for bulk writes to keyspaces with mutation tracking enabled.
+ *
+ * <p>
+ * Tracked stream session uploads and triggers import on the <em>coordinator 
node only</em>. Cassandra's coordinated
+ * transfer then propagates the data to all other replicas, avoiding the 
duplicate-row updates that would occur if each
+ * replica independently streamed the data to its peers.
+ * <p>
+ */
+public class TrackedDirectStreamSession extends 
StreamSession<TransportContext.DirectDataBulkWriterContext>
+{
+    public TrackedDirectStreamSession(BulkWriterContext writerContext,
+                                      SortedSSTableWriter sstableWriter,
+                                      
TransportContext.DirectDataBulkWriterContext transportContext,
+                                      String sessionID,
+                                      Range<BigInteger> tokenRange,
+                                      ReplicaAwareFailureHandler<RingInstance> 
failureHandler,
+                                      ExecutorService executorService)
+    {
+        super(writerContext, sstableWriter, transportContext, sessionID, 
tokenRange, failureHandler, executorService);
+    }
+
+    @Override
+    protected void onSSTablesProduced(Set<SSTableDescriptor> sstables)
+    {
+        throw new UnsupportedOperationException("TrackedDirectStreamSession is 
not yet implemented");
+    }
+
+    @Override
+    protected StreamResult doFinalizeStream()
+    {
+        throw new UnsupportedOperationException("TrackedDirectStreamSession is 
not yet implemented");
+    }
+
+    @Override
+    protected void sendRemainingSSTables()
+    {
+        throw new UnsupportedOperationException("TrackedDirectStreamSession is 
not yet implemented");
+    }
+}
diff --git 
a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/CassandraClusterInfoTest.java
 
b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/CassandraClusterInfoTest.java
index 5ed4c522..763082a3 100644
--- 
a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/CassandraClusterInfoTest.java
+++ 
b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/CassandraClusterInfoTest.java
@@ -30,8 +30,10 @@ import org.junit.jupiter.api.Test;
 import o.a.c.sidecar.client.shaded.common.response.TimeSkewResponse;
 import org.apache.cassandra.spark.bulkwriter.token.TokenRangeMapping;
 import org.apache.cassandra.spark.exception.TimeSkewTooLargeException;
+import org.apache.cassandra.spark.utils.CqlUtils;
 
 import static org.apache.cassandra.spark.TestUtils.range;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatNoException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
@@ -75,6 +77,50 @@ public class CassandraClusterInfoTest
         return new MockClusterInfoForTimeSkew(allowanceMinutes, remoteNow);
     }
 
+    @Test
+    void testGetTrackedReplicationType()
+    {
+        String schema = "CREATE KEYSPACE mykeyspace "
+                        + "WITH replication = {'class': 
'NetworkTopologyStrategy', 'dc1': '3'}"
+                        + "  AND durable_writes = true"
+                        + "  AND replication_type = 'tracked';";
+        CassandraClusterInfo ci = mockClusterInfoWithSchema("mykeyspace", 
schema);
+        assertThat(ci.getReplicationType())
+        .describedAs("Keyspace with replication_type = 'tracked' should return 
'tracked'")
+        .isEqualTo("tracked");
+    }
+
+    @Test
+    void testGetUntrackedReplicationType()
+    {
+        String schema = "CREATE KEYSPACE k "
+                        + "WITH replication = {'class': 
'NetworkTopologyStrategy', 'dc1': '3'}"
+                        + "  AND durable_writes = true"
+                        + "  AND replication_type = 'untracked';";
+        CassandraClusterInfo ci = mockClusterInfoWithSchema("k", schema);
+        assertThat(ci.getReplicationType())
+        .describedAs("Keyspace with replication_type = 'untracked' should 
return 'untracked'")
+        .isEqualTo("untracked");
+    }
+
+    @Test
+    void testGetReplicationTypeReturnsNullWhenPropertyAbsent()
+    {
+        // Schema without replication_type — pre-mutation-tracking clusters
+        String schema = "CREATE KEYSPACE k "
+                        + "WITH replication = {'class': 
'NetworkTopologyStrategy', 'dc1': '3'}"
+                        + "  AND durable_writes = true;";
+        CassandraClusterInfo ci = mockClusterInfoWithSchema("k", schema);
+        assertThat(ci.getReplicationType())
+        .describedAs("Keyspace without replication_type property should return 
null")
+        .isNull();
+    }
+
+    private static CassandraClusterInfo mockClusterInfoWithSchema(String 
keyspace, String schemaStr)
+    {
+        return new MockClusterInfoForSchema(keyspace, schemaStr);
+    }
+
     private static class MockClusterInfoForTimeSkew extends 
CassandraClusterInfo
     {
         private CassandraContext cassandraContext;
@@ -107,4 +153,39 @@ public class CassandraClusterInfoTest
             when(cassandraContext.sidecarPort()).thenReturn(9043);
         }
     }
+
+    /**
+     * Minimal ClusterInfo stub that returns a fixed keyspace schema string, 
used to test
+     * {@link CassandraClusterInfo#getReplicationType()}.
+     */
+    private static class MockClusterInfoForSchema extends CassandraClusterInfo
+    {
+        private final String keyspaceName;
+        private final String schemaStr;
+
+        MockClusterInfoForSchema(String keyspace, String schemaStr)
+        {
+            super((BulkSparkConf) null);
+            this.keyspaceName = keyspace;
+            this.schemaStr = schemaStr;
+        }
+
+        @Override
+        protected CassandraContext buildCassandraContext()
+        {
+            return mock(CassandraContext.class, RETURNS_DEEP_STUBS);
+        }
+
+        @Override
+        public String getKeyspaceSchema(boolean cached)
+        {
+            return schemaStr;
+        }
+
+        @Override
+        public String getReplicationType()
+        {
+            return CqlUtils.extractReplicationType(schemaStr, keyspaceName);
+        }
+    }
 }
diff --git 
a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/CassandraDirectDataTransportContextTest.java
 
b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/CassandraDirectDataTransportContextTest.java
new file mode 100644
index 00000000..0bd2170b
--- /dev/null
+++ 
b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/CassandraDirectDataTransportContextTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.spark.bulkwriter;
+
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import com.google.common.collect.BoundType;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Range;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import 
org.apache.cassandra.spark.bulkwriter.token.MultiClusterReplicaAwareFailureHandler;
+import org.apache.cassandra.spark.bulkwriter.token.ReplicaAwareFailureHandler;
+import org.apache.cassandra.spark.bulkwriter.token.TokenRangeMapping;
+import org.apache.cassandra.spark.data.ReplicationFactor;
+import org.apache.cassandra.spark.utils.XXHash32DigestAlgorithm;
+
+import static 
org.apache.cassandra.spark.data.ReplicationFactor.ReplicationStrategy.NetworkTopologyStrategy;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests that {@link CassandraDirectDataTransportContext#createStreamSession}.
+ */
+public class CassandraDirectDataTransportContextTest
+{
+    @TempDir
+    private Path folder;
+
+    private MockBulkWriterContext writerContext;
+    private MockTableWriter tableWriter;
+    private ExecutorService executor;
+    private Range<BigInteger> range;
+
+    @BeforeEach
+    public void setup()
+    {
+        ImmutableMap<String, Integer> rfOptions = ImmutableMap.of("DC1", 3);
+        ReplicationFactor rf = new ReplicationFactor(NetworkTopologyStrategy, 
rfOptions);
+        TokenRangeMapping<RingInstance> tokenRangeMapping = 
TokenRangeMappingUtils.buildTokenRangeMapping(0, rfOptions, 12);
+        writerContext = new MockBulkWriterContext(tokenRangeMapping);
+        writerContext.setReplicationFactor(rf);
+        tableWriter = new MockTableWriter(folder);
+        range = Range.range(BigInteger.valueOf(101L), BoundType.CLOSED, 
BigInteger.valueOf(199L), BoundType.CLOSED);
+        executor = Executors.newSingleThreadExecutor();
+    }
+
+    @AfterEach
+    public void tearDown()
+    {
+        executor.shutdownNow();
+    }
+
+    @Test
+    void createDirectStreamSessionForUntrackedKeyspace()
+    {
+        writerContext.setTrackedKeyspace(false);
+        CassandraDirectDataTransportContext transportContext = 
stubTransportContext();
+
+        StreamSession<?> session = transportContext.createStreamSession(
+        writerContext,
+        "session-untracked",
+        new SortedSSTableWriter(tableWriter, folder, new 
XXHash32DigestAlgorithm(), 1),
+        range,
+        buildFailureHandler(),
+        executor);
+
+        assertThat(session)
+        .describedAs("Untracked keyspace should produce a DirectStreamSession")
+        .isInstanceOf(DirectStreamSession.class);
+    }
+
+    @Test
+    void createTrackedDirectStreamSessionForTrackedKeyspace()
+    {
+        writerContext.setTrackedKeyspace(true);
+        CassandraDirectDataTransportContext transportContext = 
stubTransportContext();
+
+        StreamSession<?> session = transportContext.createStreamSession(
+        writerContext,
+        "session-tracked",
+        new SortedSSTableWriter(tableWriter, folder, new 
XXHash32DigestAlgorithm(), 1),
+        range,
+        buildFailureHandler(),
+        executor);
+
+        assertThat(session)
+        .describedAs("Tracked keyspace should produce a 
TrackedDirectStreamSession")
+        .isInstanceOf(TrackedDirectStreamSession.class);
+    }
+
+    @Test
+    void createDirectStreamSessionForAbsentReplicationType()
+    {
+        CassandraDirectDataTransportContext transportContext = 
stubTransportContext();
+
+        StreamSession<?> session = transportContext.createStreamSession(
+        writerContext,
+        "session-null-type",
+        new SortedSSTableWriter(tableWriter, folder, new 
XXHash32DigestAlgorithm(), 1),
+        range,
+        buildFailureHandler(),
+        executor);
+
+        assertThat(session)
+        .describedAs("Null replication type (pre-mutation-tracking) should 
produce a DirectStreamSession")
+        .isInstanceOf(DirectStreamSession.class);
+    }
+
+    private CassandraDirectDataTransportContext stubTransportContext()
+    {
+        DirectDataTransferApi mockApi =
+        ((TransportContext.DirectDataBulkWriterContext) 
writerContext.transportContext()).dataTransferApi();
+
+        return new CassandraDirectDataTransportContext(writerContext)
+        {
+            @Override
+            protected DirectDataTransferApi createDirectDataTransferApi()
+            {
+                return mockApi;
+            }
+        };
+    }
+
+    private ReplicaAwareFailureHandler<RingInstance> buildFailureHandler()
+    {
+        return new 
MultiClusterReplicaAwareFailureHandler<>(writerContext.cluster().getPartitioner());
+    }
+}
diff --git 
a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/MockBulkWriterContext.java
 
b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/MockBulkWriterContext.java
index cb5564f0..be05715c 100644
--- 
a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/MockBulkWriterContext.java
+++ 
b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/bulkwriter/MockBulkWriterContext.java
@@ -99,6 +99,7 @@ public class MockBulkWriterContext implements 
BulkWriterContext, ClusterInfo, Jo
 
     private final UUID jobId;
     private boolean skipClean = false;
+    private String replicationType = null;
     public int refreshClusterInfoCallCount = 0;  // CHECKSTYLE IGNORE: Public 
mutable field
     private final Map<CassandraInstance, List<UploadRequest>> uploads = new 
ConcurrentHashMap<>();
     private final Map<CassandraInstance, List<String>> commits = new 
ConcurrentHashMap<>();
@@ -204,6 +205,17 @@ public class MockBulkWriterContext implements 
BulkWriterContext, ClusterInfo, Jo
         this.replicationFactor = replicationFactor;
     }
 
+    @Override
+    public String getReplicationType()
+    {
+        return replicationType;
+    }
+
+    public void setTrackedKeyspace(boolean tracked)
+    {
+        this.replicationType = tracked ? "tracked" : "untracked";
+    }
+
     @Override
     public CassandraContext getCassandraContext()
     {
diff --git 
a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/utils/CqlUtilsTest.java
 
b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/utils/CqlUtilsTest.java
index 5bb363d9..58fb5c96 100644
--- 
a/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/utils/CqlUtilsTest.java
+++ 
b/cassandra-analytics-core/src/test/java/org/apache/cassandra/spark/utils/CqlUtilsTest.java
@@ -833,6 +833,43 @@ public class CqlUtilsTest extends VersionRunner
         assertThat(isTimeRangeFilterSupported("")).isFalse();
     }
 
+    @Test
+    public void testExtractReplicationType()
+    {
+        String schemaStrTracked
+        = "CREATE KEYSPACE k WITH REPLICATION "
+          + "= { 'class' : 
'org.apache.cassandra.locator.NetworkTopologyStrategy', 'dc1': '3' }"
+          + " AND DURABLE_WRITES = true AND replication_type = 'tracked';";
+        assertThat(CqlUtils.extractReplicationType(schemaStrTracked, 
"k")).isEqualTo("tracked");
+        String schemaStrTrackedCaseInsensitive
+        = "CREATE KEYSPACE k WITH REPLICATION "
+          + "= { 'class' : 
'org.apache.cassandra.locator.NetworkTopologyStrategy', 'dc1': '3' }"
+          + " AND DURABLE_WRITES = true AND REPLICATION_TYPE = 'Tracked';";
+        
assertThat(CqlUtils.extractReplicationType(schemaStrTrackedCaseInsensitive, 
"k")).isEqualTo("Tracked");
+        String schemaStrUntracked
+        = "CREATE KEYSPACE k WITH REPLICATION "
+          + "= { 'class' : 
'org.apache.cassandra.locator.NetworkTopologyStrategy', 'dc1': '3' }"
+          + " AND DURABLE_WRITES = true AND replication_type = 'untracked';";
+        assertThat(CqlUtils.extractReplicationType(schemaStrUntracked, 
"k")).isEqualTo("untracked");
+        String schemaStrNoReplicationType
+        = "CREATE KEYSPACE k WITH REPLICATION "
+          + "= { 'class' : 
'org.apache.cassandra.locator.NetworkTopologyStrategy', 'dc1': '3' }"
+          + " AND DURABLE_WRITES = true;";
+        assertThat(CqlUtils.extractReplicationType(schemaStrNoReplicationType, 
"k")).isNull();
+    }
+
+    @Test
+    public void testIsTracked()
+    {
+        assertThat(CqlUtils.isTracked("tracked")).isTrue();
+        assertThat(CqlUtils.isTracked("TRACKED")).isTrue();
+        assertThat(CqlUtils.isTracked("Tracked")).isTrue();
+        assertThat(CqlUtils.isTracked("untracked")).isFalse();
+        assertThat(CqlUtils.isTracked("random")).isFalse();
+        assertThat(CqlUtils.isTracked(null)).isFalse();
+        assertThat(CqlUtils.isTracked("")).isFalse();
+    }
+
     private static String loadFullSchemaSample() throws IOException
     {
         Path fullSchemaSampleFile = 
ResourceUtils.writeResourceToPath(CqlUtilsTest.class.getClassLoader(), 
tempPath, "cql/fullSchema.cql");
diff --git 
a/cassandra-bridge/src/main/java/org/apache/cassandra/bridge/CassandraBridge.java
 
b/cassandra-bridge/src/main/java/org/apache/cassandra/bridge/CassandraBridge.java
index 67c5bc7a..d2d67ab9 100644
--- 
a/cassandra-bridge/src/main/java/org/apache/cassandra/bridge/CassandraBridge.java
+++ 
b/cassandra-bridge/src/main/java/org/apache/cassandra/bridge/CassandraBridge.java
@@ -62,6 +62,7 @@ import 
org.apache.cassandra.spark.sparksql.filters.PruneColumnFilter;
 import org.apache.cassandra.spark.sparksql.filters.SparkRangeFilter;
 import org.apache.cassandra.analytics.stats.Stats;
 import org.apache.cassandra.spark.sparksql.filters.SSTableTimeRangeFilter;
+import org.apache.cassandra.spark.utils.CqlUtils;
 import org.apache.cassandra.spark.utils.TimeProvider;
 import org.apache.cassandra.util.CompressionUtil;
 import org.jetbrains.annotations.NotNull;
@@ -167,6 +168,18 @@ public abstract class CassandraBridge
         return cassandraTypes().maybeQuoteIdentifier(identifier);
     }
 
+    /**
+     * @return {@code true} if mutation tracking is enabled given the 
replication type, {@code false} otherwise
+     */
+    public boolean isTracked(String replicationType)
+    {
+        if (replicationType == null)
+        {
+            return false;
+        }
+        return CqlUtils.isTracked(replicationType);
+    }
+
     // CQL Type Parsing
 
     public CqlField.CqlType readType(CqlField.CqlType.InternalType type, Input 
input)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to