Skip to content

Commit 992ce99

Browse files
committed
Fix build
Revert "Remove Z3 SQL correctness-measurement feature; drop core->client-java-sql dependency" This reverts commit c8712da545379779d6a27512e7b37b9be953486c.
1 parent 2fee4ad commit 992ce99

5 files changed

Lines changed: 134 additions & 2 deletions

File tree

core/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
<groupId>org.evomaster</groupId>
4747
<artifactId>evomaster-client-java-controller-api</artifactId>
4848
</dependency>
49+
<dependency>
50+
<groupId>org.evomaster</groupId>
51+
<artifactId>evomaster-client-java-sql</artifactId>
52+
</dependency>
4953
<dependency>
5054
<groupId>org.evomaster</groupId>
5155
<artifactId>evomaster-client-java-instrumentation-shared</artifactId>

core/src/main/kotlin/org/evomaster/core/EMConfig.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,6 +1972,14 @@ class EMConfig {
19721972
@DependsOnTrueFor("generateSqlDataWithZ3")
19731973
var collectSqlZ3Stats = false
19741974

1975+
@Experimental
1976+
@Cfg("Measure the correctness of Z3-generated SQL inserts by computing the heuristic " +
1977+
"distance between the original failing WHERE query and the generated INSERT data. " +
1978+
"Distance=0 means the insert satisfies the WHERE; distance>0 means it does not. " +
1979+
"Only meaningful when generateSqlDataWithZ3=true.")
1980+
@DependsOnTrueFor("generateSqlDataWithZ3")
1981+
var measureSqlZ3Correctness = false
1982+
19751983
@Experimental
19761984
@Cfg("Soft timeout, in milliseconds, for each Z3 solver invocation when generating SQL data. " +
19771985
"If a query exceeds it, Z3 returns 'unknown' for that query instead of running unbounded. " +

core/src/main/kotlin/org/evomaster/core/search/service/Statistics.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ class Statistics : SearchListener {
104104
private val sqlZ3SeenQueryHashes = mutableSetOf<Int>()
105105
private var sqlZ3UniqueQueriesCount = 0
106106

107+
// Z3-based SQL data generation correctness distance statistics (only when measureSqlZ3Correctness=true)
108+
private var sqlZ3CorrectnessCheckCount = 0
109+
private var sqlZ3CorrectnessZeroDistanceCount = 0
110+
private var sqlZ3CorrectnessNonZeroDistanceCount = 0
111+
private val sqlZ3CorrectnessAvgDistance = IncrementalAverage()
112+
private var sqlZ3CorrectnessEvalFailureCount = 0
113+
107114
// mongo heuristic evaluation statistic
108115
private var mongoHeuristicEvaluationSuccessCount = 0
109116
private var mongoHeuristicEvaluationFailureCount = 0
@@ -301,6 +308,22 @@ class Statistics : SearchListener {
301308
internal fun getSqlZ3CacheHitCount() = sqlZ3CacheHitCount
302309
internal fun getSqlZ3CacheMissCount() = sqlZ3CacheMissCount
303310

311+
fun reportSqlZ3CorrectnessDistance(sqlDistance: Double, evaluationFailure: Boolean) {
312+
sqlZ3CorrectnessCheckCount++
313+
if (evaluationFailure) {
314+
// sqlDistance is a sentinel value (e.g. Double.MAX_VALUE) in this case,
315+
// and must not pollute the average of real distances
316+
sqlZ3CorrectnessEvalFailureCount++
317+
return
318+
}
319+
if (sqlDistance == 0.0) {
320+
sqlZ3CorrectnessZeroDistanceCount++
321+
} else {
322+
sqlZ3CorrectnessNonZeroDistanceCount++
323+
}
324+
sqlZ3CorrectnessAvgDistance.addValue(sqlDistance)
325+
}
326+
304327
fun getMongoHeuristicsEvaluationCount(): Int = mongoHeuristicEvaluationSuccessCount + mongoHeuristicEvaluationFailureCount
305328

306329
fun getSqlHeuristicsEvaluationCount(): Int = sqlHeuristicEvaluationSuccessCount + sqlHeuristicEvaluationFailureCount
@@ -477,6 +500,15 @@ class Statistics : SearchListener {
477500
add(Pair("sqlZ3AvgSmtlibSizeBytes", "%.1f".format(sqlZ3SmtlibSizeBytes.mean)))
478501
}
479502

503+
// correctness distance stats (only emitted when measureSqlZ3Correctness=true)
504+
if (config.measureSqlZ3Correctness) {
505+
add(Pair("sqlZ3CorrectnessChecks", "$sqlZ3CorrectnessCheckCount"))
506+
add(Pair("sqlZ3CorrectnessZeroDistance", "$sqlZ3CorrectnessZeroDistanceCount"))
507+
add(Pair("sqlZ3CorrectnessNonZero", "$sqlZ3CorrectnessNonZeroDistanceCount"))
508+
add(Pair("sqlZ3CorrectnessAvgDist", "%.4f".format(sqlZ3CorrectnessAvgDistance.mean)))
509+
add(Pair("sqlZ3CorrectnessEvalFailures", "$sqlZ3CorrectnessEvalFailureCount"))
510+
}
511+
480512
for(phase in ExecutionPhaseController.Phase.entries){
481513
add(Pair("phase_${phase.name}", "${epc.getPhaseDurationInSeconds(phase)}"))
482514
}

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

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ import com.google.inject.Inject
55
import net.sf.jsqlparser.JSQLParserException
66
import net.sf.jsqlparser.parser.CCJSqlParserUtil
77
import net.sf.jsqlparser.statement.Statement
8+
import net.sf.jsqlparser.statement.insert.Insert
89
import org.apache.commons.io.FileUtils
910
import org.evomaster.client.java.controller.api.dto.database.schema.ColumnDto
1011
import org.evomaster.client.java.controller.api.dto.database.schema.DatabaseType
1112
import org.evomaster.client.java.controller.api.dto.database.schema.DbInfoDto
1213
import org.evomaster.client.java.controller.api.dto.database.schema.TableDto
1314
import org.evomaster.core.EMConfig
1415
import org.evomaster.core.logging.LoggingUtil
16+
import org.evomaster.client.java.sql.DataRow
17+
import org.evomaster.client.java.sql.QueryResult
18+
import org.evomaster.client.java.sql.QueryResultSet
19+
import org.evomaster.client.java.sql.heuristic.SqlHeuristicsCalculator
20+
import org.evomaster.client.java.sql.heuristic.TableColumnResolver
21+
import org.evomaster.client.java.sql.internal.SqlDistanceWithMetrics
1522
import org.evomaster.core.search.gene.BooleanGene
1623
import org.evomaster.core.search.gene.Gene
1724
import org.evomaster.core.search.gene.numeric.DoubleGene
@@ -205,7 +212,33 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
205212
Z3Result.Status.SAT -> {
206213
stats?.reportSqlZ3Sat(z3TimeMs)
207214
z3ResultCache?.set(cacheKey, z3Result)
208-
toSqlActionList(schemaDto, z3Result.solution)
215+
val sqlActions = toSqlActionList(schemaDto, z3Result.solution)
216+
if (::config.isInitialized && config.measureSqlZ3Correctness && queryStatement !is Insert) {
217+
/*
218+
* INSERT statements have no WHERE clause, so SqlHeuristicsCalculator has no
219+
* predicate to evaluate distance against and will always report a failure.
220+
* Correctness measurement only makes sense for queries that filter rows
221+
* (SELECT, DELETE, UPDATE). In the future this could be extended to verify
222+
* that the generated rows satisfy insertion preconditions such as FK constraints
223+
* or NOT NULL columns that Z3 SQL generation currently leaves unconstrained.
224+
*
225+
* Note: SqlHeuristicsCalculator is SELECT-oriented. For DELETE/UPDATE the distance
226+
* computation may fail and be reported as an evaluation failure (sqlDistanceEvaluationFailure)
227+
* rather than a real distance; such failures are counted separately and are excluded
228+
* from the average, so they do not distort the correctness metric.
229+
*/
230+
val distResult = computeCorrectnessDistance(sqlQuery, schemaDto, sqlActions)
231+
if (distResult.sqlDistanceEvaluationFailure) {
232+
LoggingUtil.getInfoLogger().warn("SQL-Z3: correctness evaluation failure for query '$sqlQuery'")
233+
} else if (distResult.sqlDistance != 0.0) {
234+
LoggingUtil.getInfoLogger().warn("SQL-Z3: non-zero correctness distance (${distResult.sqlDistance}) for query '$sqlQuery'")
235+
}
236+
statisticsRef?.get()?.reportSqlZ3CorrectnessDistance(
237+
distResult.sqlDistance,
238+
distResult.sqlDistanceEvaluationFailure
239+
)
240+
}
241+
sqlActions
209242
}
210243
Z3Result.Status.UNSAT -> {
211244
stats?.reportSqlZ3Unsat(z3TimeMs)
@@ -519,4 +552,60 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
519552
}
520553

521554
private fun leadingBarResourcesFolder() = if (resourcesFolder.endsWith("/")) resourcesFolder else "$resourcesFolder/"
555+
556+
private fun computeCorrectnessDistance(
557+
sqlQuery: String,
558+
schemaDto: DbInfoDto,
559+
sqlActions: List<SqlAction>
560+
): SqlDistanceWithMetrics {
561+
val queryResultSet = toQueryResultSet(schemaDto, sqlActions)
562+
val calculator = SqlHeuristicsCalculator.SqlHeuristicsCalculatorBuilder()
563+
.withTableColumnResolver(TableColumnResolver(schemaDto))
564+
.withSourceQueryResultSet(queryResultSet)
565+
.build()
566+
return calculator.computeDistance(sqlQuery)
567+
}
568+
569+
private fun toQueryResultSet(schemaDto: DbInfoDto, sqlActions: List<SqlAction>): QueryResultSet {
570+
val queryResultSet = QueryResultSet()
571+
val byTable = sqlActions.groupBy { it.table.id.name }
572+
for ((tableName, actions) in byTable) {
573+
val columnNames = actions.first().seeTopGenes().map { it.name }
574+
val queryResult = QueryResult(columnNames, tableName)
575+
for (action in actions) {
576+
val values: List<Any?> = action.seeTopGenes().map { gene -> extractGeneValue(gene) }
577+
queryResult.addRow(DataRow(tableName, columnNames, values))
578+
}
579+
queryResultSet.addQueryResult(queryResult)
580+
}
581+
// Tables not present in Z3's SAT model (e.g. the optional side of a LEFT OUTER JOIN)
582+
// still need an (empty) QueryResult, otherwise SqlHeuristicsCalculator NPEs when it
583+
// looks them up unconditionally while walking the FROM/JOIN clause.
584+
for (table in schemaDto.tables) {
585+
if (table.id.name !in byTable.keys) {
586+
val columnNames = table.columns.map { it.name }
587+
queryResultSet.addQueryResult(QueryResult(columnNames, table.id.name))
588+
}
589+
}
590+
return queryResultSet
591+
}
592+
593+
private fun extractGeneValue(gene: Gene): Any? {
594+
val inner = if (gene is SqlPrimaryKeyGene) gene.gene else gene
595+
return when (inner) {
596+
is IntegerGene -> inner.value
597+
is LongGene -> inner.value
598+
is StringGene -> inner.value
599+
is DoubleGene -> inner.value
600+
is BooleanGene -> inner.value
601+
is ImmutableDataHolderGene -> inner.value
602+
else -> {
603+
LoggingUtil.getInfoLogger().warn(
604+
"SQL-Z3: extractGeneValue() fallback to raw string for unhandled gene type " +
605+
"${inner.javaClass.name} (outer: ${gene.javaClass.name}, name: ${gene.name})"
606+
)
607+
inner.getValueAsRawString()
608+
}
609+
}
610+
}
522611
}

docs/options.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ There are 3 types of options:
328328
|`maxSizeOfHandlingResource`| __Int__. Specify a maximum number of handling (remove/add) resource size at once, e.g., add 3 resource at most. *Constraints*: `min=0.0`. *Default value*: `0`.|
329329
|`maxSizeOfMutatingInitAction`| __Int__. Specify a maximum number of handling (remove/add) init actions at once, e.g., add 3 init actions at most. *Constraints*: `min=0.0`. *Default value*: `0`.|
330330
|`maxTestSizeStrategy`| __Enum__. Specify a strategy to handle a max size of a test. *Valid values*: `SPECIFIED, DPC_INCREASING, DPC_DECREASING`. *Default value*: `SPECIFIED`.|
331-
|`measureSqlZ3Correctness`| __Boolean__. Measure the correctness of Z3-generated SQL inserts by computing the heuristic distance between the original failing WHERE query and the generated INSERT data. Distance=0 means the insert satisfies the WHERE; distance>0 means it does not. Only meaningful when generateSqlDataWithZ3=true. *Depends on*: `generateSqlDataWithZ3=true`. *Default value*: `false`.|
332331
|`mutationTargetsSelectionStrategy`| __Enum__. Specify a strategy to select targets for evaluating mutation. *Valid values*: `FIRST_NOT_COVERED_TARGET, EXPANDED_UPDATED_NOT_COVERED_TARGET, UPDATED_NOT_COVERED_TARGET`. *Default value*: `FIRST_NOT_COVERED_TARGET`.|
333332
|`onePlusLambdaLambdaOffspringSize`| __Int__. 1+(λ,λ) GA: number of offspring (λ) per generation. *Constraints*: `min=1.0`. *Default value*: `4`.|
334333
|`prematureStopStrategy`| __Enum__. Specify how 'improvement' is defined: either any kind of improvement even if partial (ANY), or at least one new target is fully covered (NEW). *Valid values*: `ANY, NEW`. *Default value*: `NEW`.|

0 commit comments

Comments
 (0)