diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/Comparisons.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/Comparisons.java index 6b30c1f32b..ad76c585e7 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/Comparisons.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/expressions/Comparisons.java @@ -234,24 +234,24 @@ public static Object toClassWithRealEquals(@Nullable Object obj) { @SuppressWarnings("unchecked") public static int compare(@Nullable Object fieldValue, @Nullable Object comparand) { - if (fieldValue == null) { - if (comparand == null) { - return 0; - } else { - return -1; - } - } else if (comparand == null) { - return 1; + return toComparable(fieldValue).compareTo(toComparable(comparand)); + } + + @SpotBugsSuppressWarnings("NP_BOOLEAN_RETURN_NULL") + private static boolean compareEquals(@Nonnull Object value, @Nonnull Object comparand) { + if (value instanceof Message) { + return MessageHelpers.compareMessageEquals(value, comparand); } else { - return toComparable(fieldValue).compareTo(toComparable(comparand)); + return toClassWithRealEquals(value).equals(toClassWithRealEquals(comparand)); } } - @Nullable @SpotBugsSuppressWarnings("NP_BOOLEAN_RETURN_NULL") - private static Boolean compareEquals(Object value, Object comparand) { - if (value == null || comparand == null) { - return null; + private static boolean compareNotDistinctFrom(Object value, Object comparand) { + if (value == null && comparand == null) { + return true; + } else if (value == null || comparand == null) { + return false; } else { if (value instanceof Message) { return MessageHelpers.compareMessageEquals(value, comparand); @@ -285,6 +285,9 @@ private static Boolean compareStartsWith(@Nullable Object value, @Nullable Objec @Nullable @SpotBugsSuppressWarnings("NP_BOOLEAN_RETURN_NULL") private static Boolean compareLike(@Nullable Object value, @Nullable Object pattern) { + if (value == null) { + return null; + } if (!(value instanceof String)) { throw new RecordCoreException("Illegal comparand value type: " + value); } @@ -634,7 +637,9 @@ public enum Type { @API(API.Status.EXPERIMENTAL) SORT(false), @API(API.Status.EXPERIMENTAL) - LIKE; + LIKE, + IS_DISTINCT_FROM(true), + NOT_DISTINCT_FROM(false); @Nonnull private static final Supplier> protoEnumBiMapSupplier = @@ -707,28 +712,44 @@ public static Type invertComparisonType(@Nonnull final Comparisons.Type type) { @Nullable @SpotBugsSuppressWarnings("NP_BOOLEAN_RETURN_NULL") public static Boolean evalComparison(@Nonnull Type type, @Nullable Object value, @Nullable Object comparand) { - if (value == null) { - return null; - } switch (type) { case STARTS_WITH: return compareStartsWith(value, comparand); case IN: return compareIn(value, comparand); case EQUALS: + if (value == null || comparand == null) { + return null; + } return compareEquals(value, comparand); case NOT_EQUALS: - if (comparand == null) { + if (value == null || comparand == null) { return null; } return !compareEquals(value, comparand); + case IS_DISTINCT_FROM: + return !compareNotDistinctFrom(value, comparand); + case NOT_DISTINCT_FROM: + return compareNotDistinctFrom(value, comparand); case LESS_THAN: + if (value == null || comparand == null) { + return null; + } return compare(value, comparand) < 0; case LESS_THAN_OR_EQUALS: + if (value == null || comparand == null) { + return null; + } return compare(value, comparand) <= 0; case GREATER_THAN: + if (value == null || comparand == null) { + return null; + } return compare(value, comparand) > 0; case GREATER_THAN_OR_EQUALS: + if (value == null || comparand == null) { + return null; + } return compare(value, comparand) >= 0; case LIKE: return compareLike(value, comparand); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/RangeConstraints.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/RangeConstraints.java index f8b978d410..48ab55977b 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/RangeConstraints.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/RangeConstraints.java @@ -729,6 +729,8 @@ private boolean canBeUsedInScanPrefix(@Nonnull final Comparisons.Comparison comp case STARTS_WITH: case NOT_NULL: case IS_NULL: + case NOT_DISTINCT_FROM: + case IS_DISTINCT_FROM: return true; case TEXT_CONTAINS_ALL: case TEXT_CONTAINS_ALL_WITHIN: diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RelOpValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RelOpValue.java index 14fe6c43b9..9e6b9b7a9e 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RelOpValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RelOpValue.java @@ -184,6 +184,13 @@ private static Optional promoteOperandsAndCreatePredicate(@Nulla @Nonnull Value leftChild, @Nonnull Value rightChild, @Nonnull final Comparisons.Type comparisonType) { + if (leftChild.getResultType().getTypeCode() == Type.TypeCode.NULL && rightChild.getResultType().getTypeCode() == Type.TypeCode.NULL) { + if (comparisonType == Comparisons.Type.NOT_DISTINCT_FROM) { + return Optional.of(ConstantPredicate.TRUE); + } else if (comparisonType == Comparisons.Type.IS_DISTINCT_FROM) { + return Optional.of(ConstantPredicate.FALSE); + } + } // maximumType may return null, but only for non-primitive types which is not possible here final var maxtype = Verify.verifyNotNull(Type.maximumType(leftChild.getResultType(), rightChild.getResultType())); @@ -255,6 +262,8 @@ private static Comparisons.Type swapBinaryComparisonOperator(@Nonnull Comparison switch (type) { case EQUALS: case NOT_EQUALS: + case NOT_DISTINCT_FROM: + case IS_DISTINCT_FROM: return type; case LESS_THAN: return Comparisons.Type.GREATER_THAN; @@ -290,6 +299,20 @@ private static Value encapsulate(@Nonnull final String functionName, } else { final Typed arg1 = arguments.get(1); final Type res1 = arg1.getResultType(); + if ("isDistinctFrom".equals(functionName)) { + if (res0.getTypeCode() == Type.TypeCode.NULL && res1.getTypeCode() != Type.TypeCode.NULL) { + return encapsulate("notNull", Comparisons.Type.NOT_NULL, List.of(arg1)); + } else if (res1.getTypeCode() == Type.TypeCode.NULL && res0.getTypeCode() != Type.TypeCode.NULL) { + return encapsulate("notNull", Comparisons.Type.NOT_NULL, List.of(arg0)); + } + } + if ("notDistinctFrom".equals(functionName)) { + if (res0.getTypeCode() == Type.TypeCode.NULL && res1.getTypeCode() != Type.TypeCode.NULL) { + return encapsulate("isNull", Comparisons.Type.IS_NULL, List.of(arg1)); + } else if (res1.getTypeCode() == Type.TypeCode.NULL && res0.getTypeCode() != Type.TypeCode.NULL) { + return encapsulate("isNull", Comparisons.Type.IS_NULL, List.of(arg0)); + } + } SemanticException.check(res1.isPrimitive() || res1.isEnum() || res1.isUuid(), SemanticException.ErrorCode.COMPARAND_TO_COMPARISON_IS_OF_COMPLEX_TYPE); final BinaryPhysicalOperator physicalOperator = @@ -451,6 +474,36 @@ private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction } } + /** + * The {@code isDistinctFrom} function. + */ + @AutoService(BuiltInFunction.class) + public static class IsDistinctFromFn extends BuiltInFunction { + public IsDistinctFromFn() { + super("isDistinctFrom", + List.of(new Type.Any(), new Type.Any()), IsDistinctFromFn::encapsulate); + } + + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.IS_DISTINCT_FROM, arguments); + } + } + + /** + * The {@code notDistinctFrom} function. + */ + @AutoService(BuiltInFunction.class) + public static class NotDistinctFromFn extends BuiltInFunction { + public NotDistinctFromFn() { + super("notDistinctFrom", + List.of(new Type.Any(), new Type.Any()), NotDistinctFromFn::encapsulate); + } + + private static Value encapsulate(@Nonnull BuiltInFunction builtInFunction, @Nonnull final List arguments) { + return RelOpValue.encapsulate(builtInFunction.getFunctionName(), Comparisons.Type.NOT_DISTINCT_FROM, arguments); + } + } + private enum BinaryPhysicalOperator { // TODO think about equality epsilon for floating-point types. EQ_BU(Comparisons.Type.EQUALS, Type.TypeCode.BOOLEAN, Type.TypeCode.UNKNOWN, (l, r) -> null), @@ -764,6 +817,7 @@ private enum BinaryPhysicalOperator { GTE_UID(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.UNKNOWN, Type.TypeCode.UUID, (l, r) -> null), GTE_IDU(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.UUID, Type.TypeCode.UNKNOWN, (l, r) -> null), + EQ_BN(Comparisons.Type.EQUALS, Type.TypeCode.BOOLEAN, Type.TypeCode.NULL, (l, r) -> null), EQ_IN(Comparisons.Type.EQUALS, Type.TypeCode.INT, Type.TypeCode.NULL, (l, r) -> null), EQ_LN(Comparisons.Type.EQUALS, Type.TypeCode.LONG, Type.TypeCode.NULL, (l, r) -> null), @@ -894,6 +948,121 @@ private enum BinaryPhysicalOperator { GT_IDN(Comparisons.Type.GREATER_THAN, Type.TypeCode.UUID, Type.TypeCode.NULL, (l, r) -> null), GTE_NID(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.NULL, Type.TypeCode.UUID, (l, r) -> null), GTE_IDN(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.UUID, Type.TypeCode.NULL, (l, r) -> null), + + IS_DISTINCT_FROM_BU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.BOOLEAN, Type.TypeCode.UNKNOWN, (l, r) -> true), + IS_DISTINCT_FROM_BB(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.BOOLEAN, Type.TypeCode.BOOLEAN, (l, r) -> (boolean)l != (boolean)r), + IS_DISTINCT_FROM_IU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.UNKNOWN, (l, r) -> true), + IS_DISTINCT_FROM_II(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.INT, (l, r) -> (int)l != (int)r), + IS_DISTINCT_FROM_IL(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.LONG, (l, r) -> (int)l != (long)r), + IS_DISTINCT_FROM_IF(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.FLOAT, (l, r) -> (int)l != (float)r), + IS_DISTINCT_FROM_ID(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.DOUBLE, (l, r) -> (int)l != (double)r), + IS_DISTINCT_FROM_LU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.UNKNOWN, (l, r) -> true), + IS_DISTINCT_FROM_LI(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.INT, (l, r) -> (long)l != (int)r), + IS_DISTINCT_FROM_LL(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.LONG, (l, r) -> (long)l != (long)r), + IS_DISTINCT_FROM_LF(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.FLOAT, (l, r) -> (long)l != (float)r), + IS_DISTINCT_FROM_LD(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.DOUBLE, (l, r) -> (long)l != (double)r), + IS_DISTINCT_FROM_FU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.UNKNOWN, (l, r) -> true), + IS_DISTINCT_FROM_FI(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.INT, (l, r) -> (float)l != (int)r), + IS_DISTINCT_FROM_FL(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.LONG, (l, r) -> (float)l != (long)r), + IS_DISTINCT_FROM_FF(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.FLOAT, (l, r) -> (float)l != (float)r), + IS_DISTINCT_FROM_FD(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.DOUBLE, (l, r) -> (float)l != (double)r), + IS_DISTINCT_FROM_DU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.UNKNOWN, (l, r) -> true), + IS_DISTINCT_FROM_DI(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.INT, (l, r) -> (double)l != (int)r), + IS_DISTINCT_FROM_DL(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.LONG, (l, r) -> (double)l != (long)r), + IS_DISTINCT_FROM_DF(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.FLOAT, (l, r) -> (double)l != (float)r), + IS_DISTINCT_FROM_DD(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.DOUBLE, (l, r) -> (double)l != (double)r), + IS_DISTINCT_FROM_SU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.STRING, Type.TypeCode.UNKNOWN, (l, r) -> true), + IS_DISTINCT_FROM_SS(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.STRING, Type.TypeCode.STRING, (l, r) -> !l.equals(r)), // TODO: locale-aware comparison + IS_DISTINCT_FROM_UU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.UNKNOWN, (l, r) -> false), + IS_DISTINCT_FROM_UB(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.BOOLEAN, (l, r) -> true), + IS_DISTINCT_FROM_UI(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.INT, (l, r) -> true), + IS_DISTINCT_FROM_UL(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.LONG, (l, r) -> true), + IS_DISTINCT_FROM_UF(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.FLOAT, (l, r) -> true), + IS_DISTINCT_FROM_UD(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.DOUBLE, (l, r) -> true), + IS_DISTINCT_FROM_US(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.STRING, (l, r) -> true), + IS_DISTINCT_FROM_UV(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.VERSION, (l, r) -> true), + IS_DISTINCT_FROM_VU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.VERSION, Type.TypeCode.UNKNOWN, (l, r) -> null), + IS_DISTINCT_FROM_VV(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.VERSION, Type.TypeCode.VERSION, (l, r) -> !l.equals(r)), + IS_DISTINCT_FROM_BYU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.BYTES, Type.TypeCode.UNKNOWN, (l, r) -> null), + IS_DISTINCT_FROM_BYBY(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.BYTES, Type.TypeCode.BYTES, (l, r) -> Comparisons.evalComparison(Comparisons.Type.IS_DISTINCT_FROM, l, r)), + IS_DISTINCT_FROM_UBY(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.BYTES, (l, r) -> true), + IS_DISTINCT_FROM_EE(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.ENUM, Type.TypeCode.ENUM, (l, r) -> Comparisons.evalComparison(Comparisons.Type.IS_DISTINCT_FROM, l, r)), + IS_DISTINCT_FROM_ES(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.ENUM, Type.TypeCode.STRING, (l, r) -> { + final var otherValue = PromoteValue.PhysicalOperator.stringToEnumValue(((Descriptors.EnumValueDescriptor) l).getType(), (String) r); + return Comparisons.evalComparison(Comparisons.Type.IS_DISTINCT_FROM, l, otherValue); + }), + IS_DISTINCT_FROM_SE(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.STRING, Type.TypeCode.ENUM, (l, r) -> { + final var otherValue = PromoteValue.PhysicalOperator.stringToEnumValue(((Descriptors.EnumValueDescriptor) r).getType(), (String) l); + return Comparisons.evalComparison(Comparisons.Type.EQUALS, otherValue, r); + }), + IS_DISTINCT_FROM_EU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.ENUM, Type.TypeCode.UNKNOWN, (l, r) -> true), + IS_DISTINCT_FROM_UE(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.ENUM, (l, r) -> true), + IS_DISTINCT_FROM_IDID(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UUID, Type.TypeCode.UUID, (l, r) -> Comparisons.evalComparison(Comparisons.Type.IS_DISTINCT_FROM, l, r)), + IS_DISTINCT_FROM_IDS(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UUID, Type.TypeCode.STRING, (l, r) -> Comparisons.evalComparison(Comparisons.Type.IS_DISTINCT_FROM, l, PromoteValue.PhysicalOperator.stringToUuidValue((String) r))), + IS_DISTINCT_FROM_SID(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.STRING, Type.TypeCode.UUID, (l, r) -> Comparisons.evalComparison(Comparisons.Type.IS_DISTINCT_FROM, PromoteValue.PhysicalOperator.stringToUuidValue((String) l), r)), + IS_DISTINCT_FROM_UID(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.UUID, (l, r) -> true), + IS_DISTINCT_FROM_IDU(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.UUID, Type.TypeCode.UNKNOWN, (l, r) -> true), + IS_DISTINCT_FROM_NN(Comparisons.Type.IS_DISTINCT_FROM, Type.TypeCode.NULL, Type.TypeCode.NULL, (l, r) -> false), + + NOT_DISTINCT_FROM_BU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.BOOLEAN, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_BB(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.BOOLEAN, Type.TypeCode.BOOLEAN, (l, r) -> (boolean)l == (boolean)r), + NOT_DISTINCT_FROM_IU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_II(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.INT, (l, r) -> (int)l == (int)r), + NOT_DISTINCT_FROM_IL(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.LONG, (l, r) -> (int)l == (long)r), + NOT_DISTINCT_FROM_IF(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.FLOAT, (l, r) -> (int)l == (float)r), + NOT_DISTINCT_FROM_ID(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.INT, Type.TypeCode.DOUBLE, (l, r) -> (int)l == (double)r), + NOT_DISTINCT_FROM_LU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_LI(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.INT, (l, r) -> (long)l == (int)r), + NOT_DISTINCT_FROM_LL(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.LONG, (l, r) -> (long)l == (long)r), + NOT_DISTINCT_FROM_LF(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.FLOAT, (l, r) -> (long)l == (float)r), + NOT_DISTINCT_FROM_LD(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.LONG, Type.TypeCode.DOUBLE, (l, r) -> (long)l == (double)r), + NOT_DISTINCT_FROM_FU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_FI(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.INT, (l, r) -> (float)l == (int)r), + NOT_DISTINCT_FROM_FL(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.LONG, (l, r) -> (float)l == (long)r), + NOT_DISTINCT_FROM_FF(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.FLOAT, (l, r) -> (float)l == (float)r), + NOT_DISTINCT_FROM_FD(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.FLOAT, Type.TypeCode.DOUBLE, (l, r) -> (float)l == (double)r), + NOT_DISTINCT_FROM_DU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_DI(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.INT, (l, r) -> (double)l == (int)r), + NOT_DISTINCT_FROM_DL(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.LONG, (l, r) -> (double)l == (long)r), + NOT_DISTINCT_FROM_DF(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.FLOAT, (l, r) -> (double)l == (float)r), + NOT_DISTINCT_FROM_DD(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.DOUBLE, Type.TypeCode.DOUBLE, (l, r) -> (double)l == (double)r), + NOT_DISTINCT_FROM_SU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.STRING, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_SS(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.STRING, Type.TypeCode.STRING, Object::equals), // TODO: locale-aware comparison + NOT_DISTINCT_FROM_UU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.UNKNOWN, (l, r) -> true), + NOT_DISTINCT_FROM_UB(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.BOOLEAN, (l, r) -> false), + NOT_DISTINCT_FROM_UI(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.INT, (l, r) -> false), + NOT_DISTINCT_FROM_UL(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.LONG, (l, r) -> false), + NOT_DISTINCT_FROM_UF(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.FLOAT, (l, r) -> false), + NOT_DISTINCT_FROM_UD(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.DOUBLE, (l, r) -> false), + NOT_DISTINCT_FROM_US(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.STRING, (l, r) -> false), + NOT_DISTINCT_FROM_UV(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.VERSION, (l, r) -> false), + NOT_DISTINCT_FROM_VU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.VERSION, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_VV(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.VERSION, Type.TypeCode.VERSION, Object::equals), + + NOT_DISTINCT_FROM_BYU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.BYTES, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_BYBY(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.BYTES, Type.TypeCode.BYTES, (l, r) -> Comparisons.evalComparison(Comparisons.Type.NOT_DISTINCT_FROM, l, r)), + NOT_DISTINCT_FROM_UBY(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.BYTES, (l, r) -> false), + + NOT_DISTINCT_FROM_EE(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.ENUM, Type.TypeCode.ENUM, (l, r) -> Comparisons.evalComparison(Comparisons.Type.NOT_DISTINCT_FROM, l, r)), + NOT_DISTINCT_FROM_ES(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.ENUM, Type.TypeCode.STRING, (l, r) -> { + final var otherValue = PromoteValue.PhysicalOperator.stringToEnumValue(((Descriptors.EnumValueDescriptor) l).getType(), (String) r); + return Comparisons.evalComparison(Comparisons.Type.NOT_DISTINCT_FROM, l, otherValue); + }), + NOT_DISTINCT_FROM_SE(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.STRING, Type.TypeCode.ENUM, (l, r) -> { + final var otherValue = PromoteValue.PhysicalOperator.stringToEnumValue(((Descriptors.EnumValueDescriptor) r).getType(), (String) l); + return Comparisons.evalComparison(Comparisons.Type.NOT_DISTINCT_FROM, otherValue, r); + }), + NOT_DISTINCT_FROM_EU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.ENUM, Type.TypeCode.UNKNOWN, (l, r) -> false), + NOT_DISTINCT_FROM_UE(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.ENUM, (l, r) -> false), + + NOT_DISTINCT_FROM_IDID(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UUID, Type.TypeCode.UUID, (l, r) -> Comparisons.evalComparison(Comparisons.Type.NOT_DISTINCT_FROM, l, r)), + NOT_DISTINCT_FROM_IDS(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UUID, Type.TypeCode.STRING, (l, r) -> Comparisons.evalComparison(Comparisons.Type.NOT_DISTINCT_FROM, l, PromoteValue.PhysicalOperator.stringToUuidValue((String) r))), + NOT_DISTINCT_FROM_SID(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.STRING, Type.TypeCode.UUID, (l, r) -> Comparisons.evalComparison(Comparisons.Type.NOT_DISTINCT_FROM, PromoteValue.PhysicalOperator.stringToUuidValue((String) l), r)), + NOT_DISTINCT_FROM_UID(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UNKNOWN, Type.TypeCode.UUID, (l, r) -> false), + NOT_DISTINCT_FROM_IDU(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.UUID, Type.TypeCode.UNKNOWN, (l, r) -> false), + + NOT_DISTINCT_FROM_NN(Comparisons.Type.NOT_DISTINCT_FROM, Type.TypeCode.NULL, Type.TypeCode.NULL, (l, r) -> true), + ; // We can pass down UUID or String till here. diff --git a/fdb-record-layer-core/src/main/proto/record_query_plan.proto b/fdb-record-layer-core/src/main/proto/record_query_plan.proto index 53c135799f..6b790bc323 100644 --- a/fdb-record-layer-core/src/main/proto/record_query_plan.proto +++ b/fdb-record-layer-core/src/main/proto/record_query_plan.proto @@ -918,7 +918,6 @@ message PBinaryRelOpValue { GTE_SID = 268; GTE_UID = 269; GTE_IDU = 270; - EQ_BN = 271; EQ_IN = 272; EQ_LN = 273; @@ -1049,6 +1048,102 @@ message PBinaryRelOpValue { GT_IDN = 398; GTE_NID = 399; GTE_IDN = 400; + IS_DISTINCT_FROM_BU = 401; + IS_DISTINCT_FROM_BB = 402; + IS_DISTINCT_FROM_IU = 403; + IS_DISTINCT_FROM_II = 404; + IS_DISTINCT_FROM_IL = 405; + IS_DISTINCT_FROM_IF = 406; + IS_DISTINCT_FROM_ID = 407; + IS_DISTINCT_FROM_LU = 408; + IS_DISTINCT_FROM_LI = 409; + IS_DISTINCT_FROM_LL = 410; + IS_DISTINCT_FROM_LF = 411; + IS_DISTINCT_FROM_LD = 412; + IS_DISTINCT_FROM_FU = 413; + IS_DISTINCT_FROM_FI = 414; + IS_DISTINCT_FROM_FL = 415; + IS_DISTINCT_FROM_FF = 416; + IS_DISTINCT_FROM_FD = 417; + IS_DISTINCT_FROM_DU = 418; + IS_DISTINCT_FROM_DI = 419; + IS_DISTINCT_FROM_DL = 420; + IS_DISTINCT_FROM_DF = 421; + IS_DISTINCT_FROM_DD = 422; + IS_DISTINCT_FROM_SU = 423; + IS_DISTINCT_FROM_SS = 424; + IS_DISTINCT_FROM_UU = 425; + IS_DISTINCT_FROM_UB = 426; + IS_DISTINCT_FROM_UI = 427; + IS_DISTINCT_FROM_UL = 428; + IS_DISTINCT_FROM_UF = 429; + IS_DISTINCT_FROM_UD = 430; + IS_DISTINCT_FROM_US = 431; + IS_DISTINCT_FROM_UV = 432; + IS_DISTINCT_FROM_VU = 433; + IS_DISTINCT_FROM_VV = 434; + IS_DISTINCT_FROM_BYU = 435; + IS_DISTINCT_FROM_BYBY = 436; + IS_DISTINCT_FROM_UBY = 437; + IS_DISTINCT_FROM_EE = 438; + IS_DISTINCT_FROM_ES = 439; + IS_DISTINCT_FROM_SE = 440; + IS_DISTINCT_FROM_EU = 441; + IS_DISTINCT_FROM_UE = 442; + IS_DISTINCT_FROM_IDID = 443; + IS_DISTINCT_FROM_IDS = 444; + IS_DISTINCT_FROM_SID = 445; + IS_DISTINCT_FROM_UID = 446; + IS_DISTINCT_FROM_IDU = 447; + IS_DISTINCT_FROM_NN = 448; + NOT_DISTINCT_FROM_BU = 449; + NOT_DISTINCT_FROM_BB = 450; + NOT_DISTINCT_FROM_IU = 451; + NOT_DISTINCT_FROM_II = 452; + NOT_DISTINCT_FROM_IL = 453; + NOT_DISTINCT_FROM_IF = 454; + NOT_DISTINCT_FROM_ID = 455; + NOT_DISTINCT_FROM_LU = 456; + NOT_DISTINCT_FROM_LI = 457; + NOT_DISTINCT_FROM_LL = 458; + NOT_DISTINCT_FROM_LF = 459; + NOT_DISTINCT_FROM_LD = 460; + NOT_DISTINCT_FROM_FU = 461; + NOT_DISTINCT_FROM_FI = 462; + NOT_DISTINCT_FROM_FL = 463; + NOT_DISTINCT_FROM_FF = 464; + NOT_DISTINCT_FROM_FD = 465; + NOT_DISTINCT_FROM_DU = 466; + NOT_DISTINCT_FROM_DI = 467; + NOT_DISTINCT_FROM_DL = 468; + NOT_DISTINCT_FROM_DF = 469; + NOT_DISTINCT_FROM_DD = 470; + NOT_DISTINCT_FROM_SU = 471; + NOT_DISTINCT_FROM_SS = 472; + NOT_DISTINCT_FROM_UU = 473; + NOT_DISTINCT_FROM_UB = 474; + NOT_DISTINCT_FROM_UI = 475; + NOT_DISTINCT_FROM_UL = 476; + NOT_DISTINCT_FROM_UF = 477; + NOT_DISTINCT_FROM_UD = 478; + NOT_DISTINCT_FROM_US = 479; + NOT_DISTINCT_FROM_UV = 480; + NOT_DISTINCT_FROM_VU = 481; + NOT_DISTINCT_FROM_VV = 482; + NOT_DISTINCT_FROM_BYU = 483; + NOT_DISTINCT_FROM_BYBY = 484; + NOT_DISTINCT_FROM_UBY = 485; + NOT_DISTINCT_FROM_EE = 486; + NOT_DISTINCT_FROM_ES = 487; + NOT_DISTINCT_FROM_SE = 488; + NOT_DISTINCT_FROM_EU = 489; + NOT_DISTINCT_FROM_UE = 490; + NOT_DISTINCT_FROM_IDID = 491; + NOT_DISTINCT_FROM_IDS = 492; + NOT_DISTINCT_FROM_SID = 493; + NOT_DISTINCT_FROM_UID = 494; + NOT_DISTINCT_FROM_IDU = 495; + NOT_DISTINCT_FROM_NN = 496; } optional PRelOpValue super = 1; optional PBinaryPhysicalOperator operator = 2; @@ -1173,6 +1268,8 @@ message PComparison { TEXT_CONTAINS_ANY_PREFIX = 17; SORT = 18; LIKE = 19; + IS_DISTINCT_FROM = 20; + NOT_DISTINCT_FROM = 21; } extensions 5000 to max; diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/QueryExpressionTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/QueryExpressionTest.java index 899d9489a6..1b922a3a74 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/QueryExpressionTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/expressions/QueryExpressionTest.java @@ -417,7 +417,7 @@ protected void testParameterComparison(String name, String field, Object val1, C final Bindings bindings = Bindings.newBuilder() .set("fooParam", val2) .build(); - if (val1 != null && val2 != null && (type == Comparisons.Type.IN && !(val2 instanceof List) || type.name().startsWith("TEXT_"))) { + if (val1 != null && val2 != null && (type == Comparisons.Type.IN && !(val2 instanceof List)) || type.name().startsWith("TEXT_")) { assertThrows(name, RecordCoreException.class, () -> evaluate(qc, bindings, rec)); } else { diff --git a/fdb-relational-core/src/main/antlr/RelationalParser.g4 b/fdb-relational-core/src/main/antlr/RelationalParser.g4 index df04fa6ecc..879fa3f3e4 100644 --- a/fdb-relational-core/src/main/antlr/RelationalParser.g4 +++ b/fdb-relational-core/src/main/antlr/RelationalParser.g4 @@ -1164,6 +1164,7 @@ unaryOperator comparisonOperator : '=' | '>' | '<' | '<' '=' | '>' '=' | '<' '>' | '!' '=' // | '<' '=' '>' // no support for null-safe equality + | IS NOT? DISTINCT FROM ; logicalOperator diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java index bacce45c8b..9c7ec3d668 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java @@ -145,6 +145,8 @@ private static ImmutableMap BuiltInFunctionCatalog.resolve("coalesce", argumentsCount)) .put("is null", argumentsCount -> BuiltInFunctionCatalog.resolve("isNull", argumentsCount)) .put("is not null", argumentsCount -> BuiltInFunctionCatalog.resolve("notNull", argumentsCount)) + .put("isdistinctfrom", argumentsCount -> BuiltInFunctionCatalog.resolve("isDistinctFrom", argumentsCount)) + .put("isnotdistinctfrom", argumentsCount -> BuiltInFunctionCatalog.resolve("notDistinctFrom", argumentsCount)) .put("range", argumentsCount -> BuiltInFunctionCatalog.resolve("range", argumentsCount)) .put("__pattern_for_like", argumentsCount -> BuiltInFunctionCatalog.resolve("patternForLike", argumentsCount)) .put("__internal_array", argumentsCount -> BuiltInFunctionCatalog.resolve("array", argumentsCount)) diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index 3c008556e0..0c81cbcdb2 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -194,6 +194,11 @@ void like(YamlTest.Runner runner) throws Exception { runner.runYamsql("like.yamsql"); } + @TestTemplate + void distinctFrom(YamlTest.Runner runner) throws Exception { + runner.runYamsql("distinct-from.yamsql"); + } + @TestTemplate void functions(YamlTest.Runner runner) throws Exception { runner.runYamsql("functions.yamsql"); diff --git a/yaml-tests/src/test/resources/distinct-from.metrics.binpb b/yaml-tests/src/test/resources/distinct-from.metrics.binpb new file mode 100644 index 0000000000..f0bd098956 --- /dev/null +++ b/yaml-tests/src/test/resources/distinct-from.metrics.binpb @@ -0,0 +1,167 @@ + +U +distinct-from-tests>EXPLAIN select * from t1 WHERE col2 is distinct from null +c ǝ?(70ـ8E@ ISCAN(I2 ([null],>)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
range: ((null), ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Index
I2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +N +distinct-from-tests7EXPLAIN select * from t1 WHERE col1 is DISTINCT from 10 + (508J@ lCOVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 IS_DISTINCT_FROM promote(@c10 AS INT) | FETCH digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Predicate Filter
WHERE q61.COL1 IS_DISTINCT_FROM promote(@c10 AS INT)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 4 [ label=<
Index
I1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 -> 2 [ label=< q61> label="q61" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q63> label="q63" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +P +distinct-from-tests9EXPLAIN select * from t1 WHERE null is distinct from col2 +τd ?(70ʋ8E@ ISCAN(I2 ([null],>)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
range: ((null), ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Index
I2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +N +distinct-from-tests7EXPLAIN select * from t1 WHERE 10 is distinct from col1 + (508J@ kCOVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 IS_DISTINCT_FROM promote(@c6 AS INT) | FETCH digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Predicate Filter
WHERE q61.COL1 IS_DISTINCT_FROM promote(@c6 AS INT)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 4 [ label=<
Index
I1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 -> 2 [ label=< q61> label="q61" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q63> label="q63" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +P +distinct-from-tests9EXPLAIN select * from t1 WHERE null is distinct from null +̌c >(808Q@ +ECOVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER false | FETCH digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Predicate Filter
WHERE false
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 4 [ label=<
Index
I1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 -> 2 [ label=< q64> label="q64" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q66> label="q66" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +L +distinct-from-tests5EXPLAIN select * from t1 WHERE 10 is distinct from 10 +ƣc ɢ@(80͂8Q@ +XCOVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER @c6 IS_DISTINCT_FROM @c6 | FETCH digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Predicate Filter
WHERE @c6 IS_DISTINCT_FROM @c6
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 4 [ label=<
Index
I1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 -> 2 [ label=< q64> label="q64" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q66> label="q66" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +] +not-distinct-from-testsBEXPLAIN select * from t1 WHERE col2 is not distinct from null + ƴ(708E@ ISCAN(I2 [[null],[null]])digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
range: [(null), (null)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Index
I2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +V +not-distinct-from-tests;EXPLAIN select * from t1 WHERE col1 is not distinct from 20 + (50ߎ8J@ mCOVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 NOT_DISTINCT_FROM promote(@c11 AS INT) | FETCH digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Predicate Filter
WHERE q61.COL1 NOT_DISTINCT_FROM promote(@c11 AS INT)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 4 [ label=<
Index
I1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 -> 2 [ label=< q61> label="q61" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q63> label="q63" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +X +not-distinct-from-tests=EXPLAIN select * from t1 WHERE null is not distinct from col2 + (708E@ ISCAN(I2 [[null],[null]])digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
range: [(null), (null)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Index
I2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +V +not-distinct-from-tests;EXPLAIN select * from t1 WHERE 20 is not distinct from col1 +̰  (508J@ lCOVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 NOT_DISTINCT_FROM promote(@c6 AS INT) | FETCH digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(INT AS ID, )" ]; + 2 [ label=<
Predicate Filter
WHERE q61.COL1 NOT_DISTINCT_FROM promote(@c6 AS INT)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 4 [ label=<
Index
I1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 -> 2 [ label=< q61> label="q61" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q63> label="q63" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +_ +not-distinct-from-testsDEXPLAIN select count(*) from t1 WHERE null is not distinct from null + (<08`@ISCAN(I2 <,>) | MAP (_ AS _0) | AGG (count_star(*) AS _0) | ON EMPTY NULL | MAP (coalesce_long(_._0._0, promote(0l AS LONG)) AS _0)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (coalesce_long(q6._0._0, promote(0l AS LONG)) AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0)" ]; + 2 [ label=<
Value Computation
$q6 OR NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0 AS _0)" ]; + 3 [ label=<
Streaming Aggregate
COLLECT (count_star(*) AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0 AS _0)" ]; + 4 [ label=<
Value Computation
MAP (q2 AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, AS _0)" ]; + 5 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 6 [ label=<
Index
I2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 -> 2 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q88> label="q88" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 5 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +[ +not-distinct-from-tests@EXPLAIN select count(*) from t1 WHERE 10 is not distinct from 10 + (>08v@COVERING(I2 <,> -> [COL2: KEY[0], ID: KEY[2]]) | FILTER @c9 NOT_DISTINCT_FROM @c9 | FETCH | MAP (_ AS _0) | AGG (count_star(*) AS _0) | ON EMPTY NULL | MAP (coalesce_long(_._0._0, promote(0l AS LONG)) AS _0)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (coalesce_long(q6._0._0, promote(0l AS LONG)) AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0)" ]; + 2 [ label=<
Value Computation
$q6 OR NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0 AS _0)" ]; + 3 [ label=<
Streaming Aggregate
COLLECT (count_star(*) AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0 AS _0)" ]; + 4 [ label=<
Value Computation
MAP (q67 AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, AS _0)" ]; + 5 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(INT AS ID, )" ]; + 6 [ label=<
Predicate Filter
WHERE @c9 NOT_DISTINCT_FROM @c9
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 7 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 8 [ label=<
Index
I2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ]; + 3 -> 2 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q113> label="q113" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ label=< q67> label="q67" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 5 [ label=< q74> label="q74" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 6 [ label=< q72> label="q72" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 8 -> 7 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} \ No newline at end of file diff --git a/yaml-tests/src/test/resources/distinct-from.metrics.yaml b/yaml-tests/src/test/resources/distinct-from.metrics.yaml new file mode 100644 index 0000000000..d2ab107cff --- /dev/null +++ b/yaml-tests/src/test/resources/distinct-from.metrics.yaml @@ -0,0 +1,130 @@ +distinct-from-tests: +- query: EXPLAIN select * from t1 WHERE col2 is distinct from null + explain: ISCAN(I2 ([null],>) + task_count: 668 + task_total_time_ms: 209 + transform_count: 182 + transform_time_ms: 132 + transform_yield_count: 55 + insert_time_ms: 12 + insert_new_count: 69 + insert_reused_count: 9 +- query: EXPLAIN select * from t1 WHERE col1 is DISTINCT from 10 + explain: 'COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 IS_DISTINCT_FROM + promote(@c10 AS INT) | FETCH' + task_count: 683 + task_total_time_ms: 41 + transform_count: 182 + transform_time_ms: 14 + transform_yield_count: 53 + insert_time_ms: 3 + insert_new_count: 74 + insert_reused_count: 9 +- query: EXPLAIN select * from t1 WHERE null is distinct from col2 + explain: ISCAN(I2 ([null],>) + task_count: 668 + task_total_time_ms: 209 + transform_count: 182 + transform_time_ms: 134 + transform_yield_count: 55 + insert_time_ms: 12 + insert_new_count: 69 + insert_reused_count: 9 +- query: EXPLAIN select * from t1 WHERE 10 is distinct from col1 + explain: 'COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 IS_DISTINCT_FROM + promote(@c6 AS INT) | FETCH' + task_count: 683 + task_total_time_ms: 39 + transform_count: 182 + transform_time_ms: 14 + transform_yield_count: 53 + insert_time_ms: 3 + insert_new_count: 74 + insert_reused_count: 9 +- query: EXPLAIN select * from t1 WHERE null is distinct from null + explain: 'COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER false | FETCH' + task_count: 721 + task_total_time_ms: 207 + transform_count: 192 + transform_time_ms: 131 + transform_yield_count: 56 + insert_time_ms: 15 + insert_new_count: 81 + insert_reused_count: 10 +- query: EXPLAIN select * from t1 WHERE 10 is distinct from 10 + explain: 'COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER @c6 IS_DISTINCT_FROM + @c6 | FETCH' + task_count: 721 + task_total_time_ms: 207 + transform_count: 192 + transform_time_ms: 135 + transform_yield_count: 56 + insert_time_ms: 13 + insert_new_count: 81 + insert_reused_count: 10 +not-distinct-from-tests: +- query: EXPLAIN select * from t1 WHERE col2 is not distinct from null + explain: ISCAN(I2 [[null],[null]]) + task_count: 668 + task_total_time_ms: 47 + transform_count: 181 + transform_time_ms: 17 + transform_yield_count: 55 + insert_time_ms: 3 + insert_new_count: 69 + insert_reused_count: 9 +- query: EXPLAIN select * from t1 WHERE col1 is not distinct from 20 + explain: 'COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 NOT_DISTINCT_FROM + promote(@c11 AS INT) | FETCH' + task_count: 683 + task_total_time_ms: 48 + transform_count: 183 + transform_time_ms: 18 + transform_yield_count: 53 + insert_time_ms: 3 + insert_new_count: 74 + insert_reused_count: 9 +- query: EXPLAIN select * from t1 WHERE null is not distinct from col2 + explain: ISCAN(I2 [[null],[null]]) + task_count: 668 + task_total_time_ms: 46 + transform_count: 181 + transform_time_ms: 17 + transform_yield_count: 55 + insert_time_ms: 3 + insert_new_count: 69 + insert_reused_count: 9 +- query: EXPLAIN select * from t1 WHERE 20 is not distinct from col1 + explain: 'COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 NOT_DISTINCT_FROM + promote(@c6 AS INT) | FETCH' + task_count: 683 + task_total_time_ms: 27 + transform_count: 183 + transform_time_ms: 11 + transform_yield_count: 53 + insert_time_ms: 2 + insert_new_count: 74 + insert_reused_count: 9 +- query: EXPLAIN select count(*) from t1 WHERE null is not distinct from null + explain: ISCAN(I2 <,>) | MAP (_ AS _0) | AGG (count_star(*) AS _0) | ON EMPTY + NULL | MAP (coalesce_long(_._0._0, promote(0l AS LONG)) AS _0) + task_count: 723 + task_total_time_ms: 57 + transform_count: 204 + transform_time_ms: 26 + transform_yield_count: 60 + insert_time_ms: 3 + insert_new_count: 96 + insert_reused_count: 8 +- query: EXPLAIN select count(*) from t1 WHERE 10 is not distinct from 10 + explain: 'COVERING(I2 <,> -> [COL2: KEY[0], ID: KEY[2]]) | FILTER @c9 NOT_DISTINCT_FROM + @c9 | FETCH | MAP (_ AS _0) | AGG (count_star(*) AS _0) | ON EMPTY NULL | + MAP (coalesce_long(_._0._0, promote(0l AS LONG)) AS _0)' + task_count: 861 + task_total_time_ms: 59 + transform_count: 220 + transform_time_ms: 24 + transform_yield_count: 62 + insert_time_ms: 3 + insert_new_count: 118 + insert_reused_count: 6 diff --git a/yaml-tests/src/test/resources/distinct-from.yamsql b/yaml-tests/src/test/resources/distinct-from.yamsql new file mode 100644 index 0000000000..a042d07481 --- /dev/null +++ b/yaml-tests/src/test/resources/distinct-from.yamsql @@ -0,0 +1,101 @@ +# +# distinct-from.yamsql +# +# This source file is part of the FoundationDB open source project +# +# Copyright 2021-2024 Apple Inc. and the FoundationDB project authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +--- +options: + supported_version: !current_version +--- +schema_template: + create table t1(id integer, col1 integer, col2 integer, primary key(id)) + create index i1 as select col1 from t1 + create index cnt as select count(*) from t1 + create index i2 as select col2 from t1 +--- +setup: + steps: + - query: INSERT INTO T1 + VALUES (1, 10, 1), + (2, 10, null), + (3, 10, 3), + (4, 10, null), + (5, 10, 5), + (6, 20, null), + (7, 20, null), + (8, 20, null), + (9, 20, 9), + (10, 20, 10) +--- +test_block: + name: distinct-from-tests + tests: + - + - query: select * from t1 WHERE col2 is distinct from null + - explain: "ISCAN(I2 ([null],>)" + - result: [{ID: 1, 10, 1}, {ID: 3, 10, 3}, {ID: 5, 10, 5}, {ID: 9, 20, 9}, {ID: 10, 20, 10}] + - + - query: select * from t1 WHERE col1 is DISTINCT from 10 + - explain: "COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 IS_DISTINCT_FROM promote(@c10 AS INT) | FETCH" + - result: [{ID: 6, 20, !null }, {ID: 7, 20, !null }, {ID: 8, 20, !null }, {ID: 9, 20, 9}, {ID: 10, 20, 10}] + - + - query: select * from t1 WHERE null is distinct from col2 + - explain: "ISCAN(I2 ([null],>)" + - result: [{ID: 1, 10, 1}, {ID: 3, 10, 3}, {ID: 5, 10, 5}, {ID: 9, 20, 9}, {ID: 10, 20, 10}] + - + - query: select * from t1 WHERE 10 is distinct from col1 + - explain: "COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 IS_DISTINCT_FROM promote(@c6 AS INT) | FETCH" + - result: [{ID: 6, 20, !null }, {ID: 7, 20, !null }, {ID: 8, 20, !null }, {ID: 9, 20, 9}, {ID: 10, 20, 10}] + - + - query: select * from t1 WHERE null is distinct from null + - explain: "COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER false | FETCH" + - result: [] + - + - query: select * from t1 WHERE 10 is distinct from 10 + - explain: "COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER @c6 IS_DISTINCT_FROM @c6 | FETCH" + - result: [] + +--- +test_block: + name: not-distinct-from-tests + tests: + - + - query: select * from t1 WHERE col2 is not distinct from null + - explain: "ISCAN(I2 [[null],[null]])" + - result: [{ID: 2, 10, !null }, {ID: 4, 10, !null }, {ID: 6, 20, !null }, {ID: 7, 20, !null }, {ID: 8, 20, !null }] + - + - query: select * from t1 WHERE col1 is not distinct from 20 + - explain: "COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 NOT_DISTINCT_FROM promote(@c11 AS INT) | FETCH" + - result: [{ID: 6, 20, !null }, {ID: 7, 20, !null }, {ID: 8, 20, !null }, {ID: 9, 20, 9}, {ID: 10, 20, 10}] + - + - query: select * from t1 WHERE null is not distinct from col2 + - explain: "ISCAN(I2 [[null],[null]])" + - result: [{ID: 2, 10, !null }, {ID: 4, 10, !null }, {ID: 6, 20, !null }, {ID: 7, 20, !null }, {ID: 8, 20, !null }] + - + - query: select * from t1 WHERE 20 is not distinct from col1 + - explain: "COVERING(I1 <,> -> [COL1: KEY[0], ID: KEY[2]]) | FILTER _.COL1 NOT_DISTINCT_FROM promote(@c6 AS INT) | FETCH" + - result: [{ID: 6, 20, !null }, {ID: 7, 20, !null }, {ID: 8, 20, !null }, {ID: 9, 20, 9}, {ID: 10, 20, 10}] + - + - query: select count(*) from t1 WHERE null is not distinct from null + - explain: "ISCAN(I2 <,>) | MAP (_ AS _0) | AGG (count_star(*) AS _0) | ON EMPTY NULL | MAP (coalesce_long(_._0._0, promote(0l AS LONG)) AS _0)" + - result: [{10}] + - + - query: select count(*) from t1 WHERE 10 is not distinct from 10 + - explain: "COVERING(I2 <,> -> [COL2: KEY[0], ID: KEY[2]]) | FILTER @c9 NOT_DISTINCT_FROM @c9 | FETCH | MAP (_ AS _0) | AGG (count_star(*) AS _0) | ON EMPTY NULL | MAP (coalesce_long(_._0._0, promote(0l AS LONG)) AS _0)" + - result: [{10}] +...