Skip to content

Values comparison #521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions table/src/main/java/tech/ydb/table/values/DecimalValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,85 @@ public ValueProtos.Value toPb() {
return ProtoValue.fromDecimal(high, low);
}

@Override
public int compareTo(Value<?> other) {
if (other == null) {
throw new IllegalArgumentException("Cannot compare with null value");
}

// Handle comparison with OptionalValue
if (other instanceof OptionalValue) {
OptionalValue otherOptional = (OptionalValue) other;

// Check that the item type matches this decimal type
if (!getType().equals(otherOptional.getType().getItemType())) {
throw new IllegalArgumentException(
"Cannot compare DecimalValue with OptionalValue of different item type: " +
getType() + " vs " + otherOptional.getType().getItemType());
}

// Non-empty value is greater than empty optional
if (!otherOptional.isPresent()) {
return 1;
}

// Compare with the wrapped value
return compareTo(otherOptional.get());
}

if (!(other instanceof DecimalValue)) {
throw new IllegalArgumentException("Cannot compare DecimalValue with " + other.getClass().getSimpleName());
}

DecimalValue otherDecimal = (DecimalValue) other;

// Handle special values first
if (isNan() || otherDecimal.isNan()) {
// NaN is not comparable, but we need to provide a consistent ordering
if (isNan() && otherDecimal.isNan()) {
return 0;
}
if (isNan()) {
return 1; // NaN is considered greater than any other value
}
return -1;
}

if (isInf() && otherDecimal.isInf()) {
return 0;
}
if (isInf()) {
return 1; // Positive infinity is greater than any finite value
}
if (otherDecimal.isInf()) {
return -1;
}

if (isNegativeInf() && otherDecimal.isNegativeInf()) {
return 0;
}
if (isNegativeInf()) {
return -1; // Negative infinity is less than any finite value
}
if (otherDecimal.isNegativeInf()) {
return 1;
}

// Compare finite values
if (isNegative() != otherDecimal.isNegative()) {
return isNegative() ? -1 : 1;
}

// Both have the same sign, compare magnitudes
int highComparison = Long.compareUnsigned(high, otherDecimal.high);
if (highComparison != 0) {
return isNegative() ? -highComparison : highComparison;
}

int lowComparison = Long.compareUnsigned(low, otherDecimal.low);
return isNegative() ? -lowComparison : lowComparison;
}

/**
* Write long to a big-endian buffer.
*/
Expand Down
108 changes: 108 additions & 0 deletions table/src/main/java/tech/ydb/table/values/DictValue.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package tech.ydb.table.values;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

Expand Down Expand Up @@ -116,4 +118,110 @@ public ValueProtos.Value toPb() {
}
return builder.build();
}

@Override
public int compareTo(Value<?> other) {
if (other == null) {
throw new IllegalArgumentException("Cannot compare with null value");
}

// Handle comparison with OptionalValue
if (other instanceof OptionalValue) {
OptionalValue otherOptional = (OptionalValue) other;

// Check that the item type matches this dict type
if (!getType().equals(otherOptional.getType().getItemType())) {
throw new IllegalArgumentException(
"Cannot compare DictValue with OptionalValue of different item type: " +
getType() + " vs " + otherOptional.getType().getItemType());
}

// Non-empty value is greater than empty optional
if (!otherOptional.isPresent()) {
return 1;
}

// Compare with the wrapped value
return compareTo(otherOptional.get());
}

if (!(other instanceof DictValue)) {
throw new IllegalArgumentException("Cannot compare DictValue with " + other.getClass().getSimpleName());
}

DictValue otherDict = (DictValue) other;

// Convert to sorted lists for lexicographical comparison
List<Map.Entry<Value<?>, Value<?>>> thisEntries = new ArrayList<>(items.entrySet());
List<Map.Entry<Value<?>, Value<?>>> otherEntries = new ArrayList<>(otherDict.items.entrySet());

// Sort entries by key first, then by value
thisEntries.sort((e1, e2) -> {
int keyComparison = compareValues(e1.getKey(), e2.getKey());
if (keyComparison != 0) {
return keyComparison;
}
return compareValues(e1.getValue(), e2.getValue());
});

otherEntries.sort((e1, e2) -> {
int keyComparison = compareValues(e1.getKey(), e2.getKey());
if (keyComparison != 0) {
return keyComparison;
}
return compareValues(e1.getValue(), e2.getValue());
});

// Compare sorted entries lexicographically
int minLength = Math.min(thisEntries.size(), otherEntries.size());
for (int i = 0; i < minLength; i++) {
Map.Entry<Value<?>, Value<?>> thisEntry = thisEntries.get(i);
Map.Entry<Value<?>, Value<?>> otherEntry = otherEntries.get(i);

int keyComparison = compareValues(thisEntry.getKey(), otherEntry.getKey());
if (keyComparison != 0) {
return keyComparison;
}

int valueComparison = compareValues(thisEntry.getValue(), otherEntry.getValue());
if (valueComparison != 0) {
return valueComparison;
}
}

// If we reach here, one dict is a prefix of the other
// The shorter dict comes first
return Integer.compare(thisEntries.size(), otherEntries.size());
}

private static int compareValues(Value<?> a, Value<?> b) {
// Handle null values
if (a == null && b == null) {
return 0;
}
if (a == null) {
return -1;
}
if (b == null) {
return 1;
}

// Check that the types are the same
if (!a.getType().equals(b.getType())) {
throw new IllegalArgumentException("Cannot compare values of different types: " +
a.getType() + " vs " + b.getType());
}

// Use the actual compareTo method of the values
if (a instanceof Comparable && b instanceof Comparable) {
try {
return ((Comparable<Value<?>>) a).compareTo((Value<?>) b);
} catch (ClassCastException e) {
// Fall back to error
}
}

throw new IllegalArgumentException("Cannot compare values of different types: " +
a.getClass().getSimpleName() + " vs " + b.getClass().getSimpleName());
}
}
80 changes: 80 additions & 0 deletions table/src/main/java/tech/ydb/table/values/ListValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,84 @@ public ValueProtos.Value toPb() {
}
return builder.build();
}

