Skip to content

Commit 20960da

Browse files
authored
Add support for UUID as primitive type in SQL (#3198)
1 parent e8ee2ce commit 20960da

File tree

54 files changed

+1344
-79
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1344
-79
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public enum ErrorCode {
5050
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."),
5151
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."),
5252
INVALID_ENUM_VALUE(12, "Invalid enum value for the enum type"),
53+
INVALID_UUID_VALUE(13, "Invalid UUID value for the UUID type"),
5354

5455
// insert, update, deletes
5556
UPDATE_TRANSFORM_AMBIGUOUS(1_000, "The transformations used in an UPDATE statement are ambiguous."),
@@ -109,7 +110,11 @@ public static void check(final boolean condition, @Nonnull final ErrorCode messa
109110

110111
public static void check(final boolean condition, @Nonnull final ErrorCode message, @Nonnull final String additionalErrorMessage) {
111112
if (!condition) {
112-
throw new SemanticException(message, additionalErrorMessage);
113+
fail(message, additionalErrorMessage);
113114
}
114115
}
116+
117+
public static void fail(@Nonnull final ErrorCode message, @Nonnull final String additionalErrorMessage) {
118+
throw new SemanticException(message, additionalErrorMessage);
119+
}
115120
}

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

Lines changed: 152 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.apple.foundationdb.record.PlanSerializable;
2525
import com.apple.foundationdb.record.PlanSerializationContext;
2626
import com.apple.foundationdb.record.RecordCoreException;
27+
import com.apple.foundationdb.record.TupleFieldsProto;
2728
import com.apple.foundationdb.record.logging.LogMessageKeys;
2829
import com.apple.foundationdb.record.planprotos.PType;
2930
import com.apple.foundationdb.record.planprotos.PType.PAnyRecordType;
@@ -36,13 +37,14 @@
3637
import com.apple.foundationdb.record.planprotos.PType.PRecordType;
3738
import com.apple.foundationdb.record.planprotos.PType.PRelationType;
3839
import com.apple.foundationdb.record.planprotos.PType.PTypeCode;
40+
import com.apple.foundationdb.record.planprotos.PType.PUuidType;
3941
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordVersion;
40-
import com.apple.foundationdb.record.query.plan.explain.DefaultExplainFormatter;
41-
import com.apple.foundationdb.record.query.plan.explain.ExplainTokens;
4242
import com.apple.foundationdb.record.query.plan.cascades.Narrowable;
4343
import com.apple.foundationdb.record.query.plan.cascades.NullableArrayTypeUtils;
4444
import com.apple.foundationdb.record.query.plan.cascades.values.PromoteValue;
4545
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
46+
import com.apple.foundationdb.record.query.plan.explain.DefaultExplainFormatter;
47+
import com.apple.foundationdb.record.query.plan.explain.ExplainTokens;
4648
import com.apple.foundationdb.record.query.plan.serialization.PlanSerialization;
4749
import com.apple.foundationdb.record.util.ProtoUtils;
4850
import com.apple.foundationdb.util.StringUtils;
@@ -93,6 +95,13 @@ public interface Type extends Narrowable<Type>, PlanSerializable {
9395
@Nonnull
9496
None NONE = new None();
9597

98+
@Nonnull
99+
Uuid UUID_NULL_INSTANCE = new Uuid(true);
100+
101+
@Nonnull
102+
Uuid UUID_NON_NULL_INSTANCE = new Uuid(false);
103+
104+
96105
/**
97106
* A map from Java {@link Class} to corresponding {@link TypeCode}.
98107
*/
@@ -169,6 +178,15 @@ default boolean isEnum() {
169178
return getTypeCode().equals(TypeCode.ENUM);
170179
}
171180

181+
/**
182+
* Checks whether a {@link Type} is {@link Uuid}.
183+
*
184+
* @return <code>true</code> if the {@link Type} is {@link Uuid}, otherwise <code>false</code>.
185+
*/
186+
default boolean isUuid() {
187+
return false;
188+
}
189+
172190
/**
173191
* Checks whether a {@link Type} is nullable.
174192
*
@@ -322,6 +340,15 @@ static None noneType() {
322340
return Type.NONE;
323341
}
324342

343+
@Nonnull
344+
static Uuid uuidType(boolean withNullability) {
345+
if (withNullability) {
346+
return UUID_NULL_INSTANCE;
347+
} else {
348+
return UUID_NON_NULL_INSTANCE;
349+
}
350+
}
351+
325352
/**
326353
* For a given {@link TypeCode}, it returns a corresponding <i>nullable</i> {@link Type}.
327354
* <br>
@@ -381,23 +408,23 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri
381408
if (protoLabel == FieldDescriptorProto.Label.LABEL_REPEATED) {
382409
// collection type
383410
return fromProtoTypeToArray(descriptor, protoType, typeCode, false);
384-
} else {
385-
if (typeCode.isPrimitive()) {
386-
return primitiveType(typeCode, isNullable);
387-
} else if (typeCode == TypeCode.ENUM) {
388-
final var enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor);
389-
return Enum.fromProtoValues(isNullable, enumDescriptor.getValues());
390-
} else if (typeCode == TypeCode.RECORD) {
391-
Objects.requireNonNull(descriptor);
392-
final var messageDescriptor = (Descriptors.Descriptor)descriptor;
393-
if (NullableArrayTypeUtils.describesWrappedArray(messageDescriptor)) {
394-
// find TypeCode of array elements
395-
final var elementField = messageDescriptor.findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName());
396-
final var elementTypeCode = TypeCode.fromProtobufType(elementField.getType());
397-
return fromProtoTypeToArray(descriptor, protoType, elementTypeCode, true);
398-
} else {
399-
return Record.fromFieldDescriptorsMap(isNullable, Record.toFieldDescriptorMap(messageDescriptor.getFields()));
400-
}
411+
} else if (typeCode.isPrimitive()) {
412+
return primitiveType(typeCode, isNullable);
413+
} else if (typeCode == TypeCode.ENUM) {
414+
final var enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor);
415+
return Enum.fromProtoValues(isNullable, enumDescriptor.getValues());
416+
} else if (typeCode == TypeCode.RECORD) {
417+
Objects.requireNonNull(descriptor);
418+
final var messageDescriptor = (Descriptors.Descriptor)descriptor;
419+
if (NullableArrayTypeUtils.describesWrappedArray(messageDescriptor)) {
420+
// find TypeCode of array elements
421+
final var elementField = messageDescriptor.findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName());
422+
final var elementTypeCode = TypeCode.fromProtobufType(elementField.getType());
423+
return fromProtoTypeToArray(descriptor, protoType, elementTypeCode, true);
424+
} else if (TupleFieldsProto.UUID.getDescriptor().equals(messageDescriptor)) {
425+
return Type.uuidType(isNullable);
426+
} else {
427+
return Record.fromFieldDescriptorsMap(isNullable, Record.toFieldDescriptorMap(messageDescriptor.getFields()));
401428
}
402429
}
403430

@@ -411,7 +438,9 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri
411438
* @return A {@link Array} object that corresponds to the protobuf {@link com.google.protobuf.Descriptors.Descriptor}.
412439
*/
413440
@Nonnull
414-
private static Array fromProtoTypeToArray(@Nullable Descriptors.GenericDescriptor descriptor, @Nonnull Descriptors.FieldDescriptor.Type protoType, @Nonnull TypeCode typeCode, boolean isNullable) {
441+
private static Array fromProtoTypeToArray(@Nullable Descriptors.GenericDescriptor descriptor,
442+
@Nonnull Descriptors.FieldDescriptor.Type protoType,
443+
@Nonnull TypeCode typeCode, boolean isNullable) {
415444
if (typeCode.isPrimitive()) {
416445
final var primitiveType = primitiveType(typeCode, false);
417446
return new Array(isNullable, primitiveType);
@@ -504,7 +533,7 @@ static Type maximumType(@Nonnull final Type t1, @Nonnull final Type t2) {
504533
return t2EnumValues == null ? t1Enum.withNullability(isResultNullable) : null;
505534
}
506535
return t1EnumValues.equals(t2EnumValues) ? t1Enum.withNullability(isResultNullable) : null;
507-
} else if ((t1.isPrimitive() || t1.isEnum()) && (t2.isPrimitive() || t2.isEnum())) {
536+
} else if ((t1.isPrimitive() || t1.isEnum()) || t1.isUuid() && (t2.isPrimitive() || t2.isEnum() || t2.isUuid())) {
508537
if (t1.getTypeCode() == t2.getTypeCode()) {
509538
return t1.withNullability(isResultNullable);
510539
}
@@ -613,6 +642,9 @@ static Type fromObject(@Nullable final Object object) {
613642
if (typeCode.isPrimitive()) {
614643
return Type.primitiveType(typeCode, false);
615644
}
645+
if (typeCode == TypeCode.UUID) {
646+
return Type.uuidType(false);
647+
}
616648
throw new RecordCoreException("Unable to convert value to Type")
617649
.addLogInfo(LogMessageKeys.VALUE, object);
618650
}
@@ -663,6 +695,7 @@ enum TypeCode {
663695
VERSION(FDBRecordVersion.class, FieldDescriptorProto.Type.TYPE_BYTES, true, false),
664696
ENUM(Enum.class, FieldDescriptorProto.Type.TYPE_ENUM, false, false),
665697
RECORD(Message.class, null, false, false),
698+
UUID(java.util.UUID.class, null, false, false),
666699
ARRAY(List.class, null, false, false),
667700
RELATION(null, null, false, false),
668701
NONE(null, null, false, false);
@@ -839,6 +872,8 @@ public PTypeCode toProto(@Nonnull final PlanSerializationContext serializationCo
839872
return PTypeCode.RELATION;
840873
case NONE:
841874
return PTypeCode.NONE;
875+
case UUID:
876+
return PTypeCode.UUID;
842877
default:
843878
throw new RecordCoreException("unable to find type code mapping. did you forgot to add it here?");
844879
}
@@ -876,6 +911,8 @@ public static TypeCode fromProto(@Nonnull final PlanSerializationContext seriali
876911
return RECORD;
877912
case ARRAY:
878913
return ARRAY;
914+
case UUID:
915+
return UUID;
879916
case RELATION:
880917
return RELATION;
881918
case NONE:
@@ -2904,4 +2941,98 @@ public Array fromProto(@Nonnull final PlanSerializationContext serializationCont
29042941
}
29052942
}
29062943
}
2944+
2945+
class Uuid implements Type {
2946+
2947+
public static final String MESSAGE_NAME = TupleFieldsProto.UUID.getDescriptor().getName();
2948+
2949+
private final boolean isNullable;
2950+
2951+
private Uuid(boolean isNullable) {
2952+
this.isNullable = isNullable;
2953+
}
2954+
2955+
@Override
2956+
public TypeCode getTypeCode() {
2957+
return TypeCode.UUID;
2958+
}
2959+
2960+
@Override
2961+
public boolean isNullable() {
2962+
return isNullable;
2963+
}
2964+
2965+
@Nonnull
2966+
@Override
2967+
public Type withNullability(final boolean newIsNullable) {
2968+
if (newIsNullable) {
2969+
return UUID_NULL_INSTANCE;
2970+
} else {
2971+
return UUID_NON_NULL_INSTANCE;
2972+
}
2973+
}
2974+
2975+
@Nonnull
2976+
@Override
2977+
public ExplainTokens describe() {
2978+
return new ExplainTokens().addKeyword(getTypeCode().toString());
2979+
}
2980+
2981+
@Override
2982+
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) {
2983+
FieldDescriptorProto.Builder builder = FieldDescriptorProto.newBuilder()
2984+
.setNumber(fieldNumber)
2985+
.setName(fieldName)
2986+
.setLabel(label)
2987+
.setTypeName(TupleFieldsProto.UUID.getDescriptor().getFullName());
2988+
typeNameOptional.ifPresent(builder::setTypeName);
2989+
descriptorBuilder.addField(builder);
2990+
}
2991+
2992+
@Nonnull
2993+
@Override
2994+
public PType toTypeProto(@Nonnull final PlanSerializationContext serializationContext) {
2995+
return PType.newBuilder().setUuidType(toProto(serializationContext)).build();
2996+
}
2997+
2998+
@Nonnull
2999+
@Override
3000+
public PUuidType toProto(@Nonnull final PlanSerializationContext serializationContext) {
3001+
return PUuidType.newBuilder()
3002+
.setIsNullable(isNullable)
3003+
.build();
3004+
}
3005+
3006+
@Override
3007+
public boolean isUuid() {
3008+
return true;
3009+
}
3010+
3011+
@Nonnull
3012+
public static Uuid fromProto(@Nonnull final PlanSerializationContext serializationContext,
3013+
@Nonnull final PUuidType uuidTypeProto) {
3014+
Verify.verify(uuidTypeProto.hasIsNullable());
3015+
return Type.uuidType(uuidTypeProto.getIsNullable());
3016+
}
3017+
3018+
3019+
/**
3020+
* Deserializer.
3021+
*/
3022+
@AutoService(PlanDeserializer.class)
3023+
public static class Deserializer implements PlanDeserializer<PUuidType, Uuid> {
3024+
@Nonnull
3025+
@Override
3026+
public Class<PUuidType> getProtoMessageClass() {
3027+
return PUuidType.class;
3028+
}
3029+
3030+
@Nonnull
3031+
@Override
3032+
public Uuid fromProto(@Nonnull final PlanSerializationContext serializationContext,
3033+
@Nonnull final PUuidType uuidTypeProto) {
3034+
return Uuid.fromProto(serializationContext, uuidTypeProto);
3035+
}
3036+
}
3037+
}
29073038
}

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

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

23+
import com.apple.foundationdb.record.TupleFieldsProto;
2324
import com.google.common.base.Preconditions;
2425
import com.google.common.base.Verify;
2526
import com.google.common.collect.BiMap;
@@ -52,6 +53,7 @@
5253
import java.util.Optional;
5354
import java.util.Set;
5455
import java.util.TreeSet;
56+
import java.util.stream.Collectors;
5557

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

69+
@Nonnull
70+
public static final List<FileDescriptor> DEPENDENCIES = List.of(TupleFieldsProto.getDescriptor());
71+
6772
@Nonnull
6873
private final FileDescriptorSet fileDescSet;
6974

@@ -341,13 +346,17 @@ private static Map<String, FileDescriptor> init(@Nonnull final FileDescriptorSet
341346
List<String> dependencyList = fdProto.getDependencyList();
342347
List<FileDescriptor> resolvedFdList = new ArrayList<>();
343348
for (String depName : dependencyList) {
344-
if (!allFdProtoNames.contains(depName)) {
349+
final var dependencyMaybe = DEPENDENCIES.stream().filter(d -> d.getFullName().equals(depName)).findAny();
350+
if (dependencyMaybe.isPresent()) {
351+
resolvedFdList.add(dependencyMaybe.get());
352+
} else if (allFdProtoNames.contains(depName)) {
353+
FileDescriptor fd = resolvedFileDescMap.get(depName);
354+
if (fd != null) {
355+
resolvedFdList.add(fd);
356+
}
357+
} else {
345358
throw new IllegalArgumentException("cannot resolve import " + depName + " in " + fdProto.getName());
346359
}
347-
FileDescriptor fd = resolvedFileDescMap.get(depName);
348-
if (fd != null) {
349-
resolvedFdList.add(fd);
350-
}
351360
}
352361

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

431440
private Builder() {
432441
fileDescProtoBuilder = FileDescriptorProto.newBuilder();
442+
fileDescProtoBuilder.addAllDependency(DEPENDENCIES.stream().map(FileDescriptor::getFullName).collect(Collectors.toList()));
433443
fileDescSetBuilder = FileDescriptorSet.newBuilder();
434444
typeToNameMap = HashBiMap.create();
435445
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import java.util.List;
6464
import java.util.Objects;
6565
import java.util.Optional;
66+
import java.util.UUID;
6667
import java.util.function.Supplier;
6768
import java.util.stream.Collectors;
6869

@@ -174,6 +175,9 @@ private static Object unwrapPrimitive(@Nonnull Type type, @Nullable Object field
174175
return returnList;
175176
} else if (type.getTypeCode() == Type.TypeCode.VERSION) {
176177
return FDBRecordVersion.fromBytes(((ByteString)fieldValue).toByteArray(), false);
178+
} else if (type.isUuid()) {
179+
Verify.verify(fieldValue instanceof UUID);
180+
return fieldValue;
177181
} else {
178182
// This also may need to turn ByteString's into byte[] for Type.TypeCode.BYTES
179183
return fieldValue;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public IndexEntryObjectValue(@Nonnull final CorrelationIdentifier alias,
7979
@Nonnull final TupleSource source,
8080
@Nonnull final ImmutableIntArray ordinalPath,
8181
@Nonnull final Type resultType) {
82-
Verify.verify(resultType.isPrimitive() || resultType.isEnum());
82+
Verify.verify(resultType.isPrimitive() || resultType.isEnum() || resultType.isUuid());
8383
this.indexEntryAlias = alias;
8484
this.source = source;
8585
this.ordinalPath = ordinalPath;

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,11 @@ public static <M extends Message> Object transformMessage(@Nonnull final FDBReco
360360
} else {
361361
if (currentMessage != null) {
362362
final var currentFieldType = Verify.verifyNotNull(currentRecordType.getField(messageFieldDescriptor.getIndex())).getFieldType();
363-
final var fieldResult = NullableArrayTypeUtils.unwrapIfArray(getFieldOnMessage(currentMessage, messageFieldDescriptor), currentFieldType);
363+
// If the field type is a message, then it could be one of the special types, like UUID. We do not
364+
// want to get the value of that field as a 'runtime' type, hence we get the raw message itself.
365+
var fieldResult = messageFieldDescriptor.isRepeated() || !messageFieldDescriptor.getType().equals(Descriptors.FieldDescriptor.Type.MESSAGE) ?
366+
getFieldOnMessage(currentMessage, messageFieldDescriptor) : getFieldMessageOnMessage(currentMessage, messageFieldDescriptor);
367+
fieldResult = NullableArrayTypeUtils.unwrapIfArray(fieldResult, currentFieldType);
364368
final var coercedObject =
365369
coerceObject(promotionTrieForField, targetFieldType, targetDescriptorForField, currentFieldType, fieldResult);
366370
if (coercedObject != null) {
@@ -413,6 +417,12 @@ public static Object coerceObject(@Nullable final CoercionTrieNode coercionsTrie
413417
return Verify.verifyNotNull(coercionFunction.apply(null, current));
414418
}
415419

420+
// Uuid
421+
if (targetType.isUuid()) {
422+
final var coercionFunction = Verify.verifyNotNull(coercionsTrie.getValue());
423+
return Verify.verifyNotNull(coercionFunction.apply(null, current));
424+
}
425+
416426
//
417427
// This is another leaf case: The target can be an Enum,
418428
if (targetType.isEnum()) {

0 commit comments

Comments
 (0)