Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add support for UUID as primitive type in SQL #3198

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.apple.foundationdb.record.PlanSerializable;
import com.apple.foundationdb.record.PlanSerializationContext;
import com.apple.foundationdb.record.RecordCoreException;
import com.apple.foundationdb.record.TupleFieldsProto;
import com.apple.foundationdb.record.logging.LogMessageKeys;
import com.apple.foundationdb.record.planprotos.PType;
import com.apple.foundationdb.record.planprotos.PType.PAnyRecordType;
Expand All @@ -36,13 +37,14 @@
import com.apple.foundationdb.record.planprotos.PType.PRecordType;
import com.apple.foundationdb.record.planprotos.PType.PRelationType;
import com.apple.foundationdb.record.planprotos.PType.PTypeCode;
import com.apple.foundationdb.record.planprotos.PType.PUuidType;
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordVersion;
import com.apple.foundationdb.record.query.plan.explain.DefaultExplainFormatter;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokens;
import com.apple.foundationdb.record.query.plan.cascades.Narrowable;
import com.apple.foundationdb.record.query.plan.cascades.NullableArrayTypeUtils;
import com.apple.foundationdb.record.query.plan.cascades.values.PromoteValue;
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.apple.foundationdb.record.query.plan.explain.DefaultExplainFormatter;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokens;
import com.apple.foundationdb.record.query.plan.serialization.PlanSerialization;
import com.apple.foundationdb.record.util.ProtoUtils;
import com.apple.foundationdb.util.StringUtils;
Expand Down Expand Up @@ -93,6 +95,13 @@ public interface Type extends Narrowable<Type>, PlanSerializable {
@Nonnull
None NONE = new None();

@Nonnull
Uuid UUID_NULL_INSTANCE = new Uuid(true);

@Nonnull
Uuid UUID_NON_NULL_INSTANCE = new Uuid(false);


/**
* A map from Java {@link Class} to corresponding {@link TypeCode}.
*/
Expand Down Expand Up @@ -169,6 +178,15 @@ default boolean isEnum() {
return getTypeCode().equals(TypeCode.ENUM);
}

/**
* Checks whether a {@link Type} is {@link Enum}.
*
* @return <code>true</code> if the {@link Type} is {@link Enum}, otherwise <code>false</code>.
*/
default boolean isUuid() {
return false;
}

/**
* Checks whether a {@link Type} is nullable.
*
Expand Down Expand Up @@ -322,6 +340,15 @@ static None noneType() {
return Type.NONE;
}

@Nonnull
static Uuid uuidType(boolean withNullability) {
if (withNullability) {
return UUID_NULL_INSTANCE;
} else {
return UUID_NON_NULL_INSTANCE;
}
}

/**
* For a given {@link TypeCode}, it returns a corresponding <i>nullable</i> {@link Type}.
* <br>
Expand Down Expand Up @@ -381,23 +408,23 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri
if (protoLabel == FieldDescriptorProto.Label.LABEL_REPEATED) {
// collection type
return fromProtoTypeToArray(descriptor, protoType, typeCode, false);
} else {
if (typeCode.isPrimitive()) {
return primitiveType(typeCode, isNullable);
} else if (typeCode == TypeCode.ENUM) {
final var enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor);
return Enum.fromProtoValues(isNullable, enumDescriptor.getValues());
} else if (typeCode == TypeCode.RECORD) {
Objects.requireNonNull(descriptor);
final var messageDescriptor = (Descriptors.Descriptor)descriptor;
if (NullableArrayTypeUtils.describesWrappedArray(messageDescriptor)) {
// find TypeCode of array elements
final var elementField = messageDescriptor.findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName());
final var elementTypeCode = TypeCode.fromProtobufType(elementField.getType());
return fromProtoTypeToArray(descriptor, protoType, elementTypeCode, true);
} else {
return Record.fromFieldDescriptorsMap(isNullable, Record.toFieldDescriptorMap(messageDescriptor.getFields()));
}
} else if (typeCode.isPrimitive()) {
return primitiveType(typeCode, isNullable);
} else if (typeCode == TypeCode.ENUM) {
final var enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor);
return Enum.fromProtoValues(isNullable, enumDescriptor.getValues());
} else if (typeCode == TypeCode.RECORD) {
Objects.requireNonNull(descriptor);
final var messageDescriptor = (Descriptors.Descriptor)descriptor;
if (NullableArrayTypeUtils.describesWrappedArray(messageDescriptor)) {
// find TypeCode of array elements
final var elementField = messageDescriptor.findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName());
final var elementTypeCode = TypeCode.fromProtobufType(elementField.getType());
return fromProtoTypeToArray(descriptor, protoType, elementTypeCode, true);
} else if (TupleFieldsProto.UUID.getDescriptor().equals(messageDescriptor)) {
return Type.uuidType(isNullable);
} else {
return Record.fromFieldDescriptorsMap(isNullable, Record.toFieldDescriptorMap(messageDescriptor.getFields()));
}
}

Expand All @@ -411,7 +438,9 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri
* @return A {@link Array} object that corresponds to the protobuf {@link com.google.protobuf.Descriptors.Descriptor}.
*/
@Nonnull
private static Array fromProtoTypeToArray(@Nullable Descriptors.GenericDescriptor descriptor, @Nonnull Descriptors.FieldDescriptor.Type protoType, @Nonnull TypeCode typeCode, boolean isNullable) {
private static Array fromProtoTypeToArray(@Nullable Descriptors.GenericDescriptor descriptor,
@Nonnull Descriptors.FieldDescriptor.Type protoType,
@Nonnull TypeCode typeCode, boolean isNullable) {
if (typeCode.isPrimitive()) {
final var primitiveType = primitiveType(typeCode, false);
return new Array(isNullable, primitiveType);
Expand Down Expand Up @@ -504,7 +533,7 @@ static Type maximumType(@Nonnull final Type t1, @Nonnull final Type t2) {
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())) {
} else if ((t1.isPrimitive() || t1.isEnum()) || t1.isUuid() && (t2.isPrimitive() || t2.isEnum() || t2.isUuid())) {
if (t1.getTypeCode() == t2.getTypeCode()) {
return t1.withNullability(isResultNullable);
}
Expand Down Expand Up @@ -613,6 +642,9 @@ static Type fromObject(@Nullable final Object object) {
if (typeCode.isPrimitive()) {
return Type.primitiveType(typeCode, false);
}
if (typeCode == TypeCode.UUID) {
return Type.uuidType(false);
}
throw new RecordCoreException("Unable to convert value to Type")
.addLogInfo(LogMessageKeys.VALUE, object);
}
Expand Down Expand Up @@ -663,6 +695,7 @@ enum TypeCode {
VERSION(FDBRecordVersion.class, FieldDescriptorProto.Type.TYPE_BYTES, true, false),
ENUM(Enum.class, FieldDescriptorProto.Type.TYPE_ENUM, false, false),
RECORD(Message.class, null, false, false),
UUID(java.util.UUID.class, null, false, false),
ARRAY(List.class, null, false, false),
RELATION(null, null, false, false),
NONE(null, null, false, false);
Expand Down Expand Up @@ -776,22 +809,13 @@ public static TypeCode fromProtobufType(@Nonnull final Descriptors.FieldDescript
case FLOAT:
return TypeCode.FLOAT;
case INT64:
case UINT64:
case FIXED64:
case SFIXED64:
case SINT64:
return TypeCode.LONG;
case INT32:
case FIXED32:
case UINT32:
case SFIXED32:
case SINT32:
return TypeCode.INT;
case BOOL:
return TypeCode.BOOLEAN;
case STRING:
return TypeCode.STRING;
case GROUP:
case ENUM:
return TypeCode.ENUM;
case MESSAGE:
Expand Down Expand Up @@ -839,6 +863,8 @@ public PTypeCode toProto(@Nonnull final PlanSerializationContext serializationCo
return PTypeCode.RELATION;
case NONE:
return PTypeCode.NONE;
case UUID:
return PTypeCode.UUID;
default:
throw new RecordCoreException("unable to find type code mapping. did you forgot to add it here?");
}
Expand Down Expand Up @@ -876,6 +902,8 @@ public static TypeCode fromProto(@Nonnull final PlanSerializationContext seriali
return RECORD;
case ARRAY:
return ARRAY;
case UUID:
return UUID;
case RELATION:
return RELATION;
case NONE:
Expand Down Expand Up @@ -2904,4 +2932,98 @@ public Array fromProto(@Nonnull final PlanSerializationContext serializationCont
}
}
}

class Uuid implements Type {

public static final String MESSAGE_NAME = TupleFieldsProto.UUID.getDescriptor().getName();

private final boolean isNullable;

private Uuid(boolean isNullable) {
this.isNullable = isNullable;
}

@Override
public TypeCode getTypeCode() {
return TypeCode.UUID;
}

@Override
public boolean isNullable() {
return isNullable;
}

@Nonnull
@Override
public Type withNullability(final boolean newIsNullable) {
if (newIsNullable) {
return UUID_NULL_INSTANCE;
} else {
return UUID_NON_NULL_INSTANCE;
}
}

@Nonnull
@Override
public ExplainTokens describe() {
return new ExplainTokens().addKeyword(getTypeCode().toString());
}

@Override
public void addProtoField(@Nonnull final TypeRepository.Builder typeRepositoryBuilder, @Nonnull final DescriptorProto.Builder descriptorBuilder, final int fieldNumber, @Nonnull final String fieldName, @Nonnull final Optional<String> typeNameOptional, @Nonnull final FieldDescriptorProto.Label label) {
FieldDescriptorProto.Builder builder = FieldDescriptorProto.newBuilder()
.setNumber(fieldNumber)
.setName(fieldName)
.setLabel(label)
.setTypeName(TupleFieldsProto.UUID.getDescriptor().getFullName());
typeNameOptional.ifPresent(builder::setTypeName);
descriptorBuilder.addField(builder);
}

@Nonnull
@Override
public PType toTypeProto(@Nonnull final PlanSerializationContext serializationContext) {
return PType.newBuilder().setUuidType(toProto(serializationContext)).build();
}

@Nonnull
@Override
public PUuidType toProto(@Nonnull final PlanSerializationContext serializationContext) {
return PUuidType.newBuilder()
.setIsNullable(isNullable)
.build();
}

@Override
public boolean isUuid() {
return true;
}

@Nonnull
public static Uuid fromProto(@Nonnull final PlanSerializationContext serializationContext,
@Nonnull final PUuidType uuidTypeProto) {
Verify.verify(uuidTypeProto.hasIsNullable());
return Type.uuidType(uuidTypeProto.getIsNullable());
}


/**
* Deserializer.
*/
@AutoService(PlanDeserializer.class)
public static class Deserializer implements PlanDeserializer<PUuidType, Uuid> {
@Nonnull
@Override
public Class<PUuidType> getProtoMessageClass() {
return PUuidType.class;
}

@Nonnull
@Override
public Uuid fromProto(@Nonnull final PlanSerializationContext serializationContext,
@Nonnull final PUuidType uuidTypeProto) {
return Uuid.fromProto(serializationContext, uuidTypeProto);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

package com.apple.foundationdb.record.query.plan.cascades.typing;

import com.apple.foundationdb.record.TupleFieldsProto;
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import com.google.common.collect.BiMap;
Expand Down Expand Up @@ -52,6 +53,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

/**
* A utility class that enables mapping of a structured {@link Type} into a dynamically-generated equivalent Protobuf message
Expand All @@ -64,6 +66,9 @@ public class TypeRepository {
@Nonnull
public static final TypeRepository EMPTY_SCHEMA = empty();

@Nonnull
public static final List<FileDescriptor> DEPENDENCIES = List.of(TupleFieldsProto.getDescriptor());

@Nonnull
private final FileDescriptorSet fileDescSet;

Expand Down Expand Up @@ -341,13 +346,17 @@ private static Map<String, FileDescriptor> init(@Nonnull final FileDescriptorSet
List<String> dependencyList = fdProto.getDependencyList();
List<FileDescriptor> resolvedFdList = new ArrayList<>();
for (String depName : dependencyList) {
if (!allFdProtoNames.contains(depName)) {
final var dependencyMaybe = DEPENDENCIES.stream().filter(d -> d.getFullName().equals(depName)).findAny();
if (dependencyMaybe.isPresent()) {
resolvedFdList.add(dependencyMaybe.get());
} else if (allFdProtoNames.contains(depName)) {
FileDescriptor fd = resolvedFileDescMap.get(depName);
if (fd != null) {
resolvedFdList.add(fd);
}
} else {
throw new IllegalArgumentException("cannot resolve import " + depName + " in " + fdProto.getName());
}
FileDescriptor fd = resolvedFileDescMap.get(depName);
if (fd != null) {
resolvedFdList.add(fd);
}
}

if (resolvedFdList.size() == dependencyList.size()) { // dependencies resolved
Expand Down Expand Up @@ -430,6 +439,7 @@ public static class Builder {

private Builder() {
fileDescProtoBuilder = FileDescriptorProto.newBuilder();
fileDescProtoBuilder.addAllDependency(DEPENDENCIES.stream().map(FileDescriptor::getFullName).collect(Collectors.toList()));
fileDescSetBuilder = FileDescriptorSet.newBuilder();
typeToNameMap = HashBiMap.create();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.google.common.collect.Iterables;
import com.google.common.primitives.ImmutableIntArray;
import com.google.protobuf.ByteString;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.Message;

import javax.annotation.Nonnull;
Expand All @@ -63,6 +64,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -174,6 +176,14 @@ private static Object unwrapPrimitive(@Nonnull Type type, @Nullable Object field
return returnList;
} else if (type.getTypeCode() == Type.TypeCode.VERSION) {
return FDBRecordVersion.fromBytes(((ByteString)fieldValue).toByteArray(), false);
} else if (type.isUuid()) {
if (fieldValue instanceof UUID) {
return fieldValue;
}
Verify.verify(fieldValue instanceof DynamicMessage);
final var message = (DynamicMessage) fieldValue;
return new UUID((Long) message.getField(message.getDescriptorForType().findFieldByName("most_significant_bits")),
(Long) message.getField(message.getDescriptorForType().findFieldByName("least_significant_bits")));
} else {
// This also may need to turn ByteString's into byte[] for Type.TypeCode.BYTES
return fieldValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public IndexEntryObjectValue(@Nonnull final CorrelationIdentifier alias,
@Nonnull final TupleSource source,
@Nonnull final ImmutableIntArray ordinalPath,
@Nonnull final Type resultType) {
Verify.verify(resultType.isPrimitive() || resultType.isEnum());
Verify.verify(resultType.isPrimitive() || resultType.isEnum() || resultType.isUuid());
this.indexEntryAlias = alias;
this.source = source;
this.ordinalPath = ordinalPath;
Expand Down
Loading