From c59de9005db0a0836db85cc4aa6d0d62cd7f70a9 Mon Sep 17 00:00:00 2001 From: Pranjal Gupta Date: Wed, 15 Jan 2025 00:00:25 +0000 Subject: [PATCH] support for enums in rel ops --- docs/ReleaseNotes.md | 2 +- .../plan/cascades/SemanticException.java | 1 + .../query/plan/cascades/typing/Type.java | 33 ++---- .../plan/cascades/values/FieldValue.java | 2 +- .../plan/cascades/values/PromoteValue.java | 8 +- .../plan/cascades/values/RelOpValue.java | 77 +++++++++++- .../src/main/proto/record_query_plan.proto | 34 ++++++ .../query/plan/cascades/BooleanValueTest.java | 20 +++- .../test/java/JDBCYamlIntegrationTests.java | 7 ++ .../src/test/java/YamlIntegrationTests.java | 5 + yaml-tests/src/test/resources/enum.yamsql | 112 ++++++++++++++++++ 11 files changed, 272 insertions(+), 29 deletions(-) create mode 100644 yaml-tests/src/test/resources/enum.yamsql diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 55509feb90..226cbdca3d 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -33,7 +33,7 @@ Our API stability annotations have been updated to reflect greater API instabili * **Feature** Feature 2 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN) * **Feature** Feature 3 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN) * **Feature** Feature 4 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN) -* **Feature** Feature 5 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN) +* **Feature** Support enums in rel ops [(Issue #3011)](https://github.com/FoundationDB/fdb-record-layer/issues/3011) * **Breaking change** Change 1 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN) * **Breaking change** Change 2 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN) * **Breaking change** Change 3 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN) diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java index 122c4c15e0..e3bd71a800 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/SemanticException.java @@ -49,6 +49,7 @@ public enum ErrorCode { FUNCTION_UNDEFINED_FOR_GIVEN_ARGUMENT_TYPES(9, "The function is not defined for the given argument types"), ORDERING_IS_OF_INCOMPATIBLE_TYPE(10, "The specified ordering expecting an argument of a primitive or record type, is invoked with an argument of an array type or other complex type."), ARGUMENT_TO_COLLATE_IS_OF_COMPLEX_TYPE(11, "The argument to a collate expression expecting an argument of a primitive type, is invoked with an argument of a complex type, e.g. an array or a record."), + INVALID_ENUM_VALUE(12, "Invalid enum value for the enum type"), // insert, update, deletes UPDATE_TRANSFORM_AMBIGUOUS(1_000, "The transformations used in an UPDATE statement are ambiguous."), diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java index 631cb52989..95bbd29494 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java @@ -493,13 +493,18 @@ static Type maximumType(@Nonnull final Type t1, @Nonnull final Type t2) { return t1.withNullability(true); } - if (t1.isPrimitive() != t2.isPrimitive()) { - return null; - } - boolean isResultNullable = t1.isNullable() || t2.isNullable(); - if (t1.isPrimitive()) { + if (t1.isEnum() && t2.isEnum()) { + final var t1Enum = (Enum)t1; + final var t2Enum = (Enum)t2; + final var t1EnumValues = t1Enum.enumValues; + final var t2EnumValues = t2Enum.enumValues; + if (t1EnumValues == null) { + return t2EnumValues == null ? t1Enum.withNullability(isResultNullable) : null; + } + return t1EnumValues.equals(t2EnumValues) ? t1Enum.withNullability(isResultNullable) : null; + } else if ((t1.isPrimitive() || t1.isEnum()) && (t2.isPrimitive() || t2.isEnum())) { if (t1.getTypeCode() == t2.getTypeCode()) { return t1.withNullability(isResultNullable); } @@ -509,25 +514,11 @@ static Type maximumType(@Nonnull final Type t1, @Nonnull final Type t2) { if (PromoteValue.isPromotable(t2, t1)) { return t1.withNullability(isResultNullable); } - // Type are primitive but not equal, no promotion possible. - return null; - } - - if (t1.isEnum() != t2.isEnum()) { + // Type are primitive or enum but not compatible, no promotion possible. return null; } - if (t1.isEnum()) { - final var t1Enum = (Enum)t1; - final var t2Enum = (Enum)t2; - final var t1EnumValues = t1Enum.enumValues; - final var t2EnumValues = t2Enum.enumValues; - if (t1EnumValues == null) { - return t2EnumValues == null ? t1Enum.withNullability(isResultNullable) : null; - } - return t1EnumValues.equals(t2EnumValues) ? t1Enum.withNullability(isResultNullable) : null; - } - + // neither of the types are null, primitives or enums if (t1.getTypeCode() != t2.getTypeCode()) { return null; } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java index 9548754642..fbb9491fbd 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java @@ -70,7 +70,7 @@ * A value representing the contents of a (non-repeated, arbitrarily-nested) field of a quantifier. */ @API(API.Status.EXPERIMENTAL) -public class FieldValue extends AbstractValue implements ValueWithChild { +public class FieldValue extends AbstractValue implements ValueWithChild, CreatesDynamicTypesValue { private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Field-Value"); @Nonnull diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PromoteValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PromoteValue.java index 541e5a7f00..e03ed72590 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PromoteValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PromoteValue.java @@ -87,7 +87,7 @@ enum PhysicalOperator { NULL_TO_RECORD(Type.TypeCode.NULL, Type.TypeCode.RECORD, (descriptor, in) -> null), NONE_TO_ARRAY(Type.TypeCode.NONE, Type.TypeCode.ARRAY, (descriptor, in) -> in), NULL_TO_ENUM(Type.TypeCode.NULL, Type.TypeCode.ENUM, (descriptor, in) -> null), - STRING_TO_ENUM(Type.TypeCode.STRING, Type.TypeCode.ENUM, ((descriptor, in) -> ((Descriptors.EnumDescriptor)descriptor).findValueByName((String)in))); + STRING_TO_ENUM(Type.TypeCode.STRING, Type.TypeCode.ENUM, ((descriptor, in) -> stringToEnumValue((Descriptors.EnumDescriptor) descriptor, (String) in))); @Nonnull private static final Supplier> protoEnumBiMapSupplier = @@ -139,6 +139,12 @@ public static PhysicalOperator fromProto(@Nonnull final PlanSerializationContext return Objects.requireNonNull(getProtoEnumBiMap().inverse().get(physicalOperatorProto)); } + public static Descriptors.EnumValueDescriptor stringToEnumValue(Descriptors.EnumDescriptor enumDescriptor, String value) { + final var maybeValue = enumDescriptor.findValueByName(value); + SemanticException.check(maybeValue != null, SemanticException.ErrorCode.INVALID_ENUM_VALUE, value); + return maybeValue; + } + @Nonnull private static BiMap getProtoEnumBiMap() { return protoEnumBiMapSupplier.get(); 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 611c8e4301..bab13d6e7a 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 @@ -58,6 +58,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Streams; +import com.google.protobuf.Descriptors; import com.google.protobuf.Message; import javax.annotation.Nonnull; @@ -275,7 +276,7 @@ private static Value encapsulate(@Nonnull final String functionName, Verify.verify(arguments.size() == 1 || arguments.size() == 2); final Typed arg0 = arguments.get(0); final Type res0 = arg0.getResultType(); - SemanticException.check(res0.isPrimitive(), SemanticException.ErrorCode.COMPARAND_TO_COMPARISON_IS_OF_COMPLEX_TYPE); + SemanticException.check(res0.isPrimitive() || res0.isEnum(), SemanticException.ErrorCode.COMPARAND_TO_COMPARISON_IS_OF_COMPLEX_TYPE); if (arguments.size() == 1) { final UnaryPhysicalOperator physicalOperator = getUnaryOperatorMap().get(new UnaryComparisonSignature(comparisonType, res0.getTypeCode())); @@ -289,7 +290,7 @@ private static Value encapsulate(@Nonnull final String functionName, } else { final Typed arg1 = arguments.get(1); final Type res1 = arg1.getResultType(); - SemanticException.check(res1.isPrimitive(), SemanticException.ErrorCode.COMPARAND_TO_COMPARISON_IS_OF_COMPLEX_TYPE); + SemanticException.check(res1.isPrimitive() || res1.isEnum(), SemanticException.ErrorCode.COMPARAND_TO_COMPARISON_IS_OF_COMPLEX_TYPE); final BinaryPhysicalOperator physicalOperator = getBinaryOperatorMap().get(new BinaryComparisonSignature(comparisonType, res0.getTypeCode(), res1.getTypeCode())); @@ -664,6 +665,73 @@ private enum BinaryPhysicalOperator { GT_BYBY(Comparisons.Type.GREATER_THAN, Type.TypeCode.BYTES, Type.TypeCode.BYTES, (l, r) -> Comparisons.evalComparison(Comparisons.Type.GREATER_THAN, l, r)), GTE_BYU(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.BYTES, Type.TypeCode.UNKNOWN, (l, r) -> null), GTE_BYBY(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.BYTES, Type.TypeCode.BYTES, (l, r) -> Comparisons.evalComparison(Comparisons.Type.GREATER_THAN_OR_EQUALS, l, r)), + + EQ_EE(Comparisons.Type.EQUALS, Type.TypeCode.ENUM, Type.TypeCode.ENUM, (l, r) -> Comparisons.evalComparison(Comparisons.Type.EQUALS, l, r)), + EQ_ES(Comparisons.Type.EQUALS, 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.EQUALS, l, otherValue); + }), + EQ_SE(Comparisons.Type.EQUALS, 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); + }), + EQ_EU(Comparisons.Type.EQUALS, Type.TypeCode.ENUM, Type.TypeCode.UNKNOWN, (l, r) -> null), + EQ_UE(Comparisons.Type.EQUALS, Type.TypeCode.UNKNOWN, Type.TypeCode.ENUM, (l, r) -> null), + NEQ_EE(Comparisons.Type.NOT_EQUALS, Type.TypeCode.ENUM, Type.TypeCode.ENUM, (l, r) -> Comparisons.evalComparison(Comparisons.Type.NOT_EQUALS, l, r)), + NEQ_ES(Comparisons.Type.NOT_EQUALS, 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_EQUALS, l, otherValue); + }), + NEQ_SE(Comparisons.Type.NOT_EQUALS, 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); + }), + NEQ_EU(Comparisons.Type.NOT_EQUALS, Type.TypeCode.ENUM, Type.TypeCode.UNKNOWN, (l, r) -> null), + NEQ_UE(Comparisons.Type.NOT_EQUALS, Type.TypeCode.UNKNOWN, Type.TypeCode.ENUM, (l, r) -> null), + LT_EE(Comparisons.Type.LESS_THAN, Type.TypeCode.ENUM, Type.TypeCode.ENUM, (l, r) -> Comparisons.evalComparison(Comparisons.Type.LESS_THAN, l, r)), + LT_ES(Comparisons.Type.LESS_THAN, 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.LESS_THAN, l, otherValue); + }), + LT_SE(Comparisons.Type.LESS_THAN, 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.LESS_THAN, otherValue, r); + }), + LT_EU(Comparisons.Type.LESS_THAN, Type.TypeCode.ENUM, Type.TypeCode.UNKNOWN, (l, r) -> null), + LT_UE(Comparisons.Type.LESS_THAN, Type.TypeCode.UNKNOWN, Type.TypeCode.ENUM, (l, r) -> null), + LTE_EE(Comparisons.Type.LESS_THAN_OR_EQUALS, Type.TypeCode.ENUM, Type.TypeCode.ENUM, (l, r) -> Comparisons.evalComparison(Comparisons.Type.LESS_THAN_OR_EQUALS, l, r)), + LTE_ES(Comparisons.Type.LESS_THAN_OR_EQUALS, 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.LESS_THAN, l, otherValue); + }), + LTE_SE(Comparisons.Type.LESS_THAN_OR_EQUALS, 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.LESS_THAN, otherValue, r); + }), + LTE_EU(Comparisons.Type.LESS_THAN_OR_EQUALS, Type.TypeCode.ENUM, Type.TypeCode.UNKNOWN, (l, r) -> null), + LTE_UE(Comparisons.Type.LESS_THAN_OR_EQUALS, Type.TypeCode.UNKNOWN, Type.TypeCode.ENUM, (l, r) -> null), + GT_EE(Comparisons.Type.GREATER_THAN, Type.TypeCode.ENUM, Type.TypeCode.ENUM, (l, r) -> Comparisons.evalComparison(Comparisons.Type.GREATER_THAN, l, r)), + GT_ES(Comparisons.Type.GREATER_THAN, 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.GREATER_THAN, l, otherValue); + }), + GT_SE(Comparisons.Type.GREATER_THAN, 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.GREATER_THAN, otherValue, r); + }), + GT_EU(Comparisons.Type.GREATER_THAN, Type.TypeCode.ENUM, Type.TypeCode.UNKNOWN, (l, r) -> null), + GT_UE(Comparisons.Type.GREATER_THAN, Type.TypeCode.UNKNOWN, Type.TypeCode.ENUM, (l, r) -> null), + GTE_EE(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.ENUM, Type.TypeCode.ENUM, (l, r) -> Comparisons.evalComparison(Comparisons.Type.GREATER_THAN_OR_EQUALS, l, r)), + GTE_ES(Comparisons.Type.GREATER_THAN_OR_EQUALS, 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.GREATER_THAN_OR_EQUALS, l, otherValue); + }), + GTE_SE(Comparisons.Type.GREATER_THAN_OR_EQUALS, 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.GREATER_THAN_OR_EQUALS, otherValue, r); + }), + GTE_EU(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.ENUM, Type.TypeCode.UNKNOWN, (l, r) -> null), + GTE_UE(Comparisons.Type.GREATER_THAN_OR_EQUALS, Type.TypeCode.UNKNOWN, Type.TypeCode.ENUM, (l, r) -> null), ; @Nonnull @@ -753,7 +821,10 @@ private enum UnaryPhysicalOperator { IS_NOT_NULL_BI(Comparisons.Type.NOT_NULL, Type.TypeCode.BOOLEAN, Objects::nonNull), IS_NULL_BY(Comparisons.Type.IS_NULL, Type.TypeCode.BYTES, Objects::isNull), - IS_NOT_NULL_BY(Comparisons.Type.NOT_NULL, Type.TypeCode.BYTES, Objects::nonNull); + IS_NOT_NULL_BY(Comparisons.Type.NOT_NULL, Type.TypeCode.BYTES, Objects::nonNull), + + IS_NULL_EI(Comparisons.Type.IS_NULL, Type.TypeCode.ENUM, Objects::isNull), + IS_NOT_NULL_EI(Comparisons.Type.NOT_NULL, Type.TypeCode.ENUM, Objects::nonNull); @Nonnull private static final Supplier> protoEnumBiMapSupplier = 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 34482dcd2b..50f7a90a83 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 @@ -831,6 +831,37 @@ message PBinaryRelOpValue { GTE_BYU = 209; GTE_BYBY = 210; + EQ_EE = 211; + EQ_ES = 212; + EQ_SE = 213; + EQ_EU = 214; + EQ_UE = 215; + NEQ_EE = 216; + NEQ_ES = 217; + NEQ_SE = 218; + NEQ_EU = 219; + NEQ_UE = 220; + LT_EE = 221; + LT_ES = 222; + LT_SE = 223; + LT_EU = 224; + LT_UE = 225; + LTE_EE = 226; + LTE_ES = 227; + LTE_SE = 228; + LTE_EU = 229; + LTE_UE = 230; + GT_EE = 231; + GT_ES = 232; + GT_SE = 233; + GT_EU = 234; + GT_UE = 235; + GTE_EE = 236; + GTE_ES = 237; + GTE_SE = 238; + GTE_EU = 239; + GTE_UE = 240; + } optional PRelOpValue super = 1; optional PBinaryPhysicalOperator operator = 2; @@ -865,6 +896,9 @@ message PUnaryRelOpValue { IS_NULL_BY = 15; IS_NOT_NULL_BY = 16; + + IS_NULL_EI = 17; + IS_NOT_NULL_EI = 18; } optional PRelOpValue super = 1; optional PUnaryPhysicalOperator operator = 2; diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/BooleanValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/BooleanValueTest.java index 67b60e23cf..0531fac330 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/BooleanValueTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/BooleanValueTest.java @@ -72,7 +72,20 @@ * */ class BooleanValueTest { + + private static final Type.Enum ENUM_TYPE_FOR_TEST = new Type.Enum(false, List.of( + new Type.Enum.EnumValue("SPADES", 0), + new Type.Enum.EnumValue("HEARTS", 1), + new Type.Enum.EnumValue("DIAMONDS", 2), + new Type.Enum.EnumValue("CLUBS", 3) + ), "enumTestType"); + + private static final TypeRepository.Builder typeRepositoryBuilder = TypeRepository.newBuilder().setName("foo").setPackage("a.b.c") + .addTypeIfNeeded(ENUM_TYPE_FOR_TEST); + private static final FieldValue F = FieldValue.ofFieldName(QuantifiedObjectValue.of(Quantifier.current(), Type.Record.fromFields(true, ImmutableList.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.INT), Optional.of("f"))))), "f"); + private static final FieldValue F_ENUM_1 = FieldValue.ofFieldName(QuantifiedObjectValue.of(Quantifier.current(), Type.Record.fromFields(true, ImmutableList.of(Type.Record.Field.of(ENUM_TYPE_FOR_TEST, Optional.of("f_enum_1"))))), "f_enum_1"); + private static final FieldValue F_ENUM_2 = FieldValue.ofFieldName(QuantifiedObjectValue.of(Quantifier.current(), Type.Record.fromFields(true, ImmutableList.of(Type.Record.Field.of(ENUM_TYPE_FOR_TEST, Optional.of("f_enum_2"))))), "f_enum_2"); private static final LiteralValue BOOL_TRUE = new LiteralValue<>(Type.primitiveType(Type.TypeCode.BOOLEAN), true); private static final LiteralValue BOOL_FALSE = new LiteralValue<>(Type.primitiveType(Type.TypeCode.BOOLEAN), false); private static final LiteralValue BOOL_NULL = new LiteralValue<>(Type.primitiveType(Type.TypeCode.BOOLEAN), null); @@ -92,13 +105,14 @@ class BooleanValueTest { private static final LiteralValue STRING_1 = new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), "a"); private static final LiteralValue STRING_2 = new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), "b"); private static final LiteralValue STRING_3 = new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), "c"); + private static final LiteralValue ENUM_STRING_1 = new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING, false), "HEARTS"); + private static final LiteralValue ENUM_STRING_2 = new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING, false), "DIAMONDS"); + private static final LiteralValue BYTES_1 = new LiteralValue<>("foo".getBytes(StandardCharsets.UTF_8)); private static final LiteralValue BYTES_2 = new LiteralValue<>("bar".getBytes(StandardCharsets.UTF_8)); private static final LiteralValue BYTES_3 = new LiteralValue<>("baz".getBytes(StandardCharsets.UTF_8)); private static final ThrowsValue THROWS_VALUE = new ThrowsValue(Type.primitiveType(Type.TypeCode.INT)); - private static final TypeRepository.Builder typeRepositoryBuilder = TypeRepository.newBuilder().setName("foo").setPackage("a.b.c"); - private static final ArithmeticValue ADD_INTS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(List.of(INT_1, INT_2)); private static final ArithmeticValue ADD_LONGS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(List.of(LONG_1, LONG_2)); private static final ArithmeticValue ADD_FLOATS_1_2 = (ArithmeticValue) new ArithmeticValue.AddFn().encapsulate(List.of(FLOAT_1, FLOAT_2)); @@ -396,6 +410,8 @@ public Stream provideArguments(final ExtensionContext conte Arguments.of(List.of(BOOL_NULL, BOOL_FALSE), new RelOpValue.NotEqualsFn(), ConstantPredicate.NULL), Arguments.of(List.of(BOOL_NULL, BOOL_TRUE), new RelOpValue.NotEqualsFn(), ConstantPredicate.NULL), + Arguments.of(List.of(F_ENUM_1, F_ENUM_2), new RelOpValue.EqualsFn(), new ValuePredicate(F_ENUM_1, new Comparisons.ValueComparison(Comparisons.Type.EQUALS, F_ENUM_2))), + /* translation of predicates involving a field value, make sure field value is always LHS */ Arguments.of(List.of(F, INT_1), new RelOpValue.EqualsFn(), new ValuePredicate(F, new Comparisons.SimpleComparison(Comparisons.Type.EQUALS, 1))), Arguments.of(List.of(INT_1, F), new RelOpValue.EqualsFn(), new ValuePredicate(F, new Comparisons.SimpleComparison(Comparisons.Type.EQUALS, 1))), diff --git a/yaml-tests/src/test/java/JDBCYamlIntegrationTests.java b/yaml-tests/src/test/java/JDBCYamlIntegrationTests.java index ded63c16a1..0d172ecb77 100644 --- a/yaml-tests/src/test/java/JDBCYamlIntegrationTests.java +++ b/yaml-tests/src/test/java/JDBCYamlIntegrationTests.java @@ -144,4 +144,11 @@ public void prepared() throws Exception { public void aggregateIndexTests() throws Exception { super.aggregateIndexTests(); } + + @Override + @Test + @Disabled("TODO: sql.Type OTHER is not supported in the ResultSet") + public void enumTest() throws Exception { + super.enumTest(); + } } diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index f4dcb00ae1..2d16b2fa06 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -241,4 +241,9 @@ public void bitmap() throws Exception { public void recursiveCte() throws Exception { doRun("recursive-cte.yamsql"); } + + @Test + public void enumTest() throws Exception { + doRun("enum.yamsql"); + } } diff --git a/yaml-tests/src/test/resources/enum.yamsql b/yaml-tests/src/test/resources/enum.yamsql new file mode 100644 index 0000000000..a638da3f57 --- /dev/null +++ b/yaml-tests/src/test/resources/enum.yamsql @@ -0,0 +1,112 @@ +# +# enum.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. + +--- +schema_template: + CREATE TYPE AS ENUM MOOD ( 'JOYFUL', 'HAPPY', 'RELAXED', 'INDIFFERENT', 'CONFUSED', 'SAD', 'ANXIOUS', 'ANGRY' ) + CREATE TABLE TABLE_A ("id" BIGINT, "name" STRING, "mood" MOOD, PRIMARY KEY("id") ) + WITH OPTIONS(ENABLE_LONG_ROWS=true) +--- +setup: + steps: + - query: INSERT INTO TABLE_A + VALUES (1, 'foo', 'HAPPY'), + (2, 'bar', 'CONFUSED'), + (3, 'baz', 'JOYFUL'), + (4, 'qux', 'ANGRY'), + (5, 'quux', 'RELAXED'), + (6, 'corge', 'JOYFUL'), + (7, 'grault', 'ANXIOUS'), + (8, 'garply', 'SAD'), + (9, 'waldo', 'JOYFUL'), + (10, 'fred', 'INDIFFERENT'), + (11, 'plugh', 'CONFUSED'), + (12, 'xyzzy', 'INDIFFERENT'), + (13, 'thud', 'SAD') + - query: INSERT INTO TABLE_A("id", "name") + VALUES (14, 'alice'), + (15, 'bob') +--- +test_block: + tests: + - + - query: SELECT * From TABLE_A where "mood" = 'CONFUSED' + - unorderedResult: [{2, 'bar', 'CONFUSED'}, + {11, 'plugh', 'CONFUSED'}] + - + - query: SELECT * From TABLE_A where "mood" > 'INDIFFERENT' + - unorderedResult: [{2, 'bar', 'CONFUSED'}, + {4, 'qux', 'ANGRY'}, + {7, 'grault', 'ANXIOUS'}, + {8, 'garply', 'SAD'}, + {11, 'plugh', 'CONFUSED'}, + {13, 'thud', 'SAD'}] + - + - query: SELECT * From TABLE_A where "mood" >= 'INDIFFERENT' + - unorderedResult: [{2, 'bar', 'CONFUSED'}, + {4, 'qux', 'ANGRY'}, + {7, 'grault', 'ANXIOUS'}, + {8, 'garply', 'SAD'}, + {10, 'fred', 'INDIFFERENT'}, + {11, 'plugh', 'CONFUSED'}, + {12, 'xyzzy', 'INDIFFERENT'}, + {13, 'thud', 'SAD'}] + - + - query: SELECT * From TABLE_A where "mood" < 'INDIFFERENT' + - unorderedResult: [{1, 'foo', 'HAPPY'}, + {3, 'baz', 'JOYFUL'}, + {5, 'quux', 'RELAXED'}, + {6, 'corge', 'JOYFUL'}, + {9, 'waldo', 'JOYFUL'}] + - + - query: SELECT * From TABLE_A where "mood" <= 'INDIFFERENT' + - unorderedResult: [{1, 'foo', 'HAPPY'}, + {3, 'baz', 'JOYFUL'}, + {5, 'quux', 'RELAXED'}, + {6, 'corge', 'JOYFUL'}, + {9, 'waldo', 'JOYFUL'}, + {10, 'fred', 'INDIFFERENT'}, + {12, 'xyzzy', 'INDIFFERENT'}] + - + - query: SELECT * From TABLE_A where "mood" != 'INDIFFERENT' + - unorderedResult: [{1, 'foo', 'HAPPY'}, + {2, 'bar', 'CONFUSED'}, + {3, 'baz', 'JOYFUL'}, + {4, 'qux', 'ANGRY'}, + {5, 'quux', 'RELAXED'}, + {6, 'corge', 'JOYFUL'}, + {7, 'grault', 'ANXIOUS'}, + {8, 'garply', 'SAD'}, + {9, 'waldo', 'JOYFUL'}, + {11, 'plugh', 'CONFUSED'}, + {13, 'thud', 'SAD'}] + - + - query: SELECT * From TABLE_A where "mood" IS NOT NULL AND "id" < 5 + - unorderedResult: [{1, 'foo', 'HAPPY'}, + {2, 'bar', 'CONFUSED'}, + {3, 'baz', 'JOYFUL'}, + {4, 'qux', 'ANGRY'}] + - + - query: SELECT * From TABLE_A where "mood" IS NULL + - unorderedResult: [{14, 'alice', !null }, + {15, 'bob', !null }] + - + - query: SELECT * From TABLE_A where "mood" = 'NOT_A_VALID_MOOD' + - error: "XXXXX" +...