diff --git a/client-java/distance-heuristics/src/main/java/org/evomaster/client/java/distance/heuristics/DistanceHelper.java b/client-java/distance-heuristics/src/main/java/org/evomaster/client/java/distance/heuristics/DistanceHelper.java index 08fe510043..98a568053b 100644 --- a/client-java/distance-heuristics/src/main/java/org/evomaster/client/java/distance/heuristics/DistanceHelper.java +++ b/client-java/distance-heuristics/src/main/java/org/evomaster/client/java/distance/heuristics/DistanceHelper.java @@ -7,14 +7,15 @@ import java.time.chrono.ChronoLocalDateTime; import java.util.Date; import java.util.Objects; +import java.util.UUID; /** * All generic distance functions on JVM types should be defined here. * Recall the distinction between "distance" and "heuristic" terms in EvoMaster: - * + *

* - distance: a value between 0 and MAX. If 0, constraint is solved. * - heuristic: a value between 0 and 1. If 0, constraint is NOT solved. If solved, value is 1. - * + *

* The "distance"s are what usually used in literature. * However, in EvoMaster we need [0,1] "heuristic"s (due to the handling of Many Objective Optimization). */ @@ -44,23 +45,23 @@ public class DistanceHelper { * @param delta * @return */ - public static double increasedDistance(double distance, double delta){ + public static double increasedDistance(double distance, double delta) { - if(distance < 0){ + if (distance < 0) { throw new IllegalArgumentException("Negative distance: " + distance); } - if(delta < 0){ + if (delta < 0) { throw new IllegalArgumentException("Invalid negative delta: " + delta); } - if(delta == 0){ + if (delta == 0) { throw new IllegalArgumentException("Meaningless 0 delta"); } - if(Double.isInfinite(distance) || distance == Double.MAX_VALUE){ + if (Double.isInfinite(distance) || distance == Double.MAX_VALUE) { return distance; } - if(distance > (Double.MAX_VALUE - delta)){ + if (distance > (Double.MAX_VALUE - delta)) { return Double.MAX_VALUE; } @@ -75,10 +76,10 @@ public static double increasedDistance(double distance, double delta){ * @return */ public static double addDistances(double a, double b) { - if(a < 0){ + if (a < 0) { throw new IllegalArgumentException("Negative distance: " + a); } - if(b < 0){ + if (b < 0) { throw new IllegalArgumentException("Negative distance: " + b); } double sum = a + b; @@ -92,36 +93,37 @@ public static double addDistances(double a, double b) { /** * Return a h=[0,1] heuristics from a scaled distance, taking into account a starting base + * * @param base * @param distance * @return */ - public static double heuristicFromScaledDistanceWithBase(double base, double distance){ + public static double heuristicFromScaledDistanceWithBase(double base, double distance) { - if(base < 0 || base >= 1){ + if (base < 0 || base >= 1) { throw new IllegalArgumentException("Invalid base: " + base); } - if(distance < 0){ + if (distance < 0) { throw new IllegalArgumentException("Negative distance: " + distance); } - if(Double.isInfinite(distance) || distance == Double.MAX_VALUE){ + if (Double.isInfinite(distance) || distance == Double.MAX_VALUE) { return base; } - return base + ((1 - base) / (distance + 1)); + return base + ((1 - base) / (distance + 1)); } - public static double scaleHeuristicWithBase(double heuristic, double base){ + public static double scaleHeuristicWithBase(double heuristic, double base) { - if(heuristic < 0 || heuristic >= 1){ + if (heuristic < 0 || heuristic >= 1) { throw new IllegalArgumentException("Invalid heuristic: " + base); } - if(base < 0 || base >= 1){ + if (base < 0 || base >= 1) { throw new IllegalArgumentException("Invalid base: " + base); } - return base + ((1-base)*heuristic); + return base + ((1 - base) * heuristic); } public static int distanceToDigit(char c) { @@ -144,7 +146,7 @@ public static int distanceToRange(int c, int minInclusive, int maxInclusive) { //1 of 2 will be necessarily a 0 long dist = Math.max(diffAfter, 0) + Math.max(diffBefore, 0); - if(dist > Integer.MAX_VALUE){ + if (dist > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } assert (dist >= 0); @@ -166,7 +168,7 @@ public static long getLeftAlignmentDistance(String a, String b) { dist += Math.abs(a.charAt(i) - b.charAt(i)); } - if(dist < 0){ + if (dist < 0) { dist = Long.MAX_VALUE; // overflow } @@ -242,10 +244,10 @@ public static double getDistanceToEquality(LocalTime a, LocalTime b) { public static double getDistance(Object left, Object right) { - if(left == null && right == null){ + if (left == null && right == null) { return 0; } - if(left == null || right == null){ + if (left == null || right == null) { return Double.MAX_VALUE; } @@ -330,4 +332,19 @@ public static double getDistance(Object left, Object right) { return distance; } + + /** + * Computes the Hamming distance between two UUIDs. The Hamming distance is determined by + * counting the number of differing bits between the most and least significant bits + * of the two UUIDs. + * + * @param left the first UUID + * @param right the second UUID + * @return the Hamming distance between the two UUIDs + */ + public static int getDistance(UUID left, UUID right) { + long diff1 = left.getMostSignificantBits() ^ right.getMostSignificantBits(); + long diff2 = left.getLeastSignificantBits() ^ right.getLeastSignificantBits(); + return Long.bitCount(diff1) + Long.bitCount(diff2); + } } diff --git a/client-java/distance-heuristics/src/main/java/org/evomaster/client/java/distance/heuristics/TruthnessUtils.java b/client-java/distance-heuristics/src/main/java/org/evomaster/client/java/distance/heuristics/TruthnessUtils.java index dc7d54d689..ef172d9590 100644 --- a/client-java/distance-heuristics/src/main/java/org/evomaster/client/java/distance/heuristics/TruthnessUtils.java +++ b/client-java/distance-heuristics/src/main/java/org/evomaster/client/java/distance/heuristics/TruthnessUtils.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.Objects; +import java.util.UUID; public class TruthnessUtils { @@ -132,6 +133,7 @@ public static Truthness getEqualityTruthness(double a, double b) { /** * Returns a truthness value for comparing how close a length was to 0. + * * @param len a positive value for a length * @return a Truthness instance */ @@ -190,13 +192,13 @@ public static Truthness buildOrAggregationTruthness(Truthness... truthnesses) { *

* This method returns XOR(a,b) as (a AND NOT b) OR (NOT a AND b). * - * @param left the first Truthness instance + * @param left the first Truthness instance * @param right the second Truthness instance * @return a new Truthness instance representing the XOR aggregation of the input Truthness instances */ public static Truthness buildXorAggregationTruthness(Truthness left, Truthness right) { - Truthness leftAndNotRight = buildAndAggregationTruthness(left,right.invert()); - Truthness notLeftAndRight = buildAndAggregationTruthness(left.invert(),right); + Truthness leftAndNotRight = buildAndAggregationTruthness(left, right.invert()); + Truthness notLeftAndRight = buildAndAggregationTruthness(left.invert(), right); Truthness orAggregation = buildOrAggregationTruthness(leftAndNotRight, notLeftAndRight); return orAggregation; } @@ -294,7 +296,7 @@ private static double trueOrAverageTrue(Truthness... truthnesses) { * and creates a Truthness instance where the `ofTrue` field is the scaled value and * the `ofFalse` field is set to 1.0. * - * @param base the base value used for scaling + * @param base the base value used for scaling * @param ofTrueToScale the value to be scaled * @return a new Truthness instance with the scaled `ofTrue` value and `ofFalse` set to 1.0 */ @@ -305,5 +307,16 @@ public static Truthness buildScaledTruthness(double base, double ofTrueToScale) } + public static Truthness getEqualityTruthness(UUID left, UUID right) { + Objects.requireNonNull(left); + Objects.requireNonNull(right); + + double distance = DistanceHelper.getDistance(left, right); + double normalizedDistance = normalizeValue(distance); + return new Truthness( + 1d - normalizedDistance, + !left.equals(right) ? 1d : 0d + ); + } } diff --git a/client-java/distance-heuristics/src/test/java/org/evomaster/client/java/distance/heuristics/DistanceHelperTest.java b/client-java/distance-heuristics/src/test/java/org/evomaster/client/java/distance/heuristics/DistanceHelperTest.java index 8700cdf562..be785edd23 100644 --- a/client-java/distance-heuristics/src/test/java/org/evomaster/client/java/distance/heuristics/DistanceHelperTest.java +++ b/client-java/distance-heuristics/src/test/java/org/evomaster/client/java/distance/heuristics/DistanceHelperTest.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import java.util.UUID; + import static org.evomaster.client.java.distance.heuristics.DistanceHelper.*; import static org.junit.jupiter.api.Assertions.*; @@ -68,9 +70,33 @@ public void testDoubleOverflowsDistance() { @Test public void testDoubleMaxDistance() { - double upperBound = Double.MAX_VALUE /2; + double upperBound = Double.MAX_VALUE / 2; double lowerBound = -upperBound; double distance = getDistanceToEquality(lowerBound, upperBound); assertEquals(Double.MAX_VALUE, distance); } -} \ No newline at end of file + + @Test + public void testDistanceUUIDEquals() { + UUID left = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + UUID right = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + double distance = getDistance(left, right); + assertEquals(0, distance); + } + + @Test + public void testDistanceUUIDNotEquals() { + UUID left = UUID.fromString("123e4567-e89b-12d3-a456-426614174001"); + UUID right = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + double distance = getDistance(left, right); + assertEquals(1, distance); + } + + @Test + public void testDistanceUUIDNotEqualsThree() { + UUID left = UUID.fromString("123e4567-e89b-12d3-a456-426614174003"); + UUID right = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + double distance = getDistance(left, right); + assertEquals(2, distance); + } +} diff --git a/client-java/distance-heuristics/src/test/java/org/evomaster/client/java/distance/heuristics/TruthnessUtilsTest.java b/client-java/distance-heuristics/src/test/java/org/evomaster/client/java/distance/heuristics/TruthnessUtilsTest.java new file mode 100644 index 0000000000..5af69e921c --- /dev/null +++ b/client-java/distance-heuristics/src/test/java/org/evomaster/client/java/distance/heuristics/TruthnessUtilsTest.java @@ -0,0 +1,32 @@ +package org.evomaster.client.java.distance.heuristics; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.evomaster.client.java.distance.heuristics.TruthnessUtils.normalizeValue; +import static org.junit.jupiter.api.Assertions.*; + +class TruthnessUtilsTest { + + @Test + public void testGetEqualityTruthnessEqualsUUID() { + UUID left = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + UUID right = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + Truthness t = TruthnessUtils.getEqualityTruthness(left, right); + assertTrue(t.isTrue()); + assertEquals(1.0, t.getOfTrue()); + assertEquals(0.0, t.getOfFalse()); + } + + @Test + public void testGetEqualityTruthnessNotEqualsUUID() { + UUID left = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + UUID right = UUID.fromString("123e4567-e89b-12d3-a456-426614174001"); + Truthness t = TruthnessUtils.getEqualityTruthness(left, right); + assertFalse(t.isTrue()); + assertEquals(1.0 - normalizeValue(1), t.getOfTrue()); + assertEquals(1.0, t.getOfFalse()); + } + +} diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/DataRow.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/DataRow.java index 608f6b3d2b..838486fab4 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/DataRow.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/DataRow.java @@ -9,6 +9,7 @@ import java.util.*; import java.util.stream.Collectors; +import static org.evomaster.client.java.sql.heuristic.SqlStringUtils.nullSafeEndsWithIgnoreCase; import static org.evomaster.client.java.sql.heuristic.SqlStringUtils.nullSafeEqualsIgnoreCase; /** @@ -102,10 +103,10 @@ public Object getValueByName(String name, String table) { * since 'y','n','on','off', 'yes' and 'no' * are also considered boolean literals. */ - if (n!=null && n.equalsIgnoreCase("true")) { + if (n != null && n.equalsIgnoreCase("true")) { return true; } - if (n!= null && n.equalsIgnoreCase("false")) { + if (n != null && n.equalsIgnoreCase("false")) { return false; } @@ -128,11 +129,11 @@ public Object getValueByName(String name, String table) { boolean matchColumnName = nullSafeEqualsIgnoreCase(n, desc.getColumnName()) || nullSafeEqualsIgnoreCase(n, desc.getAliasColumnName()); - if (!matchColumnName){ + if (!matchColumnName) { continue; } //no defined table, or exact match - if(t == null || t.isEmpty() || nullSafeEqualsIgnoreCase(t, desc.getTableName()) ){ + if (t == null || t.isEmpty() || nullSafeEqualsIgnoreCase(t, desc.getTableName())) { return getValue(i); } /* @@ -141,20 +142,20 @@ there can be many unnamed tables (eg results of sub-selects) with same column names. At this moment, we would not be able to distinguish them */ - if(nullSafeEqualsIgnoreCase(t, SqlNameContext.UNNAMED_TABLE)){ + if (nullSafeEqualsIgnoreCase(t, SqlNameContext.UNNAMED_TABLE)) { candidates.add(i); } - if(!t.contains(".") && desc.getTableName().toLowerCase().endsWith("."+t.toLowerCase())){ + if (!t.contains(".") && nullSafeEndsWithIgnoreCase(desc.getTableName(), "." + t)) { candidates.add(i); } } - if(candidates.size() > 1){ + if (candidates.size() > 1) { SimpleLogger.uniqueWarn("More than one table candidate for: " + t); } - if(candidates.size() >= 1){ + if (candidates.size() >= 1) { return getValue(candidates.get(0)); } diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/QueryResult.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/QueryResult.java index 4144c1492a..a523fdb094 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/QueryResult.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/QueryResult.java @@ -1,5 +1,6 @@ package org.evomaster.client.java.sql; +import net.sf.jsqlparser.statement.select.OrderByElement; import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto; import java.lang.reflect.Method; @@ -195,4 +196,26 @@ public String getTableName() { } return variableDescriptors.get(0).getTableName(); } + + /** + * Creates a new QueryResult containing only the first 'limit' rows + * of the current QueryResult. + * + * @param limit the maximum number of rows to include in the new QueryResult + * @return a new QueryResult with at most 'limit' rows + */ + public QueryResult limit(long limit) { + if (limit < 0) { + throw new IllegalArgumentException("Limit must be non-negative"); + } + if (limit >= rows.size()) { + return this; + } + QueryResult limitedResult = new QueryResult(this.variableDescriptors); + for (int i = 0; i < limit; i++) { + limitedResult.addRow(this.rows.get(i)); + } + return limitedResult; + } + } diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/SqlDataType.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/SqlDataType.java index 0ef250f2c1..897122d1af 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/SqlDataType.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/SqlDataType.java @@ -52,6 +52,9 @@ public enum SqlDataType { VARCHAR_IGNORECASE, MEDIUMTEXT, + // UUID Type + UUID, + // Binary Types LONGBLOB, MEDIUMBLOB, @@ -167,4 +170,8 @@ public static boolean isBooleanType(SqlDataType dataType) { } } + public static boolean isUUIDType(SqlDataType dataType) { + return dataType == SqlDataType.UUID; + } + } diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/ConversionHelper.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/ConversionHelper.java index 0e9906ad6d..e81df576e6 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/ConversionHelper.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/ConversionHelper.java @@ -6,6 +6,7 @@ import java.sql.Time; import java.time.*; import java.util.Date; +import java.util.UUID; import static java.util.Objects.nonNull; import static org.evomaster.client.java.sql.heuristic.SqlHeuristicsCalculator.FALSE_TRUTHNESS; @@ -113,4 +114,21 @@ public static Boolean convertToBoolean(Object object) { } } + public static UUID convertToUUID(Object object) { + if (nonNull(object)) { + return convertToNonNullUUID(object); + } else { + return null; + } + } + + private static UUID convertToNonNullUUID(Object object) { + if (object instanceof UUID) { + return (UUID) object; + } else if (object instanceof String){ + return UUID.fromString((String) object); + } else { + throw new IllegalArgumentException("Type must be UUID or string"); + } + } } diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/QueryResultUtils.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/QueryResultUtils.java index 309c6a0556..48ecf5c6fb 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/QueryResultUtils.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/QueryResultUtils.java @@ -4,10 +4,7 @@ import org.evomaster.client.java.sql.QueryResult; import org.evomaster.client.java.sql.VariableDescriptor; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; public class QueryResultUtils { @@ -79,4 +76,23 @@ public static QueryResult addAliasToQueryResult(QueryResult queryResult, String return newQueryResult; } + + /** + * Creates a new QueryResult containing only distinct rows from the given QueryResult. + * + * @param source + * @return + */ + public static QueryResult createDistinctQueryResult(QueryResult source) { + QueryResult result = new QueryResult(source.seeVariableDescriptors()); + for (DataRow row : source.seeRows()) { + List values = row.seeValues(); + boolean alreadyExists = result.seeRows().stream() + .anyMatch(r -> r.seeValues().equals(values)); + if (!alreadyExists) { + result.addRow(row); + } + } + return result; + } } diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlBaseTableReference.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlBaseTableReference.java index 6929bc1b59..62046e65d7 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlBaseTableReference.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlBaseTableReference.java @@ -12,13 +12,17 @@ public class SqlBaseTableReference extends SqlTableReference { private final SqlTableId tableId; - public SqlBaseTableReference(String name) { - Objects.requireNonNull(name); - this.tableId = new SqlTableId(name); + public SqlBaseTableReference(String catalog, String schema, String baseTableName) { + Objects.requireNonNull(baseTableName); + this.tableId = new SqlTableId(catalog, schema, baseTableName); + } + + public SqlBaseTableReference(String baseTableName) { + this(null, null, baseTableName); } public String getName() { - return tableId.getTableId(); + return tableId.getTableName(); } public SqlTableId getTableId() { diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlCastHelper.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlCastHelper.java index e368fd9247..0ce259aa56 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlCastHelper.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlCastHelper.java @@ -3,6 +3,7 @@ import org.evomaster.client.java.sql.SqlDataType; import java.util.Objects; +import java.util.UUID; import static org.evomaster.client.java.sql.SqlDataType.*; @@ -179,6 +180,21 @@ public static Object castTo(SqlDataType dataType, Object value) { return castToDateTime(value); } + if (SqlDataType.isUUIDType(dataType)) { + return castoToUUID(value); + } + throw new IllegalArgumentException("Must implement casting to " + dataType + ": " + value); } + + private static Object castoToUUID(Object value) { + if (value instanceof String) { + String s = (String) value; + return java.util.UUID.fromString(s); + } else if (value instanceof UUID){ + return value; + } else { + throw new IllegalArgumentException("Cannot cast to UUID: " + value); + } + } } diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlDerivedTableReference.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlDerivedTable.java similarity index 79% rename from client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlDerivedTableReference.java rename to client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlDerivedTable.java index 72dc60ab8b..86eaf4db2f 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlDerivedTableReference.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlDerivedTable.java @@ -7,11 +7,11 @@ * Derived tables are temporary tables created within a query. * A derived table is defined by a subquery in the FROM clause, or an the WHERE clause. */ -public class SqlDerivedTableReference extends SqlTableReference{ +public class SqlDerivedTable extends SqlTableReference { private final Select select; - public SqlDerivedTableReference(Select select) { + public SqlDerivedTable(Select select) { this.select = select; } diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlExpressionEvaluator.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlExpressionEvaluator.java index 9be451f955..af342f8517 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlExpressionEvaluator.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlExpressionEvaluator.java @@ -305,6 +305,8 @@ private Truthness evaluateTruthnessForComparisonOperator(Object concreteLeftValu truthnessOfExpression = calculateTruthnessForInstantComparison(convertToInstant(concreteLeftValue), convertToInstant(concreteRightValue), comparisonOperatorType); } else if (concreteLeftValue instanceof OffsetTime || concreteRightValue instanceof OffsetTime) { truthnessOfExpression = calculateTruthnessForInstantComparison(convertToInstant(concreteLeftValue), convertToInstant(concreteLeftValue), comparisonOperatorType); + } else if (concreteLeftValue instanceof UUID || concreteRightValue instanceof UUID) { + truthnessOfExpression = calculateTruthnessForUUIDComparison(convertToUUID(concreteLeftValue), convertToUUID(concreteRightValue), comparisonOperatorType); } else if (concreteLeftValue instanceof Object[] && concreteRightValue instanceof Object[]) { truthnessOfExpression = calculateTruthnessForArrayComparison((Object[]) concreteLeftValue, (Object[]) concreteRightValue, comparisonOperatorType); } else { @@ -319,6 +321,20 @@ private Truthness evaluateTruthnessForComparisonOperator(Object concreteLeftValu return truthness; } + private static Truthness calculateTruthnessForUUIDComparison(UUID left, UUID right, ComparisonOperatorType comparisonOperatorType) { + Objects.requireNonNull(left); + Objects.requireNonNull(right); + + switch (comparisonOperatorType) { + case EQUALS_TO: + return TruthnessUtils.getEqualityTruthness(left, right); + case NOT_EQUALS_TO: + return TruthnessUtils.getEqualityTruthness(left, right).invert(); + default: + throw new IllegalArgumentException("Unsupported UUID binary operator: " + comparisonOperatorType); + } + } + private static Truthness calculateTruthnessForInstantComparison(Instant leftInstant, Instant rightInstant, ComparisonOperatorType comparisonOperatorType) { Objects.requireNonNull(leftInstant); Objects.requireNonNull(rightInstant); @@ -381,6 +397,9 @@ private static double toDouble(Boolean booleanValue) { } public static Truthness getEqualityTruthness(String a, String b) { + Objects.requireNonNull(a); + Objects.requireNonNull(b); + if (a.equals(b)) { return TRUE_TRUTHNESS; } else { @@ -484,6 +503,37 @@ public void visit(NullValue nullValue) { evaluationStack.push(null); } + private Object visitAggregationFunction(SqlAggregateFunction sqlAggregateFunction, Expression parameterExpression) { + final Object functionResult; + List values = new ArrayList<>(); + if (parameterExpression instanceof Column) { + for (DataRow dataRow : this.getCurrentQueryResult().seeRows()) { + SqlExpressionEvaluator expressionEvaluator = new SqlExpressionEvaluator.SqlExpressionEvaluatorBuilder() + .withTaintHandler(this.taintHandler) + .withTableColumnResolver(this.tableColumnResolver) + .withQueryResultSet(this.queryResultSet) + .withCurrentQueryResult(this.getCurrentQueryResult()) + .withDataRowStack(this.dataRowStack) + .withCurrentDataRow(dataRow) + .withParentStatementEvaluator(this.parentStatementEvaluator) + .build(); + parameterExpression.accept(expressionEvaluator); + final Object value = expressionEvaluator.popAsSingleValue(); + values.add(value); + } + } else if (parameterExpression instanceof AllColumns) { + for (DataRow dataRow : getCurrentQueryResult().seeRows()) { + values.add(dataRow); + } + } else { + parameterExpression.accept(this); + Object value = this.popAsSingleValue(); + values.add(value); + } + functionResult = sqlAggregateFunction.evaluate(values); + return functionResult; + } + @Override public void visit(Function function) { String functionName = function.getName(); @@ -492,42 +542,33 @@ public void visit(Function function) { throw new UnsupportedOperationException("Function " + functionName + " needs to be implemented"); } final Object functionResult; - List values = new ArrayList<>(); if (sqlFunction instanceof SqlAggregateFunction) { - Expression parameterExpression = function.getParameters().get(0); - - if (parameterExpression instanceof Column) { - for (DataRow dataRow : this.getCurrentQueryResult().seeRows()) { - SqlExpressionEvaluator expressionEvaluator = new SqlExpressionEvaluator.SqlExpressionEvaluatorBuilder() - .withTaintHandler(this.taintHandler) - .withTableColumnResolver(this.tableColumnResolver) - .withQueryResultSet(this.queryResultSet) - .withCurrentQueryResult(this.getCurrentQueryResult()) - .withDataRowStack(this.dataRowStack) - .withCurrentDataRow(dataRow) - .withParentStatementEvaluator(this.parentStatementEvaluator) - .build(); - parameterExpression.accept(expressionEvaluator); - final Object value = expressionEvaluator.popAsSingleValue(); - values.add(value); - } - } else if (parameterExpression instanceof AllColumns) { - for (DataRow dataRow : getCurrentQueryResult().seeRows()) { - values.add(dataRow); - } - } else { - parameterExpression.accept(this); - Object value = this.popAsSingleValue(); - values.add(value); + if (function.getParameters().size() != 1) { + throw new UnsupportedOperationException( + String.format("Unsupported aggregate function %s with %s parameters", + functionName, + function.getParameters().size())); } - functionResult = sqlFunction.evaluate(values); + Expression parameterExpression = function.getParameters().get(0); + functionResult = visitAggregationFunction( + (SqlAggregateFunction) sqlFunction, + parameterExpression); } else { super.visit(function); - for (int i = 0; i < function.getParameters().size(); i++) { - Object concreteParameter = popAsSingleValue(); - values.add(concreteParameter); + final List values; + if (function.getParameters() != null && !function.getParameters().isEmpty()) { + List concreteParameters = popAsListOfValues(); + if (function.getParameters().size() != concreteParameters.size()) { + throw new IllegalStateException( + String.format("Mismatch in number of parameters for function %s: %s expected but %s found", + functionName, + function.getParameters().size(), + concreteParameters.size())); + } + values = concreteParameters; + } else { + values = new ArrayList<>(); } - Collections.reverse(values); functionResult = sqlFunction.evaluate(values.toArray(new Object[]{})); } this.evaluationStack.push(functionResult); @@ -1248,9 +1289,29 @@ public void visit(TimeKeyExpression timeKeyExpression) { @Override public void visit(DateTimeLiteralExpression dateTimeLiteralExpression) { - String dateTimeAsString = dateTimeLiteralExpression.getValue(); - String dateTimeWithoutEnclosingQuotes = SqlStringUtils.removeEnclosingQuotes(dateTimeAsString); - evaluationStack.push(dateTimeWithoutEnclosingQuotes); + final String dateTimeAsString = SqlStringUtils.removeEnclosingQuotes(dateTimeLiteralExpression.getValue()); + final DateTimeLiteralExpression.DateTime dateTimeType = dateTimeLiteralExpression.getType(); + Object dateTimeValue; + switch (dateTimeType) { + case DATE: + dateTimeValue = java.sql.Date.valueOf(dateTimeAsString); + break; + case TIME: + dateTimeValue = java.sql.Time.valueOf(dateTimeAsString); + break; + case TIMESTAMP: + dateTimeValue = java.sql.Timestamp.valueOf(dateTimeAsString); + break; + case TIMESTAMPTZ: + // Example literal: 2025-01-22 15:30:45+02:00 + // Convert spaces to 'T' to comply with ISO-8601 + String isoString = dateTimeAsString.replace(' ', 'T'); + dateTimeValue = java.time.OffsetDateTime.parse(isoString); + break; + default: + throw new IllegalArgumentException("Unsupported DateTimeLiteralExpression type: " + dateTimeType); + } + evaluationStack.push(dateTimeValue); } @Override @@ -1388,7 +1449,7 @@ public void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter) { public void visit(AllColumns allColumns) { List values = new ArrayList<>(getCurrentDataRow().seeValues()); evaluationStack.push(values); - } + } @Override public void visit(AllTableColumns allTableColumns) { diff --git a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlHeuristicsCalculator.java b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlHeuristicsCalculator.java index 07c51feda3..2d9a83cd00 100644 --- a/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlHeuristicsCalculator.java +++ b/client-java/sql/src/main/java/org/evomaster/client/java/sql/heuristic/SqlHeuristicsCalculator.java @@ -2,6 +2,7 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.Parenthesis; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; @@ -10,15 +11,13 @@ import net.sf.jsqlparser.statement.update.Update; import org.evomaster.client.java.distance.heuristics.Truthness; import org.evomaster.client.java.distance.heuristics.TruthnessUtils; -import org.evomaster.client.java.sql.DataRow; -import org.evomaster.client.java.sql.QueryResult; -import org.evomaster.client.java.sql.QueryResultSet; -import org.evomaster.client.java.sql.VariableDescriptor; +import org.evomaster.client.java.sql.*; import org.evomaster.client.java.sql.heuristic.function.FunctionFinder; import org.evomaster.client.java.sql.heuristic.function.SqlAggregateFunction; import org.evomaster.client.java.sql.heuristic.function.SqlFunction; import org.evomaster.client.java.sql.internal.SqlDistanceWithMetrics; import org.evomaster.client.java.sql.internal.SqlParserUtils; +import org.evomaster.client.java.sql.internal.SqlTableId; import org.evomaster.client.java.sql.internal.TaintHandler; import org.evomaster.client.java.utils.SimpleLogger; @@ -406,36 +405,67 @@ private QueryResult createQueryResultLeftJoin(QueryResult leftQueryResult, Query SqlHeuristicResult computeHeuristic(Select select) { tableColumnResolver.enterStatementeContext(select); final SqlHeuristicResult heuristicResult; - if (select instanceof SetOperationList) { - SetOperationList unionQuery = (SetOperationList) select; - List subqueries = unionQuery.getSelects(); + heuristicResult = computeHeuristicUnion(subqueries, unionQuery.getLimit()); + } else if (select instanceof ParenthesedSelect) { + // Handle case of parenthesed SELECT + ParenthesedSelect parenthesedSelect = (ParenthesedSelect) select; + Select subquery = parenthesedSelect.getSelect(); + final SqlHeuristicResult subqueryHeuristicResult = computeHeuristic(subquery); + if (parenthesedSelect.getLimit() != null) { + long limitValue = getLimitValue(parenthesedSelect.getLimit()); + QueryResult queryResult = subqueryHeuristicResult.getQueryResult().limit(limitValue); + heuristicResult = new SqlHeuristicResult(subqueryHeuristicResult.getTruthness(), queryResult); + } else { + heuristicResult = subqueryHeuristicResult; + } + } else if (select instanceof PlainSelect) { + // Handle case of plain SELECT + PlainSelect plainSelect = (PlainSelect) select; + final FromItem fromItem = getFrom(plainSelect); + final List joins = getJoins(plainSelect); + final Expression whereClause = getWhere(plainSelect); + if (plainSelect.getGroupBy() != null) { + heuristicResult = computeHeuristicSelectGroupByHaving( + plainSelect.getSelectItems(), + fromItem, + joins, + whereClause, + plainSelect.getGroupBy().getGroupByExpressionList(), + plainSelect.getHaving(), + plainSelect.getOrderByElements(), + plainSelect.getLimit()); + } else { + SqlHeuristicResult heuristicResultBeforeDistinct = computeHeuristicSelect( + plainSelect.getSelectItems(), + fromItem, + joins, + whereClause, + plainSelect.getOrderByElements(), + plainSelect.getLimit()); + + if (plainSelect.getDistinct() != null) { + QueryResult distinctQueryResult; + if (plainSelect.getDistinct().getOnSelectItems() != null && !plainSelect.getDistinct().getOnSelectItems().isEmpty()) { + distinctQueryResult = createQueryResultDistinctOn(heuristicResultBeforeDistinct.getQueryResult(), plainSelect.getDistinct().getOnSelectItems()); + } else { + distinctQueryResult = QueryResultUtils.createDistinctQueryResult(heuristicResultBeforeDistinct.getQueryResult()); + } + heuristicResult = new SqlHeuristicResult(heuristicResultBeforeDistinct.getTruthness(), distinctQueryResult); + } else { + heuristicResult = heuristicResultBeforeDistinct; + } + } } else { - heuristicResult = computeHeuristicSelect( - plainSelect.getSelectItems(), - fromItem, - joins, - whereClause); + throw new IllegalArgumentException("Cannot calculate heuristics for SQL command of type " + select.getClass().getName()); } - } else { - throw new IllegalArgumentException("Cannot calculate heuristics for SQL command of type " + select.getClass().getName()); } tableColumnResolver.exitCurrentStatementContext(); return heuristicResult; @@ -448,13 +478,54 @@ private SqlHeuristicResult computeHeuristicSelect(List> selectItem return heuristicResult; } + private SqlHeuristicResult computeHeuristicSelect(List> selectItems, + FromItem fromItem, + List joins, + Expression whereClause, + Limit limit) { + final SqlHeuristicResult intermediateHeuristicResult = computeHeuristic(fromItem, joins, whereClause); + QueryResult queryResult = createQueryResult(intermediateHeuristicResult.getQueryResult(), selectItems); + + if (limit != null) { + long limitValue = getLimitValue(limit); + queryResult = queryResult.limit(limitValue); + } + + final SqlHeuristicResult heuristicResult = new SqlHeuristicResult(intermediateHeuristicResult.getTruthness(), queryResult); + return heuristicResult; + } + + private SqlHeuristicResult computeHeuristicSelect(List> selectItems, + FromItem fromItem, + List joins, + Expression whereClause, + List orderByElements, + Limit limit) { + final SqlHeuristicResult intermediateHeuristicResult = computeHeuristic(fromItem, joins, whereClause); + QueryResult queryResult = createQueryResult(intermediateHeuristicResult.getQueryResult(), selectItems); + + if (orderByElements != null && !orderByElements.isEmpty()) { + queryResult = createQueryResultOrderBy(queryResult, orderByElements); + } + + if (limit != null) { + long limitValue = getLimitValue(limit); + queryResult = queryResult.limit(limitValue); + } + + final SqlHeuristicResult heuristicResult = new SqlHeuristicResult(intermediateHeuristicResult.getTruthness(), queryResult); + return heuristicResult; + } + private SqlHeuristicResult computeHeuristicSelectGroupByHaving( List> selectItems, FromItem fromItem, List joins, Expression whereClause, List groupByExpressions, - Expression having) { + Expression having, + List orderByElements, + Limit limit) { final SqlHeuristicResult intermediateHeuristicResult = computeHeuristic(fromItem, joins, whereClause); @@ -475,9 +546,11 @@ private SqlHeuristicResult computeHeuristicSelectGroupByHaving( final List truthnesses = new ArrayList<>(); for (QueryResult groupByQueryResult : groupByQueryResults.values()) { QueryResult aggregatedQueryResult = createQueryResult(groupByQueryResult, selectItems); - if (aggregatedQueryResult.size() != 1) { - throw new IllegalStateException("An aggregated query result cannot have " + aggregatedQueryResult.size() + "rows"); + if (aggregatedQueryResult.isEmpty()) { + throw new IllegalStateException("An aggregated query result cannot be empty"); } + // If more than one row is present, we always pick the first one. + // This is the semantics of the SELECT DISTINCT ON (columns) in PostgreSQL DataRow dataRow = aggregatedQueryResult.seeRows().get(0); if (having != null) { final Truthness truthness = evaluateAll(Collections.singletonList(having), groupByQueryResult); @@ -490,7 +563,7 @@ private SqlHeuristicResult computeHeuristicSelectGroupByHaving( } } final List variableDescriptors = createSelectVariableDescriptors(selectItems, sourceQueryResult.seeVariableDescriptors()); - final QueryResult queryResult = new QueryResult(variableDescriptors); + QueryResult queryResult = new QueryResult(variableDescriptors); for (DataRow groupByDataRow : groupByDataRows) { queryResult.addRow(groupByDataRow); } @@ -505,6 +578,15 @@ private SqlHeuristicResult computeHeuristicSelectGroupByHaving( intermediateHeuristicResult.getTruthness(), havingTruthness); + if (orderByElements != null && !orderByElements.isEmpty()) { + queryResult = createQueryResultOrderBy(queryResult, orderByElements); + } + + if (limit != null) { + long limitValue = getLimitValue(limit); + queryResult = queryResult.limit(limitValue); + } + return new SqlHeuristicResult(groupByHavingTruthness, queryResult); } @@ -669,6 +751,9 @@ private static boolean hasAnyTableColumn(Expression expression) { } } } + } else if (expression instanceof Parenthesis) { + Parenthesis parenthesisExpression = (Parenthesis) expression; + return hasAnyTableColumn(parenthesisExpression.getExpression()); } return false; } @@ -728,7 +813,7 @@ && isAggregateFunction(((Function) selectItem.getExpression()).getName())) { return selectVariableDescriptors; } - private SqlHeuristicResult computeHeuristicUnion(List subqueries, Limit limit) { List subqueryResults = new ArrayList<>(); for (Select subquery : subqueries) { SqlHeuristicResult subqueryResult = computeHeuristic(subquery); @@ -742,7 +827,12 @@ private SqlHeuristicResult computeHeuristicUnion(List