You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by if...@apache.org on 2021/07/15 07:27:45 UTC

[cassandra-harry] 01/04: Core improvements

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

ifesdjeen pushed a commit to branch CASSANDRA-16262-2
in repository https://gitbox.apache.org/repos/asf/cassandra-harry.git

commit f22d52e8e995411fd8190c8d282582975a8e02b1
Author: Alex Petrov <ol...@gmail.com>
AuthorDate: Mon Jul 12 17:04:37 2021 +0200

    Core improvements
    
        Major features:
          * Implement updates
          * Make sure we can advance RNGs from zero as well
          * Fix a problem with predictable descriptor
        Bugfixes:
          * Fix column mask inconsistencies
          * Fix a problem with partition key liveness info
        Quality of life improvements:
          * Get rid of driver dependency for query generation
          * Get rid of guava dependency
          * Add reusable config files
          * Switch from streams to iterables
        General improvements:
          * Make unset and nil descriptors more distinct and harder to generate particularly for the smaller descriptors
          * Fixed schema configurator to allow empty column sets
          * Move workloads to a common dir
          * Fixed schema configurator to output correct json
          * No-op checker to execute with Quorum, not ALL
          * Make tag for build unique
    
    Patch by Alex Petrov for CASSANDRA-16262
---
 harry-core/pom.xml                                 |   9 +-
 harry-core/src/harry/core/Configuration.java       |  44 +++++-
 harry-core/src/harry/core/VisibleForTesting.java   |   5 +
 harry-core/src/harry/corruptor/RowCorruptor.java   |   2 +-
 harry-core/src/harry/ddl/ColumnSpec.java           |  10 +-
 harry-core/src/harry/ddl/SchemaGenerators.java     |  76 +++++----
 harry-core/src/harry/ddl/SchemaSpec.java           | 102 ++++++++++---
 .../src/harry/generators/DataGenerators.java       |  17 ++-
 .../src/harry/generators/RandomGenerator.java      |   3 +-
 harry-core/src/harry/generators/RngUtils.java      |   6 +-
 harry-core/src/harry/generators/Surjections.java   |   2 +-
 .../harry/model/AlwaysSamePartitionSelector.java   |  69 +++++++++
 harry-core/src/harry/model/NoOpChecker.java        |   3 +-
 harry-core/src/harry/model/OpSelectors.java        |  54 +++++--
 harry-core/src/harry/model/SelectHelper.java       | 114 ++++++++++----
 .../model/clock/ApproximateMonotonicClock.java     |   3 +-
 harry-core/src/harry/model/clock/OffsetClock.java  |  20 +++
 harry-core/src/harry/model/sut/PrintlnSut.java     |  18 +++
 harry-core/src/harry/operations/DeleteHelper.java  |  61 ++++----
 harry-core/src/harry/operations/Relation.java      |  80 ++--------
 harry-core/src/harry/operations/WriteHelper.java   | 170 +++++++++++----------
 harry-core/src/harry/reconciler/Reconciler.java    |  61 +++++---
 .../harry/runner/CorruptingPartitionVisitor.java   |   1 -
 harry-core/src/harry/runner/DataTracker.java       |   3 +
 .../src/harry/runner/DefaultDataTracker.java       |   2 +-
 .../src/harry/runner/MutatingPartitionVisitor.java |   1 +
 .../src/harry/runner/MutatingRowVisitor.java       |  44 +++++-
 harry-core/src/harry/runner/Operation.java         |  20 ++-
 harry-core/src/harry/runner/QueryGenerator.java    |   2 +-
 harry-core/src/harry/util/BitSet.java              |   2 +-
 harry-core/src/harry/util/TestRunner.java          |   9 +-
 harry-core/test/harry/model/OpSelectorsTest.java   |  34 +++--
 32 files changed, 691 insertions(+), 356 deletions(-)

diff --git a/harry-core/pom.xml b/harry-core/pom.xml
index fef070d..d313e7a 100755
--- a/harry-core/pom.xml
+++ b/harry-core/pom.xml
@@ -33,10 +33,11 @@
     <name>Harry Core</name>
 
     <dependencies>
-        <dependency>
-            <groupId>com.datastax.cassandra</groupId>
-            <artifactId>cassandra-driver-core</artifactId>
-        </dependency>
+         <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.25</version>
+         </dependency>
 
         <dependency>
             <groupId>org.apache.commons</groupId>
diff --git a/harry-core/src/harry/core/Configuration.java b/harry-core/src/harry/core/Configuration.java
index bd011c2..4a2a1e6 100644
--- a/harry-core/src/harry/core/Configuration.java
+++ b/harry-core/src/harry/core/Configuration.java
@@ -38,10 +38,13 @@ import harry.ddl.SchemaGenerators;
 import harry.ddl.SchemaSpec;
 import harry.generators.Surjections;
 import harry.generators.distribution.Distribution;
+import harry.model.AlwaysSamePartitionSelector;
 import harry.model.Model;
 import harry.model.OpSelectors;
 import harry.model.QuiescentChecker;
 import harry.model.clock.ApproximateMonotonicClock;
+import harry.model.clock.OffsetClock;
+import harry.model.sut.PrintlnSut;
 import harry.model.sut.SystemUnderTest;
 import harry.runner.AllPartitionsValidator;
 import harry.runner.CorruptingPartitionVisitor;
@@ -92,6 +95,11 @@ public class Configuration
         mapper.registerSubtypes(CorruptingPartitionVisitorConfiguration.class);
         mapper.registerSubtypes(RecentPartitionsValidatorConfiguration.class);
         mapper.registerSubtypes(FixedSchemaProviderConfiguration.class);
+        mapper.registerSubtypes(AlwaysSamePartitionSelector.AlwaysSamePartitionSelectorConfiguration.class);
+        mapper.registerSubtypes(OffsetClock.OffsetClockConfiguration.class);
+        mapper.registerSubtypes(PrintlnSut.PrintlnSutConfiguration.class);
+        mapper.registerSubtypes(NoOpDataTrackerConfiguration.class);
+        mapper.registerSubtypes(NoOpMetricReporterConfiguration.class);
     }
 
     public final long seed;
@@ -407,7 +415,7 @@ public class Configuration
 
     }
 
-    @JsonTypeName("no_op_tracker")
+    @JsonTypeName("no_op")
     public static class NoOpDataTrackerConfiguration implements DataTrackerConfiguration
     {
         @JsonCreator
@@ -613,7 +621,7 @@ public class Configuration
         }
     }
 
