Skip to content

Commit 9fee0ee

Browse files
committed
Remove dependency to DTO
1 parent 3f39f8b commit 9fee0ee

File tree

5 files changed

+192
-224
lines changed

5 files changed

+192
-224
lines changed

core/src/main/kotlin/org/evomaster/core/solver/SMTConditionVisitor.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.evomaster.core.solver
22

3-
import org.evomaster.client.java.controller.api.dto.database.schema.TableDto
3+
import org.evomaster.core.sql.schema.Table
44
import org.evomaster.core.utils.StringUtils.convertToAscii
55
import org.evomaster.dbconstraint.ast.*
66
import org.evomaster.solver.smtlib.AssertSMTNode
@@ -20,7 +20,7 @@ import java.util.*
2020
class SMTConditionVisitor(
2121
private val defaultTableName: String,
2222
private val tableAliases: Map<String, String>,
23-
private val tables: List<TableDto>,
23+
private val tables: List<Table>,
2424
private val rowIndex: Int
2525
) : SqlConditionVisitor<SMTNode, Void> {
2626

@@ -157,6 +157,7 @@ class SMTConditionVisitor(
157157
}
158158
}
159159

160+
160161
/**
161162
* Maps SQL comparison operators to SMT-LIB comparators.
162163
*

core/src/main/kotlin/org/evomaster/core/solver/SMTLibZ3DbConstraintSolver.kt

Lines changed: 93 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import org.evomaster.core.search.gene.Gene
1616
import org.evomaster.core.search.gene.numeric.DoubleGene
1717
import org.evomaster.core.search.gene.numeric.IntegerGene
1818
import org.evomaster.core.search.gene.placeholder.ImmutableDataHolderGene
19-
import org.evomaster.core.search.gene.sql.SqlPrimaryKeyGene
2019
import org.evomaster.core.search.gene.string.StringGene
20+
import org.evomaster.core.search.gene.sql.SqlPrimaryKeyGene
2121
import org.evomaster.core.sql.SqlAction
2222
import org.evomaster.core.sql.schema.*
2323
import org.evomaster.core.utils.StringUtils.convertToAscii
@@ -38,7 +38,6 @@ import javax.annotation.PostConstruct
3838
import javax.annotation.PreDestroy
3939
import kotlin.io.path.createDirectories
4040
import 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

Comments
 (0)