rename activitytype modules to driver modules

This commit is contained in:
Jonathan Shook
2020-05-05 11:27:31 -05:00
parent 1a74499307
commit 2a93ac4987
189 changed files with 22 additions and 22 deletions

32
driver-cqlverify/pom.xml Normal file
View File

@@ -0,0 +1,32 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.nosqlbench</groupId>
<artifactId>mvn-defaults</artifactId>
<version>3.12.100-SNAPSHOT</version>
<relativePath>../mvn-defaults</relativePath>
</parent>
<artifactId>driver-cqlverify</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
A CQL content verifier ActivityType, based on the CQL ActivityType
built on http://nosqlbench.io/
</description>
<dependencies>
<!-- core dependencies -->
<dependency>
<groupId>io.nosqlbench</groupId>
<artifactId>driver-cql</artifactId>
<version>3.12.100-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,20 @@
package io.nosqlbench.activitytype.cqlverify;
import io.nosqlbench.activitytype.cql.core.CqlAction;
import io.nosqlbench.activitytype.cql.core.CqlActivity;
import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
public class CqlVerifyAction extends CqlAction implements ActivityDefObserver {
public CqlVerifyAction(ActivityDef activityDef, int slot, CqlActivity cqlActivity) {
super(activityDef, slot, cqlActivity);
}
@Override
public void onActivityDefUpdate(ActivityDef activityDef) {
super.onActivityDefUpdate(activityDef);
}
}

View File

@@ -0,0 +1,21 @@
package io.nosqlbench.activitytype.cqlverify;
import io.nosqlbench.activitytype.cql.core.CqlActionDispenser;
import io.nosqlbench.activitytype.cql.core.CqlActivity;
import io.nosqlbench.engine.api.activityapi.core.Action;
public class CqlVerifyActionDispenser extends CqlActionDispenser {
public CqlVerifyActionDispenser(CqlActivity cqlActivity) {
super(cqlActivity);
}
public Action getAction(int slot) {
long async= getCqlActivity().getActivityDef().getParams().getOptionalLong("async").orElse(0L);
if (async>0) {
return new CqlVerifyAsyncAction(getCqlActivity().getActivityDef(), slot, getCqlActivity());
} else {
return new CqlVerifyAction(getCqlActivity().getActivityDef(), slot, getCqlActivity());
}
}
}

View File

