From 8b197fc148e92d8baa296a0f7284b1a8393f0d6f Mon Sep 17 00:00:00 2001 From: Pranjal Gupta Date: Wed, 14 Dec 2022 19:16:49 +0000 Subject: [PATCH] add support for enum in deepCopyIfNeeded() in RecordConstructorValue (#1933) fix deepCopy for Enums --- .../plan/cascades/values/MessageHelpers.java | 6 + .../values/RecordConstructorValue.java | 10 +- .../values/RecordConstructorValueTest.java | 148 ++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValueTest.java diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/MessageHelpers.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/MessageHelpers.java index 88be1b49bf..feacadfb69 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/MessageHelpers.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/MessageHelpers.java @@ -267,6 +267,9 @@ public static Message deepCopyMessageIfNeeded(@Nonnull final Descriptors.Descrip for (final var element : (List)entry.getValue()) { if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { builder.addRepeatedField(targetField, deepCopyMessageIfNeeded(targetField.getMessageType(), (Message)element)); + } else if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.ENUM) { + final var enumValue = ((Descriptors.EnumValueDescriptor) element).getName(); + builder.addRepeatedField(targetField, targetField.getEnumType().findValueByName(enumValue)); } else { builder.addRepeatedField(targetField, element); } @@ -283,6 +286,9 @@ public static Message deepCopyMessageIfNeeded(@Nonnull final Descriptors.Descrip .build(); builder.setField(targetField, mergedObject); } + } else if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.ENUM) { + final var enumValue = ((Descriptors.EnumValueDescriptor) entry.getValue()).getName(); + builder.setField(targetField, targetField.getEnumType().findValueByName(enumValue)); } else { builder.setField(targetField, entry.getValue()); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValue.java index 1a42e16257..412ab9272e 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValue.java @@ -35,12 +35,14 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.google.auto.service.AutoService; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Suppliers; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Streams; +import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import com.google.protobuf.Message; @@ -152,9 +154,10 @@ private DynamicMessage.Builder newMessageBuilderForType(@Nonnull TypeRepository * @return an object that is either {@code field} if a copy could be avoided or a new copy of {@code field} whose * constituent messages are {@link DynamicMessage}s based on dynamically-created descriptors. */ + @VisibleForTesting @Nullable @SuppressWarnings("PMD.CompareObjectsWithEquals") - private Object deepCopyIfNeeded(@Nonnull TypeRepository typeRepository, + public static Object deepCopyIfNeeded(@Nonnull TypeRepository typeRepository, @Nonnull final Type fieldType, @Nullable final Object field) { if (field == null) { @@ -178,6 +181,11 @@ private Object deepCopyIfNeeded(@Nonnull TypeRepository typeRepository, return resultBuilder.build(); } + if (fieldType instanceof Type.Enum) { + final var typeName = typeRepository.getProtoTypeName(fieldType); + return typeRepository.getEnumValue(typeName, ((Descriptors.EnumValueDescriptor)field).getName()); + } + Verify.verify(fieldType instanceof Type.Record); final var message = (Message)field; final var declaredDescriptor = Verify.verifyNotNull(typeRepository.getMessageDescriptor(fieldType)); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValueTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValueTest.java new file mode 100644 index 0000000000..743e448e02 --- /dev/null +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/values/RecordConstructorValueTest.java @@ -0,0 +1,148 @@ +/* + * RecordConstructorValueTest.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2022 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. + */ + +package com.apple.foundationdb.record.query.plan.cascades.values; + +import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.Message; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Test cases to test {@link RecordConstructorValue#deepCopyIfNeeded}. + */ +public class RecordConstructorValueTest { + + private static final String[] suites = new String[] {"SPADES", "HEARTS", "DIAMONDS", "CLUBS"}; + + private static Type.Enum getCardsEnum() { + final var enumValues = new ArrayList(); + for (var i = 0; i < suites.length; i++) { + enumValues.add(new Type.Enum.EnumValue(suites[i], i)); + } + return new Type.Enum(false, enumValues, "enumType"); + } + + @Test + public void deepCopyMessageWithEnumTest() { + final var enumType = getCardsEnum(); + var type = Type.Record.fromFieldsWithName("simpleType", false, List.of(Type.Record.Field.of(enumType, Optional.of("suit")))); + var repo = TypeRepository.newBuilder(); + type.defineProtoType(repo); + + final var typeRepoSrc = repo.build(); + final var typeRepoTarget = repo.build(); + + final var typeDescriptor = typeRepoSrc.getMessageDescriptor("simpleType"); + var messageBuilder = DynamicMessage.newBuilder(Objects.requireNonNull(typeDescriptor)); + var enumFieldSrc = typeDescriptor.findFieldByName("suit"); + messageBuilder.setField(enumFieldSrc, Objects.requireNonNull(typeRepoSrc.getEnumValue("enumType", "SPADES"))); + var message = messageBuilder.build(); + + var copiedMessage = RecordConstructorValue.deepCopyIfNeeded(typeRepoTarget, type, message); + Assertions.assertNotNull(copiedMessage); + Assertions.assertNotSame(message, copiedMessage); + var enumFieldTargetValue = ((Message)copiedMessage).getField(Objects.requireNonNull(typeRepoTarget.getMessageDescriptor("simpleType")).findFieldByName("suit")); + Assertions.assertTrue(enumFieldTargetValue instanceof Descriptors.EnumValueDescriptor); + Assertions.assertEquals(((Descriptors.EnumValueDescriptor) message.getField(enumFieldSrc)).getName(), ((Descriptors.EnumValueDescriptor) enumFieldTargetValue).getName()); + Assertions.assertNotSame(message.getField(enumFieldSrc), enumFieldTargetValue); + } + + @Test + public void deepCopyEnumValueTest() { + final var enumType = getCardsEnum(); + var repo = TypeRepository.newBuilder(); + enumType.defineProtoType(repo); + + final var typeRepoSrc = repo.build(); + final var typeRepoTarget = repo.build(); + + final var enumValueSrc = typeRepoSrc.getEnumValue("enumType", "SPADES"); + + final var copied = RecordConstructorValue.deepCopyIfNeeded(typeRepoTarget, enumType, enumValueSrc); + Assertions.assertTrue(copied instanceof Descriptors.EnumValueDescriptor); + Assertions.assertEquals(enumValueSrc.getName(), ((Descriptors.EnumValueDescriptor) copied).getName()); + Assertions.assertNotSame(enumValueSrc, copied); + } + + @Test + public void deepCopyMessageWithRepeatedEnumValueTest() { + final var enumType = getCardsEnum(); + var arrayType = new Type.Array(false, enumType); + var type = Type.Record.fromFieldsWithName("simpleType", false, List.of(Type.Record.Field.of(arrayType, Optional.of("suits")))); + var repo = TypeRepository.newBuilder(); + arrayType.defineProtoType(repo); + type.defineProtoType(repo); + + final var typeRepoSrc = repo.build(); + final var typeRepoTarget = repo.build(); + + final var typeDescriptor = typeRepoSrc.getMessageDescriptor("simpleType"); + var messageBuilder = DynamicMessage.newBuilder(Objects.requireNonNull(typeDescriptor)); + var enumFieldSrc = typeDescriptor.findFieldByName("suits"); + for (int i = suites.length - 1; i >= 0; i--) { + messageBuilder.addRepeatedField(enumFieldSrc, Objects.requireNonNull(typeRepoSrc.getEnumValue("enumType", suites[i]))); + } + var message = messageBuilder.build(); + + var copiedMessage = RecordConstructorValue.deepCopyIfNeeded(typeRepoTarget, type, message); + Assertions.assertNotNull(copiedMessage); + var enumFieldTarget = Objects.requireNonNull(typeRepoTarget.getMessageDescriptor("simpleType")).findFieldByName("suits"); + for (int i = 0; i < 4; i++) { + var enumFieldTargetValue = ((Message) copiedMessage).getRepeatedField(enumFieldTarget, i); + Assertions.assertTrue(enumFieldTargetValue instanceof Descriptors.EnumValueDescriptor); + Assertions.assertEquals(((Descriptors.EnumValueDescriptor) message.getRepeatedField(enumFieldSrc, i)).getName(), ((Descriptors.EnumValueDescriptor) enumFieldTargetValue).getName()); + Assertions.assertNotSame(message.getRepeatedField(enumFieldSrc, i), enumFieldTargetValue); + } + } + + @Test + public void deepCopyEnumValueArrayTest() { + final var enumType = getCardsEnum(); + var arrayType = new Type.Array(false, enumType); + var repo = TypeRepository.newBuilder(); + arrayType.defineProtoType(repo); + + final var typeRepoSrc = repo.build(); + final var typeRepoTarget = repo.build(); + + final var list = new ArrayList(); + for (int i = suites.length - 1; i >= 0; i--) { + list.add(Objects.requireNonNull(typeRepoSrc.getEnumValue("enumType", suites[i]))); + } + + var copied = RecordConstructorValue.deepCopyIfNeeded(typeRepoTarget, arrayType, list); + Assertions.assertNotNull(copied); + for (int i = 0; i < 4; i++) { + var enumFieldTargetValue = ((List) copied).get(i); + Assertions.assertTrue(enumFieldTargetValue instanceof Descriptors.EnumValueDescriptor); + Assertions.assertEquals(list.get(i).getName(), ((Descriptors.EnumValueDescriptor) enumFieldTargetValue).getName()); + Assertions.assertNotSame(list.get(i), enumFieldTargetValue); + } + } +}