Skip to content

Commit b70e865

Browse files
committed
Refactor SQL foreign key handling to store target columns.
1 parent d6c1e9f commit b70e865

File tree

12 files changed

+133
-50
lines changed

12 files changed

+133
-50
lines changed

core/src/main/kotlin/org/evomaster/core/search/gene/sql/SqlForeignKeyGene.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,26 @@ import org.evomaster.core.sql.schema.TableId
2525
* known beforehand, as primary keys could be dynamically generated by the database.
2626
*/
2727
class SqlForeignKeyGene(
28+
/**
29+
* The name of this column in the table.
30+
*/
2831
sourceColumn: String,
32+
/**
33+
* The position in the list of SQL insertion actions
34+
* where the FK is being inserted.
35+
*/
2936
uniqueId: Long,
3037
/**
3138
* The id of the table this FK points to
3239
*/
3340
val targetTable: TableId,
41+
/**
42+
* The name of the column in the target table this FK points to.
43+
*/
44+
val targetColumn: String,
45+
/**
46+
* If true, this FK can be NULL.
47+
*/
3448
val nullable: Boolean,
3549
/**
3650
* A negative value means this FK is not bound yet.
@@ -43,7 +57,10 @@ class SqlForeignKeyGene(
4357

4458
@Deprecated("Rather use the one forcing TableId")
4559
constructor(sourceColumn: String, uniqueId: Long, targetTable: String, nullable: Boolean, uniqueIdOfPrimaryKey: Long =-1)
46-
: this(sourceColumn, uniqueId, TableId(targetTable), nullable, uniqueIdOfPrimaryKey)
60+
: this(sourceColumn, uniqueId, TableId(targetTable), "UNKNOWN_COLUMN", nullable, uniqueIdOfPrimaryKey)
61+
62+
constructor(sourceColumn: String, uniqueId: Long, targetTable: TableId, nullable: Boolean, uniqueIdOfPrimaryKey: Long =-1)
63+
: this(sourceColumn, uniqueId, targetTable, "UNKNOWN_COLUMN", nullable, uniqueIdOfPrimaryKey)
4764

4865
init {
4966
if (uniqueId < 0) {
@@ -72,7 +89,7 @@ class SqlForeignKeyGene(
7289
return this
7390
}
7491

75-
override fun copyContent() = SqlForeignKeyGene(name, uniqueId, targetTable, nullable, uniqueIdOfPrimaryKey)
92+
override fun copyContent() = SqlForeignKeyGene(name, uniqueId, targetTable, targetColumn, nullable, uniqueIdOfPrimaryKey)
7693

7794
override fun setValueWithRawString(value: String) {
7895
throw IllegalStateException("cannot set value with string ($value) for ${this.javaClass.simpleName}")

core/src/main/kotlin/org/evomaster/core/sql/SqlActionGeneBuilder.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ class SqlActionGeneBuilder {
5454
//TODO handle all constraints and cases
5555
column.autoIncrement ->
5656
SqlAutoIncrementGene(column.name)
57-
fk != null ->
58-
SqlForeignKeyGene(column.name, id, fk.targetTableId, column.nullable)
57+
fk != null -> {
58+
handleForeignKeyColumn(fk, column, id)
59+
}
5960

6061
else -> when (column.type) {
6162
// Man: TODO need to check
@@ -406,6 +407,25 @@ class SqlActionGeneBuilder {
406407
return gene
407408
}
408409

410+
private fun handleForeignKeyColumn(
411+
fk: ForeignKey,
412+
column: Column,
413+
id: Long,
414+
): SqlForeignKeyGene {
415+
val indexOfSourceColumn = fk.sourceColumns.indexOf(column)
416+
if (indexOfSourceColumn == -1) {
417+
throw IllegalArgumentException("Column $column is not part of foreign key $fk")
418+
}
419+
val targetColumn = fk.targetColumns[indexOfSourceColumn]
420+
return SqlForeignKeyGene(
421+
column.name,
422+
id,
423+
fk.targetTableId,
424+
targetColumn.name,
425+
column.nullable
426+
)
427+
}
428+
409429
private fun handleSqlGeometry(column: Column): ChoiceGene<*> {
410430
return ChoiceGene(name = column.name,
411431
listOf(buildSqlPointGene(column),

core/src/main/kotlin/org/evomaster/core/sql/SqlInsertBuilder.kt

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.evomaster.core.sql
22

3-
import org.evomaster.client.java.controller.api.dto.SqlDtoUtils
43
import org.evomaster.client.java.controller.api.dto.database.operations.DataRowDto
54
import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto
65
import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto
@@ -179,32 +178,61 @@ class SqlInsertBuilder(
179178
tableToColumns: MutableMap<TableId, MutableSet<Column>>
180179
): MutableSet<ForeignKey> {
181180
val fks = mutableSetOf<ForeignKey>()
182-
181+
val tableId = TableId.fromDto(databaseType, tableDto.id)
183182
for (fk in tableDto.foreignKeys) {
184183

185-
val tableKey = SqlActionUtils.getTableKey(tableToColumns.keys, fk.targetTable)
184+
val targetTableId = SqlActionUtils.getTableKey(tableToColumns.keys, fk.targetTable)
186185

187-
if(tableKey == null || tableToColumns[tableKey] == null) {
186+
if(targetTableId == null || tableToColumns[targetTableId] == null) {
188187
throw IllegalArgumentException("Foreign key for non-existent table ${fk.targetTable}")
189188
}
190-
val sourceColumns = mutableSetOf<Column>()
189+
val sourceColumns = getSourceColumnsOfForeignKey(fk, tableToColumns, tableId)
190+
val targetColumns = getTargetColumnsOfForeignKey(fk, tableToColumns, targetTableId)
191+
if (sourceColumns.size!=targetColumns.size) {
192+
throw IllegalArgumentException("Foreign key for table ${fk.targetTable} has ${sourceColumns.size} source columns but ${targetColumns.size} target columns")
193+
}
194+
val foreignKey = ForeignKey(sourceColumns, targetTableId, targetColumns)
195+
fks.add(foreignKey)
196+
}
197+
return fks
198+
}
191199

192-
for (cname in fk.sourceColumns) {
193-
// TODO: wrong check, as should be based on targetColumns, when we ll introduce them
194-
// val c = targetTable.find { it.name.equals(cname, ignoreCase = true) }
195-
// ?: throw IllegalArgumentException("Issue in foreign key: table ${f.targetTable} does not have a column called $cname")
200+
private fun getSourceColumnsOfForeignKey(
201+
fk: ForeignKeyDto,
202+
tableToColumns: MutableMap<TableId, MutableSet<Column>>,
203+
tableId: TableId
204+
): MutableList<Column> {
205+
val sourceColumns = mutableListOf<Column>()
206+
for (sourceColumnName in fk.sourceColumns) {
196207

197-
val id = TableId.fromDto(databaseType, tableDto.id)
208+
val c = tableToColumns[tableId]!!.find { it.name.equals(sourceColumnName, ignoreCase = true) }
209+
?: throw IllegalArgumentException("Issue in foreign key: table ${tableId.name} does not have a column called $sourceColumnName")
198210

199-
val c = tableToColumns[id]!!.find { it.name.equals(cname, ignoreCase = true) }
200-
?: throw IllegalArgumentException("Issue in foreign key: table ${tableDto.id.name} does not have a column called $cname")
211+
sourceColumns.add(c)
212+
}
213+
return sourceColumns
214+
}
201215

202-
sourceColumns.add(c)
216+
private fun getTargetColumnsOfForeignKey(
217+
foreignKeyDto: ForeignKeyDto,
218+
tableToColumns: MutableMap<TableId, MutableSet<Column>>,
219+
targetTableId: TableId,
220+
): MutableList<Column> {
221+
val targetColumns = mutableListOf<Column>()
222+
if (foreignKeyDto.targetColumns.isEmpty()) {
223+
val targetTablePrimaryKeys = tableToColumns[targetTableId]!!.filter { it.primaryKey }
224+
if (targetTablePrimaryKeys.isEmpty()) {
225+
throw IllegalArgumentException("Foreign key for table ${foreignKeyDto.targetTable} has no specified target columns and no primary key found")
226+
}
227+
targetColumns.addAll(targetTablePrimaryKeys)
228+
} else {
229+
for (targetColumnName in foreignKeyDto.targetColumns) {
230+
val targetColumn = tableToColumns[targetTableId]!!.find { it.name.equals(targetColumnName, ignoreCase = true) }
231+
?: throw IllegalArgumentException("Issue in foreign key: table ${foreignKeyDto.targetTable} does not have a column called $targetColumnName")
232+
targetColumns.add(targetColumn)
203233
}
204-
205-
fks.add(ForeignKey(sourceColumns, tableKey))
206234
}
207-
return fks
235+
return targetColumns
208236
}
209237

210238
private fun generateColumnsFrom(

core/src/main/kotlin/org/evomaster/core/sql/schema/ForeignKey.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,19 @@ package org.evomaster.core.sql.schema
77

88
data class ForeignKey(
99

10-
val sourceColumns: Set<Column>,
10+
/**
11+
* The columns in the source table that are referenced by this foreign key
12+
*/
13+
val sourceColumns: List<Column>,
1114

12-
val targetTableId: TableId
13-
)
15+
/**
16+
* The target table that is referenced by this foreign key
17+
*/
18+
val targetTableId: TableId,
19+
20+
/**
21+
* The columns in the target table that are referenced by this foreign key.
22+
* The order should match the order of the source columns.
23+
*/
24+
val targetColumns: List<Column>
25+
)

