Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.evomaster.client.java.controller.api.dto.database.cassandra;

/**
* A single column of a Cassandra table, as read from the driver's own metadata on the SUT side and
* reported to the core process.
*/
public class CassandraColumnDto {

private String name;

/**
* The type of the column, as named in CQL, eg "text", "int", "map<text, int>".
*/
private String cqlType;

/**
* Whether this column is part of the partition key of the table.
*/
private boolean partitionKey;

/**
* Whether this column is one of the clustering columns of the table.
*/
private boolean clusteringColumn;

/**
* Needed to deserialize the DTO, as it is sent over HTTP.
*/
public CassandraColumnDto() {
}

public CassandraColumnDto(String name, String cqlType, boolean partitionKey, boolean clusteringColumn) {
this.name = name;
this.cqlType = cqlType;
this.partitionKey = partitionKey;
this.clusteringColumn = clusteringColumn;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getCqlType() {
return cqlType;
}

public void setCqlType(String cqlType) {
this.cqlType = cqlType;
}

public boolean isPartitionKey() {
return partitionKey;
}

public void setPartitionKey(boolean partitionKey) {
this.partitionKey = partitionKey;
}

public boolean isClusteringColumn() {
return clusteringColumn;
}

public void setClusteringColumn(boolean clusteringColumn) {
this.clusteringColumn = clusteringColumn;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.evomaster.client.java.controller.api.dto.database.cassandra;

import java.util.ArrayList;
import java.util.List;

/**
* The shape of a Cassandra table, ie the columns a row of it is composed of, as read from the
* driver's own metadata on the SUT side. It is what the core process bases the data it generates
* for such a table on.
*/
public class CassandraTableSchemaDto {

private String keyspaceName;

private String tableName;

/**
* All the columns of the table, in the order the driver reports them.
*/
private List<CassandraColumnDto> columns = new ArrayList<>();

/**
* Needed to deserialize the DTO, as it is sent over HTTP.
*/
public CassandraTableSchemaDto() {
}

public CassandraTableSchemaDto(String keyspaceName, String tableName, List<CassandraColumnDto> columns) {
this.keyspaceName = keyspaceName;
this.tableName = tableName;
this.columns = columns;
}

public String getKeyspaceName() {
return keyspaceName;
}

public void setKeyspaceName(String keyspaceName) {
this.keyspaceName = keyspaceName;
}

public String getTableName() {
return tableName;
}

public void setTableName(String tableName) {
this.tableName = tableName;
}

public List<CassandraColumnDto> getColumns() {
return columns;
}

public void setColumns(List<CassandraColumnDto> columns) {
this.columns = columns;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.evomaster.client.java.controller.api.dto.database.execution;

import org.evomaster.client.java.controller.api.dto.database.cassandra.CassandraTableSchemaDto;

import java.util.Objects;

/**
Expand All @@ -10,17 +12,24 @@ public class CassandraFailedQuery {
/**
* The keyspace the table belongs to.
*/
private final String keyspaceName;
private String keyspaceName;
/**
* The table the query targeted.
*/
private final String tableName;
private String tableName;
/**
* The shape of the table's rows, null when it was never captured, ie when no query referencing
* the table was intercepted while the schema of its tables was being tracked.
*/
private CassandraTableSchemaDto tableSchema;

/**
* The schema of the table's rows, if known.
* Needed to deserialize the DTO, as it is sent over HTTP.
*/
private String tableSchema;
public CassandraFailedQuery() {
}

public CassandraFailedQuery(String keyspaceName, String tableName, String tableSchema) {
public CassandraFailedQuery(String keyspaceName, String tableName, CassandraTableSchemaDto tableSchema) {
this.keyspaceName = Objects.requireNonNull(keyspaceName);
this.tableName = Objects.requireNonNull(tableName);
this.tableSchema = tableSchema;
Expand All @@ -30,11 +39,23 @@ public String getKeyspaceName() {
return keyspaceName;
}

public void setKeyspaceName(String keyspaceName) {
this.keyspaceName = keyspaceName;
}

public String getTableName() {
return tableName;
}

public String getTableSchema() {
public void setTableName(String tableName) {
this.tableName = tableName;
}

public CassandraTableSchemaDto getTableSchema() {
return tableSchema;
}

public void setTableSchema(CassandraTableSchemaDto tableSchema) {
this.tableSchema = tableSchema;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.evomaster.client.java.controller.internal.db.cassandra;

import org.evomaster.client.java.controller.api.dto.database.cassandra.CassandraColumnDto;
import org.evomaster.client.java.controller.api.dto.database.cassandra.CassandraTableSchemaDto;
import org.evomaster.client.java.controller.api.dto.database.execution.CassandraExecutionsDto;
import org.evomaster.client.java.controller.api.dto.database.execution.CassandraFailedQuery;
import org.evomaster.client.java.controller.cassandra.calculator.CassandraHeuristicsCalculator;
Expand Down Expand Up @@ -44,13 +46,6 @@ public class CassandraHandler {
private static final String METHOD_AS_INTERNAL = "asInternal";
private static final String METHOD_GET_OBJECT = "getObject";

/*
Constants used during schema description
*/
private static final String PARTITION_KEY_COLUMN_SUFFIX = " PARTITION KEY";
private static final String CLUSTERING_COLUMN_SUFFIX = " CLUSTERING";
private static final char COLUMN_NAME_TYPE_SEPARATOR = ' ';

private static final String SELECT_ALL_PREFIX = "SELECT * FROM ";
private static final char KEYSPACE_TABLE_SEPARATOR = '.';

Expand Down Expand Up @@ -185,32 +180,27 @@ public CassandraExecutionsDto getExecutionDto() {

private CassandraFailedQuery extractRelevantInfo(ExecutedCqlCommand info) {
CassandraTableMetadata schema = tableSchemas.get(new TableKey(info.getKeyspaceName(), info.getTableName()));
String tableSchema = schema != null ? describeTableSchema(schema) : null;
CassandraTableSchemaDto tableSchema = schema != null ? toDto(schema) : null;

return new CassandraFailedQuery(info.getKeyspaceName(), info.getTableName(), tableSchema);
}

/**
* Renders a table's columns as {@code name type [PARTITION KEY|CLUSTERING]} entries, so the
* schema can be attached to a {@link CassandraFailedQuery} (a plain string, to keep
* Converts a captured table schema into the DTO it is reported with, which is what keeps
* controller-api free of a dependency on the instrumentation module's
* {@link CassandraTableMetadata}).
* {@link CassandraTableMetadata}.
*/
private static String describeTableSchema(CassandraTableMetadata schema) {
return schema.getColumns().stream()
.map(CassandraHandler::describeColumn)
.collect(Collectors.joining(", "));
private static CassandraTableSchemaDto toDto(CassandraTableMetadata schema) {
List<CassandraColumnDto> columns = schema.getColumns().stream()
.map(CassandraHandler::toDto)
.collect(Collectors.toList());

return new CassandraTableSchemaDto(schema.getKeyspaceName(), schema.getTableName(), columns);
}

private static String describeColumn(CassandraColumnMetadata column) {
StringBuilder description = new StringBuilder(column.getName()).append(COLUMN_NAME_TYPE_SEPARATOR).append(column.getCqlType());
if (column.isPartitionKey()) {
description.append(PARTITION_KEY_COLUMN_SUFFIX);
}
if (column.isClusteringColumn()) {
description.append(CLUSTERING_COLUMN_SUFFIX);
}
return description.toString();
private static CassandraColumnDto toDto(CassandraColumnMetadata column) {
return new CassandraColumnDto(column.getName(), column.getCqlType(),
column.isPartitionKey(), column.isClusteringColumn());
}

private CqlDistanceWithMetrics computeQueryDistance(ExecutedCqlCommand info) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static void initClass() {
connection.execute("CREATE KEYSPACE IF NOT EXISTS " + KEYSPACE +
" WITH replication = {'class':'SimpleStrategy','replication_factor':1}");
connection.execute("CREATE TABLE IF NOT EXISTS " + KEYSPACE + "." + TABLE +
" (id int PRIMARY KEY, name text)");
" (id int PRIMARY KEY, name text, elapsed duration)");
}

@AfterAll
Expand Down Expand Up @@ -77,6 +77,26 @@ public void testInsert() {
assertTrue(connection.execute("SELECT * FROM " + KEYSPACE + "." + TABLE).iterator().hasNext());
}

/**
* A duration is written as a bare literal, ie not enclosed in quotes the way a text is, and it
* carries at most one leading sign, applying to the whole value. This is what
* CassandraLiteralRenderer in the core module relies on when rendering the value of a
* CqlDurationGene, so it is checked here against a real Cassandra.
*/
@Test
public void testInsertDuration() {
List<CassandraInsertionDto> insertions = CassandraDsl.cassandra()
.insertInto(KEYSPACE, TABLE).d("id", "1").d("elapsed", "1mo2d3ns")
.and().insertInto(KEYSPACE, TABLE).d("id", "2").d("elapsed", "-1mo2d3ns")
.dtos();

CassandraInsertionResultsDto resultsDto = CassandraScriptRunner.executeInsert(connection, insertions);

assertTrue(resultsDto.executionResults.get(0));
assertTrue(resultsDto.executionResults.get(1));
assertEquals(2, connection.execute("SELECT * FROM " + KEYSPACE + "." + TABLE).all().size());
}

@Test
public void testInsertionFailureDoesNotStopFollowingInsertions() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import org.evomaster.client.java.controller.api.dto.database.cassandra.CassandraColumnDto;
import org.evomaster.client.java.controller.api.dto.database.cassandra.CassandraTableSchemaDto;
import org.evomaster.client.java.controller.api.dto.database.execution.CassandraExecutionsDto;
import org.evomaster.client.java.instrumentation.ExecutedCqlCommand;
import org.evomaster.client.java.instrumentation.cassandra.CassandraColumnMetadata;
Expand Down Expand Up @@ -81,6 +83,23 @@ private static CassandraTableMetadata tableSchema(String tableName) {
Collections.singletonList(new CassandraColumnMetadata("id", "int", true, false)));
}

/**
* Asserts that the reported schema is the one {@link #tableSchema(String)} captured, ie that
* every part of the captured metadata reached the DTO.
*/
private void assertReportedSchemaOfTable(CassandraTableSchemaDto reported) {
assertNotNull(reported);
assertEquals(KEYSPACE, reported.getKeyspaceName());
assertEquals(TABLE, reported.getTableName());
assertEquals(1, reported.getColumns().size());

CassandraColumnDto column = reported.getColumns().get(0);
assertEquals("id", column.getName());
assertEquals("int", column.getCqlType());
assertTrue(column.isPartitionKey());
assertFalse(column.isClusteringColumn());
}

@Test
public void testSelectDistance_zeroWhenRowMatches() {
session.execute("INSERT INTO " + KEYSPACE + "." + TABLE + " (id, age, name) VALUES (1, 30, 'John Doe')");
Expand Down Expand Up @@ -168,7 +187,7 @@ public void testEmptyTable_recordedAsFailedQueryWithSchema() {
assertEquals(1, dto.failedQueries.size());
assertEquals(KEYSPACE, dto.failedQueries.get(0).getKeyspaceName());
assertEquals(TABLE, dto.failedQueries.get(0).getTableName());
assertEquals("id int PARTITION KEY", dto.failedQueries.get(0).getTableSchema());
assertReportedSchemaOfTable(dto.failedQueries.get(0).getTableSchema());
}

/**
Expand Down Expand Up @@ -226,7 +245,7 @@ public void testReset_preservesTableSchema() {
CassandraExecutionsDto dto = handler.getExecutionDto();
assertEquals(1, dto.failedQueries.size());
// schema was captured before reset() and must still be attached afterwards
assertEquals("id int PARTITION KEY", dto.failedQueries.get(0).getTableSchema());
assertReportedSchemaOfTable(dto.failedQueries.get(0).getTableSchema());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.evomaster.core.database.cassandra

import org.evomaster.client.java.controller.api.dto.database.cassandra.CassandraColumnDto

/**
* A single column of a Cassandra table, as reported by the SUT driver in the schema of the table a
* failed CQL query targeted.
*/
data class CassandraColumn(

val name: String,

/**
* The CQL type of the column, as named in the CQL schema, eg "text", "int", "map<text, int>".
*/
val cqlType: String,

/**
* Whether this column is part of the table's partition key.
*/
val isPartitionKey: Boolean = false,

/**
* Whether this column is one of the table's clustering columns.
*/
val isClusteringColumn: Boolean = false
) {

companion object {

/**
* @param dto the column of a table, as reported by the SUT driver
*/
fun fromDto(dto: CassandraColumnDto) = CassandraColumn(
name = dto.name,
cqlType = dto.cqlType,
isPartitionKey = dto.isPartitionKey,
isClusteringColumn = dto.isClusteringColumn
)
}
}
Loading
Loading