Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3bfb1f0
Add end-to-end tests for composite primary key support in Postgres in…
jgaleotti Apr 1, 2026
1534a09
Add end-to-end tests for multi-column foreign key support in Postgres…
jgaleotti Apr 1, 2026
f2e7b99
DbInfoExtractor extracts the target columns in the foreign keys
jgaleotti Apr 2, 2026
ed8453d
Add tests for composite foreign key support in Postgres and MySQL dat…
jgaleotti Apr 2, 2026
c072298
Refactor and consolidate composite foreign key tests across databases…
jgaleotti Apr 2, 2026
5dc9ba2
Merge remote-tracking branch 'origin/master' into sql-multi-column-fo…
jgaleotti Apr 2, 2026
a32b7f8
Disable `PostgresMultiColumnFKEMTest` until supporting multiple colum…
jgaleotti Apr 2, 2026
d6c1e9f
bugfix: Unique constraint evaluation correctly handles the presene of…
jgaleotti Apr 3, 2026
b70e865
Refactor SQL foreign key handling to store target columns.
jgaleotti Apr 3, 2026
0b21414
Add test for multi-column foreign key gene creation
jgaleotti Apr 3, 2026
7483daf
Add test for implicit composite foreign keys and for multiple foreign…
jgaleotti Apr 3, 2026
b5ccd1d
removed deprecated constructor
jgaleotti Apr 3, 2026
b26ddda
`isValidForeignKeys` checks for constraints accross multiple columns
jgaleotti Apr 3, 2026
d6504a5
Merge remote-tracking branch 'origin/master' into sql-multi-column-fo…
jgaleotti Apr 3, 2026
d2f2e1c
Add other source columns in foreign key for multiple column foreign k…
jgaleotti Apr 5, 2026
cc21cab
Update tests to include `otherSourceColumnsInCompositeFK` in `SqlFore…
jgaleotti Apr 5, 2026
d750291
`SqlForeignKeyGene.isReferenceToNonPrintable()` and `getValueAsPrinta…
jgaleotti Apr 5, 2026
e369123
Add support for CASCADE in TRUNCATE for PostgreSQL / handling of mult…
jgaleotti Apr 6, 2026
4fda1fd
isValidForeignKeys() consider the case where a foreign key column is …
jgaleotti Apr 7, 2026
7d89618
Merge remote-tracking branch 'origin/master' into sql-multi-column-fo…
jgaleotti Apr 9, 2026
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
Expand Up @@ -146,6 +146,9 @@ public void testBasicForeignKey() throws Exception {
assertEquals(1, foreignKey.sourceColumns.size());
assertTrue(foreignKey.sourceColumns.stream().anyMatch(c -> c.equalsIgnoreCase("barId")));
assertTrue(foreignKey.targetTable.equalsIgnoreCase("Bar"));

assertEquals(1, foreignKey.targetColumns.size());
assertTrue(foreignKey.targetColumns.stream().anyMatch(c -> c.equalsIgnoreCase("id")));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.evomaster.client.java.controller.internal.db.sql.mysql;

import org.evomaster.client.java.controller.DatabaseTestTemplate;
import org.evomaster.client.java.controller.api.dto.database.schema.ForeignKeyDto;
import org.evomaster.client.java.controller.api.dto.database.schema.DbInfoDto;
import org.evomaster.client.java.controller.api.dto.database.schema.TableDto;
import org.evomaster.client.java.sql.SqlScriptRunner;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,52 @@
import java.util.ArrayList;
import java.util.List;

/**
* Represents a foreign key relationship in a database schema.
*
* A foreign key establishes a connection between two database tables,
* where one table (source) references the primary key or a unique column
* in another table (target).
*
* This class captures the metadata for the foreign key, including the columns
* involved in the relationship and the target table being referenced.
*/
public class ForeignKeyDto {

/**
* A list of column names in the source table of the foreign key relationship.
*
* These column names correspond to the columns in the source table
* that are used in the foreign key constraint. They establish the
* connection to the target table by referencing its primary key
* or unique columns.
*
* The order of the columns in this list corresponds to the order
* in which they are defined in the foreign key constraint.
*/
public List<String> sourceColumns = new ArrayList<>();

/**
* The name of the target table in a foreign key relationship.
*
* This variable specifies the table being referenced by the foreign key.
* The value corresponds to the physical name of the target table in the database.
* The foreign key relationship indicates that a column or set of columns in
* the source table references a column or set of columns (usually the primary key)
* in the target table.
*/
public String targetTable;

//TODO likely ll need to handle targetColumns if we have multi-columns
/**
* A list of column names in the target table of the foreign key relationship.
*
* These column names represent the columns in the target table
* that are referenced by the foreign key constraint. They typically
* refer to primary key columns or unique columns in the target table.
*
* The order of the columns in this list corresponds to the order
* defined in the foreign key relationship, ensuring a one-to-one
* mapping with the source columns.
*/
public List<String> targetColumns = new ArrayList<>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ private static List<String> cleanDataInTables(List<String> tableToSkip,
if (doDropTable) {
dropTableIfExists(statement, ts);
} else {
truncateTable(statement, ts, restartIdentityWhenTruncating);
truncateTable(statement, ts, restartIdentityWhenTruncating, type);
}
} else {
//note: if one at a time, need to make sure to first disable FK checks
Expand All @@ -300,7 +300,7 @@ private static List<String> cleanDataInTables(List<String> tableToSkip,
if (type == DatabaseType.MS_SQL_SERVER)
deleteTables(statement, t, schema, tablesHaveIdentifies);
else
truncateTable(statement, t, restartIdentityWhenTruncating);
truncateTable(statement, t, restartIdentityWhenTruncating, type);
}
}
}
Expand All @@ -327,12 +327,15 @@ private static void deleteTables(Statement statement, String table, String schem
statement.executeUpdate("DBCC CHECKIDENT ('" + tableWithSchema + "', RESEED, 0)");
}

private static void truncateTable(Statement statement, String table, boolean restartIdentityWhenTruncating) throws SQLException {
private static void truncateTable(Statement statement, String table, boolean restartIdentityWhenTruncating, DatabaseType type) throws SQLException {
String sql = "TRUNCATE TABLE " + table;
if (restartIdentityWhenTruncating) {
statement.executeUpdate("TRUNCATE TABLE " + table + " RESTART IDENTITY");
} else {
statement.executeUpdate("TRUNCATE TABLE " + table);
sql += " RESTART IDENTITY";
}
if (type == DatabaseType.POSTGRES) {
sql += " CASCADE";
}
statement.executeUpdate(sql);
}

private static void resetSequences(Statement s, DatabaseType type, String schemaName, List<String> sequenceToClean) throws SQLException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -654,14 +654,22 @@ private static void handleTableEntry(Connection connection, DbInfoDto schemaDto,


ResultSet fks = md.getImportedKeys(tableDto.id.catalog, tableDto.id.schema, tableDto.id.name);
Map<String, ForeignKeyDto> foreignKeysByName = new HashMap<>();
while (fks.next()) {
//TODO need to see how to handle case of multi-columns

ForeignKeyDto fkDto = new ForeignKeyDto();
fkDto.sourceColumns.add(fks.getString("FKCOLUMN_NAME"));
fkDto.targetTable = fks.getString("PKTABLE_NAME");

tableDto.foreignKeys.add(fkDto);
String fkName = fks.getString("FK_NAME");
String sourceColumn = fks.getString("FKCOLUMN_NAME");
String targetTable = fks.getString("PKTABLE_NAME");
String targetColumn = fks.getString("PKCOLUMN_NAME");

ForeignKeyDto fkDto = foreignKeysByName.get(fkName);
if (fkDto == null) {
fkDto = new ForeignKeyDto();
fkDto.targetTable = targetTable;
foreignKeysByName.put(fkName, fkDto);
tableDto.foreignKeys.add(fkDto);
}
fkDto.sourceColumns.add(sourceColumn);
fkDto.targetColumns.add(targetColumn);
}
fks.close();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.evomaster.client.java.sql;

import org.evomaster.client.java.controller.api.dto.database.schema.DatabaseType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;

import java.sql.Connection;
import java.sql.DriverManager;


public class DbInfoExtractorH2Test extends DbInfoExtractorTestBase {

private static Connection connection;

@BeforeAll
public static void initClass() throws Exception {
connection = DriverManager.getConnection("jdbc:h2:mem:db_test", "sa", "");
}

@AfterAll
public static void afterClass() throws Exception {
connection.close();
}

@BeforeEach
public void initTest() throws Exception {
//custom H2 command
SqlScriptRunner.execCommand(connection, "DROP ALL OBJECTS;");
}


@Override
protected DatabaseType getDbType() {
return DatabaseType.H2;
}

@Override
protected Connection getConnection() {
return connection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.evomaster.client.java.sql;

import org.evomaster.client.java.controller.api.dto.database.schema.DatabaseType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class DbInfoExtractorMySQLTest extends DbInfoExtractorTestBase {

private static final String DB_NAME = "test";

private static final int PORT = 3306;

private static final String MYSQL_VERSION = "8.0.27";

public static final GenericContainer mysql = new GenericContainer("mysql:" + MYSQL_VERSION)
.withEnv(new HashMap<String, String>(){{
put("MYSQL_ROOT_PASSWORD", "root");
put("MYSQL_DATABASE", DB_NAME);
put("MYSQL_USER", "test");
put("MYSQL_PASSWORD", "test");
}})
.withExposedPorts(PORT);

private static Connection connection;

@BeforeAll
public static void initClass() throws Exception {

mysql.start();

String host = mysql.getContainerIpAddress();
int port = mysql.getMappedPort(PORT);
String url = "jdbc:mysql://"+host+":"+port+"/"+DB_NAME;

connection = DriverManager.getConnection(url, "test", "test");

}

@AfterAll
public static void afterClass() throws Exception {
connection.close();
mysql.stop();
}

@AfterEach
public void afterTest() throws SQLException {
SqlScriptRunner.execCommand(connection, "DROP TABLE IF EXISTS example_table;");
}


@Override
protected DatabaseType getDbType() {
return DatabaseType.MYSQL;
}

@Override
protected Connection getConnection() {
return connection;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.evomaster.client.java.sql;

import org.evomaster.client.java.controller.api.dto.database.schema.DatabaseType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.testcontainers.containers.GenericContainer;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Collections;

public class DbInfoExtractorPostgresTest extends DbInfoExtractorTestBase {

private static final String POSTGRES_VERSION = "14";

private static final GenericContainer<?> postgres = new GenericContainer("postgres:" + POSTGRES_VERSION)
.withExposedPorts(5432)
.withTmpFs(Collections.singletonMap("/var/lib/postgresql/data", "rw"))
.withEnv("POSTGRES_HOST_AUTH_METHOD","trust");

private static Connection connection;

@BeforeAll
public static void initClass() throws Exception{
postgres.start();
String host = postgres.getHost();
int port = postgres.getMappedPort(5432);
String url = "jdbc:postgresql://"+host+":"+port+"/postgres";

connection = DriverManager.getConnection(url, "postgres", "");
}

@AfterAll
public static void afterClass() throws Exception{
connection.close();
postgres.stop();
}

@BeforeEach
public void initTest() throws Exception {
/*
see:
https://stackoverflow.com/questions/3327312/how-can-i-drop-all-the-tables-in-a-postgresql-database
*/
SqlScriptRunner.execCommand(connection, "DROP SCHEMA public CASCADE;");
SqlScriptRunner.execCommand(connection, "CREATE SCHEMA public;");
SqlScriptRunner.execCommand(connection, "GRANT ALL ON SCHEMA public TO postgres;");
SqlScriptRunner.execCommand(connection, "GRANT ALL ON SCHEMA public TO public;");
}

@Override
protected Connection getConnection(){
return connection;
}

@Override
protected DatabaseType getDbType() {
return DatabaseType.POSTGRES;
}


}
Loading
Loading