core/src/test/kotlin/org/evomaster/core/TestUtils.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ object TestUtils {
7777

7878
val fkColumName = "fkId"
7979
val fkId = Column(fkColumName, ColumnDataType.INTEGER, 10, primaryKey = false, databaseType = DatabaseType.H2)
80-
val foreignKeyGene = SqlForeignKeyGene(fkColumName, bId, TableId(aTable), false, uniqueIdOfPrimaryKey = aUniqueId)
80+
val foreignKeyGene = SqlForeignKeyGene(fkColumName, bId, TableId(aTable), "id", false, uniqueIdOfPrimaryKey = aUniqueId)
8181

8282
val barInsertion = generateFakeDbAction(bId, bUniqueId, bTable, bValue, fkId, foreignKeyGene)
8383

@@ -109,4 +109,4 @@ object TestUtils {
109109
)
110110
}
111111

112-
}
112+
}

core/src/test/kotlin/org/evomaster/core/output/TestCaseWriterTest.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ class TestCaseWriterTest : WriterTestBase(){
328328
val firstInsertionId = 1001L
329329
val insertIntoTable0 = SqlAction(table0, setOf(idColumn), firstInsertionId, listOf(primaryKeyTable0Gene))
330330
val secondInsertionId = 1002L
331-
val foreignKeyGene = SqlForeignKeyGene(fkColumn.name, secondInsertionId, "Table0", false, uniqueIdOfPrimaryKey = pkGeneUniqueId)
331+
val foreignKeyGene = SqlForeignKeyGene(fkColumn.name, secondInsertionId, TableId("Table0"), idColumn.name, false, uniqueIdOfPrimaryKey = pkGeneUniqueId)
332332

333333
val insertIntoTable1 = SqlAction(table1, setOf(idColumn, fkColumn), secondInsertionId, listOf(primaryKeyTable1Gene, foreignKeyGene))
334334

@@ -427,7 +427,7 @@ class TestCaseWriterTest : WriterTestBase(){
427427
val firstInsertionId = 1001L
428428
val insertIntoTable0 = SqlAction(table0, setOf(idColumn), firstInsertionId, listOf(primaryKeyTable0Gene))
429429
val secondInsertionId = 1002L
430-
val foreignKeyGene = SqlForeignKeyGene(fkColumn.name, secondInsertionId, "Table0", true, -1L)
430+
val foreignKeyGene = SqlForeignKeyGene(fkColumn.name, secondInsertionId, TableId("Table0"), idColumn.name, true, -1L)
431431

432432
val insertIntoTable1 = SqlAction(table1, setOf(idColumn, fkColumn), secondInsertionId, listOf(primaryKeyTable1Gene, foreignKeyGene))
433433

@@ -585,7 +585,7 @@ class TestCaseWriterTest : WriterTestBase(){
585585
val firstInsertionId = 1001L
586586
val insertIntoTable0 = SqlAction(table0, setOf(idColumn), firstInsertionId, listOf(primaryKeyTable0Gene))
587587
val secondInsertionId = 1002L
588-
val foreignKeyGene = SqlForeignKeyGene(fkColumn.name, secondInsertionId, "Table0", false, pkGeneUniqueId)
588+
val foreignKeyGene = SqlForeignKeyGene(fkColumn.name, secondInsertionId, TableId("Table0"), idColumn.name, false, pkGeneUniqueId)
589589

590590
val insertIntoTable1 = SqlAction(table1, setOf(idColumn, fkColumn), secondInsertionId, listOf(primaryKeyTable1Gene, foreignKeyGene))
591591

@@ -640,13 +640,13 @@ class TestCaseWriterTest : WriterTestBase(){
640640

641641

642642
val insertId1 = 1002L
643-
val fkGene0 = SqlForeignKeyGene(table1_Id.name, insertId1, "Table0", false, insertId0)
643+
val fkGene0 = SqlForeignKeyGene(table1_Id.name, insertId1, TableId("Table0"), table0_Id.name, false, insertId0)
644644
val pkGene1 = SqlPrimaryKeyGene(table1_Id.name, "Table1", fkGene0, insertId1)
645645
val insert1 = SqlAction(table1, setOf(table1_Id), insertId1, listOf(pkGene1))
646646

647647

648648
val insertId2 = 1003L
649-
val fkGene1 = SqlForeignKeyGene(table2_Id.name, insertId2, "Table1", false, insertId1)
649+
val fkGene1 = SqlForeignKeyGene(table2_Id.name, insertId2, TableId("Table1"), table1_Id.name, false, insertId1)
650650
val pkGene2 = SqlPrimaryKeyGene(table2_Id.name, "Table2", fkGene1, insertId2)
651651
val insert2 = SqlAction(table2, setOf(table2_Id), insertId2, listOf(pkGene2))
652652

@@ -1027,7 +1027,7 @@ class TestCaseWriterTest : WriterTestBase(){
10271027
val fooInsertionId = 1001L
10281028
val fooInsertion = SqlAction(foo, setOf(fooId), fooInsertionId, listOf(pkFoo))
10291029
val barInsertionId = 1002L
1030-
val foreignKeyGene = SqlForeignKeyGene(fkId.name, barInsertionId, "Foo", false, uniqueIdOfPrimaryKey = pkGeneUniqueId)
1030+
val foreignKeyGene = SqlForeignKeyGene(fkId.name, barInsertionId, TableId("Foo"), fooId.name, false, uniqueIdOfPrimaryKey = pkGeneUniqueId)
10311031
val barInsertion = SqlAction(bar, setOf(fooId, fkId), barInsertionId, listOf(pkBar, foreignKeyGene))
10321032

10331033
val fooAction = RestCallAction("1", HttpVerb.GET, RestPath("/foo"), mutableListOf())
@@ -1099,7 +1099,7 @@ public void test() throws Exception {
10991099
val fooInsertionId = 1001L
11001100
val fooInsertion = SqlAction(foo, setOf(fooId), fooInsertionId, listOf(pkFoo))
11011101
val barInsertionId = 1002L
1102-
val foreignKeyGene = SqlForeignKeyGene(fkId.name, barInsertionId, "Foo", false, uniqueIdOfPrimaryKey = pkGeneUniqueId)
1102+
val foreignKeyGene = SqlForeignKeyGene(fkId.name, barInsertionId, TableId("Foo"), fooId.name, false, uniqueIdOfPrimaryKey = pkGeneUniqueId)
11031103
val barInsertion = SqlAction(bar, setOf(fooId, fkId), barInsertionId, listOf(pkBar, foreignKeyGene))
11041104

11051105
val fooAction = RestCallAction("1", HttpVerb.GET, RestPath("/foo"), mutableListOf())

core/src/test/kotlin/org/evomaster/core/search/gene/GeneSamplerForTests.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.evomaster.core.search.gene.interfaces.ComparableGene
88
import org.evomaster.core.search.gene.mongo.ObjectIdGene
99
import org.evomaster.core.search.gene.regex.*
1010
import org.evomaster.core.search.gene.sql.*
11+
import org.evomaster.core.sql.schema.TableId
1112
import org.evomaster.core.search.gene.sql.geometric.*
1213
import org.evomaster.core.search.gene.network.CidrGene
1314
import org.evomaster.core.search.gene.network.InetGene
@@ -375,7 +376,8 @@ object GeneSamplerForTests {
375376
private fun sampleSqlForeignKeyGene(rand: Randomness): SqlForeignKeyGene {
376377
return SqlForeignKeyGene(sourceColumn = "rand source column",
377378
uniqueId = rand.nextLong(min = 0L, max = Long.MAX_VALUE),
378-
targetTable = "rand target table",
379+
targetTable = TableId("rand target table"),
380+
targetColumn = "rand target column",
379381
nullable = rand.nextBoolean(),
380382
uniqueIdOfPrimaryKey = rand.nextLong())
381383
}

core/src/test/kotlin/org/evomaster/core/search/gene/sql/SqlPrimaryKeyGeneTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ class SqlPrimaryKeyGeneTest {
126126
val tableId = TableId("table")
127127
val pk = SqlPrimaryKeyGene("pk", tableId, IntegerGene("id", 1), 10L)
128128

129-
val fk1 = SqlForeignKeyGene("fk", 100L, tableId, false, 10L) // bound to pk
130-
val fk2 = SqlForeignKeyGene("fk2", 101L, tableId, false, 20L) // bound to something else
131-
val fk3 = SqlForeignKeyGene("fk3", 102L, tableId, false, -1L) // unbound
129+
val fk1 = SqlForeignKeyGene("fk", 100L, tableId, "id", false, 10L) // bound to pk
130+
val fk2 = SqlForeignKeyGene("fk2", 101L, tableId, "id", false, 20L) // bound to something else
131+
val fk3 = SqlForeignKeyGene("fk3", 102L, tableId, "id", false, -1L) // unbound
132132

133133
val table = Table(tableId, emptySet(), emptySet())
134134
val action1 = SqlAction(table, emptySet(), 10L, listOf(pk))

core/src/test/kotlin/org/evomaster/core/search/impact/impactinfocollection/sql/SqlForeignKeyGeneImpactTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.evomaster.core.search.impact.impactinfocollection.sql
22

3+
import org.evomaster.core.sql.schema.TableId
34
import org.evomaster.core.search.gene.Gene
45
import org.evomaster.core.search.gene.sql.SqlForeignKeyGene
56
import org.evomaster.core.search.impact.impactinfocollection.GeneImpact
@@ -25,7 +26,8 @@ class SqlForeignKeyGeneImpactTest : GeneImpactTest() {
2526
override fun getGene(): Gene = SqlForeignKeyGene(
2627
sourceColumn = "source",
2728
uniqueId = 1L,
28-
targetTable = "fake",
29+
targetTable = TableId("fake"),
30+
targetColumn = "id",
2931
nullable = false,
3032
uniqueIdOfPrimaryKey = 1L
3133
)
@@ -34,4 +36,4 @@ class SqlForeignKeyGeneImpactTest : GeneImpactTest() {
3436
assert(impact is SqlForeignKeyGeneImpact)
3537
}
3638

37-
}
39+
}

core/src/test/kotlin/org/evomaster/core/search/structuralelement/gene/SqlGeneStructureTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.evomaster.core.search.gene.numeric.IntegerGene
55
import org.evomaster.core.search.gene.numeric.LongGene
66
import org.evomaster.core.search.gene.wrapper.OptionalGene
77
import org.evomaster.core.search.gene.wrapper.NullableGene
8+
import org.evomaster.core.sql.schema.TableId
89
import org.evomaster.core.search.gene.sql.*
910
import org.junit.jupiter.api.Assertions.assertEquals
1011
import org.junit.jupiter.api.Assertions.assertTrue
@@ -29,14 +30,14 @@ class SqlForeignKeyGeneStructureTest : GeneStructuralElementBaseTest() {
2930

3031
override fun throwExceptionInRandomnessTest(): Boolean = false
3132

32-
override fun getCopyFromTemplate(): Gene = SqlForeignKeyGene("id",1L, "table", false, 1L)
33+
override fun getCopyFromTemplate(): Gene = SqlForeignKeyGene("id",1L, TableId("table"), "id", false, 1L)
3334

3435
override fun assertCopyFrom(base: Gene) {
3536
assertTrue(base is SqlForeignKeyGene)
3637
assertEquals(1L, (base as SqlForeignKeyGene).uniqueIdOfPrimaryKey)
3738
}
3839

39-
override fun getStructuralElement(): SqlForeignKeyGene = SqlForeignKeyGene("id",1L, "table", false, 0L)
40+
override fun getStructuralElement(): SqlForeignKeyGene = SqlForeignKeyGene("id",1L, TableId("table"), "id", false, 0L)
4041

4142
override fun getExpectedChildrenSize(): Int = 0
4243
}

0 commit comments

Comments
 (0)