@Override
public int compareTo(Value<?> other) {
if (other == null) {
throw new IllegalArgumentException("Cannot compare with null value");
}

// Handle comparison with OptionalValue
if (other instanceof OptionalValue) {
OptionalValue otherOptional = (OptionalValue) other;

// Check that the item type matches this list type
if (!getType().equals(otherOptional.getType().getItemType())) {
throw new IllegalArgumentException(
"Cannot compare ListValue with OptionalValue of different item type: " +
getType() + " vs " + otherOptional.getType().getItemType());
}

// Non-empty value is greater than empty optional
if (!otherOptional.isPresent()) {
return 1;
}

// Compare with the wrapped value
return compareTo(otherOptional.get());
}

if (!(other instanceof ListValue)) {
throw new IllegalArgumentException("Cannot compare ListValue with " + other.getClass().getSimpleName());
}

ListValue otherList = (ListValue) other;

// Compare elements lexicographically
int minLength = Math.min(items.length, otherList.items.length);
for (int i = 0; i < minLength; i++) {
Value<?> thisItem = items[i];
Value<?> otherItem = otherList.items[i];

int itemComparison = compareValues(thisItem, otherItem);
if (itemComparison != 0) {
return itemComparison;
}
}

// If we reach here, one list is a prefix of the other
// The shorter list comes first
return Integer.compare(items.length, otherList.items.length);
}

private static int compareValues(Value<?> a, Value<?> b) {
// Handle null values
if (a == null && b == null) {
return 0;
}
if (a == null) {
return -1;
}
if (b == null) {
return 1;
}

// Check that the types are the same
if (!a.getType().equals(b.getType())) {
throw new IllegalArgumentException("Cannot compare values of different types: " +
a.getType() + " vs " + b.getType());
}

// Use the actual compareTo method of the values
if (a instanceof Comparable && b instanceof Comparable) {
try {
return ((Comparable<Value<?>>) a).compareTo((Value<?>) b);
} catch (ClassCastException e) {
// Fall back to error
}
}

throw new IllegalArgumentException("Cannot compare values of different types: " +
a.getClass().getSimpleName() + " vs " + b.getClass().getSimpleName());
}
}
34 changes: 34 additions & 0 deletions table/src/main/java/tech/ydb/table/values/NullValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,38 @@ public NullType getType() {
public ValueProtos.Value toPb() {
return ProtoValue.nullValue();
}

@Override
public int compareTo(Value<?> other) {
if (other == null) {
throw new IllegalArgumentException("Cannot compare with null value");
}

// Handle comparison with OptionalValue
if (other instanceof OptionalValue) {
OptionalValue otherOptional = (OptionalValue) other;

// Check that the item type matches this null type
if (!getType().equals(otherOptional.getType().getItemType())) {
throw new IllegalArgumentException(
"Cannot compare NullValue with OptionalValue of different item type: " +
getType() + " vs " + otherOptional.getType().getItemType());
}

// Non-empty value is greater than empty optional
if (!otherOptional.isPresent()) {
return 1;
}

// Compare with the wrapped value
return compareTo(otherOptional.get());
}

if (!(other instanceof NullValue)) {
throw new IllegalArgumentException("Cannot compare NullValue with " + other.getClass().getSimpleName());
}

// All NullValue instances are equal
return 0;
}
}
61 changes: 61 additions & 0 deletions table/src/main/java/tech/ydb/table/values/OptionalValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,65 @@ public ValueProtos.Value toPb() {

return ProtoValue.optional();
}

@Override
public int compareTo(Value<?> other) {
if (other == null) {
throw new IllegalArgumentException("Cannot compare with null value");
}

// Handle comparison with another OptionalValue
if (other instanceof OptionalValue) {
OptionalValue otherOptional = (OptionalValue) other;

// Check that the item types are the same
if (!type.getItemType().equals(otherOptional.type.getItemType())) {
throw new IllegalArgumentException("Cannot compare OptionalValue with different item types: " +
type.getItemType() + " vs " + otherOptional.type.getItemType());
}

// Handle empty values: empty values are considered less than non-empty values
if (value == null && otherOptional.value == null) {
return 0;
}
if (value == null) {
return -1;
}
if (otherOptional.value == null) {
return 1;
}

// Both values are non-null and have the same type, compare them using their compareTo method
return compareValues(value, otherOptional.value);
}

// Handle comparison with non-optional values of the same underlying type
if (type.getItemType().equals(other.getType())) {
// This OptionalValue is empty, so it's less than any non-optional value
if (value == null) {
return -1;
}

// This OptionalValue has a value, compare it with the non-optional value
return compareValues(value, other);
}

// Types are incompatible
throw new IllegalArgumentException("Cannot compare OptionalValue with incompatible type: " +
type.getItemType() + " vs " + other.getType());
}

private static int compareValues(Value<?> a, Value<?> b) {
// Since we've already verified the types are the same, we can safely cast
// and use the compareTo method of the actual value type
if (a instanceof Comparable && b instanceof Comparable) {
try {
return ((Comparable<Value<?>>) a).compareTo((Value<?>) b);
} catch (ClassCastException e) {
// Fall back to error
}
}
throw new IllegalArgumentException("Cannot compare values of different types: " +
a.getClass().getSimpleName() + " vs " + b.getClass().getSimpleName());
}
}
Loading
Loading