@@ -0,0 +1,124 @@
package io.nosqlbench.activitytype.cqlverify;
import io.nosqlbench.activitytype.cql.core.CqlActivity;
import io.nosqlbench.activitytype.cql.statements.rsoperators.AssertSingleRowResultSet;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.virtdata.core.bindings.Bindings;
import io.nosqlbench.virtdata.core.bindings.BindingsTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class CqlVerifyActivity extends CqlActivity {
private final static Logger logger = LoggerFactory.getLogger(CqlVerifyActivity.class);
private BindingsTemplate expectedValuesTemplate;
private VerificationMetrics verificationMetrics;
public CqlVerifyActivity(ActivityDef activityDef) {
super(activityDef);
}
@Override
public synchronized void initActivity() {
this.verificationMetrics = new VerificationMetrics(getActivityDef());
super.initActivity();
if (this.stmts.size() > 1) {
throw new RuntimeException("More than one statement was configured as active. "
+ this.getActivityDef().getActivityType() + " requires exactly one.");
}
Optional<String> randomMapper = stmts.stream()
.flatMap(s -> s.getBindings().values().stream())
.filter(t -> t.matches(".*Random.*") || t.matches(".*random.*"))
.findAny();
if (randomMapper.isPresent()) {
throw new RuntimeException(
"You should not try to verify data generated with random mapping " +
"functions, like " + randomMapper.get() + " as it does not " +
"produce stable results in different invocation order.");
}
}
public synchronized BindingsTemplate getExpectedValuesTemplate() {
if (expectedValuesTemplate==null) {
expectedValuesTemplate = new BindingsTemplate();
Map<String, String> bindings = stmts.get(0).getBindings();
if (stmts.get(0).getParams().containsKey("verify-fields")) {
List<String> fields = new ArrayList<>();
String fieldsSpec = stmts.get(0).getParams().get("verify-fields");
String[] vfields = fieldsSpec.split("\\s*,\\s*");
for (String vfield : vfields) {
if (vfield.equals("*")) {
bindings.forEach((k,v)->fields.add(k));
} else if (vfield.startsWith("+")) {
fields.add(vfield.substring(1));
} else if (vfield.startsWith("-")) {
fields.remove(vfield.substring(1));
} else if (vfield.matches("\\w+(\\w+->[\\w-]+)?")) {
fields.add(vfield);
} else {
throw new RuntimeException("unknown verify-fields format: '" + vfield + "'");
}
}
for (String vfield : fields) {
String[] fieldNameAndBindingName = vfield.split("\\s*->\\s*", 2);
String fieldName = fieldNameAndBindingName[0];
String bindingName = fieldNameAndBindingName.length==1 ? fieldName : fieldNameAndBindingName[1];
if (!bindings.containsKey(bindingName)) {
throw new RuntimeException("binding name '" + bindingName +
"' referenced in verify-fields, but it is not present in available bindings.");
}
expectedValuesTemplate.addFieldBinding(fieldName,bindings.get(bindingName));
}
} else {
bindings.forEach((k,v)->expectedValuesTemplate.addFieldBinding(k,v));
}
}
return expectedValuesTemplate;
}
public synchronized VerificationMetrics getVerificationMetrics() {
return verificationMetrics;
}
@Override
public void shutdownActivity() {
super.shutdownActivity();
VerificationMetrics metrics = getVerificationMetrics();
long unverifiedValues = metrics.unverifiedValuesCounter.getCount();
long unverifiedRows = metrics.unverifiedRowsCounter.getCount();
if (unverifiedRows > 0 || unverifiedValues > 0) {
throw new RuntimeException(
"There were " + unverifiedValues + " unverified values across " + unverifiedRows + " unverified rows."
);
}
logger.info("verified " + metrics.verifiedValuesCounter.getCount() + " values across " + metrics.verifiedRowsCounter.getCount() + " verified rows");
}
@Override
public void onActivityDefUpdate(ActivityDef activityDef) {
super.onActivityDefUpdate(activityDef);
addResultSetCycleOperator(new AssertSingleRowResultSet());
String verify = activityDef.getParams()
.getOptionalString("compare").orElse("all");
DiffType diffType = DiffType.valueOf(verify);
Bindings verifyBindings = getExpectedValuesTemplate().resolveBindings();
var differ = new RowDifferencer.ThreadLocalWrapper(
getVerificationMetrics(),
verifyBindings,
diffType);
addRowCycleOperator(differ);
}
}

View File

@@ -0,0 +1,25 @@
package io.nosqlbench.activitytype.cqlverify;
import io.nosqlbench.engine.api.activityapi.core.ActionDispenser;
import io.nosqlbench.engine.api.activityapi.core.ActivityType;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.nb.annotations.Service;
@Service(ActivityType.class)
public class CqlVerifyActivityType implements ActivityType<CqlVerifyActivity> {
@Override
public String getName() {
return "cqlverify";
}
@Override
public ActionDispenser getActionDispenser(CqlVerifyActivity activity) {
return new CqlVerifyActionDispenser(activity);
}
@Override
public CqlVerifyActivity getActivity(ActivityDef activityDef) {
return new CqlVerifyActivity(activityDef);
}
}

View File

@@ -0,0 +1,20 @@
package io.nosqlbench.activitytype.cqlverify;
import io.nosqlbench.activitytype.cql.core.CqlActivity;
import io.nosqlbench.activitytype.cql.core.CqlAsyncAction;
import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
public class CqlVerifyAsyncAction extends CqlAsyncAction implements ActivityDefObserver {
public CqlVerifyAsyncAction(ActivityDef activityDef, int slot, CqlActivity cqlActivity) {
super(cqlActivity, slot);
}
@Override
public void onActivityDefUpdate(ActivityDef activityDef) {
super.onActivityDefUpdate(activityDef);
}
}

