@@ -16,8 +16,8 @@ import org.evomaster.core.search.gene.Gene
1616import org.evomaster.core.search.gene.numeric.DoubleGene
1717import org.evomaster.core.search.gene.numeric.IntegerGene
1818import org.evomaster.core.search.gene.placeholder.ImmutableDataHolderGene
19- import org.evomaster.core.search.gene.sql.SqlPrimaryKeyGene
2019import org.evomaster.core.search.gene.string.StringGene
20+ import org.evomaster.core.search.gene.sql.SqlPrimaryKeyGene
2121import org.evomaster.core.sql.SqlAction
2222import org.evomaster.core.sql.schema.*
2323import org.evomaster.core.utils.StringUtils.convertToAscii
@@ -38,7 +38,6 @@ import javax.annotation.PostConstruct
3838import javax.annotation.PreDestroy
3939import kotlin.io.path.createDirectories
4040import kotlin.io.path.exists
41- import kotlin.text.equals
4241
4342/* *
4443 * An SMT solver implementation using Z3 in a Docker container.
@@ -91,13 +90,93 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
9190 override fun solve (schemaDto : DbInfoDto , sqlQuery : String , numberOfRows : Int ): List <SqlAction > {
9291 // TODO: Use memoized, if it's the same schema and query, return the same result and don't do any calculation
9392
94- val generator = SmtLibGenerator (schemaDto, numberOfRows)
93+ // Convert DTOs to domain objects at the boundary, before any business logic
94+ val smtTables = Companion .buildSmtTables(schemaDto)
95+
96+ val generator = SmtLibGenerator (smtTables, schemaDto.databaseType, numberOfRows)
9597 val queryStatement = parseStatement(sqlQuery)
9698 val smtLib = generator.generateSMT(queryStatement)
9799 val fileName = storeToTmpFile(smtLib)
98100 val z3Response = executor.solveFromFile(fileName)
99101
100- return toSqlActionList(schemaDto, z3Response)
102+ return toSqlActionList(smtTables, z3Response)
103+ }
104+
105+ companion object {
106+
107+ /* *
108+ * Converts a [DbInfoDto] schema to a list of [SmtTable] domain objects.
109+ *
110+ * This is the boundary where DTOs are translated into domain objects.
111+ * Exposed as a companion method so tests can build a [SmtLibGenerator] directly
112+ * without going through the full solver pipeline.
113+ */
114+ fun buildSmtTables (schemaDto : DbInfoDto ): List <SmtTable > {
115+ return schemaDto.tables.map { tableDto ->
116+ val columns = buildColumns(schemaDto.databaseType, tableDto)
117+ val foreignKeys = buildForeignKeys(tableDto, columns)
118+ val table = Table (TableId (tableDto.id.name), columns, foreignKeys)
119+ val checkExpressions = tableDto.tableCheckExpressions.map { it.sqlCheckExpression }
120+ SmtTable (table, checkExpressions)
121+ }
122+ }
123+
124+ private fun buildColumns (databaseType : DatabaseType , tableDto : TableDto ): Set <Column > {
125+ return tableDto.columns.map { columnDto ->
126+ toColumn(columnDto, databaseType)
127+ }.toSet()
128+ }
129+
130+ private fun buildForeignKeys (tableDto : TableDto , columns : Set <Column >): Set <ForeignKey > {
131+ return tableDto.foreignKeys.map { fkDto ->
132+ val sourceColumns = fkDto.sourceColumns.mapNotNull { colName ->
133+ columns.firstOrNull { it.name.equals(colName, ignoreCase = true ) }
134+ }.toSet()
135+ ForeignKey (sourceColumns, TableId (fkDto.targetTable))
136+ }.toSet()
137+ }
138+
139+ private fun toColumn (columnDto : ColumnDto , databaseType : DatabaseType ): Column {
140+ return Column (
141+ name = columnDto.name,
142+ type = getColumnDataType(columnDto.type),
143+ size = columnDto.size,
144+ primaryKey = columnDto.primaryKey,
145+ nullable = columnDto.nullable,
146+ unique = columnDto.unique,
147+ autoIncrement = columnDto.autoIncrement,
148+ foreignKeyToAutoIncrement = columnDto.foreignKeyToAutoIncrement,
149+ lowerBound = null ,
150+ upperBound = null ,
151+ enumValuesAsStrings = null ,
152+ similarToPatterns = null ,
153+ likePatterns = null ,
154+ databaseType = databaseType,
155+ isUnsigned = false ,
156+ compositeType = null ,
157+ compositeTypeName = null ,
158+ isNotBlank = null ,
159+ minSize = null ,
160+ maxSize = null ,
161+ javaRegExPattern = null
162+ )
163+ }
164+
165+ /* *
166+ * Maps column type strings (from SQL schema) to [ColumnDataType] enum values.
167+ */
168+ private fun getColumnDataType (type : String ): ColumnDataType {
169+ return when (type.uppercase()) {
170+ " BIGINT" -> ColumnDataType .BIGINT
171+ " INTEGER" , " INT" -> ColumnDataType .INTEGER
172+ " FLOAT" -> ColumnDataType .FLOAT
173+ " DOUBLE" -> ColumnDataType .DOUBLE
174+ " TIMESTAMP" -> ColumnDataType .TIMESTAMP
175+ " CHARACTER VARYING" -> ColumnDataType .CHARACTER_VARYING
176+ " CHAR" -> ColumnDataType .CHAR
177+ else -> ColumnDataType .CHARACTER_VARYING // Default type
178+ }
179+ }
101180 }
102181
103182 /* *
@@ -127,22 +206,25 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
127206 /* *
128207 * Converts Z3's response to a list of SqlActions.
129208 *
209+ * @param smtTables The pre-built domain tables, used to look up schema metadata.
130210 * @param z3Response The response from Z3.
131211 * @return A list of SQL actions.
132212 */
133- private fun toSqlActionList (schemaDto : DbInfoDto , z3Response : Optional <MutableMap <String , SMTLibValue >>): List <SqlAction > {
213+ private fun toSqlActionList (smtTables : List < SmtTable > , z3Response : Optional <MutableMap <String , SMTLibValue >>): List <SqlAction > {
134214 if (! z3Response.isPresent) {
135215 return emptyList()
136216 }
137217
138218 val actions = mutableListOf<SqlAction >()
139219
140220 for (row in z3Response.get()) {
141- val tableName = getTableName(row.key)
221+ val smtTableName = getTableName(row.key)
142222 val columns = row.value as StructValue
143223
144- // Find table from schema and create SQL actions
145- val table = findTableByName(schemaDto, tableName)
224+ // Find the SmtTable by its SMT-safe name (which is what Z3 returns)
225+ val smtTable = smtTables.firstOrNull { it.smtName == smtTableName }
226+ ? : throw RuntimeException (" Table not found for SMT name: $smtTableName " )
227+ val table = smtTable.table
146228
147229 /*
148230 * The invariant requires that action.insertionId == primaryKey.uniqueId (and same for FK).
@@ -153,7 +235,7 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
153235
154236 // Create the list of genes with the values
155237 val genes = mutableListOf<Gene >()
156- // smtColumn is the Ascii version from SmtLib; resolve back to original DB column name
238+ // smtColumn is the ASCII version from SmtLib; resolve back to original DB column name
157239 for (smtColumn in columns.fields) {
158240 val dbColumn = table.columns.firstOrNull {
159241 convertToAscii(it.name).equals(smtColumn, ignoreCase = true )
@@ -163,14 +245,14 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
163245 var gene: Gene = IntegerGene (dbColumnName, 0 )
164246 when (val columnValue = columns.getField(smtColumn)) {
165247 is StringValue -> {
166- gene = if (hasColumnType(schemaDto, table, dbColumnName, " BOOLEAN" ) ) {
248+ gene = if (dbColumn?.type == ColumnDataType . BOOLEAN || dbColumn?.type == ColumnDataType . BOOL ) {
167249 BooleanGene (dbColumnName, toBoolean(columnValue.value))
168250 } else {
169251 StringGene (dbColumnName, columnValue.value)
170252 }
171253 }
172254 is LongValue -> {
173- gene = if (hasColumnType(schemaDto, table, dbColumnName, " TIMESTAMP" ) ) {
255+ gene = if (dbColumn?.type == ColumnDataType . TIMESTAMP ) {
174256 val epochSeconds = columnValue.value.toLong()
175257 val localDateTime = LocalDateTime .ofInstant(
176258 Instant .ofEpochSecond(epochSeconds), ZoneOffset .UTC
@@ -205,26 +287,6 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
205287 return value.equals(" True" , ignoreCase = true )
206288 }
207289
208- private fun hasColumnType (
209- schemaDto : DbInfoDto ,
210- table : Table ,
211- columnName : String? ,
212- expectedType : String
213- ): Boolean {
214-
215- if (columnName == null ) return false
216-
217- val tableDto = schemaDto.tables.firstOrNull {
218- it.id.name.equals(table.id.name, ignoreCase = true )
219- } ? : return false
220-
221- val col = tableDto.columns.firstOrNull {
222- it.name.equals(columnName, ignoreCase = true )
223- } ? : return false
224-
225- return col.type.equals(expectedType, ignoreCase = true )
226- }
227-
228290 /* *
229291 * Extracts the table name from the key by removing the last character (index).
230292 *
@@ -235,117 +297,6 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
235297 return key.substring(0 , key.length - 1 ) // Remove last character
236298 }
237299
238- /* *
239- * Finds a table by its name from the schema and constructs a Table object.
240- *
241- * @param schema The database schema.
242- * @param tableName The name of the table to find.
243- * @return The Table object.
244- */
245- private fun findTableByName (schema : DbInfoDto , tableName : String ): Table {
246- val tableDto = schema.tables.find { it.id.name.equals(tableName, ignoreCase = true ) }
247- ? : throw RuntimeException (" Table not found: $tableName " )
248- return Table (
249- TableId (tableDto.id.name) , // TODO other info, eg schema
250- findColumns(schema,tableDto), // Convert columns from DTO
251- findForeignKeys(tableDto) // TODO: Implement this method
252- )
253- }
254-
255- /* *
256- * Converts a list of ColumnDto to a set of Column objects.
257- *
258- * @param tableDto The table DTO containing column definitions.
259- * @return A set of Column objects.
260- */
261- private fun findColumns (schemaDto : DbInfoDto , tableDto : TableDto ): Set <Column > {
262- return tableDto.columns.map { columnDto ->
263- toColumnFromDto(columnDto, schemaDto.databaseType)
264- }.toSet()
265- }
266-
267- /* *
268- * Converts ColumnDto to a Column object.
269- *
270- * @param columnDto The column DTO.
271- * @param databaseType The type of the database.
272- * @return The Column object.
273- */
274- private fun toColumnFromDto (
275- columnDto : ColumnDto ,
276- databaseType : DatabaseType
277- ): Column {
278- val name = columnDto.name
279- val type = getColumnDataType(columnDto.type)
280- val nullable: Boolean = columnDto.nullable
281- val primaryKey = columnDto.primaryKey
282- val unique = columnDto.unique
283- val autoIncrement = columnDto.autoIncrement
284- val foreignKeyToAutoIncrement = columnDto.foreignKeyToAutoIncrement
285- val size = columnDto.size
286- val lowerBound = null
287- val upperBound = null
288- val enumValuesAsStrings = null
289- val similarToPatterns = null
290- val likePatterns = null
291- val isUnsigned = false
292- val compositeType = null
293- val compositeTypeName = 0
294- val isNotBlank = null
295- val minSize = null
296- val maxSize = null
297- val javaRegExPattern = null
298-
299- val column = Column (
300- name,
301- type,
302- size,
303- primaryKey,
304- nullable,
305- unique,
306- autoIncrement,
307- foreignKeyToAutoIncrement,
308- lowerBound,
309- upperBound,
310- enumValuesAsStrings,
311- similarToPatterns,
312- likePatterns,
313- databaseType,
314- isUnsigned,
315- compositeType,
316- compositeTypeName,
317- isNotBlank,
318- minSize,
319- maxSize,
320- javaRegExPattern
321- )
322- return column
323- }
324-
325- /* *
326- * Maps column types to ColumnDataType.
327- *
328- * @param type The column type as a string.
329- * @return The corresponding ColumnDataType.
330- */
331- private fun getColumnDataType (type : String ): ColumnDataType {
332- return when (type) {
333- " BIGINT" -> ColumnDataType .BIGINT
334- " INTEGER" -> ColumnDataType .INTEGER
335- " FLOAT" -> ColumnDataType .FLOAT
336- " DOUBLE" -> ColumnDataType .DOUBLE
337- " TIMESTAMP" -> ColumnDataType .TIMESTAMP
338- " CHARACTER VARYING" -> ColumnDataType .CHARACTER_VARYING
339- " CHAR" -> ColumnDataType .CHAR
340- else -> ColumnDataType .CHARACTER_VARYING // Default type
341- }
342- }
343-
344- // TODO: Implement this method
345- private fun findForeignKeys (tableDto : TableDto ): Set <ForeignKey > {
346- return emptySet() // Placeholder
347- }
348-
349300 /* *
350301 * Stores the SMTLib problem to a file in the resources' folder.
351302 *
0 commit comments