Skip to content

Commit e1c387d

Browse files
authored
Support isDistinctFrom and isNotDistinctFrom operators (#3357)
This resolves #3504.
1 parent 5fc17e0 commit e1c387d

File tree

12 files changed

+879
-46
lines changed

12 files changed

+879
-46
lines changed

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/Comparisons.java

Lines changed: 63 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import com.apple.foundationdb.record.util.ProtoUtils;
7474
import com.apple.foundationdb.tuple.ByteArrayUtil;
7575
import com.apple.foundationdb.tuple.ByteArrayUtil2;
76+
import com.apple.foundationdb.tuple.Tuple;
7677
import com.google.auto.service.AutoService;
7778
import com.google.common.base.Suppliers;
7879
import com.google.common.base.Verify;
@@ -211,11 +212,9 @@ private static Comparable toComparable(@Nullable Object obj) {
211212
}
212213
}
213214

214-
@Nullable
215-
public static Object toClassWithRealEquals(@Nullable Object obj) {
216-
if (obj == null) {
217-
return null;
218-
} else if (obj instanceof ByteString) {
215+
@Nonnull
216+
public static Object toClassWithRealEquals(@Nonnull Object obj) {
217+
if (obj instanceof ByteString) {
219218
return obj;
220219
} else if (obj instanceof byte[]) {
221220
return ByteString.copyFrom((byte[])obj);
@@ -231,30 +230,30 @@ public static Object toClassWithRealEquals(@Nullable Object obj) {
231230
}
232231

233232
@SuppressWarnings("unchecked")
234-
public static int compare(@Nullable Object fieldValue, @Nullable Object comparand) {
235-
if (fieldValue == null) {
236-
if (comparand == null) {
237-
return 0;
238-
} else {
239-
return -1;
240-
}
241-
} else if (comparand == null) {
242-
return 1;
233+
public static int compare(@Nonnull Object fieldValue, @Nonnull Object comparand) {
234+
return toComparable(fieldValue).compareTo(toComparable(comparand));
235+
}
236+
237+
@SpotBugsSuppressWarnings("NP_BOOLEAN_RETURN_NULL")
238+
private static boolean compareEquals(@Nonnull Object value, @Nonnull Object comparand) {
239+
if (value instanceof Message) {
240+
return MessageHelpers.compareMessageEquals(value, comparand);
243241
} else {
244-
return toComparable(fieldValue).compareTo(toComparable(comparand));
242+
return toClassWithRealEquals(value).equals(toClassWithRealEquals(comparand));
245243
}
246244
}
247245

248-
@Nullable
249246
@SpotBugsSuppressWarnings("NP_BOOLEAN_RETURN_NULL")
250-
private static Boolean compareEquals(Object value, Object comparand) {
251-
if (value == null || comparand == null) {
252-
return null;
247+
private static boolean compareNotDistinctFrom(@Nullable Object value, @Nullable Object comparand) {
248+
if (value == null && comparand == null) {
249+
return true;
250+
} else if (value == null || comparand == null) {
251+
return false;
253252
} else {
254253
if (value instanceof Message) {
255254
return MessageHelpers.compareMessageEquals(value, comparand);
256255
} else {
257-
return toClassWithRealEquals(value).equals(toClassWithRealEquals(comparand));
256+
return toClassWithRealEquals(Objects.requireNonNull(value)).equals(toClassWithRealEquals(Objects.requireNonNull(comparand)));
258257
}
259258
}
260259
}
@@ -283,6 +282,9 @@ private static Boolean compareStartsWith(@Nullable Object value, @Nullable Objec
283282
@Nullable
284283
@SpotBugsSuppressWarnings("NP_BOOLEAN_RETURN_NULL")
285284
private static Boolean compareLike(@Nullable Object value, @Nullable Object pattern) {
285+
if (value == null) {
286+
return null;
287+
}
286288
if (!(value instanceof String)) {
287289
throw new RecordCoreException("Illegal comparand value type: " + value);
288290
}
@@ -311,7 +313,12 @@ private static Boolean compareListStartsWith(@Nullable Object value, @Nonnull Li
311313
if (i > list.size()) {
312314
return false;
313315
}
314-
if (!toClassWithRealEquals(comparand.get(i)).equals(toClassWithRealEquals(list.get(i)))) {
316+
if (comparand.get(i) == null && list.get(i) == null) {
317+
continue;
318+
}
319+
if (comparand.get(i) == null || list.get(i) == null) {
320+
return false;
321+
} else if (!toClassWithRealEquals(comparand.get(i)).equals(toClassWithRealEquals(list.get(i)))) {
315322
return false;
316323
}
317324
}
@@ -336,11 +343,12 @@ private static Boolean compareIn(@Nullable Object value, @Nullable Object compar
336343
return true;
337344
}
338345
} else {
339-
if (toClassWithRealEquals(value).equals(toClassWithRealEquals(comparandItem))) {
346+
if (comparandItem == null) {
347+
hasNull = true;
348+
} else if (toClassWithRealEquals(value).equals(toClassWithRealEquals(comparandItem))) {
340349
return true;
341350
}
342351
}
343-
hasNull |= comparandItem == null;
344352
}
345353
return hasNull ? null : false;
346354
} else {
@@ -632,7 +640,9 @@ public enum Type {
632640
@API(API.Status.EXPERIMENTAL)
633641
SORT(false),
634642
@API(API.Status.EXPERIMENTAL)
635-
LIKE;
643+
LIKE,
644+
IS_DISTINCT_FROM(false),
645+
NOT_DISTINCT_FROM(true);
636646

637647
@Nonnull
638648
private static final Supplier<BiMap<Type, PComparisonType>> protoEnumBiMapSupplier =
@@ -682,7 +692,7 @@ private static BiMap<Type, PComparisonType> getProtoEnumBiMap() {
682692
}
683693

684694
@Nullable
685-
public static Type invertComparisonType(@Nonnull final Comparisons.Type type) {
695+
public static Type invertComparisonType(@Nonnull final Type type) {
686696
if (type.isUnary()) {
687697
return null;
688698
}
@@ -705,28 +715,44 @@ public static Type invertComparisonType(@Nonnull final Comparisons.Type type) {
705715
@Nullable
706716
@SpotBugsSuppressWarnings("NP_BOOLEAN_RETURN_NULL")
707717
public static Boolean evalComparison(@Nonnull Type type, @Nullable Object value, @Nullable Object comparand) {
708-
if (value == null) {
709-
return null;
710-
}
711718
switch (type) {
712719
case STARTS_WITH:
713720
return compareStartsWith(value, comparand);
714721
case IN:
715722
return compareIn(value, comparand);
716723
case EQUALS:
724+
if (value == null || comparand == null) {
725+
return null;
726+
}
717727
return compareEquals(value, comparand);
718728
case NOT_EQUALS:
719-
if (comparand == null) {
729+
if (value == null || comparand == null) {
720730
return null;
721731
}
722732
return !compareEquals(value, comparand);
733+
case IS_DISTINCT_FROM:
734+
return !compareNotDistinctFrom(value, comparand);
735+
case NOT_DISTINCT_FROM:
736+
return compareNotDistinctFrom(value, comparand);
723737
case LESS_THAN:
738+
if (value == null || comparand == null) {
739+
return null;
740+
}
724741
return compare(value, comparand) < 0;
725742
case LESS_THAN_OR_EQUALS:
743+
if (value == null || comparand == null) {
744+
return null;
745+
}
726746
return compare(value, comparand) <= 0;
727747
case GREATER_THAN:
748+
if (value == null || comparand == null) {
749+
return null;
750+
}
728751
return compare(value, comparand) > 0;
729752
case GREATER_THAN_OR_EQUALS:
753+
if (value == null || comparand == null) {
754+
return null;
755+
}
730756
return compare(value, comparand) >= 0;
731757
case LIKE:
732758
return compareLike(value, comparand);
@@ -823,7 +849,7 @@ default Object getComparand() {
823849

824850
/**
825851
* Get whether the comparison is with the result of a multi-column key.
826-
* If so, {@link #getComparand} will return a {@link com.apple.foundationdb.tuple.Tuple}.
852+
* If so, {@link #getComparand} will return a {@link Tuple}.
827853
* @return {@code true} if the comparand is for multiple key columns
828854
*/
829855
default boolean hasMultiColumnComparand() {
@@ -1302,9 +1328,7 @@ public ConstrainedBoolean semanticEqualsTyped(@Nonnull final Comparison other, @
13021328
public Boolean eval(@Nullable FDBRecordStoreBase<?> store, @Nonnull EvaluationContext context, @Nullable Object value) {
13031329
// this is at evaluation time --> always use the context binding
13041330
final Object comparand = getComparand(store, context);
1305-
if (comparand == null) {
1306-
return null;
1307-
} else if (comparand == COMPARISON_SKIPPED_BINDING) {
1331+
if (comparand == COMPARISON_SKIPPED_BINDING) {
13081332
return Boolean.TRUE;
13091333
} else {
13101334
return evalComparison(type, value, comparand);
@@ -1620,9 +1644,7 @@ public ConstrainedBoolean semanticEqualsTyped(@Nonnull final Comparison other, @
16201644
public Boolean eval(@Nullable FDBRecordStoreBase<?> store, @Nonnull EvaluationContext context, @Nullable Object v) {
16211645
// this is at evaluation time --> always use the context binding
16221646
final Object comparand = getComparand(store, context);
1623-
if (comparand == null) {
1624-
return null;
1625-
} else if (comparand == COMPARISON_SKIPPED_BINDING) {
1647+
if (comparand == COMPARISON_SKIPPED_BINDING) {
16261648
return Boolean.TRUE;
16271649
} else {
16281650
return evalComparison(type, v, comparand);
@@ -1739,7 +1761,7 @@ public static class ListComparison implements Comparison {
17391761
@SuppressWarnings("rawtypes")
17401762
private final List comparand;
17411763
@Nullable
1742-
private final Descriptors.FieldDescriptor.JavaType javaType;
1764+
private final JavaType javaType;
17431765

17441766
@Nonnull
17451767
@SuppressWarnings("rawtypes")
@@ -1757,8 +1779,8 @@ public ListComparison(@Nonnull Type type, @Nonnull List comparand) {
17571779
default:
17581780
throw new RecordCoreException("ListComparison only supports EQUALS, NOT_EQUALS, STARTS_WITH and IN");
17591781
}
1760-
if (comparand == null || (this.type == Type.IN && comparand.stream().anyMatch(o -> o == null))) {
1761-
throw new NullPointerException("List comparand is null, or contains null");
1782+
if (this.type == Type.IN && comparand.stream().anyMatch(o -> o == null)) {
1783+
throw new NullPointerException("List comparand contains null");
17621784
}
17631785
if (comparand.isEmpty()) {
17641786
javaType = null;
@@ -1772,10 +1794,10 @@ public ListComparison(@Nonnull Type type, @Nonnull List comparand) {
17721794
}
17731795
}
17741796
this.comparand = comparand;
1775-
this.comparandListWithEqualsSupplier = Suppliers.memoize(() -> Lists.transform(comparand, Comparisons::toClassWithRealEquals));
1797+
this.comparandListWithEqualsSupplier = Suppliers.memoize(() -> Lists.transform(comparand, obj -> obj != null ? toClassWithRealEquals(obj) : null));
17761798
}
17771799

1778-
private static Descriptors.FieldDescriptor.JavaType getJavaType(@Nonnull Object o) {
1800+
private static JavaType getJavaType(@Nonnull Object o) {
17791801
if (o instanceof Boolean) {
17801802
return JavaType.BOOLEAN;
17811803
} else if (o instanceof ByteString || o instanceof byte[]) {

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/RangeConstraints.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,8 @@ private boolean canBeUsedInScanPrefix(@Nonnull final Comparisons.Comparison comp
763763
case STARTS_WITH:
764764
case NOT_NULL:
765765
case IS_NULL:
766+
case NOT_DISTINCT_FROM:
767+
case IS_DISTINCT_FROM:
766768
return true;
767769
case TEXT_CONTAINS_ALL:
768770
case TEXT_CONTAINS_ALL_WITHIN:

0 commit comments

Comments
 (0)