View File

@@ -0,0 +1,33 @@
package io.nosqlbench.activitytype.cqlverify;
public enum DiffType {
/// Verify that fields named in the row are present in the reference map.
rowfields(0x1),
/// Verify that fields in the reference map are present in the row data.
reffields(0x1<<1),
/// Verify that all fields present in either the row or the reference data
/// are also present in the other.
fields(0x1|0x1<<1),
/// Verify that all values of the same named field are equal, according to
/// {@link Object#equals(Object)}}.
values(0x1<<2),
/// Cross-verify all fields and field values between the reference data and
/// the actual data.
all(0x1|0x1<<1|0x1<<2);
public int bitmask;
DiffType(int bit) {
this.bitmask = bit;
}
public boolean is(DiffType option) {
return (bitmask & option.bitmask) > 0;
}
}

View File

@@ -0,0 +1,312 @@
package io.nosqlbench.activitytype.cqlverify;
import com.datastax.driver.core.*;
import io.nosqlbench.activitytype.cql.api.RowCycleOperator;
import io.nosqlbench.activitytype.cql.errorhandling.exceptions.RowVerificationException;
import io.nosqlbench.virtdata.core.bindings.Bindings;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
/**
* <p>RowDifferencer uses the metadata associated with a row to access and compare
* {@link Row} values in a type-specific way.
* </p>
*/
public class RowDifferencer implements RowCycleOperator {
private final StringBuilder logbuffer = new StringBuilder();
private final Map<String, Object> refMap = new HashMap<>();
private final DiffType difftype;
private final Bindings bindings;
private final VerificationMetrics metrics;
private RowDifferencer(VerificationMetrics metrics, Bindings bindings, DiffType diffType) {
this.metrics = metrics;
this.bindings = bindings;
this.difftype = diffType;
}
/**
* see {@link DataType}
*
* @param typeName The DataType.Name of the field in question
* @param row The row to read the field value from
* @param fieldName The field name to read
* @param genValue the generated value to compare against
* @return true, if the value is equal
*/
private static boolean isEqual(DataType.Name typeName, Row row, String fieldName, Object genValue) {
switch (typeName) {
case ASCII: // ASCII(1, String.class)
case VARCHAR: // VARCHAR(13, String.class)
case TEXT: // TEXT(10, String.class)
String textValue = row.getString(fieldName);
return textValue.equals(genValue);
case BIGINT: // BIGINT(2, Long.class)
case COUNTER: // COUNTER(5, Long.class)
long longValue = row.getLong(fieldName);
return longValue == (long) genValue;
case BLOB: // BLOB(3, ByteBuffer.class)
// TODO: How do we test this one?
case CUSTOM: // CUSTOM(0, ByteBuffer.class)
ByteBuffer blobValue = row.getBytes(fieldName);
return blobValue.equals(genValue);
case BOOLEAN: // BOOLEAN(4, Boolean.class)
boolean boolValue = row.getBool(fieldName);
return boolValue == (boolean) genValue;
case DECIMAL: // DECIMAL(6, BigDecimal.class)
BigDecimal bigDecimalValue = row.getDecimal(fieldName);
return bigDecimalValue.equals(genValue);
case DOUBLE: // DOUBLE(7, Double.class)
double doubleValue = row.getDouble(fieldName);
return doubleValue == (double) genValue;
case FLOAT: // FLOAT(8, Float.class)
float floatValue = row.getFloat(fieldName);
return floatValue == (float) genValue;
case INET: // INET(16, InetAddress.class)
InetAddress inetAddressValue = row.getInet(fieldName);
return inetAddressValue.equals(genValue);
case INT: // INT(9, Integer.class)
int intValue = row.getInt(fieldName);
return intValue == (int) genValue;
case TIMESTAMP: // TIMESTAMP(11, Date.class)
Date timestamp = row.getTimestamp(fieldName);
return timestamp.equals(genValue);
case UUID: // UUID(12, UUID.class)
case TIMEUUID: // TIMEUUID(15, UUID.class)
UUID uuidValue = row.getUUID(fieldName);
return uuidValue.equals(genValue);
case VARINT: // VARINT(14, BigInteger.class)
BigInteger bigIntValue = row.getVarint(fieldName);
return bigIntValue.equals(genValue);
case LIST: // LIST(32, List.class)
// TODO: How do we make getCollection methods work with non-String CQL types?
List<?> list = row.getList(fieldName, String.class);
return list.equals(genValue);
case SET: // SET(34, Set.class)
Set<?> set = row.getSet(fieldName, String.class);
return set.equals(genValue);
case MAP: // MAP(33, Map.class)
Map<?, ?> map = row.getMap(fieldName, String.class, String.class);
return map.equals(genValue);
case UDT: // UDT(48, UDTValue.class)
UDTValue udtValue = row.getUDTValue(fieldName);
return udtValue.equals(genValue);
case TUPLE: // TUPLE(49, TupleValue.class)
TupleValue tupleValue = row.getTupleValue(fieldName);
return tupleValue.equals(genValue);
case SMALLINT:
short shortVal = row.getShort(fieldName);
return shortVal == (Short) genValue;
case TINYINT:
byte byteValue = row.getByte(fieldName);
return byteValue == (byte) genValue;
case DATE:
LocalDate dateValue = row.getDate(fieldName);
return dateValue.equals(genValue);
case TIME:
long timeValue = row.getTime(fieldName);
return timeValue == (long) genValue;
default:
throw new RuntimeException("Unrecognized type:" + typeName);
}
}
private static String prettyPrint(DataType.Name typeName, Row row, String fieldName) {
switch (typeName) {
case ASCII: // ASCII(1, String.class)
case VARCHAR: // VARCHAR(13, String.class)
case TEXT: // TEXT(10, String.class)
return row.getString(fieldName);
case BIGINT: // BIGINT(2, Long.class)
case COUNTER: // COUNTER(5, Long.class)
long counterValue = row.getLong(fieldName);
return String.valueOf(counterValue);
case BLOB: // BLOB(3, ByteBuffer.class)
case CUSTOM: // CUSTOM(0, ByteBuffer.class)
ByteBuffer blobValue = row.getBytes(fieldName);
return String.valueOf(blobValue);
case BOOLEAN: // BOOLEAN(4, Boolean.class)
boolean boolValue = row.getBool(fieldName);
return String.valueOf(boolValue);
case DECIMAL: // DECIMAL(6, BigDecimal.class)
BigDecimal bigDecimalValue = row.getDecimal(fieldName);
return String.valueOf(bigDecimalValue);
case DOUBLE: // DOUBLE(7, Double.class)
double doubleValue = row.getDouble(fieldName);
return String.valueOf(doubleValue);
case FLOAT: // FLOAT(8, Float.class)
float floatValue = row.getFloat(fieldName);
return String.valueOf(floatValue);
case INET: // INET(16, InetAddress.class)
InetAddress inetAddressValue = row.getInet(fieldName);
return String.valueOf(inetAddressValue);
case INT: // INT(9, Integer.class)
int intValue = row.getInt(fieldName);
return String.valueOf(intValue);
case TIMESTAMP: // TIMESTAMP(11, Date.class)
Date timestamp = row.getTimestamp(fieldName);
return String.valueOf(timestamp);
case UUID: // UUID(12, UUID.class)
case TIMEUUID: // TIMEUUID(15, UUID.class)
UUID uuidValue = row.getUUID(fieldName);
return String.valueOf(uuidValue);
case VARINT: // VARINT(14, BigInteger.class)
BigInteger bigIntValue = row.getVarint(fieldName);
return String.valueOf(bigIntValue);
case LIST: // LIST(32, List.class)
List<?> list = row.getList(fieldName, String.class);
return String.valueOf(list);
case SET: // SET(34, Set.class)
Set<?> set = row.getSet(fieldName, String.class);
return String.valueOf(set);
case MAP: // MAP(33, Map.class)
Map<?, ?> map = row.getMap(fieldName, String.class, String.class);
return String.valueOf(map);
case UDT: // UDT(48, UDTValue.class)
UDTValue udtValue = row.getUDTValue(fieldName);
return String.valueOf(udtValue);
case TUPLE: // TUPLE(49, TupleValue.class)
TupleValue tupleValue = row.getTupleValue(fieldName);
return String.valueOf(tupleValue);
case SMALLINT:
short val = row.getShort(fieldName);
return String.valueOf(val);
case TINYINT:
byte byteValue = row.getByte(fieldName);
return String.valueOf(byteValue);
case DATE:
LocalDate dateValue = row.getDate(fieldName);
return String.valueOf(dateValue);
case TIME:
long timeValue = row.getTime(fieldName);
return String.valueOf(timeValue);
default:
throw new RuntimeException("Type not recognized:" + typeName);
}
}
/**
* Compare the values of the row with the values generated.
* <p>
* Specifically,
* <ol>
* <li>Ensure the same number of fields.</li>
* <li>Ensure the same pair-wise field names.</li>
* <li>Ensure that each pair of same-named fields has the same data type.</li>
* <li>Ensure that the value of each pair of fields is equal according to the equals
* operator for the respective type.</li>
* </ol>
* *
*
* @param row A row of data
* @param referenceMap a map of values
* @return a count of differences between the row and the reference values
*/
private int compare(Row row, Map<String, Object> referenceMap) {
int diff = 0;
ColumnDefinitions cdefs = row.getColumnDefinitions();
logbuffer.setLength(0);
if (difftype.is(DiffType.reffields)) {
List<String> missingRowFields = referenceMap.keySet().stream()
.filter(gk -> !cdefs.contains(gk))
.collect(Collectors.toList());
if (missingRowFields.size() > 0) {
diff += missingRowFields.size();
logbuffer.append("\nexpected fields '");
logbuffer.append(String.join("','", missingRowFields));
logbuffer.append("' not in row.");
}
}
if (difftype.is(DiffType.rowfields)) {
List<String> missingRefFields = cdefs.asList().stream()
.map(ColumnDefinitions.Definition::getName)
.filter(k -> !referenceMap.containsKey(k))
.collect(Collectors.toList());
if (missingRefFields.size() > 0) {
diff += missingRefFields.size();
logbuffer.append("\nexpected fields '");
logbuffer.append(String.join("','", missingRefFields));
logbuffer.append("' not in reference data: " + referenceMap);
}
}
if (difftype.is(DiffType.values)) {
for (ColumnDefinitions.Definition definition : row.getColumnDefinitions()) {
String name = definition.getName();
if (referenceMap.containsKey(name)) {
DataType type = definition.getType();
if (!isEqual(type.getName(), row, name, referenceMap.get(name))) {
logbuffer.append("\nvalue differs for '").append(name).append("' ");
logbuffer.append("expected:'").append(referenceMap.get(name).toString()).append("'");
logbuffer.append(" actual:'").append(prettyPrint(type.getName(), row, name)).append("'");
diff++;
metrics.unverifiedValuesCounter.inc();
} else {
metrics.verifiedValuesCounter.inc();
}
}
}
}
if (diff == 0) {
metrics.verifiedRowsCounter.inc();
} else {
metrics.unverifiedRowsCounter.inc();
}
return diff;
}
/**
* Get the most recent detail log recorded by this thread.
*
* @return a logbuffer string, with one entry per line
*/
public String getDetail() {
return this.logbuffer.toString();
}
@Override
public int apply(Row row, long cycle) {
refMap.clear();
bindings.setMap(refMap, cycle);
int diffs = compare(row, refMap);
if (diffs > 0) {
HashMap<String, Object> mapcopy = new HashMap<>();
mapcopy.putAll(refMap);
throw new RowVerificationException(cycle, row, mapcopy, getDetail());
} else {
return 0;
}
}
public static class ThreadLocalWrapper implements RowCycleOperator {
private final VerificationMetrics metrics;
private final Bindings bindings;
private final DiffType diffType;
private ThreadLocal<RowDifferencer> tl;
public ThreadLocalWrapper(VerificationMetrics metrics, Bindings bindings, DiffType diffType) {
this.metrics = metrics;
this.bindings = bindings;
this.diffType = diffType;
tl = ThreadLocal.withInitial(() -> new RowDifferencer(metrics,bindings,diffType));
}
@Override
public int apply(Row row, long cycle) {
return tl.get().apply(row,cycle);
}
}
}

