From 92aa301730ae5d4e0f903d96cae4dd10a99ceb3f Mon Sep 17 00:00:00 2001 From: Gonzalo Tomas Guerrero Date: Tue, 23 Jun 2026 21:59:18 -0300 Subject: [PATCH 1/6] First calculator draft --- .../CassandraHeuristicsCalculator.java | 352 ++++++++++ .../cassandra/CqlDurationLiteral.java | 101 +++ .../CassandraHeuristicsCalculatorTest.java | 605 ++++++++++++++++++ .../distance/heuristics/TruthnessUtils.java | 11 + 4 files changed, 1069 insertions(+) create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CqlDurationLiteral.java create mode 100644 client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java new file mode 100644 index 0000000000..96522ae85a --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java @@ -0,0 +1,352 @@ +package org.evomaster.client.java.controller.cassandra; + +import org.evomaster.client.java.controller.cassandra.operations.*; +import org.evomaster.client.java.controller.cassandra.parser.CqlParserUtils; +import org.evomaster.client.java.distance.heuristics.DistanceHelper; +import org.evomaster.client.java.distance.heuristics.Truthness; +import org.evomaster.client.java.distance.heuristics.TruthnessUtils; +import org.evomaster.client.java.utils.SimpleLogger; + +import java.net.InetAddress; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.*; + +public class CassandraHeuristicsCalculator { + + private static final double C = DistanceHelper.H_NOT_NULL; + private static final double C_BETTER = 0.15; + + private static final Truthness TRUE_TRUTHNESS = new Truthness(1.0, C); + private static final Truthness FALSE_TRUTHNESS = new Truthness(C, 1.0); + private static final Truthness FALSE_TRUTHNESS_BETTER = new Truthness(C_BETTER, 1.0); + + private static final Object MISSING = new Object(); + + private enum ComparisonType { EQUALS, GT, GTE, LT, LTE } + + public double computeDistance(String cqlQuery, Iterable> allRows) { + return 1.0 - computeHQuery(cqlQuery, allRows).getOfTrue(); + } + + private Truthness computeHQuery(String cqlQuery, Iterable> allRows) { + if (!CqlParserUtils.canParseCqlCommand(cqlQuery)) { + return FALSE_TRUTHNESS; + } + + List> rows = toList(allRows); + + org.evomaster.client.java.controller.cassandra.parser.CqlParser.RootContext root = + CqlParserUtils.parseCqlCommand(cqlQuery); + CqlQueryOperation whereOp = CqlParserUtils.getWhereOperation(root); + + Truthness hRowSet = computeHRowSet(rows); + + if (whereOp == null) { + return hRowSet; + } + + Truthness hCondition = computeHCondition(whereOp, rows); + return TruthnessUtils.buildAndAggregationTruthness(hRowSet, hCondition); + } + + private Truthness computeHRowSet(List> rows) { + return TruthnessUtils.getTruthnessToEmpty(rows.size()).invert(); + } + + private Truthness computeHCondition(CqlQueryOperation condition, + List> rows) { + if (rows.isEmpty()) { + return FALSE_TRUTHNESS; + } + + double maxOfTrue = 0.0; + for (Map row : rows) { + double ofTrue = calculateDistance(condition, row).getOfTrue(); + if (ofTrue >= 1.0) { + return TRUE_TRUTHNESS; + } + if (ofTrue > maxOfTrue) { + maxOfTrue = ofTrue; + } + } + + return TruthnessUtils.buildScaledTruthness(C, maxOfTrue); + } + + private Truthness calculateDistance(CqlQueryOperation op, Map row) { + if (op instanceof AndOperation) + return calculateDistanceForAnd((AndOperation) op, row); + if (op instanceof EqualsOperation) + return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.EQUALS); + if (op instanceof GreaterThanOperation) + return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.GT); + if (op instanceof GreaterThanEqualsOperation) + return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.GTE); + if (op instanceof LessThanOperation) + return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.LT); + if (op instanceof LessThanEqualsOperation) + return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.LTE); + if (op instanceof InOperation) + return calculateDistanceForIn((InOperation) op, row); + if (op instanceof ContainsOperation) + return calculateDistanceForContains((ContainsOperation) op, row); + if (op instanceof ContainsKeyOperation) + return calculateDistanceForContainsKey((ContainsKeyOperation) op, row); + + return FALSE_TRUTHNESS; + } + + private Truthness calculateDistanceForAnd(AndOperation op, Map row) { + List results = new ArrayList<>(); + for (CqlQueryOperation condition : op.getConditions()) { + results.add(calculateDistance(condition, row)); + } + return TruthnessUtils.buildAndAggregationTruthness(results.toArray(new Truthness[0])); + } + + private Truthness calculateDistanceForComparison( + ComparisonOperation op, + Map row, + ComparisonType type) { + + Object rowValue = getRowValue(row, op.getColumnName()); + Object queryValue = op.getValue(); + + if (rowValue == MISSING) rowValue = null; + + if (rowValue == null && queryValue == null) return FALSE_TRUTHNESS; + if (rowValue == null || queryValue == null) return FALSE_TRUTHNESS_BETTER; + + Truthness typeResult = compareByType(rowValue, queryValue, type); + if (typeResult.isTrue()) { + return typeResult; + } + return TruthnessUtils.buildScaledTruthness(C_BETTER, typeResult.getOfTrue()); + } + + private Truthness compareByType(Object rowVal, Object queryVal, ComparisonType type) { + if (rowVal instanceof Number && queryVal instanceof Number) + return compareNumeric(rowVal, queryVal, type); + if (rowVal instanceof String || rowVal instanceof InetAddress) + return compareString(rowVal, queryVal, type); + if (rowVal instanceof Boolean) + return compareBoolean(rowVal, queryVal, type); + if (rowVal instanceof UUID) + return compareUuid(rowVal, queryVal, type); + if (isTemporalType(rowVal)) + return compareTemporal(rowVal, queryVal, type); + if (isCqlDuration(rowVal)) + return compareDuration(rowVal, queryVal, type); + + SimpleLogger.uniqueWarn("CassandraHeuristicsCalculator: unsupported type " + + rowVal.getClass().getName() + " — returning FALSE_TRUTHNESS"); + return FALSE_TRUTHNESS; + } + + private Truthness compareNumeric(Object rowVal, Object queryVal, ComparisonType type) { + double x = ((Number) rowVal).doubleValue(); + double y = ((Number) queryVal).doubleValue(); + switch (type) { + case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); + case GT: return TruthnessUtils.getLessThanTruthness(y, x); + case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); + case LT: return TruthnessUtils.getLessThanTruthness(x, y); + case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); + default: return FALSE_TRUTHNESS; + } + } + + private Truthness compareString(Object rowVal, Object queryVal, ComparisonType type) { + if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; + String a = (rowVal instanceof InetAddress) + ? ((InetAddress) rowVal).getHostAddress() + : (String) rowVal; + String b = (String) queryVal; + return TruthnessUtils.getStringEqualityTruthness(a, b); + } + + private Truthness compareBoolean(Object rowVal, Object queryVal, ComparisonType type) { + if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; + double x = ((Boolean) rowVal) ? 1.0 : 0.0; + double y = ((Boolean) queryVal) ? 1.0 : 0.0; + return TruthnessUtils.getEqualityTruthness(x, y); + } + + private Truthness compareUuid(Object rowVal, Object queryVal, ComparisonType type) { + if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; + return TruthnessUtils.getEqualityTruthness((UUID) rowVal, (UUID) queryVal); + } + + private static boolean isTemporalType(Object v) { + return v instanceof LocalDate + || v instanceof LocalTime + || v instanceof Instant; + } + + private Truthness compareTemporal(Object rowVal, Object queryVal, ComparisonType type) { + try { + double x = (double) toLong(rowVal, rowVal); + double y = (double) toLong(queryVal, rowVal); + switch (type) { + case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); + case GT: return TruthnessUtils.getLessThanTruthness(y, x); + case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); + case LT: return TruthnessUtils.getLessThanTruthness(x, y); + case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); + default: return FALSE_TRUTHNESS; + } + } catch (Exception e) { + return FALSE_TRUTHNESS; + } + } + + private static long toLong(Object value, Object rowValueHint) { + if (rowValueHint instanceof LocalDate) { + if (value instanceof String) return LocalDate.parse((String) value).toEpochDay(); + return ((LocalDate) value).toEpochDay(); + } + if (rowValueHint instanceof LocalTime) { + if (value instanceof String) return LocalTime.parse((String) value).toNanoOfDay(); + return ((LocalTime) value).toNanoOfDay(); + } + if (rowValueHint instanceof Instant) { + if (value instanceof String) return Instant.parse((String) value).toEpochMilli(); + return ((Instant) value).toEpochMilli(); + } + throw new IllegalArgumentException("Unrecognized temporal type: " + rowValueHint.getClass()); + } + + private static boolean isCqlDuration(Object v) { + return v.getClass().getName() + .equals("com.datastax.oss.driver.api.core.data.CqlDuration"); + } + + private static int getDurationMonths(Object d) throws Exception { + return (int) d.getClass().getMethod("getMonths").invoke(d); + } + + private static int getDurationDays(Object d) throws Exception { + return (int) d.getClass().getMethod("getDays").invoke(d); + } + + private static long getDurationNanos(Object d) throws Exception { + return (long) d.getClass().getMethod("getNanoseconds").invoke(d); + } + + private Truthness compareDuration(Object rowVal, Object queryVal, ComparisonType type) { + if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; + try { + int rm = getDurationMonths(rowVal); + int rd = getDurationDays(rowVal); + long rn = getDurationNanos(rowVal); + + CqlDurationLiteral q = CqlDurationLiteral.parse((String) queryVal); + + return TruthnessUtils.buildAndAggregationTruthness( + TruthnessUtils.getEqualityTruthness((long) rm, (long) q.months), + TruthnessUtils.getEqualityTruthness((long) rd, (long) q.days), + TruthnessUtils.getEqualityTruthness(rn, q.nanos) + ); + } catch (Exception e) { + return FALSE_TRUTHNESS; + } + } + + private Truthness calculateDistanceForIn(InOperation op, Map row) { + Object rowValue = getRowValue(row, op.getColumnName()); + if (rowValue == MISSING) rowValue = null; + return computeEqualsAny(rowValue, op.getValues()); + } + + private Truthness calculateDistanceForContains(ContainsOperation op, + Map row) { + Object rawCol = getRowValue(row, op.getColumnName()); + if (rawCol == MISSING || rawCol == null) return FALSE_TRUTHNESS; + // Elements are the authoritative side — pass them first so compareByType dispatches on their type. + return computeEqualsAnyElements(toElementList(rawCol), op.getValue()); + } + + private Truthness calculateDistanceForContainsKey(ContainsKeyOperation op, + Map row) { + Object rawCol = getRowValue(row, op.getColumnName()); + if (rawCol == MISSING || rawCol == null) return FALSE_TRUTHNESS; + if (!(rawCol instanceof Map)) return FALSE_TRUTHNESS; + List keys = new ArrayList<>(((Map) rawCol).keySet()); + // Keys are the authoritative side — pass them first so compareByType dispatches on their type. + return computeEqualsAnyElements(keys, op.getValue()); + } + + /** + * Used by IN: value is the row column (authoritative type), candidates are the query IN-list values. + * compareByType dispatches on value. + */ + private Truthness computeEqualsAny(Object value, List candidates) { + if (candidates.isEmpty()) return FALSE_TRUTHNESS; + + double maxOfTrue = 0.0; + for (Object candidate : candidates) { + Truthness t = evaluateEquals(value, candidate); + if (t.isTrue()) return TRUE_TRUTHNESS; + if (t.getOfTrue() > maxOfTrue) maxOfTrue = t.getOfTrue(); + } + return new Truthness(maxOfTrue, 1.0); + } + + /** + * Used by CONTAINS / CONTAINS KEY: elements are the collection elements (authoritative type), + * queryValue is what we search for. compareByType dispatches on the element. + */ + private Truthness computeEqualsAnyElements(List elements, Object queryValue) { + if (elements.isEmpty()) return FALSE_TRUTHNESS; + + double maxOfTrue = 0.0; + for (Object element : elements) { + Truthness t = evaluateEquals(element, queryValue); + if (t.isTrue()) return TRUE_TRUTHNESS; + if (t.getOfTrue() > maxOfTrue) maxOfTrue = t.getOfTrue(); + } + return new Truthness(maxOfTrue, 1.0); + } + + private Truthness evaluateEquals(Object a, Object b) { + if (a == null && b == null) return FALSE_TRUTHNESS; + if (a == null || b == null) return FALSE_TRUTHNESS_BETTER; + Truthness raw = compareByType(a, b, ComparisonType.EQUALS); + if (raw.isTrue()) return raw; + return TruthnessUtils.buildScaledTruthness(C_BETTER, raw.getOfTrue()); + } + + private static List toElementList(Object collection) { + if (collection instanceof List) return (List) collection; + if (collection instanceof Set) return new ArrayList<>((Set) collection); + if (collection instanceof Map) return new ArrayList<>(((Map) collection).values()); + return Collections.emptyList(); + } + + private static String normalizeColumnName(String name) { + if (name == null) return null; + if (name.startsWith("\"") && name.endsWith("\"")) { + return name.substring(1, name.length() - 1).toLowerCase(); + } + return name.toLowerCase(); + } + + private Object getRowValue(Map row, String rawColumnName) { + String key = normalizeColumnName(rawColumnName); + if (!row.containsKey(key)) return MISSING; + return row.get(key); + } + + private static List> toList(Iterable> iterable) { + if (iterable instanceof List) { + return (List>) iterable; + } + List> list = new ArrayList<>(); + for (Map row : iterable) { + list.add(row); + } + return list; + } +} \ No newline at end of file diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CqlDurationLiteral.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CqlDurationLiteral.java new file mode 100644 index 0000000000..998fa453fa --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CqlDurationLiteral.java @@ -0,0 +1,101 @@ +package org.evomaster.client.java.controller.cassandra; + +import java.time.Duration; +import java.time.Period; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parses a Cassandra duration string literal (as produced by CqlParserUtils) into + * its three components: months, days, and nanoseconds. + * + * Supports two formats: + * - ISO 8601: P1Y2M3DT4H5M6S + * - Standard Cassandra: 1y2mo3d4h5m6s (units may appear in any order) + */ +public class CqlDurationLiteral { + + public final int months; + public final int days; + public final long nanos; + + private CqlDurationLiteral(int months, int days, long nanos) { + this.months = months; + this.days = days; + this.nanos = nanos; + } + + public static CqlDurationLiteral parse(String text) { + if (text == null || text.isEmpty()) { + throw new IllegalArgumentException("Empty duration literal"); + } + String t = text.trim(); + if (t.startsWith("P") || t.startsWith("p")) { + return parseIso(t); + } + return parseCassandra(t); + } + + private static CqlDurationLiteral parseIso(String text) { + // Split on T (or t) to separate date and time parts + String upper = text.toUpperCase(); + int tIndex = upper.indexOf('T'); + + int months = 0; + int days = 0; + long nanos = 0L; + + if (tIndex < 0) { + // Date part only: P1Y2M3D + Period p = Period.parse(upper); + months = p.getYears() * 12 + p.getMonths(); + days = p.getDays(); + } else { + String datePart = upper.substring(0, tIndex); // e.g. "P1Y2M3D" + String timePart = upper.substring(tIndex); // e.g. "T4H5M6S" + + if (datePart.length() > 1) { + Period p = Period.parse(datePart); + months = p.getYears() * 12 + p.getMonths(); + days = p.getDays(); + } + if (timePart.length() > 1) { + Duration d = Duration.parse("P" + timePart); + nanos = d.toNanos(); + } + } + return new CqlDurationLiteral(months, days, nanos); + } + + // Matches: optional digits followed by a unit token. + // Units (longest-first to avoid 'm' consuming 'mo' or 'ms' consuming 'm'): + // mo, ms, us, µs, ns, y, d, h, m, s + private static final Pattern TOKEN = + Pattern.compile("(\\d+)(mo|ms|µs|us|ns|y|d|h|m|s)", Pattern.CASE_INSENSITIVE); + + private static CqlDurationLiteral parseCassandra(String text) { + int months = 0; + int days = 0; + long nanos = 0L; + + Matcher m = TOKEN.matcher(text); + while (m.find()) { + long value = Long.parseLong(m.group(1)); + String unit = m.group(2).toLowerCase(); + switch (unit) { + case "y": months += (int)(value * 12); break; + case "mo": months += (int) value; break; + case "d": days += (int) value; break; + case "h": nanos += value * 3_600_000_000_000L; break; + case "m": nanos += value * 60_000_000_000L; break; + case "s": nanos += value * 1_000_000_000L; break; + case "ms": nanos += value * 1_000_000L; break; + case "us": + case "µs": nanos += value * 1_000L; break; + case "ns": nanos += value; break; + default: break; + } + } + return new CqlDurationLiteral(months, days, nanos); + } +} \ No newline at end of file diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java new file mode 100644 index 0000000000..32f877e971 --- /dev/null +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java @@ -0,0 +1,605 @@ +package org.evomaster.client.java.controller.cassandra; + +import org.evomaster.client.java.distance.heuristics.DistanceHelper; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class CassandraHeuristicsCalculatorTest { + + private final CassandraHeuristicsCalculator calc = new CassandraHeuristicsCalculator(); + + private static Map row(Object... kv) { + Map m = new LinkedHashMap<>(); + for (int i = 0; i < kv.length; i += 2) m.put((String) kv[i], kv[i + 1]); + return m; + } + + @SafeVarargs + private final double dist(String cql, Map... rows) { + return calc.computeDistance(cql, Arrays.asList(rows)); + } + + private double distNoRows(String cql) { + return calc.computeDistance(cql, Collections.emptyList()); + } + + private static final double DELTA = 1e-9; + + @Test + void noWhere_emptyTable_maxDistance() { + // H-Table(0 rows) = FALSE_TRUTHNESS → ofTrue = C → distance = 1 - C + assertEquals(1.0 - DistanceHelper.H_NOT_NULL, distNoRows("SELECT * FROM t"), DELTA); + } + + @Test + void noWhere_nonEmptyTable_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t", row("id", 1L)), DELTA); + } + + @Test + void noWhere_multipleRows_zeroDistance() { + assertEquals(0.0, + dist("SELECT * FROM t", row("id", 1L), row("id", 2L)), + DELTA); + } + + @Test + void where_emptyTable_highDistance() { + double d = distNoRows("SELECT * FROM t WHERE id = 1"); + // andAggregation(FALSE, FALSE) → ofTrue ≈ C → distance ≈ 1 - C + assertTrue(d > 0.5); + } + + // Numeric equality (=) + + @Test + void numericEquals_long_exactMatch() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col = 42", row("col", 42L)), DELTA); + } + + @Test + void numericEquals_integer_exactMatch() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col = 5", row("col", 5)), DELTA); + } + + @Test + void numericEquals_double_exactMatch() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col = 3.14", row("col", 3.14)), DELTA); + } + + @Test + void numericEquals_float_exactMatch() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col = 1", row("col", 1.0f)), DELTA); + } + + @Test + void numericEquals_noMatch_nonZero() { + double d = dist("SELECT * FROM t WHERE col = 42", row("col", 43L)); + assertTrue(d > 0.0 && d < 1.0); + } + + @Test + void numericEquals_closerValueGivesLowerDistance() { + double dClose = dist("SELECT * FROM t WHERE col = 10", row("col", 11L)); + double dFar = dist("SELECT * FROM t WHERE col = 10", row("col", 100L)); + assertTrue(dClose < dFar); + } + + // Numeric ordering (>, >=, <, <=) + + @Test + void numericGT_satisfied_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col > 5", row("col", 10L)), DELTA); + } + + @Test + void numericGT_boundary_notSatisfied() { + assertTrue(dist("SELECT * FROM t WHERE col > 10", row("col", 10L)) > 0.0); + } + + @Test + void numericGT_notSatisfied_nonZero() { + assertTrue(dist("SELECT * FROM t WHERE col > 10", row("col", 5L)) > 0.0); + } + + @Test + void numericGTE_boundary_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col >= 10", row("col", 10L)), DELTA); + } + + @Test + void numericGTE_notSatisfied_nonZero() { + assertTrue(dist("SELECT * FROM t WHERE col >= 10", row("col", 9L)) > 0.0); + } + + @Test + void numericLT_satisfied_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col < 10", row("col", 5L)), DELTA); + } + + @Test + void numericLT_boundary_notSatisfied() { + assertTrue(dist("SELECT * FROM t WHERE col < 10", row("col", 10L)) > 0.0); + } + + @Test + void numericLTE_boundary_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col <= 10", row("col", 10L)), DELTA); + } + + @Test + void numericLTE_notSatisfied_nonZero() { + assertTrue(dist("SELECT * FROM t WHERE col <= 10", row("col", 11L)) > 0.0); + } + + @Test + void numericOrdering_closerToThresholdGivesBetterScore() { + // Col must be > 100; row with 99 is closer than row with 50 + double dClose = dist("SELECT * FROM t WHERE col > 100", row("col", 99L)); + double dFar = dist("SELECT * FROM t WHERE col > 100", row("col", 50L)); + assertTrue(dClose < dFar); + } + + // String equality + + @Test + void stringEquals_exactMatch_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE s = 'hello'", row("s", "hello")), DELTA); + } + + @Test + void stringEquals_differentString_nonZero() { + double d = dist("SELECT * FROM t WHERE s = 'hello'", row("s", "world")); + assertTrue(d > 0.0 && d < 1.0); + } + + @Test + void stringEquals_prefixCloserThanUnrelated() { + double dClose = dist("SELECT * FROM t WHERE s = 'hello'", row("s", "hell")); + double dFar = dist("SELECT * FROM t WHERE s = 'hello'", row("s", "xyz")); + assertTrue(dClose < dFar); + } + + @Test + void stringEquals_emptyString_exactMatch() { + assertEquals(0.0, dist("SELECT * FROM t WHERE s = ''", row("s", "")), DELTA); + } + + // Boolean equality + + @Test + void booleanEquals_trueMatch_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE active = true", row("active", true)), DELTA); + } + + @Test + void booleanEquals_falseMatch_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE active = false", row("active", false)), DELTA); + } + + @Test + void booleanEquals_mismatch_nonZero() { + double d = dist("SELECT * FROM t WHERE active = true", row("active", false)); + assertTrue(d > 0.0 && d < 1.0); + } + + // UUID equality + + private static final UUID UUID_A = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); + private static final UUID UUID_B = UUID.fromString("550e8400-e29b-41d4-a716-446655440001"); + private static final UUID UUID_C = UUID.fromString("00000000-0000-0000-0000-000000000000"); + + @Test + void uuidEquals_sameUuid_zeroDistance() { + assertEquals(0.0, + dist("SELECT * FROM t WHERE id = 550e8400-e29b-41d4-a716-446655440000", + row("id", UUID_A)), + DELTA); + } + + @Test + void uuidEquals_differentUuid_nonZero() { + double d = dist("SELECT * FROM t WHERE id = 550e8400-e29b-41d4-a716-446655440000", + row("id", UUID_B)); + assertTrue(d > 0.0 && d < 1.0); + } + + @Test + void uuidEquals_veryDifferentUuid_higherDistanceThanCloseUuid() { + double dClose = dist("SELECT * FROM t WHERE id = 550e8400-e29b-41d4-a716-446655440000", + row("id", UUID_B)); // differs in one bit of the last byte + double dFar = dist("SELECT * FROM t WHERE id = 550e8400-e29b-41d4-a716-446655440000", + row("id", UUID_C)); // all-zero UUID — many bits differ + assertTrue(dClose < dFar); + } + + // AND operator + + @Test + void and_bothSatisfied_zeroDistance() { + assertEquals(0.0, + dist("SELECT * FROM t WHERE a = 1 AND b = 'x'", + row("a", 1L, "b", "x")), + DELTA); + } + + @Test + void and_neitherSatisfied_highDistance() { + double d = dist("SELECT * FROM t WHERE a = 1 AND b = 'x'", + row("a", 999L, "b", "zzz")); + assertTrue(d > 0.0); + } + + @Test + void and_oneSatisfied_betterThanNoneSatisfied() { + double dOne = dist("SELECT * FROM t WHERE a = 1 AND b = 'x'", + row("a", 1L, "b", "zzz")); + double dNone = dist("SELECT * FROM t WHERE a = 1 AND b = 'x'", + row("a", 999L, "b", "zzz")); + assertTrue(dOne < dNone); + } + + @Test + void and_threeConditions_allSatisfied() { + assertEquals(0.0, + dist("SELECT * FROM t WHERE a = 1 AND b = 2 AND c = 3", + row("a", 1L, "b", 2L, "c", 3L)), + DELTA); + } + + // IN operator + + @Test + void in_valueInList_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col IN (1, 2, 3)", row("col", 2L)), DELTA); + } + + @Test + void in_valueFirstInList_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col IN (1, 2, 3)", row("col", 1L)), DELTA); + } + + @Test + void in_valueLastInList_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col IN (1, 2, 3)", row("col", 3L)), DELTA); + } + + @Test + void in_valueNotInList_nonZero() { + assertTrue(dist("SELECT * FROM t WHERE col IN (10, 20)", row("col", 100L)) > 0.0); + } + + @Test + void in_closerCandidateGivesBetterScore() { + double dClose = dist("SELECT * FROM t WHERE col IN (10, 20)", row("col", 11L)); + double dFar = dist("SELECT * FROM t WHERE col IN (10, 20)", row("col", 100L)); + assertTrue(dClose < dFar); + } + + @Test + void in_singleElementList_match_zeroDistance() { + assertEquals(0.0, dist("SELECT * FROM t WHERE col IN (42)", row("col", 42L)), DELTA); + } + + // CONTAINS operator + + @Test + void contains_list_valuePresent_zeroDistance() { + assertEquals(0.0, + dist("SELECT * FROM t WHERE tags CONTAINS 2", + row("tags", Arrays.asList(1L, 2L, 3L))), + DELTA); + } + + @Test + void contains_list_valueMissing_nonZero() { + double d = dist("SELECT * FROM t WHERE tags CONTAINS 5", + row("tags", Arrays.asList(10L, 20L))); + assertTrue(d > 0.0); + } + + @Test + void contains_list_closerElementGivesBetterScore() { + double dClose = dist("SELECT * FROM t WHERE tags CONTAINS 10", + row("tags", Arrays.asList(11L))); + double dFar = dist("SELECT * FROM t WHERE tags CONTAINS 10", + row("tags", Arrays.asList(100L))); + assertTrue(dClose < dFar); + } + + @Test + void contains_set_valuePresent_zeroDistance() { + Set s = new LinkedHashSet<>(Arrays.asList(1L, 2L, 3L)); + assertEquals(0.0, dist("SELECT * FROM t WHERE col CONTAINS 2", row("col", s)), DELTA); + } + + @Test + void contains_map_valueInValues_zeroDistance() { + Map scores = new LinkedHashMap<>(); + scores.put("alice", 10L); + scores.put("bob", 20L); + assertEquals(0.0, + dist("SELECT * FROM t WHERE scores CONTAINS 10", row("scores", scores)), + DELTA); + } + + @Test + void contains_map_valueMissing_nonZero() { + Map scores = new LinkedHashMap<>(); + scores.put("alice", 10L); + double d = dist("SELECT * FROM t WHERE scores CONTAINS 99", row("scores", scores)); + assertTrue(d > 0.0); + } + + @Test + void contains_nullColumn_nonZeroDistance() { + assertTrue(dist("SELECT * FROM t WHERE col CONTAINS 1", row("col", null)) > 0.0); + } + + @Test + void contains_missingColumn_nonZeroDistance() { + assertTrue(dist("SELECT * FROM t WHERE col CONTAINS 1", row("other", 99L)) > 0.0); + } + + @Test + void contains_stringList_exactMatch_zeroDistance() { + assertEquals(0.0, + dist("SELECT * FROM t WHERE tags CONTAINS 'hello'", + row("tags", Arrays.asList("hello", "world"))), + DELTA); + } + + @Test + void contains_stringList_closerStringGivesBetterScore() { + double dClose = dist("SELECT * FROM t WHERE tags CONTAINS 'hello'", + row("tags", Arrays.asList("hell"))); + double dFar = dist("SELECT * FROM t WHERE tags CONTAINS 'hello'", + row("tags", Arrays.asList("xyz"))); + assertTrue(dClose < dFar); + } + + // CONTAINS KEY operator + + @Test + void containsKey_keyExists_zeroDistance() { + Map m = new LinkedHashMap<>(); + m.put("alice", 10L); + assertEquals(0.0, + dist("SELECT * FROM t WHERE meta CONTAINS KEY 'alice'", row("meta", m)), + DELTA); + } + + @Test + void containsKey_keyAbsent_nonZero() { + Map m = new LinkedHashMap<>(); + m.put("alice", 10L); + double d = dist("SELECT * FROM t WHERE meta CONTAINS KEY 'bob'", row("meta", m)); + assertTrue(d > 0.0); + } + + @Test + void containsKey_closerKeyGivesBetterScore() { + Map m = new LinkedHashMap<>(); + m.put("bob", 10L); // close to target "alice" (different string but not totally different) + double dClose = dist("SELECT * FROM t WHERE meta CONTAINS KEY 'alice'", row("meta", m)); + + Map m2 = new LinkedHashMap<>(); + m2.put("zzzzz", 10L); + double dFar = dist("SELECT * FROM t WHERE meta CONTAINS KEY 'alice'", row("meta", m2)); + + // "bob" vs "alice" vs "zzzzz" vs "alice" — both are non-matches but distances differ + // We only assert that both are > 0 (ordering may vary depending on leftAlignmentDistance) + assertTrue(dClose > 0.0); + assertTrue(dFar > 0.0); + } + + @Test + void containsKey_nullColumn_nonZeroDistance() { + assertTrue(dist("SELECT * FROM t WHERE col CONTAINS KEY 'x'", row("col", null)) > 0.0); + } + + @Test + void containsKey_nonMapColumn_nonZeroDistance() { + assertTrue(dist("SELECT * FROM t WHERE col CONTAINS KEY 'x'", + row("col", Arrays.asList("a", "b"))) > 0.0); + } + + // NULL handling + + @Test + void nullRowValue_returnsHighDistance() { + double d = dist("SELECT * FROM t WHERE col = 1", row("col", null)); + assertTrue(d > 0.0); + } + + @Test + void missingColumn_returnsHighDistance() { + double d = dist("SELECT * FROM t WHERE col = 1", row("other", 99L)); + assertTrue(d > 0.0); + } + + @Test + void nullCell_notWorseThanMissingColumn() { + // NULL cell → FALSE_TRUTHNESS_BETTER (C_BETTER=0.15 > C=0.1) + // Missing column → FALSE_TRUTHNESS (C=0.1) + // So NULL cell ofTrue > missing column ofTrue → distance(null) < distance(missing) + double dNull = dist("SELECT * FROM t WHERE col = 1", row("col", null)); + double dMissing = dist("SELECT * FROM t WHERE col = 1", row("other", 99L)); + assertTrue(dNull <= dMissing); + } + + @Test + void bothNullQueryAndRow_falseDistance() { + // Both NULL → FALSE_TRUTHNESS, not zero distance + double d = dist("SELECT * FROM t WHERE col = NULL", row("col", null)); + assertTrue(d > 0.0); + } + + // Multi-row scenarios + + @Test + void multiRow_oneMatchingRow_zeroDistance() { + assertEquals(0.0, + dist("SELECT * FROM t WHERE col = 5", + row("col", 1L), row("col", 5L), row("col", 10L)), + DELTA); + } + + @Test + void multiRow_noMatch_bestRowDrivesScore() { + double dWithClose = dist("SELECT * FROM t WHERE col = 10", + row("col", 11L), row("col", 100L)); + double dAllFar = dist("SELECT * FROM t WHERE col = 10", + row("col", 99L), row("col", 100L)); + assertTrue(dWithClose < dAllFar); + } + + @Test + void multiRow_earlyExitOnMatch() { + // Matching row is first — should short-circuit and not evaluate the rest + assertEquals(0.0, + dist("SELECT * FROM t WHERE col = 1", + row("col", 1L), row("col", 999L), row("col", 999L)), + DELTA); + } + + // Quoted column names + + @Test + void quotedColumnName_match_zeroDistance() { + // Parser preserves quotes; normalizeColumnName strips them and lowercases + assertEquals(0.0, + dist("SELECT * FROM t WHERE \"myCol\" = 42", row("mycol", 42L)), + DELTA); + } + + @Test + void quotedColumnName_mismatch_nonZero() { + double d = dist("SELECT * FROM t WHERE \"myCol\" = 42", row("mycol", 99L)); + assertTrue(d > 0.0); + } + + // Temporal types — date (LocalDate) + + @Test + void dateEquals_exactMatch_zeroDistance() { + LocalDate d = LocalDate.of(2023, 1, 15); + assertEquals(0.0, + dist("SELECT * FROM t WHERE d = '2023-01-15'", row("d", d)), + DELTA); + } + + @Test + void dateEquals_differentDay_nonZero() { + LocalDate d = LocalDate.of(2023, 1, 16); + double distance = dist("SELECT * FROM t WHERE d = '2023-01-15'", row("d", d)); + assertTrue(distance > 0.0 && distance < 1.0); + } + + @Test + void dateLT_satisfied_zeroDistance() { + LocalDate d = LocalDate.of(2022, 12, 31); + assertEquals(0.0, + dist("SELECT * FROM t WHERE d < '2023-01-01'", row("d", d)), + DELTA); + } + + @Test + void dateGT_satisfied_zeroDistance() { + LocalDate d = LocalDate.of(2023, 6, 1); + assertEquals(0.0, + dist("SELECT * FROM t WHERE d > '2023-01-01'", row("d", d)), + DELTA); + } + + @Test + void dateGTE_boundary_zeroDistance() { + LocalDate d = LocalDate.of(2023, 1, 1); + assertEquals(0.0, + dist("SELECT * FROM t WHERE d >= '2023-01-01'", row("d", d)), + DELTA); + } + + @Test + void dateLTE_boundary_zeroDistance() { + LocalDate d = LocalDate.of(2023, 1, 1); + assertEquals(0.0, + dist("SELECT * FROM t WHERE d <= '2023-01-01'", row("d", d)), + DELTA); + } + + @Test + void date_closerDayGivesBetterScore() { + double dClose = dist("SELECT * FROM t WHERE d = '2023-01-15'", + row("d", LocalDate.of(2023, 1, 16))); // 1 day away + double dFar = dist("SELECT * FROM t WHERE d = '2023-01-15'", + row("d", LocalDate.of(2023, 6, 1))); // months away + assertTrue(dClose < dFar); + } + + // Temporal types — time (LocalTime) + + @Test + void timeEquals_exactMatch_zeroDistance() { + LocalTime t = LocalTime.of(14, 30, 0); + assertEquals(0.0, + dist("SELECT * FROM t WHERE t = '14:30:00'", row("t", t)), + DELTA); + } + + @Test + void timeGT_satisfied_zeroDistance() { + LocalTime t = LocalTime.of(15, 0, 0); + assertEquals(0.0, + dist("SELECT * FROM t WHERE t > '14:30:00'", row("t", t)), + DELTA); + } + + @Test + void timeLT_notSatisfied_nonZero() { + LocalTime t = LocalTime.of(15, 0, 0); + double d = dist("SELECT * FROM t WHERE t < '14:30:00'", row("t", t)); + assertTrue(d > 0.0); + } + + // Temporal types — timestamp (Instant) + + @Test + void timestampEquals_exactMatch_zeroDistance() { + Instant ts = Instant.parse("2023-01-15T14:30:00Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2023-01-15T14:30:00Z'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_differentTime_nonZero() { + Instant ts = Instant.parse("2023-01-15T15:00:00Z"); + double d = dist("SELECT * FROM t WHERE ts = '2023-01-15T14:30:00Z'", row("ts", ts)); + assertTrue(d > 0.0 && d < 1.0); + } + + @Test + void timestampGTE_boundary_zeroDistance() { + Instant ts = Instant.parse("2023-01-15T00:00:00Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts >= '2023-01-15T00:00:00Z'", row("ts", ts)), + DELTA); + } + + @Test + void timestamp_closerTimeGivesBetterScore() { + Instant target = Instant.parse("2023-01-15T12:00:00Z"); + double dClose = dist("SELECT * FROM t WHERE ts = '2023-01-15T12:00:00Z'", + row("ts", Instant.parse("2023-01-15T12:01:00Z"))); // 1 minute away + double dFar = dist("SELECT * FROM t WHERE ts = '2023-01-15T12:00:00Z'", + row("ts", Instant.parse("2023-01-16T12:00:00Z"))); // 1 day away + assertTrue(dClose < dFar); + } +} \ No newline at end of file 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 ef172d9590..4df33ec882 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 @@ -319,4 +319,15 @@ public static Truthness getEqualityTruthness(UUID left, UUID right) { ); } + public static Truthness getStringEqualityTruthness(String a, String b) { + Objects.requireNonNull(a); + Objects.requireNonNull(b); + if (a.equals(b)) { + return new Truthness(1.0d, DistanceHelper.H_NOT_NULL); + } + long dist = DistanceHelper.getLeftAlignmentDistance(a, b); + double ofTrue = DistanceHelper.heuristicFromScaledDistanceWithBase(DistanceHelper.H_NOT_NULL, (double) dist); + return new Truthness(ofTrue, 1.0d); + } + } From 6b6ad91fef15a8c8286fb9ebcd115500e1870c33 Mon Sep 17 00:00:00 2001 From: Gonzalo Tomas Guerrero Date: Tue, 30 Jun 2026 17:10:41 -0300 Subject: [PATCH 2/6] Fix comparison of time-related values --- .../CassandraHeuristicsCalculator.java | 56 ++++++------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java index 96522ae85a..0573e1aa1a 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java @@ -10,7 +10,9 @@ import java.net.InetAddress; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZoneOffset; import java.util.*; public class CassandraHeuristicsCalculator { @@ -204,12 +206,16 @@ private Truthness compareTemporal(Object rowVal, Object queryVal, ComparisonType private static long toLong(Object value, Object rowValueHint) { if (rowValueHint instanceof LocalDate) { - if (value instanceof String) return LocalDate.parse((String) value).toEpochDay(); - return ((LocalDate) value).toEpochDay(); + LocalDate d = (value instanceof String) + ? LocalDate.parse((String) value) + : (LocalDate) value; + return d.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli(); } if (rowValueHint instanceof LocalTime) { - if (value instanceof String) return LocalTime.parse((String) value).toNanoOfDay(); - return ((LocalTime) value).toNanoOfDay(); + LocalTime t = (value instanceof String) + ? LocalTime.parse((String) value) + : (LocalTime) value; + return LocalDateTime.of(LocalDate.of(1970, 1, 1), t).toInstant(ZoneOffset.UTC).toEpochMilli(); } if (rowValueHint instanceof Instant) { if (value instanceof String) return Instant.parse((String) value).toEpochMilli(); @@ -257,15 +263,14 @@ private Truthness compareDuration(Object rowVal, Object queryVal, ComparisonType private Truthness calculateDistanceForIn(InOperation op, Map row) { Object rowValue = getRowValue(row, op.getColumnName()); if (rowValue == MISSING) rowValue = null; - return computeEqualsAny(rowValue, op.getValues()); + return any(rowValue, op.getValues()); } private Truthness calculateDistanceForContains(ContainsOperation op, Map row) { Object rawCol = getRowValue(row, op.getColumnName()); if (rawCol == MISSING || rawCol == null) return FALSE_TRUTHNESS; - // Elements are the authoritative side — pass them first so compareByType dispatches on their type. - return computeEqualsAnyElements(toElementList(rawCol), op.getValue()); + return any(op.getValue(), toElementList(rawCol)); } private Truthness calculateDistanceForContainsKey(ContainsKeyOperation op, @@ -274,40 +279,15 @@ private Truthness calculateDistanceForContainsKey(ContainsKeyOperation op, if (rawCol == MISSING || rawCol == null) return FALSE_TRUTHNESS; if (!(rawCol instanceof Map)) return FALSE_TRUTHNESS; List keys = new ArrayList<>(((Map) rawCol).keySet()); - // Keys are the authoritative side — pass them first so compareByType dispatches on their type. - return computeEqualsAnyElements(keys, op.getValue()); + return any(op.getValue(), keys); } - /** - * Used by IN: value is the row column (authoritative type), candidates are the query IN-list values. - * compareByType dispatches on value. - */ - private Truthness computeEqualsAny(Object value, List candidates) { + private Truthness any(Object value, List candidates) { if (candidates.isEmpty()) return FALSE_TRUTHNESS; - - double maxOfTrue = 0.0; - for (Object candidate : candidates) { - Truthness t = evaluateEquals(value, candidate); - if (t.isTrue()) return TRUE_TRUTHNESS; - if (t.getOfTrue() > maxOfTrue) maxOfTrue = t.getOfTrue(); - } - return new Truthness(maxOfTrue, 1.0); - } - - /** - * Used by CONTAINS / CONTAINS KEY: elements are the collection elements (authoritative type), - * queryValue is what we search for. compareByType dispatches on the element. - */ - private Truthness computeEqualsAnyElements(List elements, Object queryValue) { - if (elements.isEmpty()) return FALSE_TRUTHNESS; - - double maxOfTrue = 0.0; - for (Object element : elements) { - Truthness t = evaluateEquals(element, queryValue); - if (t.isTrue()) return TRUE_TRUTHNESS; - if (t.getOfTrue() > maxOfTrue) maxOfTrue = t.getOfTrue(); - } - return new Truthness(maxOfTrue, 1.0); + Truthness[] truthnesses = candidates.stream() + .map(candidate -> evaluateEquals(value, candidate)) + .toArray(Truthness[]::new); + return TruthnessUtils.buildOrAggregationTruthness(truthnesses); } private Truthness evaluateEquals(Object a, Object b) { From 059e9a5d2ea5fd167359a9500ebf389240cc45e4 Mon Sep 17 00:00:00 2001 From: Gonzalo Tomas Guerrero Date: Tue, 30 Jun 2026 17:33:54 -0300 Subject: [PATCH 3/6] Improve test class coverage --- client-java/controller/pom.xml | 5 + .../CassandraHeuristicsCalculatorTest.java | 102 ++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/client-java/controller/pom.xml b/client-java/controller/pom.xml index 738d11e897..3ba75d47be 100644 --- a/client-java/controller/pom.xml +++ b/client-java/controller/pom.xml @@ -193,6 +193,11 @@ libthrift test + + org.apache.cassandra + java-driver-core + test + org.mongodb bson diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java index 32f877e971..ee932abd16 100644 --- a/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java @@ -3,6 +3,8 @@ import org.evomaster.client.java.distance.heuristics.DistanceHelper; import org.junit.jupiter.api.Test; +import com.datastax.oss.driver.api.core.data.CqlDuration; +import java.net.InetAddress; import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; @@ -219,6 +221,68 @@ void uuidEquals_veryDifferentUuid_higherDistanceThanCloseUuid() { assertTrue(dClose < dFar); } + // Duration equality (CqlDuration) + + @Test + void durationEquals_exactMatch_zeroDistance() { + // 1y3d4h = months:12, days:3, nanos:4h in nanoseconds + CqlDuration dur = CqlDuration.newInstance(12, 3, 14_400_000_000_000L); + assertEquals(0.0, + dist("SELECT * FROM t WHERE d = 1y3d4h", row("d", dur)), + DELTA); + } + + @Test + void durationEquals_differentMonths_nonZero() { + CqlDuration dur = CqlDuration.newInstance(1, 0, 0); // 1mo + double d = dist("SELECT * FROM t WHERE d = 2mo", row("d", dur)); + assertTrue(d > 0.0 && d < 1.0); + } + + @Test + void durationEquals_allComponentsDiffer_nonZero() { + CqlDuration dur = CqlDuration.newInstance(2, 1, 0); + double d = dist("SELECT * FROM t WHERE d = 1mo3d", row("d", dur)); + assertTrue(d > 0.0 && d < 1.0); + } + + @Test + void durationEquals_closerMonthsGivesBetterScore() { + CqlDuration close = CqlDuration.newInstance(1, 0, 0); // 1 month away from 2mo + CqlDuration far = CqlDuration.newInstance(10, 0, 0); // 8 months away from 2mo + double dClose = dist("SELECT * FROM t WHERE d = 2mo", row("d", close)); + double dFar = dist("SELECT * FROM t WHERE d = 2mo", row("d", far)); + assertTrue(dClose < dFar); + } + + // InetAddress equality + + @Test + void inetEquals_exactMatch_zeroDistance() throws Exception { + InetAddress addr = InetAddress.getByName("192.168.1.1"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ip = '192.168.1.1'", row("ip", addr)), + DELTA); + } + + @Test + void inetEquals_differentAddress_nonZero() throws Exception { + InetAddress addr = InetAddress.getByName("192.168.1.2"); + double d = dist("SELECT * FROM t WHERE ip = '192.168.1.1'", row("ip", addr)); + assertTrue(d > 0.0 && d < 1.0); + } + + @Test + void inetEquals_closerAddressGivesBetterScore() throws Exception { + // '192.168.1.1' vs '192.168.1.2' — one character differs + // '192.168.1.1' vs '10.0.0.1' — many characters differ + double dClose = dist("SELECT * FROM t WHERE ip = '192.168.1.1'", + row("ip", InetAddress.getByName("192.168.1.2"))); + double dFar = dist("SELECT * FROM t WHERE ip = '192.168.1.1'", + row("ip", InetAddress.getByName("10.0.0.1"))); + assertTrue(dClose < dFar); + } + // AND operator @Test @@ -287,6 +351,16 @@ void in_singleElementList_match_zeroDistance() { assertEquals(0.0, dist("SELECT * FROM t WHERE col IN (42)", row("col", 42L)), DELTA); } + @Test + void in_nullRowValue_nonZeroDistance() { + assertTrue(dist("SELECT * FROM t WHERE col IN (1, 2, 3)", row("col", null)) > 0.0); + } + + @Test + void in_missingColumn_nonZeroDistance() { + assertTrue(dist("SELECT * FROM t WHERE col IN (1, 2, 3)", row("other", 99L)) > 0.0); + } + // CONTAINS operator @Test @@ -568,6 +642,34 @@ void timeLT_notSatisfied_nonZero() { assertTrue(d > 0.0); } + @Test + void timeGTE_boundary_zeroDistance() { + LocalTime t = LocalTime.of(14, 30, 0); + assertEquals(0.0, + dist("SELECT * FROM t WHERE t >= '14:30:00'", row("t", t)), + DELTA); + } + + @Test + void timeGTE_notSatisfied_nonZero() { + LocalTime t = LocalTime.of(14, 0, 0); + assertTrue(dist("SELECT * FROM t WHERE t >= '14:30:00'", row("t", t)) > 0.0); + } + + @Test + void timeLTE_boundary_zeroDistance() { + LocalTime t = LocalTime.of(14, 30, 0); + assertEquals(0.0, + dist("SELECT * FROM t WHERE t <= '14:30:00'", row("t", t)), + DELTA); + } + + @Test + void timeLTE_notSatisfied_nonZero() { + LocalTime t = LocalTime.of(15, 0, 0); + assertTrue(dist("SELECT * FROM t WHERE t <= '14:30:00'", row("t", t)) > 0.0); + } + // Temporal types — timestamp (Instant) @Test From 8455eb6e810c4f5357c833def81ab9d496519284 Mon Sep 17 00:00:00 2001 From: Gonzalo Tomas Guerrero Date: Tue, 30 Jun 2026 18:58:28 -0300 Subject: [PATCH 4/6] Add support for integer constants in time-related types --- .../CassandraHeuristicsCalculator.java | 49 ++++++-- .../CassandraHeuristicsCalculatorTest.java | 117 ++++++++++++++++++ 2 files changed, 158 insertions(+), 8 deletions(-) diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java index 0573e1aa1a..16fffe12c3 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java @@ -12,7 +12,11 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; import java.util.*; public class CassandraHeuristicsCalculator { @@ -206,24 +210,53 @@ private Truthness compareTemporal(Object rowVal, Object queryVal, ComparisonType private static long toLong(Object value, Object rowValueHint) { if (rowValueHint instanceof LocalDate) { - LocalDate d = (value instanceof String) - ? LocalDate.parse((String) value) - : (LocalDate) value; + LocalDate d = (value instanceof Long) ? LocalDate.ofEpochDay((Long) value) + : (value instanceof String) ? LocalDate.parse((String) value) + : (LocalDate) value; return d.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli(); } if (rowValueHint instanceof LocalTime) { - LocalTime t = (value instanceof String) - ? LocalTime.parse((String) value) - : (LocalTime) value; + LocalTime t = (value instanceof Long) ? LocalTime.ofNanoOfDay((Long) value) + : (value instanceof String) ? LocalTime.parse((String) value) + : (LocalTime) value; return LocalDateTime.of(LocalDate.of(1970, 1, 1), t).toInstant(ZoneOffset.UTC).toEpochMilli(); } if (rowValueHint instanceof Instant) { - if (value instanceof String) return Instant.parse((String) value).toEpochMilli(); - return ((Instant) value).toEpochMilli(); + if (value instanceof Long) return (Long) value; + if (value instanceof Instant) return ((Instant) value).toEpochMilli(); + if (value instanceof String) return parseTimestampString((String) value).toEpochMilli(); + throw new IllegalArgumentException("Unexpected timestamp value type: " + value.getClass()); } throw new IllegalArgumentException("Unrecognized temporal type: " + rowValueHint.getClass()); } + private static final DateTimeFormatter[] TIMESTAMP_FORMATTERS = { + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mmXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mmXX"), + }; + + private static final DateTimeFormatter DATE_WITH_OFFSET = DateTimeFormatter.ofPattern("yyyy-MM-ddXX"); + + private static Instant parseTimestampString(String s) { + try { return Instant.parse(s); } catch (DateTimeParseException ignored) {} + for (DateTimeFormatter formatter : TIMESTAMP_FORMATTERS) { + try { return OffsetDateTime.parse(s, formatter).toInstant(); } catch (DateTimeParseException ignored) {} + } + // date-only with offset ("2011-02-03+0000"): OffsetDateTime.parse fails without a time + // component, so extract LocalDate and ZoneOffset from the TemporalAccessor directly. + try { + TemporalAccessor accessor = DATE_WITH_OFFSET.parse(s); + return LocalDate.from(accessor).atStartOfDay(ZoneOffset.from(accessor)).toInstant(); + } catch (DateTimeParseException ignored) {} + // date-only without offset ("2011-02-03"): treat as midnight UTC + try { return LocalDate.parse(s).atStartOfDay(ZoneOffset.UTC).toInstant(); } catch (DateTimeParseException ignored) {} + throw new IllegalArgumentException("Cannot parse timestamp string: " + s); + } + private static boolean isCqlDuration(Object v) { return v.getClass().getName() .equals("com.datastax.oss.driver.api.core.data.CqlDuration"); diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java index ee932abd16..099b0a1a15 100644 --- a/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculatorTest.java @@ -569,6 +569,21 @@ void dateEquals_exactMatch_zeroDistance() { DELTA); } + @Test + void dateEquals_integerDaysSinceEpoch_zeroDistance() { + LocalDate d = LocalDate.of(2023, 1, 15); + assertEquals(0.0, + dist("SELECT * FROM t WHERE d = " + d.toEpochDay(), row("d", d)), + DELTA); + } + + @Test + void dateEquals_integerDaysSinceEpoch_nonZero() { + LocalDate d = LocalDate.of(2023, 1, 16); + double distance = dist("SELECT * FROM t WHERE d = " + LocalDate.of(2023, 1, 15).toEpochDay(), row("d", d)); + assertTrue(distance > 0.0 && distance < 1.0); + } + @Test void dateEquals_differentDay_nonZero() { LocalDate d = LocalDate.of(2023, 1, 16); @@ -619,6 +634,21 @@ void date_closerDayGivesBetterScore() { // Temporal types — time (LocalTime) + @Test + void timeEquals_integerNanosSinceMidnight_zeroDistance() { + LocalTime t = LocalTime.of(14, 30, 0); + assertEquals(0.0, + dist("SELECT * FROM t WHERE t = " + t.toNanoOfDay(), row("t", t)), + DELTA); + } + + @Test + void timeEquals_integerNanosSinceMidnight_nonZero() { + LocalTime t = LocalTime.of(15, 0, 0); + double d = dist("SELECT * FROM t WHERE t = " + LocalTime.of(14, 30, 0).toNanoOfDay(), row("t", t)); + assertTrue(d > 0.0 && d < 1.0); + } + @Test void timeEquals_exactMatch_zeroDistance() { LocalTime t = LocalTime.of(14, 30, 0); @@ -704,4 +734,91 @@ void timestamp_closerTimeGivesBetterScore() { row("ts", Instant.parse("2023-01-16T12:00:00Z"))); // 1 day away assertTrue(dClose < dFar); } + + // Temporal types — timestamp additional constant formats + + @Test + void timestampEquals_integerEpochMs_zeroDistance() { + Instant ts = Instant.ofEpochMilli(1_299_038_700_000L); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = 1299038700000", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_integerEpochMs_nonZero() { + Instant ts = Instant.ofEpochMilli(1_299_038_700_000L + 3_600_000L); // 1 hour later + assertTrue(dist("SELECT * FROM t WHERE ts = 1299038700000", row("ts", ts)) > 0.0); + } + + @Test + void timestampEquals_spaceSeparatorNoSeconds_zeroDistance() { + Instant ts = Instant.parse("2011-02-03T04:05:00Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2011-02-03 04:05+0000'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_spaceSeparatorWithSeconds_zeroDistance() { + Instant ts = Instant.parse("2011-02-03T04:05:00Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2011-02-03 04:05:00+0000'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_spaceSeparatorWithMillis_zeroDistance() { + Instant ts = Instant.parse("2011-02-03T04:05:00.123Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2011-02-03 04:05:00.123+0000'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_tSeparatorNoSeconds_zeroDistance() { + Instant ts = Instant.parse("2011-02-03T04:05:00Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2011-02-03T04:05+0000'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_tSeparatorWithSeconds_zeroDistance() { + Instant ts = Instant.parse("2011-02-03T04:05:00Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2011-02-03T04:05:00+0000'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_tSeparatorWithMillis_zeroDistance() { + Instant ts = Instant.parse("2011-02-03T04:05:00.123Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2011-02-03T04:05:00.123+0000'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_dateOnly_zeroDistance() { + Instant ts = Instant.parse("2011-02-03T00:00:00Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2011-02-03'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_dateOnlyWithOffset_zeroDistance() { + Instant ts = Instant.parse("2011-02-03T00:00:00Z"); + assertEquals(0.0, + dist("SELECT * FROM t WHERE ts = '2011-02-03+0000'", row("ts", ts)), + DELTA); + } + + @Test + void timestampEquals_spaceSeparator_differentTime_nonZero() { + Instant ts = Instant.parse("2011-02-03T05:00:00Z"); + double d = dist("SELECT * FROM t WHERE ts = '2011-02-03 04:05:00+0000'", row("ts", ts)); + assertTrue(d > 0.0 && d < 1.0); + } } \ No newline at end of file From a7964945f9c3743f2520a29c25d821bb120400ac Mon Sep 17 00:00:00 2001 From: Gonzalo Tomas Guerrero Date: Wed, 1 Jul 2026 17:52:47 -0300 Subject: [PATCH 5/6] Created CassandraOperationEvaluator --- .../CassandraHeuristicsCalculator.java | 298 +---------------- .../CassandraOperationEvaluator.java | 311 ++++++++++++++++++ 2 files changed, 318 insertions(+), 291 deletions(-) create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraOperationEvaluator.java diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java index 16fffe12c3..3093600495 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraHeuristicsCalculator.java @@ -5,32 +5,19 @@ import org.evomaster.client.java.distance.heuristics.DistanceHelper; import org.evomaster.client.java.distance.heuristics.Truthness; import org.evomaster.client.java.distance.heuristics.TruthnessUtils; -import org.evomaster.client.java.utils.SimpleLogger; -import java.net.InetAddress; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.time.temporal.TemporalAccessor; import java.util.*; public class CassandraHeuristicsCalculator { - private static final double C = DistanceHelper.H_NOT_NULL; - private static final double C_BETTER = 0.15; + public static final double C = DistanceHelper.H_NOT_NULL; + public static final double C_BETTER = 0.15; - private static final Truthness TRUE_TRUTHNESS = new Truthness(1.0, C); - private static final Truthness FALSE_TRUTHNESS = new Truthness(C, 1.0); - private static final Truthness FALSE_TRUTHNESS_BETTER = new Truthness(C_BETTER, 1.0); + public static final Truthness TRUE_TRUTHNESS = new Truthness(1.0, C); + public static final Truthness FALSE_TRUTHNESS = new Truthness(C, 1.0); + public static final Truthness FALSE_TRUTHNESS_BETTER = new Truthness(C_BETTER, 1.0); - private static final Object MISSING = new Object(); - - private enum ComparisonType { EQUALS, GT, GTE, LT, LTE } + private final CassandraOperationEvaluator evaluator = new CassandraOperationEvaluator(); public double computeDistance(String cqlQuery, Iterable> allRows) { return 1.0 - computeHQuery(cqlQuery, allRows).getOfTrue(); @@ -69,7 +56,7 @@ private Truthness computeHCondition(CqlQueryOperation condition, double maxOfTrue = 0.0; for (Map row : rows) { - double ofTrue = calculateDistance(condition, row).getOfTrue(); + double ofTrue = evaluator.evaluate(condition, row).getOfTrue(); if (ofTrue >= 1.0) { return TRUE_TRUTHNESS; } @@ -81,277 +68,6 @@ private Truthness computeHCondition(CqlQueryOperation condition, return TruthnessUtils.buildScaledTruthness(C, maxOfTrue); } - private Truthness calculateDistance(CqlQueryOperation op, Map row) { - if (op instanceof AndOperation) - return calculateDistanceForAnd((AndOperation) op, row); - if (op instanceof EqualsOperation) - return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.EQUALS); - if (op instanceof GreaterThanOperation) - return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.GT); - if (op instanceof GreaterThanEqualsOperation) - return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.GTE); - if (op instanceof LessThanOperation) - return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.LT); - if (op instanceof LessThanEqualsOperation) - return calculateDistanceForComparison((ComparisonOperation) op, row, ComparisonType.LTE); - if (op instanceof InOperation) - return calculateDistanceForIn((InOperation) op, row); - if (op instanceof ContainsOperation) - return calculateDistanceForContains((ContainsOperation) op, row); - if (op instanceof ContainsKeyOperation) - return calculateDistanceForContainsKey((ContainsKeyOperation) op, row); - - return FALSE_TRUTHNESS; - } - - private Truthness calculateDistanceForAnd(AndOperation op, Map row) { - List results = new ArrayList<>(); - for (CqlQueryOperation condition : op.getConditions()) { - results.add(calculateDistance(condition, row)); - } - return TruthnessUtils.buildAndAggregationTruthness(results.toArray(new Truthness[0])); - } - - private Truthness calculateDistanceForComparison( - ComparisonOperation op, - Map row, - ComparisonType type) { - - Object rowValue = getRowValue(row, op.getColumnName()); - Object queryValue = op.getValue(); - - if (rowValue == MISSING) rowValue = null; - - if (rowValue == null && queryValue == null) return FALSE_TRUTHNESS; - if (rowValue == null || queryValue == null) return FALSE_TRUTHNESS_BETTER; - - Truthness typeResult = compareByType(rowValue, queryValue, type); - if (typeResult.isTrue()) { - return typeResult; - } - return TruthnessUtils.buildScaledTruthness(C_BETTER, typeResult.getOfTrue()); - } - - private Truthness compareByType(Object rowVal, Object queryVal, ComparisonType type) { - if (rowVal instanceof Number && queryVal instanceof Number) - return compareNumeric(rowVal, queryVal, type); - if (rowVal instanceof String || rowVal instanceof InetAddress) - return compareString(rowVal, queryVal, type); - if (rowVal instanceof Boolean) - return compareBoolean(rowVal, queryVal, type); - if (rowVal instanceof UUID) - return compareUuid(rowVal, queryVal, type); - if (isTemporalType(rowVal)) - return compareTemporal(rowVal, queryVal, type); - if (isCqlDuration(rowVal)) - return compareDuration(rowVal, queryVal, type); - - SimpleLogger.uniqueWarn("CassandraHeuristicsCalculator: unsupported type " - + rowVal.getClass().getName() + " — returning FALSE_TRUTHNESS"); - return FALSE_TRUTHNESS; - } - - private Truthness compareNumeric(Object rowVal, Object queryVal, ComparisonType type) { - double x = ((Number) rowVal).doubleValue(); - double y = ((Number) queryVal).doubleValue(); - switch (type) { - case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); - case GT: return TruthnessUtils.getLessThanTruthness(y, x); - case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); - case LT: return TruthnessUtils.getLessThanTruthness(x, y); - case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); - default: return FALSE_TRUTHNESS; - } - } - - private Truthness compareString(Object rowVal, Object queryVal, ComparisonType type) { - if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; - String a = (rowVal instanceof InetAddress) - ? ((InetAddress) rowVal).getHostAddress() - : (String) rowVal; - String b = (String) queryVal; - return TruthnessUtils.getStringEqualityTruthness(a, b); - } - - private Truthness compareBoolean(Object rowVal, Object queryVal, ComparisonType type) { - if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; - double x = ((Boolean) rowVal) ? 1.0 : 0.0; - double y = ((Boolean) queryVal) ? 1.0 : 0.0; - return TruthnessUtils.getEqualityTruthness(x, y); - } - - private Truthness compareUuid(Object rowVal, Object queryVal, ComparisonType type) { - if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; - return TruthnessUtils.getEqualityTruthness((UUID) rowVal, (UUID) queryVal); - } - - private static boolean isTemporalType(Object v) { - return v instanceof LocalDate - || v instanceof LocalTime - || v instanceof Instant; - } - - private Truthness compareTemporal(Object rowVal, Object queryVal, ComparisonType type) { - try { - double x = (double) toLong(rowVal, rowVal); - double y = (double) toLong(queryVal, rowVal); - switch (type) { - case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); - case GT: return TruthnessUtils.getLessThanTruthness(y, x); - case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); - case LT: return TruthnessUtils.getLessThanTruthness(x, y); - case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); - default: return FALSE_TRUTHNESS; - } - } catch (Exception e) { - return FALSE_TRUTHNESS; - } - } - - private static long toLong(Object value, Object rowValueHint) { - if (rowValueHint instanceof LocalDate) { - LocalDate d = (value instanceof Long) ? LocalDate.ofEpochDay((Long) value) - : (value instanceof String) ? LocalDate.parse((String) value) - : (LocalDate) value; - return d.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli(); - } - if (rowValueHint instanceof LocalTime) { - LocalTime t = (value instanceof Long) ? LocalTime.ofNanoOfDay((Long) value) - : (value instanceof String) ? LocalTime.parse((String) value) - : (LocalTime) value; - return LocalDateTime.of(LocalDate.of(1970, 1, 1), t).toInstant(ZoneOffset.UTC).toEpochMilli(); - } - if (rowValueHint instanceof Instant) { - if (value instanceof Long) return (Long) value; - if (value instanceof Instant) return ((Instant) value).toEpochMilli(); - if (value instanceof String) return parseTimestampString((String) value).toEpochMilli(); - throw new IllegalArgumentException("Unexpected timestamp value type: " + value.getClass()); - } - throw new IllegalArgumentException("Unrecognized temporal type: " + rowValueHint.getClass()); - } - - private static final DateTimeFormatter[] TIMESTAMP_FORMATTERS = { - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSXX"), - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssXX"), - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mmXX"), - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXX"), - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX"), - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mmXX"), - }; - - private static final DateTimeFormatter DATE_WITH_OFFSET = DateTimeFormatter.ofPattern("yyyy-MM-ddXX"); - - private static Instant parseTimestampString(String s) { - try { return Instant.parse(s); } catch (DateTimeParseException ignored) {} - for (DateTimeFormatter formatter : TIMESTAMP_FORMATTERS) { - try { return OffsetDateTime.parse(s, formatter).toInstant(); } catch (DateTimeParseException ignored) {} - } - // date-only with offset ("2011-02-03+0000"): OffsetDateTime.parse fails without a time - // component, so extract LocalDate and ZoneOffset from the TemporalAccessor directly. - try { - TemporalAccessor accessor = DATE_WITH_OFFSET.parse(s); - return LocalDate.from(accessor).atStartOfDay(ZoneOffset.from(accessor)).toInstant(); - } catch (DateTimeParseException ignored) {} - // date-only without offset ("2011-02-03"): treat as midnight UTC - try { return LocalDate.parse(s).atStartOfDay(ZoneOffset.UTC).toInstant(); } catch (DateTimeParseException ignored) {} - throw new IllegalArgumentException("Cannot parse timestamp string: " + s); - } - - private static boolean isCqlDuration(Object v) { - return v.getClass().getName() - .equals("com.datastax.oss.driver.api.core.data.CqlDuration"); - } - - private static int getDurationMonths(Object d) throws Exception { - return (int) d.getClass().getMethod("getMonths").invoke(d); - } - - private static int getDurationDays(Object d) throws Exception { - return (int) d.getClass().getMethod("getDays").invoke(d); - } - - private static long getDurationNanos(Object d) throws Exception { - return (long) d.getClass().getMethod("getNanoseconds").invoke(d); - } - - private Truthness compareDuration(Object rowVal, Object queryVal, ComparisonType type) { - if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; - try { - int rm = getDurationMonths(rowVal); - int rd = getDurationDays(rowVal); - long rn = getDurationNanos(rowVal); - - CqlDurationLiteral q = CqlDurationLiteral.parse((String) queryVal); - - return TruthnessUtils.buildAndAggregationTruthness( - TruthnessUtils.getEqualityTruthness((long) rm, (long) q.months), - TruthnessUtils.getEqualityTruthness((long) rd, (long) q.days), - TruthnessUtils.getEqualityTruthness(rn, q.nanos) - ); - } catch (Exception e) { - return FALSE_TRUTHNESS; - } - } - - private Truthness calculateDistanceForIn(InOperation op, Map row) { - Object rowValue = getRowValue(row, op.getColumnName()); - if (rowValue == MISSING) rowValue = null; - return any(rowValue, op.getValues()); - } - - private Truthness calculateDistanceForContains(ContainsOperation op, - Map row) { - Object rawCol = getRowValue(row, op.getColumnName()); - if (rawCol == MISSING || rawCol == null) return FALSE_TRUTHNESS; - return any(op.getValue(), toElementList(rawCol)); - } - - private Truthness calculateDistanceForContainsKey(ContainsKeyOperation op, - Map row) { - Object rawCol = getRowValue(row, op.getColumnName()); - if (rawCol == MISSING || rawCol == null) return FALSE_TRUTHNESS; - if (!(rawCol instanceof Map)) return FALSE_TRUTHNESS; - List keys = new ArrayList<>(((Map) rawCol).keySet()); - return any(op.getValue(), keys); - } - - private Truthness any(Object value, List candidates) { - if (candidates.isEmpty()) return FALSE_TRUTHNESS; - Truthness[] truthnesses = candidates.stream() - .map(candidate -> evaluateEquals(value, candidate)) - .toArray(Truthness[]::new); - return TruthnessUtils.buildOrAggregationTruthness(truthnesses); - } - - private Truthness evaluateEquals(Object a, Object b) { - if (a == null && b == null) return FALSE_TRUTHNESS; - if (a == null || b == null) return FALSE_TRUTHNESS_BETTER; - Truthness raw = compareByType(a, b, ComparisonType.EQUALS); - if (raw.isTrue()) return raw; - return TruthnessUtils.buildScaledTruthness(C_BETTER, raw.getOfTrue()); - } - - private static List toElementList(Object collection) { - if (collection instanceof List) return (List) collection; - if (collection instanceof Set) return new ArrayList<>((Set) collection); - if (collection instanceof Map) return new ArrayList<>(((Map) collection).values()); - return Collections.emptyList(); - } - - private static String normalizeColumnName(String name) { - if (name == null) return null; - if (name.startsWith("\"") && name.endsWith("\"")) { - return name.substring(1, name.length() - 1).toLowerCase(); - } - return name.toLowerCase(); - } - - private Object getRowValue(Map row, String rawColumnName) { - String key = normalizeColumnName(rawColumnName); - if (!row.containsKey(key)) return MISSING; - return row.get(key); - } - private static List> toList(Iterable> iterable) { if (iterable instanceof List) { return (List>) iterable; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraOperationEvaluator.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraOperationEvaluator.java new file mode 100644 index 0000000000..e088437018 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraOperationEvaluator.java @@ -0,0 +1,311 @@ +package org.evomaster.client.java.controller.cassandra; + +import org.evomaster.client.java.controller.cassandra.operations.*; +import org.evomaster.client.java.distance.heuristics.Truthness; +import org.evomaster.client.java.distance.heuristics.TruthnessUtils; +import org.evomaster.client.java.utils.SimpleLogger; + +import java.net.InetAddress; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; +import java.util.*; + +import static org.evomaster.client.java.controller.cassandra.CassandraHeuristicsCalculator.*; + +public class CassandraOperationEvaluator { + + private static final Object MISSING = new Object(); + + private enum ComparisonType { EQUALS, GT, GTE, LT, LTE } + + public Truthness evaluate(CqlQueryOperation op, Map row) { + if (op instanceof AndOperation) + return evaluateAnd((AndOperation) op, row); + if (op instanceof EqualsOperation) + return evaluateEquals((EqualsOperation) op, row); + if (op instanceof GreaterThanOperation) + return evaluateGreaterThan((GreaterThanOperation) op, row); + if (op instanceof GreaterThanEqualsOperation) + return evaluateGreaterThanEquals((GreaterThanEqualsOperation) op, row); + if (op instanceof LessThanOperation) + return evaluateLessThan((LessThanOperation) op, row); + if (op instanceof LessThanEqualsOperation) + return evaluateLessThanEquals((LessThanEqualsOperation) op, row); + if (op instanceof InOperation) + return evaluateIn((InOperation) op, row); + if (op instanceof ContainsOperation) + return evaluateContains((ContainsOperation) op, row); + if (op instanceof ContainsKeyOperation) + return evaluateContainsKey((ContainsKeyOperation) op, row); + return FALSE_TRUTHNESS; + } + + private Truthness evaluateAnd(AndOperation op, Map row) { + List results = new ArrayList<>(); + for (CqlQueryOperation condition : op.getConditions()) { + results.add(evaluate(condition, row)); + } + return TruthnessUtils.buildAndAggregationTruthness(results.toArray(new Truthness[0])); + } + + private Truthness evaluateEquals(EqualsOperation op, Map row) { + return evaluateComparison(op, row, ComparisonType.EQUALS); + } + + private Truthness evaluateGreaterThan(GreaterThanOperation op, Map row) { + return evaluateComparison(op, row, ComparisonType.GT); + } + + private Truthness evaluateGreaterThanEquals(GreaterThanEqualsOperation op, Map row) { + return evaluateComparison(op, row, ComparisonType.GTE); + } + + private Truthness evaluateLessThan(LessThanOperation op, Map row) { + return evaluateComparison(op, row, ComparisonType.LT); + } + + private Truthness evaluateLessThanEquals(LessThanEqualsOperation op, Map row) { + return evaluateComparison(op, row, ComparisonType.LTE); + } + + private Truthness evaluateIn(InOperation op, Map row) { + Object rowValue = getRowValue(row, op.getColumnName()); + if (rowValue == MISSING) rowValue = null; + return any(rowValue, op.getValues()); + } + + private Truthness evaluateContains(ContainsOperation op, Map row) { + Object rawCol = getRowValue(row, op.getColumnName()); + if (rawCol == MISSING || rawCol == null) return FALSE_TRUTHNESS; + return any(op.getValue(), toElementList(rawCol)); + } + + private Truthness evaluateContainsKey(ContainsKeyOperation op, Map row) { + Object rawCol = getRowValue(row, op.getColumnName()); + if (rawCol == MISSING || rawCol == null) return FALSE_TRUTHNESS; + if (!(rawCol instanceof Map)) return FALSE_TRUTHNESS; + List keys = new ArrayList<>(((Map) rawCol).keySet()); + return any(op.getValue(), keys); + } + + private Truthness evaluateComparison(ComparisonOperation op, Map row, ComparisonType type) { + Object rowValue = getRowValue(row, op.getColumnName()); + Object queryValue = op.getValue(); + + if (rowValue == MISSING) rowValue = null; + + if (rowValue == null && queryValue == null) return FALSE_TRUTHNESS; + if (rowValue == null || queryValue == null) return FALSE_TRUTHNESS_BETTER; + + Truthness typeResult = compareByType(rowValue, queryValue, type); + if (typeResult.isTrue()) { + return typeResult; + } + return TruthnessUtils.buildScaledTruthness(C_BETTER, typeResult.getOfTrue()); + } + + private Truthness compareByType(Object rowVal, Object queryVal, ComparisonType type) { + if (rowVal instanceof Number && queryVal instanceof Number) + return compareNumeric(rowVal, queryVal, type); + if (rowVal instanceof String || rowVal instanceof InetAddress) + return compareString(rowVal, queryVal, type); + if (rowVal instanceof Boolean) + return compareBoolean(rowVal, queryVal, type); + if (rowVal instanceof UUID) + return compareUuid(rowVal, queryVal, type); + if (isTemporalType(rowVal)) + return compareTemporal(rowVal, queryVal, type); + if (isCqlDuration(rowVal)) + return compareDuration(rowVal, queryVal, type); + + SimpleLogger.uniqueWarn("CassandraHeuristicsCalculator: unsupported type " + + rowVal.getClass().getName() + " — returning FALSE_TRUTHNESS"); + return FALSE_TRUTHNESS; + } + + private Truthness compareNumeric(Object rowVal, Object queryVal, ComparisonType type) { + double x = ((Number) rowVal).doubleValue(); + double y = ((Number) queryVal).doubleValue(); + switch (type) { + case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); + case GT: return TruthnessUtils.getLessThanTruthness(y, x); + case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); + case LT: return TruthnessUtils.getLessThanTruthness(x, y); + case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); + default: return FALSE_TRUTHNESS; + } + } + + private Truthness compareString(Object rowVal, Object queryVal, ComparisonType type) { + if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; + String a = (rowVal instanceof InetAddress) + ? ((InetAddress) rowVal).getHostAddress() + : (String) rowVal; + String b = (String) queryVal; + return TruthnessUtils.getStringEqualityTruthness(a, b); + } + + private Truthness compareBoolean(Object rowVal, Object queryVal, ComparisonType type) { + if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; + double x = ((Boolean) rowVal) ? 1.0 : 0.0; + double y = ((Boolean) queryVal) ? 1.0 : 0.0; + return TruthnessUtils.getEqualityTruthness(x, y); + } + + private Truthness compareUuid(Object rowVal, Object queryVal, ComparisonType type) { + if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; + return TruthnessUtils.getEqualityTruthness((UUID) rowVal, (UUID) queryVal); + } + + private static boolean isTemporalType(Object v) { + return v instanceof LocalDate + || v instanceof LocalTime + || v instanceof Instant; + } + + private Truthness compareTemporal(Object rowVal, Object queryVal, ComparisonType type) { + try { + double x = (double) toLong(rowVal, rowVal); + double y = (double) toLong(queryVal, rowVal); + switch (type) { + case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); + case GT: return TruthnessUtils.getLessThanTruthness(y, x); + case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); + case LT: return TruthnessUtils.getLessThanTruthness(x, y); + case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); + default: return FALSE_TRUTHNESS; + } + } catch (Exception e) { + return FALSE_TRUTHNESS; + } + } + + private static long toLong(Object value, Object rowValueHint) { + if (rowValueHint instanceof LocalDate) { + LocalDate d = (value instanceof Long) ? LocalDate.ofEpochDay((Long) value) + : (value instanceof String) ? LocalDate.parse((String) value) + : (LocalDate) value; + return d.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli(); + } + if (rowValueHint instanceof LocalTime) { + LocalTime t = (value instanceof Long) ? LocalTime.ofNanoOfDay((Long) value) + : (value instanceof String) ? LocalTime.parse((String) value) + : (LocalTime) value; + return LocalDateTime.of(LocalDate.of(1970, 1, 1), t).toInstant(ZoneOffset.UTC).toEpochMilli(); + } + if (rowValueHint instanceof Instant) { + if (value instanceof Long) return (Long) value; + if (value instanceof Instant) return ((Instant) value).toEpochMilli(); + if (value instanceof String) return parseTimestampString((String) value).toEpochMilli(); + throw new IllegalArgumentException("Unexpected timestamp value type: " + value.getClass()); + } + throw new IllegalArgumentException("Unrecognized temporal type: " + rowValueHint.getClass()); + } + + private static final DateTimeFormatter[] TIMESTAMP_FORMATTERS = { + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mmXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mmXX"), + }; + + private static final DateTimeFormatter DATE_WITH_OFFSET = DateTimeFormatter.ofPattern("yyyy-MM-ddXX"); + + private static Instant parseTimestampString(String s) { + try { return Instant.parse(s); } catch (DateTimeParseException ignored) {} + for (DateTimeFormatter formatter : TIMESTAMP_FORMATTERS) { + try { return OffsetDateTime.parse(s, formatter).toInstant(); } catch (DateTimeParseException ignored) {} + } + // date-only with offset ("2011-02-03+0000"): OffsetDateTime.parse fails without a time + // component, so extract LocalDate and ZoneOffset from the TemporalAccessor directly. + try { + TemporalAccessor accessor = DATE_WITH_OFFSET.parse(s); + return LocalDate.from(accessor).atStartOfDay(ZoneOffset.from(accessor)).toInstant(); + } catch (DateTimeParseException ignored) {} + // date-only without offset ("2011-02-03"): treat as midnight UTC + try { return LocalDate.parse(s).atStartOfDay(ZoneOffset.UTC).toInstant(); } catch (DateTimeParseException ignored) {} + throw new IllegalArgumentException("Cannot parse timestamp string: " + s); + } + + private static boolean isCqlDuration(Object v) { + return v.getClass().getName() + .equals("com.datastax.oss.driver.api.core.data.CqlDuration"); + } + + private static int getDurationMonths(Object d) throws Exception { + return (int) d.getClass().getMethod("getMonths").invoke(d); + } + + private static int getDurationDays(Object d) throws Exception { + return (int) d.getClass().getMethod("getDays").invoke(d); + } + + private static long getDurationNanos(Object d) throws Exception { + return (long) d.getClass().getMethod("getNanoseconds").invoke(d); + } + + private Truthness compareDuration(Object rowVal, Object queryVal, ComparisonType type) { + if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; + try { + int rm = getDurationMonths(rowVal); + int rd = getDurationDays(rowVal); + long rn = getDurationNanos(rowVal); + + CqlDurationLiteral q = CqlDurationLiteral.parse((String) queryVal); + + return TruthnessUtils.buildAndAggregationTruthness( + TruthnessUtils.getEqualityTruthness((long) rm, (long) q.months), + TruthnessUtils.getEqualityTruthness((long) rd, (long) q.days), + TruthnessUtils.getEqualityTruthness(rn, q.nanos) + ); + } catch (Exception e) { + return FALSE_TRUTHNESS; + } + } + + private Truthness any(Object value, List candidates) { + if (candidates.isEmpty()) return FALSE_TRUTHNESS; + Truthness[] truthnesses = candidates.stream() + .map(candidate -> evaluateEquals(value, candidate)) + .toArray(Truthness[]::new); + return TruthnessUtils.buildOrAggregationTruthness(truthnesses); + } + + private Truthness evaluateEquals(Object a, Object b) { + if (a == null && b == null) return FALSE_TRUTHNESS; + if (a == null || b == null) return FALSE_TRUTHNESS_BETTER; + Truthness raw = compareByType(a, b, ComparisonType.EQUALS); + if (raw.isTrue()) return raw; + return TruthnessUtils.buildScaledTruthness(C_BETTER, raw.getOfTrue()); + } + + private static List toElementList(Object collection) { + if (collection instanceof List) return (List) collection; + if (collection instanceof Set) return new ArrayList<>((Set) collection); + if (collection instanceof Map) return new ArrayList<>(((Map) collection).values()); + return Collections.emptyList(); + } + + private static String normalizeColumnName(String name) { + if (name == null) return null; + if (name.startsWith("\"") && name.endsWith("\"")) { + return name.substring(1, name.length() - 1).toLowerCase(); + } + return name.toLowerCase(); + } + + private Object getRowValue(Map row, String rawColumnName) { + String key = normalizeColumnName(rawColumnName); + if (!row.containsKey(key)) return MISSING; + return row.get(key); + } +} \ No newline at end of file From bcf43e2330a597f0c8cad219f6f6959692862e84 Mon Sep 17 00:00:00 2001 From: Gonzalo Tomas Guerrero Date: Wed, 1 Jul 2026 18:26:40 -0300 Subject: [PATCH 6/6] Minor refactorings --- .../CassandraOperationEvaluator.java | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraOperationEvaluator.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraOperationEvaluator.java index e088437018..f60d0c419b 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraOperationEvaluator.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/cassandra/CassandraOperationEvaluator.java @@ -133,14 +133,7 @@ private Truthness compareByType(Object rowVal, Object queryVal, ComparisonType t private Truthness compareNumeric(Object rowVal, Object queryVal, ComparisonType type) { double x = ((Number) rowVal).doubleValue(); double y = ((Number) queryVal).doubleValue(); - switch (type) { - case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); - case GT: return TruthnessUtils.getLessThanTruthness(y, x); - case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); - case LT: return TruthnessUtils.getLessThanTruthness(x, y); - case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); - default: return FALSE_TRUTHNESS; - } + return getTruthness(type, x, y); } private Truthness compareString(Object rowVal, Object queryVal, ComparisonType type) { @@ -164,41 +157,45 @@ private Truthness compareUuid(Object rowVal, Object queryVal, ComparisonType typ return TruthnessUtils.getEqualityTruthness((UUID) rowVal, (UUID) queryVal); } - private static boolean isTemporalType(Object v) { - return v instanceof LocalDate - || v instanceof LocalTime - || v instanceof Instant; + private static boolean isTemporalType(Object value) { + return value instanceof LocalDate + || value instanceof LocalTime + || value instanceof Instant; } private Truthness compareTemporal(Object rowVal, Object queryVal, ComparisonType type) { try { double x = (double) toLong(rowVal, rowVal); double y = (double) toLong(queryVal, rowVal); - switch (type) { - case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); - case GT: return TruthnessUtils.getLessThanTruthness(y, x); - case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); - case LT: return TruthnessUtils.getLessThanTruthness(x, y); - case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); - default: return FALSE_TRUTHNESS; - } + return getTruthness(type, x, y); } catch (Exception e) { return FALSE_TRUTHNESS; } } + private Truthness getTruthness(ComparisonType type, double x, double y) { + switch (type) { + case EQUALS: return TruthnessUtils.getEqualityTruthness(x, y); + case GT: return TruthnessUtils.getLessThanTruthness(y, x); + case GTE: return TruthnessUtils.getLessThanTruthness(x, y).invert(); + case LT: return TruthnessUtils.getLessThanTruthness(x, y); + case LTE: return TruthnessUtils.getLessThanTruthness(y, x).invert(); + default: return FALSE_TRUTHNESS; + } + } + private static long toLong(Object value, Object rowValueHint) { if (rowValueHint instanceof LocalDate) { - LocalDate d = (value instanceof Long) ? LocalDate.ofEpochDay((Long) value) + LocalDate dateVal = (value instanceof Long) ? LocalDate.ofEpochDay((Long) value) : (value instanceof String) ? LocalDate.parse((String) value) : (LocalDate) value; - return d.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli(); + return dateVal.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli(); } if (rowValueHint instanceof LocalTime) { - LocalTime t = (value instanceof Long) ? LocalTime.ofNanoOfDay((Long) value) + LocalTime timeVal = (value instanceof Long) ? LocalTime.ofNanoOfDay((Long) value) : (value instanceof String) ? LocalTime.parse((String) value) : (LocalTime) value; - return LocalDateTime.of(LocalDate.of(1970, 1, 1), t).toInstant(ZoneOffset.UTC).toEpochMilli(); + return LocalDateTime.of(LocalDate.of(1970, 1, 1), timeVal).toInstant(ZoneOffset.UTC).toEpochMilli(); } if (rowValueHint instanceof Instant) { if (value instanceof Long) return (Long) value; @@ -236,36 +233,36 @@ private static Instant parseTimestampString(String s) { throw new IllegalArgumentException("Cannot parse timestamp string: " + s); } - private static boolean isCqlDuration(Object v) { - return v.getClass().getName() + private static boolean isCqlDuration(Object value) { + return value.getClass().getName() .equals("com.datastax.oss.driver.api.core.data.CqlDuration"); } - private static int getDurationMonths(Object d) throws Exception { - return (int) d.getClass().getMethod("getMonths").invoke(d); + private static int getDurationMonths(Object durationVal) throws Exception { + return (int) durationVal.getClass().getMethod("getMonths").invoke(durationVal); } - private static int getDurationDays(Object d) throws Exception { - return (int) d.getClass().getMethod("getDays").invoke(d); + private static int getDurationDays(Object durationVal) throws Exception { + return (int) durationVal.getClass().getMethod("getDays").invoke(durationVal); } - private static long getDurationNanos(Object d) throws Exception { - return (long) d.getClass().getMethod("getNanoseconds").invoke(d); + private static long getDurationNanos(Object durationVal) throws Exception { + return (long) durationVal.getClass().getMethod("getNanoseconds").invoke(durationVal); } private Truthness compareDuration(Object rowVal, Object queryVal, ComparisonType type) { if (type != ComparisonType.EQUALS) return FALSE_TRUTHNESS; try { - int rm = getDurationMonths(rowVal); - int rd = getDurationDays(rowVal); - long rn = getDurationNanos(rowVal); + int months = getDurationMonths(rowVal); + int days = getDurationDays(rowVal); + long nanos = getDurationNanos(rowVal); CqlDurationLiteral q = CqlDurationLiteral.parse((String) queryVal); return TruthnessUtils.buildAndAggregationTruthness( - TruthnessUtils.getEqualityTruthness((long) rm, (long) q.months), - TruthnessUtils.getEqualityTruthness((long) rd, (long) q.days), - TruthnessUtils.getEqualityTruthness(rn, q.nanos) + TruthnessUtils.getEqualityTruthness((long) months, (long) q.months), + TruthnessUtils.getEqualityTruthness((long) days, (long) q.days), + TruthnessUtils.getEqualityTruthness(nanos, q.nanos) ); } catch (Exception e) { return FALSE_TRUTHNESS;