Permalink
Browse files

fixed series of issues in handling of foreign keys

  • Loading branch information...
arcuri82 committed Aug 24, 2018
1 parent 07bcd0c commit 00b60e45b4e155e736a1afef6c40fedf4a1c1820
@@ -7,7 +7,9 @@
public String printableValue;
/**
* If non null, then printableValue should be null
* If non null, then printableValue should be null.
* This should be an id of an InsertionDto previously
* executed
*/
public Long foreignKeyToPreviouslyGeneratedRow;
}
@@ -1,6 +1,7 @@
package org.evomaster.clientJava.controller.db;
import org.evomaster.clientJava.controllerApi.dto.database.operations.InsertionDto;
import org.evomaster.clientJava.controllerApi.dto.database.operations.InsertionEntryDto;
import java.io.*;
import java.sql.Connection;
@@ -151,7 +152,9 @@ public static void execInsert(Connection conn, List<InsertionDto> insertions) th
//From DTO Insertion Id to generated Id in database
Map<Long, Long> map = new HashMap<>();
for (InsertionDto insDto : insertions) {
for (int i = 0; i < insertions.size(); i++) {
InsertionDto insDto = insertions.get(i);
StringBuilder sql = new StringBuilder(insertSql);
sql.append(insDto.targetTable).append(" (");
@@ -162,21 +165,47 @@ public static void execInsert(Connection conn, List<InsertionDto> insertions) th
sql.append(" ) VALUES (");
for (InsertionEntryDto e : insDto.data) {
if (e.printableValue == null && e.foreignKeyToPreviouslyGeneratedRow != null) {
if (!map.containsKey(e.foreignKeyToPreviouslyGeneratedRow)) {
throw new IllegalArgumentException(
"Insertion operation at position " + i
+ " has a foreign key reference to key "
+ e.foreignKeyToPreviouslyGeneratedRow
+ " but that was not processed."
+ " Processed primary keys: "
+ map.keySet().stream().map(v -> v.toString()).collect(Collectors.joining(", "))
);
}
}
}
sql.append(insDto.data.stream()
.map(e -> e.printableValue != null
? e.printableValue
? replaceQuotes(e.printableValue)
: map.get(e.foreignKeyToPreviouslyGeneratedRow).toString()
).collect(Collectors.joining(",")));
sql.append(");");
Long id = execInsert(conn, sql.toString());
if(id != null){
if (id != null) {
map.put(insDto.id, id);
}
}
}
/**
* In SQL, strings need '' instead of "" (at least for H2).
*/
private static String replaceQuotes(String value) {
if (value.startsWith("\"") && value.endsWith("\"")) {
return "'" + value.substring(1, value.length() - 1) + "'";
}
return value;
}
/**
* @return a single id for the new row, if any was automatically generated, {@code null} otherwise
* @throws SQLException
@@ -145,7 +145,7 @@ class RestFitness : FitnessFunction<RestIndividual>() {
}
}
if(! dbData.isEmpty()){
if (!dbData.isEmpty()) {
fv.emptySelects = EmptySelects.fromDtos(dbData)
}
@@ -180,34 +180,40 @@ class RestFitness : FitnessFunction<RestIndividual>() {
private fun doInitializingActions(ind: RestIndividual) {
if(ind.dbInitialization.isEmpty()){
if (ind.dbInitialization.isEmpty()) {
return
}
val list = mutableListOf<InsertionDto>()
val previous = mutableListOf<Gene>()
for(i in 0 until ind.dbInitialization.size){
for (i in 0 until ind.dbInitialization.size) {
val action = ind.dbInitialization[i]
val insertion = InsertionDto().apply { targetTable = action.table.name }
for(g in action.seeGenes()){
if(! g.isPrintable()){
continue
for (g in action.seeGenes()) {
if (g is SqlPrimaryKeyGene) {
/*
If there is more than one primary key field, this
will be overridden.
But, as we need it only for automatically generated ones,
this shouldn't matter, as in that case there should be just 1.
*/
insertion.id = g.uniqueId
}
if(g is SqlPrimaryKeyGene){
insertion.id = g.uniqueId
if (!g.isPrintable()) {
continue
}
val entry = InsertionEntryDto()
if(g is SqlForeignKeyGene) {
if (g is SqlForeignKeyGene) {
handleSqlForeignKey(g, previous, entry)
} else if(g is SqlPrimaryKeyGene){
} else if (g is SqlPrimaryKeyGene) {
val k = g.gene
if(k is SqlForeignKeyGene){
if (k is SqlForeignKeyGene) {
handleSqlForeignKey(k, previous, entry)
} else {
entry.printableValue = g.getValueAsPrintableString()
@@ -228,12 +234,16 @@ class RestFitness : FitnessFunction<RestIndividual>() {
val dto = DatabaseCommandDto().apply { insertions = list }
val ok = rc.executeDatabaseCommand(dto)
if(! ok){
if (!ok) {
log.warn("Failed in executing database command")
}
}
private fun handleSqlForeignKey(g: SqlForeignKeyGene, previous: List< Gene>, entry: InsertionEntryDto) {
private fun handleSqlForeignKey(
g: SqlForeignKeyGene,
previous: List<Gene>,
entry: InsertionEntryDto
) {
if (g.isReferenceToNonPrintable(previous)) {
entry.foreignKeyToPreviouslyGeneratedRow = g.uniqueIdOfPrimaryKey
} else {
@@ -171,9 +171,9 @@ class RestSampler : Sampler<RestIndividual>() {
all.asSequence()
.filter { it.isMutable() }
.forEach {
if(it is SqlPrimaryKeyGene){
if (it is SqlPrimaryKeyGene) {
val g = it.gene
if(g is SqlForeignKeyGene){
if (g is SqlForeignKeyGene) {
g.randomize(randomness, false, all)
} else {
it.randomize(randomness, false)
@@ -185,9 +185,41 @@ class RestSampler : Sampler<RestIndividual>() {
}
}
if(javaClass.desiredAssertionStatus()) {
//TODO refactor if/when Kotlin will support lazy asserts
assert(verifyForeignKeys(actions))
}
return actions
}
private fun verifyForeignKeys(actions: List<DbAction>) : Boolean {
val all = actions.flatMap { it.seeGenes() }
for (i in 1 until actions.size) {
val previous = actions.subList(0, i)
actions[i].seeGenes().asSequence()
.flatMap { it.flatView().asSequence() }
.filterIsInstance<SqlForeignKeyGene>()
.filter { it.isReferenceToNonPrintable(all) }
.map { it.uniqueIdOfPrimaryKey }
.forEach {
val id = it
val match = previous.asSequence()
.flatMap { it.seeGenes().asSequence() }
.filterIsInstance<SqlPrimaryKeyGene>()
.any { it.uniqueId == id }
if(! match){
return false
}
}
}
return true
}
override fun sampleAtRandom(): RestIndividual {
@@ -54,7 +54,7 @@ class SqlForeignKeyGene(
if (pks.isEmpty()) {
if (!nullable) {
throw IllegalStateException("Trying to bind a non-nullable FK, but not valid PK is found")
throw IllegalStateException("Trying to bind a non-nullable FK, but no valid PK is found")
} else {
uniqueIdOfPrimaryKey = -1
return
@@ -25,19 +25,20 @@ class StandardMutator<T> : Mutator<T>() where T : Individual {
return copy
}
val genes = copy.seeGenes().filter(Gene::isMutable)
val genesToMutate = copy.seeGenes().filter(Gene::isMutable)
val allGenes = copy.seeGenes().flatMap { it.flatView() }
if (genes.isEmpty()) {
if (genesToMutate.isEmpty()) {
return copy
}
val p = 1.0 / genes.size
val p = 1.0 / genesToMutate.size
var mutated = false
while (!mutated) { //no point in returning a copy that is not mutated
for (gene in genes) {
for (gene in genesToMutate) {
if (!randomness.nextBoolean(p)) {
continue
@@ -47,7 +48,7 @@ class StandardMutator<T> : Mutator<T>() where T : Individual {
continue
}
mutateGene(gene, genes)
mutateGene(gene, allGenes)
mutated = true
}

0 comments on commit 00b60e4

Please sign in to comment.