View File

@@ -0,0 +1,21 @@
package io.nosqlbench.activitytype.cqlverify;
import com.codahale.metrics.Counter;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.engine.api.metrics.ActivityMetrics;
public class VerificationMetrics {
public final Counter verifiedRowsCounter;
public final Counter unverifiedRowsCounter;
public final Counter verifiedValuesCounter;
public final Counter unverifiedValuesCounter;
public VerificationMetrics(ActivityDef activityDef) {
verifiedRowsCounter = ActivityMetrics.counter(activityDef,"verifiedrows");
unverifiedRowsCounter= ActivityMetrics.counter(activityDef,"unverifiedrows");
verifiedValuesCounter = ActivityMetrics.counter(activityDef,"verifiedvalues");
unverifiedValuesCounter = ActivityMetrics.counter(activityDef,"unverifiedvalues");
}
}

View File

@@ -0,0 +1,166 @@
# cqlverify
This activity type allows you to read values from a database and compare them to
the generated values that were expected to be written, row-by-row, producing a
comparative result between the two.
The verification options include:
1. Each row contains the right fields, according to the reference data.
2. Each row contains only the fields specified in the reference data.
3. Each value of each row, by name, is equal to the referenced data,
according to the Java equals implementation for the object type
specified in that field's metadata.
The data bindings are used to generate the expected values that would be used
for an upsert. Each row is verified according to these values, and any
discrepancy is treated as an error that can be counted, logged, etc.
### Using cqlverify
The cqlverify activity type is built on top of the cql activity type. As such,
it has all of the same capabilities and options, and then some. See the cql
activity type documentation for the usual details. This doc page only covers how
the cqlverify activity extends it.
The differences between the cql and cqlverify activity types are mostly in how
how you configure for verifiable data and error handling.
##### Writing verifiable data
The cqlverify driver does not retain logged data for verification. Still, it is able to compare data as if it had a
separate data set to compare to. This is possible only because the data generation facilities used by NoSQLBench provide
realistic and voluminous synthetic data that can be recalled from a recipe and accessed dynamically.
That means, however, that you must avoid using the non-stable data mapping functions when writing data. The rule of
thumb is to avoid using any data mapping functions containing the word "Random", as these are the ones that have
historically used internal RNG state. Instead, swap in their replacements that start with "Hashed". There is a hashed
equivalent to all of the original random functions. The rng-based functions will be deprecated in a future release.
In a typical cql activity, you are allowed to name the bindings however you like, so long as the binding names match the
anchor names in your statement template. Because we need to match reference field data to actual row data pair-wise by
field name, there is a more strict requirement for cqlverify activities. The binding names themselves are now required
to match the field names that they are expected to be compared to.
The simplest way to do this is to follow this recipe:
1. Make the binding names the same as the field names that you use in
in your write statements.
2. When you configure your read statement for the cqlverify activity,
simply include the same bindings as-is, using the partition and
clustering fields in the appropriate where clauses.
*note*: It used to be an error to have bindings names in excess of what anchor
names would match. Now, it is only an error if an anchor is not qualified with
a matching binding name. This allows you to simply copy your bindings as-is
directly from your write statement with no issues.
### Configuring the verification Reader
A cqlverify activity is almost exactly like any other cql activity. However, you
configure a single read statement to access the row data you want to verify. The
bindings for the read statement should include the data mappings that you have
for the write statement. That's pretty much all you have to do.
The names of the bindings and the values they produce are considered, depending
on the *compare* setting explained below. This means that you need to make sure
that the bindings that are provided for the statement are exactly the same as
you expect the row structure, irrespective of field order. For some statements
which use the same value in more than one place, you must name these uniquely
as well.
If more than one statement is active for a cqlverify activity, then an error is
thrown. This may change in the future, but for now it is a requirement.
### Handling Verification Errors
The cqlverify activity extends on the error handling stack mechanism that is
used by the cql activity type, by introducing a new error category:
*unverified*. The default configuration for this error category is
unverified=stop
However, the usual options, including "stop", "warn", "retry", "histogram",
"count", and "ignore" are also allowed.
Care should be taken to set the other error handling categories to be strict
enough to avoid false negatives in testing. The verification on a row can only
be done if the row is actually read first. If you set the error handler stack to
only count real errors, for example, then you will be preempting the read
verifier. Therefore, there is a default setting for the cqlverify activity for
the catch-all error handler parameter *errors*.
This means that the default error handling behavior will cause an exception to
be thrown and the client will exit by default. If you wish for something less
dramatic, then set it to
errors=...,unverified->count
or
errors=...,unverified->warn
##### rows to verify
Currently, every read operation in a cqlverify activity must have a single row
in the result set. If there is no row, then the row fails validation. The same
happens if there is more than one row.
A future release may allow for paged reads for quicker verification.
### Example activity definitions
Write 100K cycles of telemetry data
... run driver=cql alias=writesome workload=cql-iot tags=group:write cycles=100000 host=...
Verify the the same 100K cycles of telemetry data
... run driver=cqlverify alias=verify workload=cql-iot tags=group:verify cycles=100000 host=...
To see how these examples work, consult the telemetry.yaml file in the nosqlbench.jar.
### CQLVerify ActivityType Parameters
(in addition to those provided by the cql activity type)
- **compare** - what to verify. Valid values are "reffields",
"rowfields", "fields", "values", or "all"
(default: all)
- rowfields - Verify that fields in the row, by name, are
not in excess of what is provided in the reference data.
- reffields - Verify that fields in the row, by name, are
present for all all of those provided in the reference data.
- fields - A synonym for rowfields AND reffields
(full set equivalence)
- values - Verify that all the pair-wise fields have equal
values, according to the type-specific equals method for
the data type identified in the row metadata by field name.
- all - A synonym for fields AND values
### CQLVerify Statement Parameters
- **verify-fields** - an optional modifier of fields to verify for a statement.
If this parameter is not provided, then it is presumed to be '*' by default.
This is a string which consists of comma-separate values. If the value
is '*', then all the bindings that are visible for the statement will be
used as expected values.
If it is a word that starts with '-', like '-field2', then the name after the
dash is removed from the list of fields to verify.
If it is a word that starts with a '+', like '+field3', or a simple word,
then the field is added to the list of fields to verify.
This parameter is useful if you have a set of default bindings and want
to specify which subset of them of them will be used just for this statement.
If any of the added fields is in the form "f->b", then it is taken as a mapping
from the field name _f_ in the schema to a binding _b_.
### Metrics
The cqlverify activity type adds some verification-specific metrics:
- alias.verifiedrows - A counter for how many rows passed verification
- alias.unverifiedrows - A counter for how many rows failed verification
- alias.verifiedvalues - A counter for how many field values were verified
- alias.unverifiedvalues - A counter for how many field values were unverified

View File

@@ -0,0 +1,2 @@
# cqlverify help topics
- cqlverify