-    @JsonTypeName("no_op_checker")
+    @JsonTypeName("no_op")
     public static class NoOpCheckerConfig implements ModelConfiguration
     {
         @JsonCreator
@@ -692,7 +700,7 @@ public class Configuration
         private Map<OpSelectors.OperationKind, Integer> operation_kind_weights = new OperationKindSelectorBuilder()
                                                                                  .addWeight(OpSelectors.OperationKind.DELETE_ROW, 1)
                                                                                  .addWeight(OpSelectors.OperationKind.DELETE_COLUMN, 1)
-                                                                                 .addWeight(OpSelectors.OperationKind.WRITE, 98)
+                                                                                 .addWeight(OpSelectors.OperationKind.INSERT, 98)
                                                                                  .build();
         private Map<OpSelectors.OperationKind, long[]> column_mask_bitsets;
         private int[] fractions;
@@ -1057,6 +1065,12 @@ public class Configuration
     @JsonTypeName("fixed")
     public static class FixedSchemaProviderConfiguration implements SchemaProviderConfiguration
     {
+        public final String keyspace;
+        public final String table;
+        public final Map<String, String> partition_keys;
+        public final Map<String, String> clustering_keys;
+        public final Map<String, String> regular_columns;
+        public final Map<String, String> static_keys;
         private final SchemaSpec schemaSpec;
 
         @JsonCreator
@@ -1067,10 +1081,28 @@ public class Configuration
                                                 @JsonProperty("regular_columns") Map<String, String> regulars,
                                                 @JsonProperty("static_columns") Map<String, String> statics)
         {
-            this.schemaSpec = SchemaGenerators.parse(keyspace, table,
-                                                     pks, cks, regulars, statics);
+            this(SchemaGenerators.parse(keyspace, table,
+                                        pks, cks, regulars, statics),
+                 pks,
+                 cks,
+                 regulars,
+                 statics);
         }
 
+        public FixedSchemaProviderConfiguration(SchemaSpec schemaSpec,
+                                                Map<String, String> pks,
+                                                Map<String, String> cks,
+                                                Map<String, String> regulars,
+                                                Map<String, String> statics)
+        {
+            this.schemaSpec = schemaSpec;
+            this.keyspace = schemaSpec.keyspace;
+            this.table = schemaSpec.table;
+            this.partition_keys = pks;
+            this.clustering_keys = cks;
+            this.regular_columns = regulars;
+            this.static_keys = statics;
+        }
         public SchemaSpec make(long seed)
         {
             return schemaSpec;
@@ -1082,7 +1114,7 @@ public class Configuration
     {
     }
 
-    @JsonTypeName("default")
+    @JsonTypeName("no_op")
     public static class NoOpMetricReporterConfiguration implements MetricReporterConfiguration
     {
         public MetricReporter make()
diff --git a/harry-core/src/harry/core/VisibleForTesting.java b/harry-core/src/harry/core/VisibleForTesting.java
new file mode 100644
index 0000000..efa712e
--- /dev/null
+++ b/harry-core/src/harry/core/VisibleForTesting.java
@@ -0,0 +1,5 @@
+package harry.core;
+
+public @interface VisibleForTesting {
+}
+
diff --git a/harry-core/src/harry/corruptor/RowCorruptor.java b/harry-core/src/harry/corruptor/RowCorruptor.java
index 4c6b005..7b19cf1 100644
--- a/harry-core/src/harry/corruptor/RowCorruptor.java
+++ b/harry-core/src/harry/corruptor/RowCorruptor.java
@@ -29,7 +29,7 @@ import harry.operations.CompiledStatement;
 
 public interface RowCorruptor
 {
-    Logger logger = LoggerFactory.getLogger(QueryResponseCorruptor.class);
+    final Logger logger = LoggerFactory.getLogger(QueryResponseCorruptor.class);
 
     boolean canCorrupt(ResultSetRow row);
 
diff --git a/harry-core/src/harry/ddl/ColumnSpec.java b/harry-core/src/harry/ddl/ColumnSpec.java
index 94e9881..6d652c4 100644
--- a/harry-core/src/harry/ddl/ColumnSpec.java
+++ b/harry-core/src/harry/ddl/ColumnSpec.java
@@ -18,20 +18,18 @@
 
 package harry.ddl;
 
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;
 
-import com.google.common.collect.ImmutableList;
-
 import harry.generators.Bijections;
 import harry.generators.StringBijection;
 
-import static harry.generators.StringBijection.getByte;
-
 public class ColumnSpec<T>
 {
     public final String name;
@@ -318,7 +316,7 @@ public class ColumnSpec<T>
         }
     };
 
-    public static final Collection<DataType<?>> DATA_TYPES = ImmutableList.of(
+    public static final Collection<DataType<?>> DATA_TYPES = Collections.unmodifiableList(Arrays.asList(
     ColumnSpec.int8Type,
     ColumnSpec.int16Type,
     ColumnSpec.int32Type,
@@ -328,7 +326,7 @@ public class ColumnSpec<T>
     ColumnSpec.doubleType,
     ColumnSpec.asciiType,
     ColumnSpec.uuidType,
-    ColumnSpec.timestampType);
+    ColumnSpec.timestampType));
 
     public static class ReversedType<T> extends DataType<T>
     {
diff --git a/harry-core/src/harry/ddl/SchemaGenerators.java b/harry-core/src/harry/ddl/SchemaGenerators.java
index 47936e1..8508d44 100644
--- a/harry-core/src/harry/ddl/SchemaGenerators.java
+++ b/harry-core/src/harry/ddl/SchemaGenerators.java
@@ -21,16 +21,14 @@ package harry.ddl;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
 import harry.generators.Generator;
 import harry.generators.Surjections;
 
@@ -43,32 +41,30 @@ public class SchemaGenerators
         return new Builder(ks);
     }
 
-    public static final Collection<ColumnSpec.DataType<?>> clusteringKeyTypes;
     public static final Map<String, ColumnSpec.DataType<?>> nameToTypeMap;
     public static final Collection<ColumnSpec.DataType<?>> columnTypes;
+    public static final Collection<ColumnSpec.DataType<?>> partitionKeyTypes;
+    public static final Collection<ColumnSpec.DataType<?>> clusteringKeyTypes;
 
     static
     {
+        partitionKeyTypes = Collections.unmodifiableList(Arrays.asList(ColumnSpec.int64Type,
+                                                                       ColumnSpec.asciiType,
+                                                                       ColumnSpec.asciiType(4, 5),
+                                                                       ColumnSpec.asciiType(4, 10)));
+
+        columnTypes = Collections.unmodifiableList(Arrays.asList(
+//        ColumnSpec.int8Type,
+//                                                                 ColumnSpec.int16Type,
+//                                                                 ColumnSpec.int32Type,
+                                                                 ColumnSpec.int64Type,
+                                                                 ColumnSpec.asciiType,
+                                                                 ColumnSpec.asciiType(4, 256),
+                                                                 ColumnSpec.asciiType(4, 512)));
+
 
-        ImmutableList.Builder<ColumnSpec.DataType<?>> builder = ImmutableList.builder();
-        builder.add(ColumnSpec.int8Type,
-                    ColumnSpec.int16Type,
-                    ColumnSpec.int32Type,
-                    ColumnSpec.int64Type,
-                    ColumnSpec.asciiType,
-                    ColumnSpec.asciiType(4, 256),
-                    ColumnSpec.asciiType(4, 512));
-
-        columnTypes = builder.build();
-        builder.add(ColumnSpec.int8Type,
-                    ColumnSpec.int16Type,
-                    ColumnSpec.int32Type,
-                    ColumnSpec.int64Type,
-                    ColumnSpec.asciiType);
-        builder = ImmutableList.builder();
-        builder.addAll(columnTypes);
-
-        ImmutableMap.Builder<String, ColumnSpec.DataType<?>> mapBuilder = ImmutableMap.builder();
+        List<ColumnSpec.DataType<?>> builder = new ArrayList<>(columnTypes);
+        Map<String, ColumnSpec.DataType<?>> mapBuilder = new HashMap<>();
 
         for (ColumnSpec.DataType<?> columnType : columnTypes)
         {
@@ -82,8 +78,8 @@ public class SchemaGenerators
         builder.add(ColumnSpec.floatType);
         builder.add(ColumnSpec.doubleType);
 
-        clusteringKeyTypes = builder.build();
-        nameToTypeMap = mapBuilder.build();
+        clusteringKeyTypes = Collections.unmodifiableList(builder);
+        nameToTypeMap = Collections.unmodifiableMap(mapBuilder);
     }
 
     @SuppressWarnings("unchecked")
@@ -126,7 +122,7 @@ public class SchemaGenerators
 
                    public ColumnSpec<?> apply(ColumnSpec.DataType<?> type)
                    {
-                       return new ColumnSpec<>(prefix + (counter++),
+                       return new ColumnSpec<>(String.format("%s%04d", prefix, counter++),
                                                type,
                                                kind);
                    }
@@ -143,7 +139,24 @@ public class SchemaGenerators
 
                    public ColumnSpec<?> apply(ColumnSpec.DataType<?> type)
                    {
-                       return ColumnSpec.ck(prefix + (counter++), type);
+                       return ColumnSpec.ck(String.format("%s%04d", prefix, counter++), type);
+                   }
+               });
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Generator<ColumnSpec<?>> partitionColumnSpecGenerator(String prefix)
+    {
+        return fromValues(partitionKeyTypes)
+               .map(new Function<ColumnSpec.DataType<?>, ColumnSpec<?>>()
+               {
+                   private int counter = 0;
+
+                   public ColumnSpec<?> apply(ColumnSpec.DataType<?> type)
+                   {
+
+                       return ColumnSpec.pk(String.format("%s%04d", prefix, counter++),
+                                            type);
                    }
                });
     }
@@ -155,10 +168,10 @@ public class SchemaGenerators
         private final String keyspace;
         private final Supplier<String> tableNameSupplier;
 
-        private Generator<ColumnSpec<?>> pkGenerator = columnSpecGenerator("pk", ColumnSpec.Kind.PARTITION_KEY);
+        private Generator<ColumnSpec<?>> pkGenerator = partitionColumnSpecGenerator("pk");
         private Generator<ColumnSpec<?>> ckGenerator = clusteringColumnSpecGenerator("ck");
         private Generator<ColumnSpec<?>> regularGenerator = columnSpecGenerator("regular", ColumnSpec.Kind.REGULAR);
-        private Generator<ColumnSpec<?>> staticGenerator = columnSpecGenerator("regular", ColumnSpec.Kind.STATIC);
+        private Generator<ColumnSpec<?>> staticGenerator = columnSpecGenerator("static", ColumnSpec.Kind.STATIC);
 
         private int minPks = 1;
         private int maxPks = 1;
@@ -457,6 +470,9 @@ public class SchemaGenerators
 
     public static List<ColumnSpec<?>> toColumns(Map<String, String> config, ColumnSpec.Kind kind, boolean allowReverse)
     {
+        if (config == null)
+            return Collections.EMPTY_LIST;
+
         List<ColumnSpec<?>> columns = new ArrayList<>(config.size());
 
         for (Map.Entry<String, String> e : config.entrySet())
diff --git a/harry-core/src/harry/ddl/SchemaSpec.java b/harry-core/src/harry/ddl/SchemaSpec.java
index f07c106..eba87c6 100644
--- a/harry-core/src/harry/ddl/SchemaSpec.java
+++ b/harry-core/src/harry/ddl/SchemaSpec.java
@@ -18,14 +18,12 @@
 
 package harry.ddl;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
-import java.util.stream.Stream;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Streams;
 
 import harry.generators.DataGenerators;
 import harry.operations.CompiledStatement;
@@ -87,22 +85,30 @@ public class SchemaSpec
         this.keyspace = keyspace;
         this.table = table;
         this.isCompactStorage = isCompactStorage;
-        this.partitionKeys = ImmutableList.copyOf(partitionKeys);
+
+        this.partitionKeys = Collections.unmodifiableList(new ArrayList<>(partitionKeys));
         for (int i = 0; i < partitionKeys.size(); i++)
             partitionKeys.get(i).setColumnIndex(i);
-        this.clusteringKeys = ImmutableList.copyOf(clusteringKeys);
+        this.clusteringKeys = Collections.unmodifiableList(new ArrayList<>(clusteringKeys));
         for (int i = 0; i < clusteringKeys.size(); i++)
             clusteringKeys.get(i).setColumnIndex(i);
-        this.staticColumns = ImmutableList.copyOf(staticColumns);
+        this.staticColumns = Collections.unmodifiableList(new ArrayList<>(staticColumns));
         for (int i = 0; i < staticColumns.size(); i++)
             staticColumns.get(i).setColumnIndex(i);
-        this.regularColumns = ImmutableList.copyOf(regularColumns);
+        this.regularColumns = Collections.unmodifiableList(new ArrayList<>(regularColumns));
         for (int i = 0; i < regularColumns.size(); i++)
             regularColumns.get(i).setColumnIndex(i);
-        this.allColumns = ImmutableList.copyOf(Iterables.concat(partitionKeys,
-                                                                clusteringKeys,
-                                                                staticColumns,
-                                                                regularColumns));
+
+        List<ColumnSpec<?>> all = new ArrayList<>();
+        for (ColumnSpec<?> columnSpec : concat(partitionKeys,
+                                               clusteringKeys,
+                                               staticColumns,
+                                               regularColumns))
+        {
+            all.add(columnSpec);
+        }
+        this.allColumns = Collections.unmodifiableList(all);
+
         this.pkGenerator = DataGenerators.createKeyGenerator(partitionKeys);
         this.ckGenerator = DataGenerators.createKeyGenerator(clusteringKeys);
 
@@ -122,7 +128,6 @@ public class SchemaSpec
     }
 
     // todo: bitset views?
-
     public BitSet regularColumnsMask()
     {
         return this.regularColumnsMask;
@@ -254,13 +259,13 @@ public class SchemaSpec
                 sb.append(" PRIMARY KEY");
         }
 
-        Streams.concat(clusteringKeys.stream(),
-                       staticColumns.stream(),
-                       regularColumns.stream())
-              .forEach((cd) -> {
-                  commaAppender.accept(sb);
-                  sb.append(cd.toCQL());
-              });
+        for (ColumnSpec<?> cd : concat(clusteringKeys,
+                                       staticColumns,
+                                       regularColumns))
+        {
+            commaAppender.accept(sb);
+            sb.append(cd.toCQL());
+        }
 
         if (clusteringKeys.size() > 0 || partitionKeys.size() > 1)
         {
@@ -409,4 +414,59 @@ public class SchemaSpec
     {
         return Objects.hash(keyspace, table, partitionKeys, clusteringKeys, regularColumns);
     }
+
+    public static <T> Iterable<T> concat(Iterable<T>... iterables)
+    {
+        assert iterables != null && iterables.length > 0;
+        if (iterables.length == 1)
+            return iterables[0];
+
+        return () -> {
+            return new Iterator<T>()
+            {
+                int idx;
+                Iterator<T> current;
+                boolean hasNext;
+
+                {
+                    idx = 0;
+                    prepareNext();
+                }
+
+                private void prepareNext()
+                {
+                    if (current != null && current.hasNext())
+                    {
+                        hasNext = true;
+                        return;
+                    }
+
+                    while (idx < iterables.length)
+                    {
+                        current = iterables[idx].iterator();
+                        idx++;
+                        if (current.hasNext())
+                        {
+                            hasNext = true;
+                            return;
+                        }
+                    }
+
+                    hasNext = false;
+                }
+
+                public boolean hasNext()
+                {
+                    return hasNext;
+                }
+
+                public T next()
+                {
+                    T next = current.next();
+                    prepareNext();
+                    return next;
+                }
+            };
+        };
+    }
 }
diff --git a/harry-core/src/harry/generators/DataGenerators.java b/harry-core/src/harry/generators/DataGenerators.java
index 6878ae4..e87e7dc 100644
--- a/harry-core/src/harry/generators/DataGenerators.java
+++ b/harry-core/src/harry/generators/DataGenerators.java
@@ -22,15 +22,22 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import com.google.common.annotations.VisibleForTesting;
-
+import harry.core.VisibleForTesting;
 import harry.ddl.ColumnSpec;
 
 public class DataGenerators
 {
-    public static final Object UNSET_VALUE = new Object();
-    public static long UNSET_DESCR = 0;
-    public static long NIL_DESCR = -1;
+    public static final Object UNSET_VALUE = new Object() {
+        public String toString()
+        {
+            return "UNSET";
+        }
+    };
+
+    // There is still a slim chance that we're going to produce either of these values by chance, but we'll catch this
+    // during value generation
+    public static long UNSET_DESCR = Long.MAX_VALUE;
+    public static long NIL_DESCR = Long.MIN_VALUE;
 
     public static Object[] inflateData(List<ColumnSpec<?>> columns, long[] descriptors)
     {
diff --git a/harry-core/src/harry/generators/RandomGenerator.java b/harry-core/src/harry/generators/RandomGenerator.java
index 869f60e..a1ca125 100644
--- a/harry-core/src/harry/generators/RandomGenerator.java
+++ b/harry-core/src/harry/generators/RandomGenerator.java
@@ -18,8 +18,7 @@
 
 package harry.generators;
 
-import com.google.common.annotations.VisibleForTesting;
-
+import harry.core.VisibleForTesting;
 
 /**
  * Random generator interface that offers:
diff --git a/harry-core/src/harry/generators/RngUtils.java b/harry-core/src/harry/generators/RngUtils.java
index 749cf7f..894204f 100644
--- a/harry-core/src/harry/generators/RngUtils.java
+++ b/harry-core/src/harry/generators/RngUtils.java
@@ -22,8 +22,12 @@ import java.util.function.LongSupplier;
 
 public class RngUtils
 {
+    private static final long CONSTANT = 0x2545F4914F6CDD1DL;
     public static long next(long input)
     {
+        if (input == 0)
+            return next(CONSTANT);
+
         return xorshift64star(input);
     }
 
@@ -32,7 +36,7 @@ public class RngUtils
         input ^= input >> 12;
         input ^= input << 25; // b
         input ^= input >> 27; // c
-        return input * 0x2545F4914F6CDD1DL;
+        return input * CONSTANT;
     }
 
     public static long[] next(long current, int n)
diff --git a/harry-core/src/harry/generators/Surjections.java b/harry-core/src/harry/generators/Surjections.java
index e5a937c..13f66ff 100644
--- a/harry-core/src/harry/generators/Surjections.java
+++ b/harry-core/src/harry/generators/Surjections.java
@@ -26,7 +26,7 @@ import java.util.function.Function;
 import java.util.function.LongFunction;
 import java.util.function.Supplier;
 
-import com.google.common.annotations.VisibleForTesting;
+import harry.core.VisibleForTesting;
 
 public class Surjections
 {
diff --git a/harry-core/src/harry/model/AlwaysSamePartitionSelector.java b/harry-core/src/harry/model/AlwaysSamePartitionSelector.java
new file mode 100644
index 0000000..43160fb
--- /dev/null
+++ b/harry-core/src/harry/model/AlwaysSamePartitionSelector.java
@@ -0,0 +1,69 @@
+package harry.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import harry.core.Configuration;
+
+/**
+ * A simple test-only descriptor selector that can used for testing things where you only need one partition
+ */
+public class AlwaysSamePartitionSelector extends OpSelectors.PdSelector
+{
+    private final long pd;
+
+    public AlwaysSamePartitionSelector(long pd)
+    {
+        this.pd = pd;
+    }
+
+    protected long pd(long lts)
+    {
+        return 0;
+    }
+
+    public long nextLts(long lts)
+    {
+        return lts + 1;
+    }
+
+    public long prevLts(long lts)
+    {
+        return lts - 1;
+    }
+
+    public long maxLtsFor(long pd)
+    {
+        return 1000;
+    }
+
+    public long minLtsAt(long position)
+    {
+        return 0;
+    }
+
+    public long minLtsFor(long pd)
+    {
+        return 0;
+    }
+
+    public long positionFor(long lts)
+    {
+        return 0;
+    }
+
+    @JsonTypeName("always_same")
+    public static class AlwaysSamePartitionSelectorConfiguration implements Configuration.PDSelectorConfiguration
+    {
+        private final long pd;
+
+        public AlwaysSamePartitionSelectorConfiguration(@JsonProperty("pd") long pd)
+        {
+            this.pd = pd;
+        }
+
+        public OpSelectors.PdSelector make(OpSelectors.Rng rng)
+        {
+            return new AlwaysSamePartitionSelector(pd);
+        }
+    }
+}
diff --git a/harry-core/src/harry/model/NoOpChecker.java b/harry-core/src/harry/model/NoOpChecker.java
index 4cf4606..a13b6ec 100644
--- a/harry-core/src/harry/model/NoOpChecker.java
+++ b/harry-core/src/harry/model/NoOpChecker.java
@@ -34,6 +34,7 @@ public class NoOpChecker implements Model
     public void validate(Query query)
     {
         run.sut.execute(query.toSelectStatement(),
-                        SystemUnderTest.ConsistencyLevel.ALL);
+                        // TODO: make it configurable
+                        SystemUnderTest.ConsistencyLevel.QUORUM);
     }
 }
diff --git a/harry-core/src/harry/model/OpSelectors.java b/harry-core/src/harry/model/OpSelectors.java
index 3adbb95..3dd4a25 100644
--- a/harry-core/src/harry/model/OpSelectors.java
+++ b/harry-core/src/harry/model/OpSelectors.java
@@ -22,9 +22,8 @@ import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 
-import com.google.common.annotations.VisibleForTesting;
-
 import harry.core.Configuration;
+import harry.core.VisibleForTesting;
 import harry.ddl.ColumnSpec;
 import harry.ddl.SchemaSpec;
 import harry.generators.Bytes;
@@ -34,6 +33,7 @@ import harry.generators.Surjections;
 import harry.generators.distribution.Distribution;
 import harry.util.BitSet;
 
+import static harry.generators.DataGenerators.NIL_DESCR;
 import static harry.generators.DataGenerators.UNSET_DESCR;
 
 /**
@@ -179,18 +179,20 @@ public interface OpSelectors
 
         public long[] vds(long pd, long cd, long lts, long opId, SchemaSpec schema)
         {
-            return descriptors(pd, cd, lts, opId, schema.regularColumns, schema.regularColumnsMask(), schema.regularColumnsOffset);
+            BitSet setColumns = columnMask(pd, lts, opId);
+            return descriptors(pd, cd, lts, opId, schema.regularColumns, schema.regularColumnsMask(), setColumns, schema.regularColumnsOffset);
         }
 
         public long[] sds(long pd, long cd, long lts, long opId, SchemaSpec schema)
         {
-            return descriptors(pd, cd, lts, opId, schema.staticColumns, schema.staticColumnsMask(), schema.staticColumnsOffset);
+            BitSet setColumns = columnMask(pd, lts, opId);
+            return descriptors(pd, cd, lts, opId, schema.staticColumns, schema.staticColumnsMask(), setColumns, schema.staticColumnsOffset);
         }
 
-        public long[] descriptors(long pd, long cd, long lts, long opId, List<ColumnSpec<?>> columns, BitSet mask, int offset)
+        private long[] descriptors(long pd, long cd, long lts, long opId, List<ColumnSpec<?>> columns, BitSet mask, BitSet setColumns, int offset)
         {
+            assert opId < opsPerModification(lts) * numberOfModifications(lts) : String.format("Operation id %d exceeds the maximum expected number of operations %d", opId, opsPerModification(lts) * numberOfModifications(lts));
             long[] descriptors = new long[columns.size()];
-            BitSet setColumns = columnMask(pd, cd, opId);
 
             for (int i = 0; i < descriptors.length; i++)
             {
@@ -199,6 +201,9 @@ public interface OpSelectors
                 {
                     ColumnSpec<?> spec = columns.get(i);
                     long vd = vd(pd, cd, lts, opId, col) & Bytes.bytePatternFor(spec.type.maxSize());
+                    assert vd != UNSET_DESCR : "Ambiguous unset descriptor generated for the value";
+                    assert vd != NIL_DESCR : "Ambiguous nil descriptor generated for the value";
+
                     descriptors[i] = vd;
                 }
                 else
@@ -387,13 +392,13 @@ public interface OpSelectors
 
                 switch (type)
                 {
+                    case UPDATE_WITH_STATICS:
                     case DELETE_COLUMN_WITH_STATICS:
                         gen = (descriptor) -> {
                             long counter = 0;
                             while (counter <= 100)
                             {
                                 BitSet bitSet = orig.inflate(descriptor);
-
                                 if ((schema.regularColumns.isEmpty() || !bitSet.allUnset(schema.regularColumnsMask))
                                     && (schema.staticColumns.isEmpty() || !bitSet.allUnset(schema.staticColumnsMask)))
                                     return bitSet;
@@ -404,6 +409,23 @@ public interface OpSelectors
                             throw new RuntimeException(String.format("Could not generate a value after %d attempts.", counter));
                         };
                         break;
+                    // Can not have an UPDATE statement without anything to update
+                    case UPDATE:
+                        gen = descriptor -> {
+                            long counter = 0;
+                            while (counter <= 100)
+                            {
+                                BitSet bitSet = orig.inflate(descriptor);
+
+                                if (!bitSet.allUnset(schema.regularColumnsMask))
+                                    return bitSet;
+
+                                descriptor = RngUtils.next(descriptor);
+                                counter++;
+                            }
+                            throw new RuntimeException(String.format("Could not generate a value after %d attempts.", counter));
+                        };
+                        break;
                     case DELETE_COLUMN:
                         gen = (descriptor) -> {
                             long counter = 0;
@@ -429,7 +451,7 @@ public interface OpSelectors
 
         public ColumnSelectorBuilder forWrite(Surjections.Surjection<BitSet> gen)
         {
-            m.put(OperationKind.WRITE, gen);
+            m.put(OperationKind.INSERT, gen);
             return this;
         }
 
@@ -547,8 +569,8 @@ public interface OpSelectors
         protected final static long BITSET_IDX_STREAM = 0x92eb607bef1L;
 
         public static OperationSelector DEFAULT_OP_SELECTOR = OperationSelector.weighted(Surjections.weights(45, 45, 3, 2, 2, 1, 1, 1),
-                                                                                         OperationKind.WRITE,
-                                                                                         OperationKind.WRITE_WITH_STATICS,
+                                                                                         OperationKind.INSERT,
+                                                                                         OperationKind.INSERT_WITH_STATICS,
                                                                                          OperationKind.DELETE_ROW,
                                                                                          OperationKind.DELETE_COLUMN,
                                                                                          OperationKind.DELETE_COLUMN_WITH_STATICS,
@@ -649,7 +671,8 @@ public interface OpSelectors
 
         public OperationKind operationType(long pd, long lts, long opId)
         {
-            return operationType(pd, lts, opId, partitionLevelOperationsMask(pd, lts));
+            OperationKind kind = operationType(pd, lts, opId, partitionLevelOperationsMask(pd, lts));
+            return kind;
         }
 
         // TODO: create this bitset once per lts
@@ -666,7 +689,8 @@ public interface OpSelectors
 
         public OperationKind operationType(long pd, long lts, long opId, BitSet partitionLevelOperationsMask)
         {
-            return operationSelector.inflate(pd ^ lts ^ opId, partitionLevelOperationsMask.isSet((int) opId));
+            long descriptor = rng.randomNumber(pd ^ lts ^ opId, BITSET_IDX_STREAM);
+            return operationSelector.inflate(descriptor, partitionLevelOperationsMask.isSet((int) opId));
         }
 
         public BitSet columnMask(long pd, long lts, long opId)
@@ -688,8 +712,10 @@ public interface OpSelectors
 
     public enum OperationKind
     {
-        WRITE(false),
-        WRITE_WITH_STATICS(true),
+        UPDATE(false),
+        INSERT(false),
+        UPDATE_WITH_STATICS(true),
+        INSERT_WITH_STATICS(true),
         DELETE_PARTITION(true),
         DELETE_ROW(false),
         DELETE_COLUMN(false),
diff --git a/harry-core/src/harry/model/SelectHelper.java b/harry-core/src/harry/model/SelectHelper.java
index 70d1eb2..fc8f6f7 100644
--- a/harry-core/src/harry/model/SelectHelper.java
+++ b/harry-core/src/harry/model/SelectHelper.java
@@ -19,13 +19,9 @@
 package harry.model;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import com.datastax.driver.core.querybuilder.Ordering;
-import com.datastax.driver.core.querybuilder.QueryBuilder;
-import com.datastax.driver.core.querybuilder.Select;
 import harry.data.ResultSetRow;
 import harry.ddl.ColumnSpec;
 import harry.ddl.SchemaSpec;
@@ -48,67 +44,119 @@ public class SelectHelper
      */
     public static CompiledStatement select(SchemaSpec schema, long pd, List<Relation> relations, boolean reverse, boolean includeWriteTime)
     {
-        Select.Selection select = QueryBuilder.select();
-        for (ColumnSpec<?> column : schema.allColumns)
-            select.column(column.name);
+        StringBuilder b = new StringBuilder();
+        b.append("SELECT ");
+
+        for (int i = 0; i < schema.allColumns.size(); i++)
+        {
+            ColumnSpec<?> spec = schema.allColumns.get(i);
+            if (i > 0)
+                b.append(", ");
+            b.append(spec.name);
+        }
 
         if (includeWriteTime)
         {
             for (ColumnSpec<?> column : schema.staticColumns)
-                select.writeTime(column.name);
+                b.append(", ")
+                 .append("writetime(")
+                 .append(column.name)
+                 .append(")");
 
             for (ColumnSpec<?> column : schema.regularColumns)
-                select.writeTime(column.name);
+                b.append(", ")
+                 .append("writetime(")
+                 .append(column.name)
+                 .append(")");
         }
 
-        Select.Where where = select.from(schema.keyspace, schema.table).where();
-        List<Object> bindings = new ArrayList<>();
+        b.append(" FROM ")
+         .append(schema.keyspace)
+         .append(".")
+         .append(schema.table)
+         .append(" WHERE ");
 
-        addRelations(schema, where, bindings, pd, relations);
-        addOrderBy(schema, where, reverse);
+        List<Object> bindings = new ArrayList<>();
 
+        schema.inflateRelations(pd,
+                                relations,
+                                new SchemaSpec.AddRelationCallback()
+                                {
+                                    boolean isFirst = true;
+                                    public void accept(ColumnSpec<?> spec, Relation.RelationKind kind, Object value)
+                                    {
+                                        if (isFirst)
+                                            isFirst = false;
+                                        else
+                                            b.append(" AND ");
+                                        b.append(kind.getClause(spec));
+                                        bindings.add(value);
+                                    }
+                                });
+        addOrderBy(schema, b, reverse);
+        b.append(";");
         Object[] bindingsArr = bindings.toArray(new Object[bindings.size()]);
-        return new CompiledStatement(where.toString(), bindingsArr);
+        return new CompiledStatement(b.toString(), bindingsArr);
     }
 
     public static CompiledStatement count(SchemaSpec schema, long pd)
     {
-        Select.Selection select = QueryBuilder.select();
-        select.countAll();
-
-        Select.Where where = select.from(schema.keyspace, schema.table).where();
-        List<Object> bindings = new ArrayList<>(schema.partitionKeys.size());
+        StringBuilder b = new StringBuilder();
+        b.append("SELECT count(*) ");
 
-        addRelations(schema, where, bindings, pd, Collections.emptyList());
+        b.append(" FROM ")
+         .append(schema.keyspace)
+         .append(".")
+         .append(schema.table)
+         .append(" WHERE ");
 
-        Object[] bindingsArr = bindings.toArray(new Object[bindings.size()]);
-        return new CompiledStatement(where.toString(), bindingsArr);
-    }
+        List<Object> bindings = new ArrayList<>(schema.partitionKeys.size());
 
-    private static void addRelations(SchemaSpec schema, Select.Where where, List<Object> bindings, long pd, List<Relation> relations)
-    {
         schema.inflateRelations(pd,
-                                relations,
-                                (spec, kind, value) -> {
-                                    where.and(kind.getClause(spec));
-                                    bindings.add(value);
+                                Collections.emptyList(),
+                                new SchemaSpec.AddRelationCallback()
+                                {
+                                    boolean isFirst = true;
+                                    public void accept(ColumnSpec<?> spec, Relation.RelationKind kind, Object value)
+                                    {
+                                        if (isFirst)
+                                            isFirst = false;
+                                        else
+                                            b.append(" AND ");
+                                        b.append(kind.getClause(spec));
+                                        bindings.add(value);
+                                    }
                                 });
+
+        Object[] bindingsArr = bindings.toArray(new Object[bindings.size()]);
+        return new CompiledStatement(b.toString(), bindingsArr);
     }
 
-    private static void addOrderBy(SchemaSpec schema, Select.Where whereClause, boolean reverse)
+    private static void addOrderBy(SchemaSpec schema, StringBuilder b, boolean reverse)
     {
         if (reverse && schema.clusteringKeys.size() > 0)
         {
-            Ordering[] ordering = new Ordering[schema.clusteringKeys.size()];
+            b.append(" ORDER BY ");
             for (int i = 0; i < schema.clusteringKeys.size(); i++)
             {
                 ColumnSpec<?> c = schema.clusteringKeys.get(i);
-                ordering[i] = c.isReversed() ? QueryBuilder.asc(c.name) : QueryBuilder.desc(c.name);
+                if (i > 0)
+                    b.append(", ");
+                b.append(c.isReversed() ? asc(c.name) : desc(c.name));
             }
-            whereClause.orderBy(ordering);
         }
     }
 
+    public static String asc(String name)
+    {
+        return name + " ASC";
+    }
+
+    public static String desc(String name)
+    {
+        return name + " DESC";
+    }
+
     public static ResultSetRow resultSetToRow(SchemaSpec schema, OpSelectors.MonotonicClock clock, Object[] result)
     {
         Object[] partitionKey = new Object[schema.partitionKeys.size()];
diff --git a/harry-core/src/harry/model/clock/ApproximateMonotonicClock.java b/harry-core/src/harry/model/clock/ApproximateMonotonicClock.java
index 59dd47e..1a64500 100644
--- a/harry-core/src/harry/model/clock/ApproximateMonotonicClock.java
+++ b/harry-core/src/harry/model/clock/ApproximateMonotonicClock.java
@@ -25,9 +25,8 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicLongArray;
 import java.util.concurrent.locks.LockSupport;
 
-import com.google.common.annotations.VisibleForTesting;
-
 import harry.core.Configuration;
+import harry.core.VisibleForTesting;
 import harry.model.OpSelectors;
 
 /**
diff --git a/harry-core/src/harry/model/clock/OffsetClock.java b/harry-core/src/harry/model/clock/OffsetClock.java
index 9f40a64..8c25394 100644
--- a/harry-core/src/harry/model/clock/OffsetClock.java
+++ b/harry-core/src/harry/model/clock/OffsetClock.java
@@ -20,6 +20,9 @@ package harry.model.clock;
 
 import java.util.concurrent.atomic.AtomicLong;
 
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeName;
 import harry.core.Configuration;
 import harry.model.OpSelectors;
 
@@ -58,4 +61,21 @@ public class OffsetClock implements OpSelectors.MonotonicClock
     {
         throw new RuntimeException("not implemented");
     }
+
+    @JsonTypeName("offset")
+    public static class OffsetClockConfiguration implements Configuration.ClockConfiguration
+    {
+        public final long offset;
+
+        @JsonCreator
+        public OffsetClockConfiguration(@JsonProperty("offset") int offset)
+        {
+            this.offset = offset;
+        }
+
+        public OpSelectors.MonotonicClock make()
+        {
+            return new OffsetClock(offset);
+        }
+    }
 }
diff --git a/harry-core/src/harry/model/sut/PrintlnSut.java b/harry-core/src/harry/model/sut/PrintlnSut.java
index ad14b0b..f1b310e 100644
--- a/harry-core/src/harry/model/sut/PrintlnSut.java
+++ b/harry-core/src/harry/model/sut/PrintlnSut.java
@@ -21,6 +21,10 @@ package harry.model.sut;
 import java.util.Arrays;
 import java.util.concurrent.CompletableFuture;
 
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import harry.core.Configuration;
+
 public class PrintlnSut implements SystemUnderTest
 {
     public boolean isShutdown()
@@ -46,4 +50,18 @@ public class PrintlnSut implements SystemUnderTest
         return CompletableFuture.supplyAsync(() -> execute(statement, cl, bindings),
                                              Runnable::run);
     }
+
+    @JsonTypeName("println")
+    public static class PrintlnSutConfiguration implements Configuration.SutConfiguration
+    {
+        @JsonCreator
+        public PrintlnSutConfiguration()
+        {
+
+        }
+        public SystemUnderTest make()
+        {
+            return new PrintlnSut();
+        }
+    }
 }
diff --git a/harry-core/src/harry/operations/DeleteHelper.java b/harry-core/src/harry/operations/DeleteHelper.java
index 6f04bad..f1b6983 100644
--- a/harry-core/src/harry/operations/DeleteHelper.java
+++ b/harry-core/src/harry/operations/DeleteHelper.java
@@ -23,11 +23,8 @@ import java.util.Collections;
 import java.util.List;
 import java.util.function.IntConsumer;
 
-import com.datastax.driver.core.querybuilder.Delete;
-import com.datastax.driver.core.querybuilder.QueryBuilder;
 import harry.ddl.ColumnSpec;
 import harry.ddl.SchemaSpec;
-import harry.runner.LoggingPartitionVisitor;
 import harry.util.BitSet;
 
 public class DeleteHelper
@@ -130,40 +127,50 @@ public class DeleteHelper
                                              BitSet mask,
                                              long ts)
     {
-        Delete delete;
-        if (columnsToDelete == null)
-            delete = QueryBuilder.delete().from(schema.keyspace, schema.table);
-        else
+        StringBuilder b = new StringBuilder();
+        b.append("DELETE ");
+        if (columnsToDelete != null)
         {
             assert mask != null;
             assert relations == null || relations.stream().allMatch((r) -> r.kind == Relation.RelationKind.EQ);
-            delete = QueryBuilder.delete(columnNames(schema.allColumns, columnsToDelete, mask))
-                                 .from(schema.keyspace, schema.table);
+            String[] names = columnNames(schema.allColumns, columnsToDelete, mask);
+            for (int i = 0; i < names.length; i++)
+            {
+                if (i > 0)
+                    b.append(", ");
+                b.append(names[i]);
+            }
+            b.append(" ");
         }
+        b.append("FROM ")
+         .append(schema.keyspace).append(".").append(schema.table)
+         .append(" USING TIMESTAMP ")
+         .append(ts)
+         .append(" WHERE ");
 
-        Delete.Where where = delete.where();
         List<Object> bindings = new ArrayList<>();
 
-        addRelations(schema, where, bindings, pd, relations);
-        delete.using(QueryBuilder.timestamp(ts));
-        delete.setForceNoValues(true);
-        Object[] bindingsArr = bindings.toArray(new Object[bindings.size()]);
-        String compiled = delete.getQueryString();
-        if (compiled.contains("built query (could not generate with default codec registry:"))
-            throw new IllegalArgumentException(String.format("Could not generate the query: %s. Bindings: (%s)",
-                                                             delete,
-                                                             CompiledStatement.bindingsToString(bindingsArr)));
-        return new CompiledStatement(compiled, bindingsArr);
-    }
-
-    private static void addRelations(SchemaSpec schema, Delete.Where where, List<Object> bindings, long pd, List<Relation> relations)
-    {
         schema.inflateRelations(pd,
                                 relations,
-                                (spec, kind, value) -> {
-                                    where.and(kind.getClause(spec));
-                                    bindings.add(value);
+                                new SchemaSpec.AddRelationCallback()
+                                {
+                                    boolean isFirst = true;
+                                    public void accept(ColumnSpec<?> spec, Relation.RelationKind kind, Object value)
+                                    {
+                                        if (isFirst)
+                                            isFirst = false;
+                                        else
+                                            b.append(" AND ");
+                                        b.append(kind.getClause(spec));
+                                        bindings.add(value);
+                                    }
                                 });
+
+        b.append(";");
+
+        Object[] bindingsArr = bindings.toArray(new Object[bindings.size()]);
+
+        return new CompiledStatement(b.toString(), bindingsArr);
     }
 
     private static String[] columnNames(List<ColumnSpec<?>> columns, BitSet selectedColumns, BitSet mask)
diff --git a/harry-core/src/harry/operations/Relation.java b/harry-core/src/harry/operations/Relation.java
index 19db0b4..87487d9 100644
--- a/harry-core/src/harry/operations/Relation.java
+++ b/harry-core/src/harry/operations/Relation.java
@@ -21,16 +21,8 @@ package harry.operations;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.datastax.driver.core.querybuilder.Clause;
 import harry.ddl.ColumnSpec;
 
-import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.gt;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.gte;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.lt;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.lte;
-
 public class Relation
 {
     public final RelationKind kind;
@@ -62,9 +54,9 @@ public class Relation
         return columnSpec.name;
     }
 
-    public Clause toClause()
+    public String toClause()
     {
-        return kind.getClause(column(), bindMarker());
+        return kind.getClause(column());
     }
 
     public String toString()
@@ -101,7 +93,7 @@ public class Relation
     public static void addRelation(long[] key, List<ColumnSpec<?>> columnSpecs, List<Relation> relations, RelationKind kind)
     {
         assert key.length == columnSpecs.size() :
-        String.format("Key size (%d) should equal to column spec size (%d)", key.length, columnSpecs.size());
+        String.format("Key size (%d) should equal to column spec size (%d). Specs: %s", key.length, columnSpecs.size(), columnSpecs);
         for (int i = 0; i < key.length; i++)
         {
             ColumnSpec<?> spec = columnSpecs.get(i);
@@ -113,17 +105,6 @@ public class Relation
     {
         LT
         {
-            @Override
-            public Clause getClause(String name, Object obj)
-            {
-                return lt(name, obj);
-            }
-
-            public Clause getClause(List<String> name, List<Object> obj)
-            {
-                return lt(name, obj);
-            }
-
             public boolean isNegatable()
             {
                 return true;
@@ -156,17 +137,6 @@ public class Relation
         },
         GT
         {
-            @Override
-            public Clause getClause(String name, Object obj)
-            {
-                return gt(name, obj);
-            }
-
-            public Clause getClause(List<String> name, List<Object> obj)
-            {
-                return gt(name, obj);
-            }
-
             public boolean isNegatable()
             {
                 return true;
@@ -199,17 +169,6 @@ public class Relation
         },
         LTE
         {
-            @Override
-            public Clause getClause(String name, Object obj)
-            {
-                return lte(name, obj);
-            }
-
-            public Clause getClause(List<String> name, List<Object> obj)
-            {
-                return lt(name, obj);
-            }
-
             public boolean isNegatable()
             {
                 return true;
@@ -242,17 +201,6 @@ public class Relation
         },
         GTE
         {
-            @Override
-            public Clause getClause(String name, Object obj)
-            {
-                return gte(name, obj);
-            }
-
-            public Clause getClause(List<String> name, List<Object> obj)
-            {
-                return gte(name, obj);
-            }
-
             public boolean isNegatable()
             {
                 return true;
@@ -285,17 +233,6 @@ public class Relation
         },
         EQ
         {
-            @Override
-            public Clause getClause(String name, Object obj)
-            {
-                return eq(name, obj);
-            }
-
-            public Clause getClause(List<String> name, List<Object> obj)
-            {
-                return eq(name, obj);
-            }
-
             public boolean isNegatable()
             {
                 return false;
@@ -329,14 +266,15 @@ public class Relation
 
         public abstract boolean match(LongComparator comparator, long l, long r);
 
-        public abstract Clause getClause(String name, Object obj);
-
-        public Clause getClause(ColumnSpec<?> spec)
+        public String getClause(String name)
         {
-            return getClause(spec.name, bindMarker());
+            return String.format("%s %s ?", name, toString());
         }
 
-        public abstract Clause getClause(List<String> name, List<Object> obj);
+        public String getClause(ColumnSpec<?> spec)
+        {
+            return getClause(spec.name);
+        }
 
         public abstract boolean isNegatable();
 
diff --git a/harry-core/src/harry/operations/WriteHelper.java b/harry-core/src/harry/operations/WriteHelper.java
index a0b7565..084f931 100644
--- a/harry-core/src/harry/operations/WriteHelper.java
+++ b/harry-core/src/harry/operations/WriteHelper.java
@@ -18,21 +18,13 @@
 
 package harry.operations;
 
+import java.util.Arrays;
 import java.util.List;
 
 import harry.ddl.ColumnSpec;
 import harry.ddl.SchemaSpec;
 import harry.generators.DataGenerators;
 
-import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.in;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.set;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.timestamp;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.truncate;
-import static com.datastax.driver.core.querybuilder.QueryBuilder.update;
-
 public class WriteHelper
 {
     public static CompiledStatement inflateInsert(SchemaSpec schema,
@@ -48,117 +40,127 @@ public class WriteHelper
         Object[] regularColumns = schema.inflateRegularColumns(vds);
 
         Object[] bindings = new Object[schema.allColumns.size()];
-        int bindingsCount = 0;
-        com.datastax.driver.core.querybuilder.Insert insert = insertInto(schema.keyspace,
-                                                                         schema.table);
 
-        bindingsCount += addValue(insert, bindings, schema.partitionKeys, partitionKey, bindingsCount);
-        bindingsCount += addValue(insert, bindings, schema.clusteringKeys, clusteringKey, bindingsCount);
+        StringBuilder b = new StringBuilder();
+        b.append("INSERT INTO ")
+         .append(schema.keyspace)
+         .append('.')
+         .append(schema.table)
+         .append(" (");
+
+        int bindingsCount = 0;
+        bindingsCount += appendStatements(b, bindings, schema.partitionKeys, partitionKey, bindingsCount, true, ",", "%s");
+        bindingsCount += appendStatements(b, bindings, schema.clusteringKeys, clusteringKey, bindingsCount, false, ",", "%s");
+        bindingsCount += appendStatements(b, bindings, schema.regularColumns, regularColumns, bindingsCount, false, ",", "%s");
         if (staticColumns != null)
-            bindingsCount += addValue(insert, bindings, schema.staticColumns, staticColumns, bindingsCount);
-        bindingsCount += addValue(insert, bindings, schema.regularColumns, regularColumns, bindingsCount);
+            bindingsCount += appendStatements(b, bindings, schema.staticColumns, staticColumns, bindingsCount, false, ",", "%s");
 
-        insert.using(timestamp(timestamp));
+        b.append(") VALUES (");
 
-        // Some of the values were unset
-        if (bindingsCount != bindings.length)
+        for (int i = 0; i < bindingsCount; i++)
         {
-            Object[] tmp = new Object[bindingsCount];
-            System.arraycopy(bindings, 0, tmp, 0, bindingsCount);
-            bindings = tmp;
+            if (i > 0)
+                b.append(", ");
+            b.append("?");
         }
 
-        return CompiledStatement.create(insert.toString(), bindings);
+        b.append(") USING TIMESTAMP ")
+         .append(timestamp);
+
+        return new CompiledStatement(b.toString(), adjustArraySize(bindings, bindingsCount));
     }
 
-    public static boolean allUnset(long[] descriptors)
+    public static Object[] adjustArraySize(Object[] bindings, int bindingsCount)
     {
-        for (long descriptor : descriptors)
+        if (bindingsCount != bindings.length)
         {
-            if (descriptor != DataGenerators.UNSET_DESCR)
-                return false;
+            Object[] tmp = new Object[bindingsCount];
+            System.arraycopy(bindings, 0, tmp, 0, bindingsCount);
+            bindings = tmp;
         }
-        return true;
-    }
-    private static int addValue(com.datastax.driver.core.querybuilder.Insert insert,
-                                Object[] bindings,
-                                List<ColumnSpec<?>> columns,
-                                Object[] data,
-                                int bound)
-    {
-        assert data.length == columns.size();
-
-        int bindingsCount = 0;
-        for (int i = 0; i < data.length; i++)
-        {
-            if (data[i] == DataGenerators.UNSET_VALUE)
-                continue;
-
-            insert.value(columns.get(i).name, bindMarker());
-            bindings[bound + bindingsCount] = data[i];
-            bindingsCount++;
-        }
-
-        return bindingsCount;
+        return bindings;
     }
 
     public static CompiledStatement inflateUpdate(SchemaSpec schema,
                                                   long pd,
                                                   long cd,
                                                   long[] vds,
+                                                  long[] sds,
                                                   long timestamp)
     {
         Object[] partitionKey = schema.inflatePartitionKey(pd);
         Object[] clusteringKey = schema.inflateClusteringKey(cd);
+        Object[] staticColumns = sds == null ? null : schema.inflateStaticColumns(sds);
         Object[] regularColumns = schema.inflateRegularColumns(vds);
 
         Object[] bindings = new Object[schema.allColumns.size()];
-        int bindingsCount = 0;
-        com.datastax.driver.core.querybuilder.Update update = update(schema.keyspace,
-                                                                     schema.table);
 
-        bindingsCount += addWith(update, bindings, schema.regularColumns, regularColumns, bindingsCount);
-        bindingsCount += addWhere(update, bindings, schema.partitionKeys, partitionKey, bindingsCount);
-        bindingsCount += addWhere(update, bindings, schema.clusteringKeys, clusteringKey, bindingsCount);
+        StringBuilder b = new StringBuilder();
+        b.append("UPDATE ")
+         .append(schema.keyspace)
+         .append('.')
+         .append(schema.table)
+         .append(" USING TIMESTAMP ")
+         .append(timestamp)
+         .append(" SET ");
 
-        update.using(timestamp(timestamp));
-        // TODO: TTL
-        // ttl.ifPresent(ts -> update.using(ttl(ts)));
+        int bindingsCount = 0;
+        bindingsCount += addSetStatements(b, bindings, schema.regularColumns, regularColumns, bindingsCount);
+        if (staticColumns != null)
+            bindingsCount += addSetStatements(b, bindings, schema.staticColumns, staticColumns, bindingsCount);
+
+        assert bindingsCount > 0 : "Can not have an UPDATE statement without any updates";
+        b.append(" WHERE ");
 
-        return CompiledStatement.create(update.toString(), bindings);
+        bindingsCount += addWhereStatements(b, bindings, schema.partitionKeys, partitionKey, bindingsCount, true);
+        bindingsCount += addWhereStatements(b, bindings, schema.clusteringKeys, clusteringKey, bindingsCount, false);
+        b.append(";");
+        return new CompiledStatement(b.toString(), adjustArraySize(bindings, bindingsCount));
     }
 
-    private static int addWith(com.datastax.driver.core.querybuilder.Update update,
-                               Object[] bindings,
-                               List<ColumnSpec<?>> columns,
-                               Object[] data,
-                               int bound)
+    private static int addSetStatements(StringBuilder b,
+                                        Object[] bindings,
+                                        List<ColumnSpec<?>> columns,
+                                        Object[] values,
+                                        int bound)
     {
-        assert data.length == columns.size();
-
-        for (int i = 0; i < data.length; i++)
-        {
-            update.with(set(columns.get(i).name, bindMarker()));
-            bindings[bound + i] = data[i];
-        }
-
-        return data.length;
+        return appendStatements(b, bindings, columns, values, bound, bound == 0, ", ", "%s = ?");
     }
 
-    private static int addWhere(com.datastax.driver.core.querybuilder.Update update,
-                                Object[] bindings,
-                                List<ColumnSpec<?>> columns,
-                                Object[] data,
-                                int bound)
+    private static int addWhereStatements(StringBuilder b,
+                                          Object[] bindings,
+                                          List<ColumnSpec<?>> columns,
+                                          Object[] values,
+                                          int bound,
+                                          boolean firstStatement)
     {
-        assert data.length == columns.size();
+        return appendStatements(b, bindings, columns, values, bound, firstStatement, " AND ", "%s = ?");
+    }
 
-        for (int i = 0; i < data.length; i++)
+    private static int appendStatements(StringBuilder b,
+                                        Object[] allBindings,
+                                        List<ColumnSpec<?>> columns,
+                                        Object[] values,
+                                        int bound,
+                                        boolean firstStatement,
+                                        String separator,
+                                        String nameFormatter)
+    {
+        int bindingsCount = 0;
+        for (int i = 0; i < values.length; i++)
         {
-            update.where().and(eq(columns.get(i).name, bindMarker()));
-            bindings[bound + i] = data[i];
-        }
+            Object value = values[i];
+            if (value == DataGenerators.UNSET_VALUE)
+                continue;
+
+            ColumnSpec<?> column = columns.get(i);
+            if (bindingsCount > 0 || !firstStatement)
+                b.append(separator);
 
-        return data.length;
+            b.append(String.format(nameFormatter, column.name));
+            allBindings[bound + bindingsCount] = value;
+            bindingsCount++;
+        }
+        return bindingsCount;
     }
 }
\ No newline at end of file
diff --git a/harry-core/src/harry/reconciler/Reconciler.java b/harry-core/src/harry/reconciler/Reconciler.java
index ca4772c..a9709b3 100644
--- a/harry-core/src/harry/reconciler/Reconciler.java
+++ b/harry-core/src/harry/reconciler/Reconciler.java
@@ -125,8 +125,10 @@ public class Reconciler
 
                         hadPartitionDeletion = true;
                         break;
-                    case WRITE_WITH_STATICS:
-                    case WRITE:
+                    case INSERT_WITH_STATICS:
+                    case INSERT:
+                    case UPDATE:
+                    case UPDATE_WITH_STATICS:
                         if (debugCd != -1 && cd == debugCd)
                             logger.info("Writing {} ({}) at {}/{}", cd, opType, lts, opId);
                         writes.add(opId);
@@ -164,12 +166,14 @@ public class Reconciler
 
                     switch (opType)
                     {
-                        case WRITE_WITH_STATICS:
+                        case INSERT_WITH_STATICS:
+                        case UPDATE_WITH_STATICS:
                             // We could apply static columns during the first iteration, but it's more convenient
                             // to reconcile static-level deletions.
                             partitionState.writeStaticRow(descriptorSelector.sds(pd, cd, lts, opId, schema),
                                                           lts);
-                        case WRITE:
+                        case INSERT:
+                        case UPDATE:
                             if (!query.match(cd))
                             {
                                 if (debugCd != -1 && cd == debugCd)
@@ -189,7 +193,8 @@ public class Reconciler
 
                             partitionState.write(cd,
                                                  descriptorSelector.vds(pd, cd, lts, opId, schema),
-                                                 lts);
+                                                 lts,
+                                                 opType == OpSelectors.OperationKind.INSERT || opType == OpSelectors.OperationKind.INSERT_WITH_STATICS);
                             break;
                         default:
                             throw new IllegalStateException();
@@ -206,7 +211,8 @@ public class Reconciler
                     switch (opType)
                     {
                         case DELETE_COLUMN_WITH_STATICS:
-                            partitionState.deleteStaticColumns(schema.staticColumnsOffset,
+                            partitionState.deleteStaticColumns(lts,
+                                                               schema.staticColumnsOffset,
                                                                descriptorSelector.columnMask(pd, lts, opId),
                                                                schema.staticColumnsMask());
                         case DELETE_COLUMN:
@@ -227,7 +233,8 @@ public class Reconciler
                                 }
                             }
 
-                            partitionState.deleteRegularColumns(cd,
+                            partitionState.deleteRegularColumns(lts,
+                                                                cd,
                                                                 schema.regularColumnsOffset,
                                                                 descriptorSelector.columnMask(pd, lts, opId),
                                                                 schema.regularColumnsMask());
@@ -270,14 +277,15 @@ public class Reconciler
                                     long lts)
         {
             if (staticRow != null)
-                staticRow = updateRowState(staticRow, schema.staticColumns, STATIC_CLUSTERING, staticVds, lts);
+                staticRow = updateRowState(staticRow, schema.staticColumns, STATIC_CLUSTERING, staticVds, lts, false);
         }
 
         private void write(long cd,
                            long[] vds,
-                           long lts)
+                           long lts,
+                           boolean writeParimaryKeyLiveness)
         {
-            rows.compute(cd, (cd_, current) -> updateRowState(current, schema.regularColumns, cd, vds, lts));
+            rows.compute(cd, (cd_, current) -> updateRowState(current, schema.regularColumns, cd, vds, lts, writeParimaryKeyLiveness));
         }
 
         private void delete(Ranges.Range range,
@@ -304,14 +312,19 @@ public class Reconciler
         private void delete(long cd,
                             long lts)
         {
-            rows.remove(cd);
+            RowState state = rows.remove(cd);
+            if (state != null)
+            {
+                for (long v : state.lts)
+                    assert lts >= v : String.format("Attempted to remove a row with a tombstone that has older timestamp (%d): %s", lts, state);
+            }
         }
         public boolean isEmpty()
         {
             return rows.isEmpty();
         }
 
-        private RowState updateRowState(RowState currentState, List<ColumnSpec<?>> columns, long cd, long[] vds, long lts)
+        private RowState updateRowState(RowState currentState, List<ColumnSpec<?>> columns, long cd, long[] vds, long lts, boolean writePrimaryKeyLiveness)
         {
             if (currentState == null)
             {
@@ -359,24 +372,30 @@ public class Reconciler
                 }
             }
 
+            if (writePrimaryKeyLiveness)
+                currentState.hasPrimaryKeyLivenessInfo = true;
+
             return currentState;
         }
 
-        private void deleteRegularColumns(long cd, int columnOffset, BitSet columns, BitSet mask)
+        private void deleteRegularColumns(long lts, long cd, int columnOffset, BitSet columns, BitSet mask)
         {
-            deleteColumns(rows.get(cd), columnOffset, columns, mask);
+            deleteColumns(lts, rows.get(cd), columnOffset, columns, mask);
         }
 
-        private void deleteStaticColumns(int columnOffset, BitSet columns, BitSet mask)
+        private void deleteStaticColumns(long lts, int columnOffset, BitSet columns, BitSet mask)
         {
-            deleteColumns(staticRow, columnOffset, columns, mask);
+            deleteColumns(lts, staticRow, columnOffset, columns, mask);
         }
 
-        private void deleteColumns(RowState state, int columnOffset, BitSet columns, BitSet mask)
+        private void deleteColumns(long lts, RowState state, int columnOffset, BitSet columns, BitSet mask)
         {
             if (state == null)
                 return;
 
+            //TODO: optimise by iterating over the columns that were removed by this deletion
+            //TODO: optimise final decision to fully remove the column by counting a number of set/unset columns
+            boolean allNil = true;
             for (int i = 0; i < state.vds.length; i++)
             {
                 if (columns.isSet(columnOffset + i, mask))
@@ -384,7 +403,14 @@ public class Reconciler
                     state.vds[i] = NIL_DESCR;
                     state.lts[i] = NO_TIMESTAMP;
                 }
+                else if (state.vds[i] != NIL_DESCR)
+                {
+                    allNil = false;
+                }
             }
+
+            if (state.cd != STATIC_CLUSTERING && allNil & !state.hasPrimaryKeyLivenessInfo)
+                delete(state.cd, lts);
         }
 
         private void deletePartition(long lts)
@@ -449,6 +475,7 @@ public class Reconciler
 
     public static class RowState
     {
+        public boolean hasPrimaryKeyLivenessInfo = false;
         public final long cd;
         public final long[] vds;
         public final long[] lts;
diff --git a/harry-core/src/harry/runner/CorruptingPartitionVisitor.java b/harry-core/src/harry/runner/CorruptingPartitionVisitor.java
index ca5bb93..d079cf3 100644
--- a/harry-core/src/harry/runner/CorruptingPartitionVisitor.java
+++ b/harry-core/src/harry/runner/CorruptingPartitionVisitor.java
@@ -15,7 +15,6 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-
 package harry.runner;
 
 import java.util.Random;
diff --git a/harry-core/src/harry/runner/DataTracker.java b/harry-core/src/harry/runner/DataTracker.java
index d5825fb..dab6fc9 100644
--- a/harry-core/src/harry/runner/DataTracker.java
+++ b/harry-core/src/harry/runner/DataTracker.java
@@ -18,6 +18,8 @@
 
 package harry.runner;
 
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeName;
 import harry.core.Configuration;
 
 public interface DataTracker
@@ -35,6 +37,7 @@ public interface DataTracker
     }
 
     public static DataTracker NO_OP = new NoOpDataTracker();
+
     class NoOpDataTracker implements DataTracker
     {
         private NoOpDataTracker() {}
diff --git a/harry-core/src/harry/runner/DefaultDataTracker.java b/harry-core/src/harry/runner/DefaultDataTracker.java
index 1b55482..7b1412a 100644
--- a/harry-core/src/harry/runner/DefaultDataTracker.java
+++ b/harry-core/src/harry/runner/DefaultDataTracker.java
@@ -23,11 +23,11 @@ import java.util.List;
 import java.util.concurrent.PriorityBlockingQueue;
 import java.util.concurrent.atomic.AtomicLong;
 
-import com.google.common.annotations.VisibleForTesting;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import harry.core.Configuration;
+import harry.core.VisibleForTesting;
 
 public class DefaultDataTracker implements DataTracker
 {
diff --git a/harry-core/src/harry/runner/MutatingPartitionVisitor.java b/harry-core/src/harry/runner/MutatingPartitionVisitor.java
index 02aa6a1..4df793e 100644
--- a/harry-core/src/harry/runner/MutatingPartitionVisitor.java
+++ b/harry-core/src/harry/runner/MutatingPartitionVisitor.java
@@ -127,6 +127,7 @@ public class MutatingPartitionVisitor extends AbstractPartitionVisitor
         if (sut.isShutdown())
             throw new IllegalStateException("System under test is shut down");
 
+        // TODO: limit a number of retries
         sut.executeAsync(statement.cql(), SystemUnderTest.ConsistencyLevel.QUORUM, statement.bindings())
            .whenComplete((res, t) -> {
                if (t != null)
diff --git a/harry-core/src/harry/runner/MutatingRowVisitor.java b/harry-core/src/harry/runner/MutatingRowVisitor.java
index b928df7..a14fc96 100644
--- a/harry-core/src/harry/runner/MutatingRowVisitor.java
+++ b/harry-core/src/harry/runner/MutatingRowVisitor.java
@@ -20,6 +20,7 @@ package harry.runner;
 
 import harry.core.MetricReporter;
 import harry.core.Run;
+import harry.core.VisibleForTesting;
 import harry.ddl.SchemaSpec;
 import harry.model.OpSelectors;
 import harry.operations.CompiledStatement;
@@ -37,21 +38,35 @@ public class MutatingRowVisitor implements Operation
 
     public MutatingRowVisitor(Run run)
     {
-        this.metricReporter = run.metricReporter;
-        this.schema = run.schemaSpec;
-        this.clock = run.clock;
-        this.descriptorSelector = run.descriptorSelector;
-        this.rangeSelector = run.rangeSelector;
+        this(run.schemaSpec,
+             run.clock,
+             run.descriptorSelector,
+             run.rangeSelector,
+             run.metricReporter);
     }
 
-    public CompiledStatement write(long lts, long pd, long cd, long opId)
+    @VisibleForTesting
+    public MutatingRowVisitor(SchemaSpec schema,
+                              OpSelectors.MonotonicClock clock,
+                              OpSelectors.DescriptorSelector descriptorSelector,
+                              QueryGenerator rangeSelector,
+                              MetricReporter metricReporter)
+    {
+        this.metricReporter = metricReporter;
+        this.schema = schema;
+        this.clock = clock;
+        this.descriptorSelector = descriptorSelector;
+        this.rangeSelector = rangeSelector;
+    }
+
+    public CompiledStatement insert(long lts, long pd, long cd, long opId)
     {
         metricReporter.insert();
         long[] vds = descriptorSelector.vds(pd, cd, lts, opId, schema);
         return WriteHelper.inflateInsert(schema, pd, cd, vds, null, clock.rts(lts));
     }
 
-    public CompiledStatement writeWithStatics(long lts, long pd, long cd, long opId)
+    public CompiledStatement insertWithStatics(long lts, long pd, long cd, long opId)
     {
         metricReporter.insert();
         long[] vds = descriptorSelector.vds(pd, cd, lts, opId, schema);
@@ -59,6 +74,21 @@ public class MutatingRowVisitor implements Operation
         return WriteHelper.inflateInsert(schema, pd, cd, vds, sds, clock.rts(lts));
     }
 
+    public CompiledStatement update(long lts, long pd, long cd, long opId)
+    {
+        metricReporter.insert();
+        long[] vds = descriptorSelector.vds(pd, cd, lts, opId, schema);
+        return WriteHelper.inflateUpdate(schema, pd, cd, vds, null, clock.rts(lts));
+    }
+
+    public CompiledStatement updateWithStatics(long lts, long pd, long cd, long opId)
+    {
+        metricReporter.insert();
+        long[] vds = descriptorSelector.vds(pd, cd, lts, opId, schema);
+        long[] sds = descriptorSelector.sds(pd, cd, lts, opId, schema);
+        return WriteHelper.inflateUpdate(schema, pd, cd, vds, sds, clock.rts(lts));
+    }
+
     public CompiledStatement deleteColumn(long lts, long pd, long cd, long opId)
     {
         metricReporter.columnDelete();
diff --git a/harry-core/src/harry/runner/Operation.java b/harry-core/src/harry/runner/Operation.java
index e56be4c..f44f3bd 100644
--- a/harry-core/src/harry/runner/Operation.java
+++ b/harry-core/src/harry/runner/Operation.java
@@ -35,12 +35,16 @@ public interface Operation
         {
             // TODO: switch to EnumMap
             // TODO: pluggable capabilities; OperationKind can/should bear its own logic
-            case WRITE:
-                return write(lts, pd, cd, opId);
+            case INSERT:
+                return insert(lts, pd, cd, opId);
+            case UPDATE:
+                return update(lts, pd, cd, opId);
             case DELETE_ROW:
                 return deleteRow(lts, pd, cd, opId);
-            case WRITE_WITH_STATICS:
-                return writeWithStatics(lts, pd, cd, opId);
+            case INSERT_WITH_STATICS:
+                return insertWithStatics(lts, pd, cd, opId);
+            case UPDATE_WITH_STATICS:
+                return updateWithStatics(lts, pd, cd, opId);
             case DELETE_PARTITION:
                 return deletePartition(lts, pd, opId);
             case DELETE_COLUMN:
@@ -56,7 +60,11 @@ public interface Operation
         }
     }
 
-    CompiledStatement write(long lts, long pd, long cd, long opId);
+    CompiledStatement insert(long lts, long pd, long cd, long opId);
+    CompiledStatement update(long lts, long pd, long cd, long opId);
+
+    CompiledStatement insertWithStatics(long lts, long pd, long cd, long opId);
+    CompiledStatement updateWithStatics(long lts, long pd, long cd, long opId);
 
     CompiledStatement deleteColumn(long lts, long pd, long cd, long opId);
 
@@ -66,8 +74,6 @@ public interface Operation
 
     CompiledStatement deletePartition(long lts, long pd, long opId);
 
-    CompiledStatement writeWithStatics(long lts, long pd, long cd, long opId);
-
     CompiledStatement deleteRange(long lts, long pd, long opId);
 
     CompiledStatement deleteSlice(long lts, long pd, long opId);
diff --git a/harry-core/src/harry/runner/QueryGenerator.java b/harry-core/src/harry/runner/QueryGenerator.java
index 65b21ac..829cf8b 100644
--- a/harry-core/src/harry/runner/QueryGenerator.java
+++ b/harry-core/src/harry/runner/QueryGenerator.java
@@ -317,7 +317,7 @@ public class QueryGenerator
                 // TODO: one of the ways to get rid of garbage here, and potentially even simplify the code is to
                 //       simply return bounds here. After bounds are created, we slice them and generate query right
                 //       from the bounds. In this case, we can even say that things like -inf/+inf are special values,
-                //       and use them as placeholdrs. Also, it'll be easier to manipulate relations.
+                //       and use them as placeholders. Also, it'll be easier to manipulate relations.
                 return new Query.ClusteringRangeQuery(Query.QueryKind.CLUSTERING_RANGE,
                                                       pd,
                                                       stitchedMin,
diff --git a/harry-core/src/harry/util/BitSet.java b/harry-core/src/harry/util/BitSet.java
index 3c70052..4dc8823 100644
--- a/harry-core/src/harry/util/BitSet.java
+++ b/harry-core/src/harry/util/BitSet.java
@@ -178,7 +178,7 @@ public interface BitSet
 
         public boolean isSet(int idx)
         {
-            assert idx < size();
+            assert idx < size() : String.format("Trying to query the bit (%s) outside the range of bitset (%s)", idx, size());
             return BitSet.isSet(bits, idx);
         }
 
diff --git a/harry-core/src/harry/util/TestRunner.java b/harry-core/src/harry/util/TestRunner.java
index 9abea62..4349fd2 100644
--- a/harry-core/src/harry/util/TestRunner.java
+++ b/harry-core/src/harry/util/TestRunner.java
@@ -44,19 +44,24 @@ public class TestRunner
 
     public static <T1, T2> void test(Generator<T1> gen1,
                                      Function<T1, Generator<T2>> gen2,
-                                     Consumer<T2> validate)
+                                     ThrowingConsumer<T2> validate) throws Throwable
     {
         test(gen1,
              (v1) -> test(gen2.apply(v1), validate));
     }
 
     public static <T1> void test(Generator<T1> gen1,
-                                 Consumer<T1> validate)
+                                 ThrowingConsumer<T1> validate) throws Throwable
     {
         for (int i = 0; i < CYCLES; i++)
         {
             validate.accept(gen1.generate(rand));
         }
     }
+
+    public static interface ThrowingConsumer<T>
+    {
+        void accept(T t) throws Throwable;
+    }
 }
 
diff --git a/harry-core/test/harry/model/OpSelectorsTest.java b/harry-core/test/harry/model/OpSelectorsTest.java
index b291cbc..266aeb9 100644
--- a/harry-core/test/harry/model/OpSelectorsTest.java
+++ b/harry-core/test/harry/model/OpSelectorsTest.java
@@ -25,7 +25,6 @@ import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
-import java.util.Random;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.BiConsumer;
@@ -39,7 +38,6 @@ import harry.core.Run;
 import harry.ddl.ColumnSpec;
 import harry.ddl.SchemaGenerators;
 import harry.ddl.SchemaSpec;
-import harry.generators.RngUtils;
 import harry.generators.Surjections;
 import harry.generators.distribution.Distribution;
 import harry.model.clock.OffsetClock;
@@ -186,10 +184,11 @@ public class OpSelectorsTest
         OpSelectors.PdSelector pdSelector = new OpSelectors.DefaultPdSelector(rng, 10, 10);
         OpSelectors.DescriptorSelector ckSelector = new OpSelectors.DefaultDescriptorSelector(rng,
                                                                                               new OpSelectors.ColumnSelectorBuilder().forAll(schema, Surjections.pick(BitSet.allUnset(0))).build(),
-                                                                                              OpSelectors.OperationSelector.weighted(Surjections.weights(10, 10, 80),
+                                                                                              OpSelectors.OperationSelector.weighted(Surjections.weights(10, 10, 40, 40),
                                                                                                                                      OpSelectors.OperationKind.DELETE_ROW,
                                                                                                                                      OpSelectors.OperationKind.DELETE_COLUMN,
-                                                                                                                                     OpSelectors.OperationKind.WRITE),
+                                                                                                                                     OpSelectors.OperationKind.INSERT,
+                                                                                                                                     OpSelectors.OperationKind.UPDATE),
                                                                                               new Distribution.ConstantDistribution(2),
                                                                                               new Distribution.ConstantDistribution(5),
                                                                                               10);
@@ -217,7 +216,13 @@ public class OpSelectorsTest
         PartitionVisitor partitionVisitor = new MutatingPartitionVisitor(run,
                                                                          (r) -> new Operation()
                                                                          {
-                                                                             public CompiledStatement write(long lts, long pd, long cd, long m)
+                                                                             public CompiledStatement insert(long lts, long pd, long cd, long m)
+                                                                             {
+                                                                                 consumer.accept(pd, cd);
+                                                                                 return compiledStatement;
+                                                                             }
+
+                                                                             public CompiledStatement update(long lts, long pd, long cd, long opId)
                                                                              {
                                                                                  consumer.accept(pd, cd);
                                                                                  return compiledStatement;
@@ -247,7 +252,13 @@ public class OpSelectorsTest
                                                                                  return compiledStatement;
                                                                              }
 
-                                                                             public CompiledStatement writeWithStatics(long lts, long pd, long cd, long opId)
+                                                                             public CompiledStatement insertWithStatics(long lts, long pd, long cd, long opId)
+                                                                             {
+                                                                                 consumer.accept(pd, cd);
+                                                                                 return compiledStatement;
+                                                                             }
+
+                                                                             public CompiledStatement updateWithStatics(long lts, long pd, long cd, long opId)
                                                                              {
                                                                                  consumer.accept(pd, cd);
                                                                                  return compiledStatement;
@@ -290,10 +301,11 @@ public class OpSelectorsTest
         OpSelectors.DescriptorSelector ckSelector = new OpSelectors.HierarchicalDescriptorSelector(rng,
                                                                                                    new int[] {10, 20},
                                                                                                    OpSelectors.columnSelectorBuilder().forAll(schema, Surjections.pick(BitSet.allUnset(0))).build(),
-                                                                                                   OpSelectors.OperationSelector.weighted(Surjections.weights(10, 10, 80),
+                                                                                                   OpSelectors.OperationSelector.weighted(Surjections.weights(10, 10, 40, 40),
                                                                                                                                           OpSelectors.OperationKind.DELETE_ROW,
                                                                                                                                           OpSelectors.OperationKind.DELETE_COLUMN,
-                                                                                                                                          OpSelectors.OperationKind.WRITE),
+                                                                                                                                          OpSelectors.OperationKind.INSERT,
+                                                                                                                                          OpSelectors.OperationKind.UPDATE),
                                                                                                    new Distribution.ConstantDistribution(2),
                                                                                                    new Distribution.ConstantDistribution(5),
                                                                                                    100);
@@ -323,8 +335,10 @@ public class OpSelectorsTest
         config.put(OpSelectors.OperationKind.DELETE_COLUMN, 1);
         config.put(OpSelectors.OperationKind.DELETE_PARTITION, 1);
         config.put(OpSelectors.OperationKind.DELETE_COLUMN_WITH_STATICS, 1);
-        config.put(OpSelectors.OperationKind.WRITE_WITH_STATICS, 1000);
-        config.put(OpSelectors.OperationKind.WRITE, 1000);
+        config.put(OpSelectors.OperationKind.UPDATE, 500);
+        config.put(OpSelectors.OperationKind.INSERT, 500);
+        config.put(OpSelectors.OperationKind.UPDATE_WITH_STATICS, 500);
+        config.put(OpSelectors.OperationKind.INSERT_WITH_STATICS, 500);
 
         int[] weights = new int[config.size()];
         for (int i = 0; i < config.values().size(); i++)

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