diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java index 9abd194543..ff4fbc1af1 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java @@ -33,6 +33,7 @@ import com.apple.foundationdb.record.query.plan.cascades.KeyExpressionVisitor; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.expressions.ExplodeExpression; +import com.apple.foundationdb.record.util.ProtoUtils; import com.google.common.collect.ImmutableList; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; @@ -57,6 +58,13 @@ public class FieldKeyExpression extends BaseKeyExpression implements AtomKeyExpression, KeyExpressionWithoutChildren { private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Field-Key-Expression"); + /** + * The internal field name used in the underlying protobuf message. This name may differ from the user-visible + * identifier to ensure compliance with protobuf field naming conventions. When working with query planning or + * user-facing operations, use {@link ProtoUtils#toUserIdentifier(String)} to convert this to the user-visible + * form. However, when constructing physical operators that directly interact with stored protobuf messages, + * this internal name should be used as-is. + */ @Nonnull private final String fieldName; @Nonnull @@ -216,7 +224,7 @@ public R expand(@Nonnull final KeyExpr public Quantifier.ForEach explodeField(@Nonnull Quantifier.ForEach baseQuantifier, @Nonnull List fieldNamePrefix) { final List fieldNames = ImmutableList.builder() .addAll(fieldNamePrefix) - .add(fieldName) + .add(ProtoUtils.toUserIdentifier(fieldName)) .build(); switch (fanType) { case FanOut: diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/KeyExpressionExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/KeyExpressionExpansionVisitor.java index bba18aff6f..19b2666024 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/KeyExpressionExpansionVisitor.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/KeyExpressionExpansionVisitor.java @@ -40,6 +40,7 @@ import com.apple.foundationdb.record.query.plan.cascades.values.EmptyValue; import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.util.ProtoUtils; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -119,7 +120,7 @@ public GraphExpansion visitExpression(@Nonnull final EmptyKeyExpression emptyKey @Nonnull @Override public GraphExpansion visitExpression(@Nonnull FieldKeyExpression fieldKeyExpression) { - final String fieldName = fieldKeyExpression.getFieldName(); + final String fieldName = ProtoUtils.toUserIdentifier(fieldKeyExpression.getFieldName()); final KeyExpression.FanType fanType = fieldKeyExpression.getFanType(); final VisitorState state = getCurrentState(); final List fieldNamePrefix = state.getFieldNamePrefix(); @@ -241,7 +242,7 @@ public GraphExpansion visitExpression(@Nonnull final NestingKeyExpression nestin case None: List newPrefix = ImmutableList.builder() .addAll(fieldNamePrefix) - .add(parent.getFieldName()) + .add(ProtoUtils.toUserIdentifier((parent.getFieldName()))) .build(); if (NullableArrayTypeUtils.isArrayWrapper(nestingKeyExpression)) { final RecordKeyExpressionProto.KeyExpression childProto = nestingKeyExpression.getChild().toKeyExpression(); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ScalarTranslationVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ScalarTranslationVisitor.java index 994b383d0b..da6b583626 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ScalarTranslationVisitor.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ScalarTranslationVisitor.java @@ -35,6 +35,7 @@ import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.util.ProtoUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -124,7 +125,7 @@ public Value visitExpression(@Nonnull FieldKeyExpression fieldKeyExpression) { } final ScalarVisitorState state = getCurrentState(); - final String fieldName = fieldKeyExpression.getFieldName(); + final String fieldName = ProtoUtils.toUserIdentifier(fieldKeyExpression.getFieldName()); final List fieldNamePrefix = state.getFieldNamePrefix(); final List fieldNames = ImmutableList.builder() .addAll(fieldNamePrefix) @@ -169,9 +170,10 @@ public Value visitExpression(@Nonnull final NestingKeyExpression nestingKeyExpre final ScalarVisitorState state = getCurrentState(); final List fieldNamePrefix = state.getFieldNamePrefix(); final KeyExpression child = nestingKeyExpression.getChild(); + final String parentFieldName = ProtoUtils.toUserIdentifier(parent.getFieldName()); final List newPrefix = ImmutableList.builder() .addAll(fieldNamePrefix) - .add(parent.getFieldName()) + .add(parentFieldName) .build(); // TODO resolve type return pop(child.expand(push(state.withFieldNamePrefix(newPrefix)))); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/CompatibleTypeEvolutionPredicate.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/CompatibleTypeEvolutionPredicate.java index 4b70cd80f2..413978bda6 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/CompatibleTypeEvolutionPredicate.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/predicates/CompatibleTypeEvolutionPredicate.java @@ -275,7 +275,7 @@ private static List computeFieldAccessForDerivation( Verify.verify(type.isRecord()); final var field = ((Type.Record)type).getField(fieldAccessor.getOrdinal()); currentTrieBuilder = - currentTrieBuilder.compute(FieldValue.ResolvedAccessor.of(field.getFieldName(), fieldAccessor.getOrdinal(), fieldAccessor.getType()), + currentTrieBuilder.compute(FieldValue.ResolvedAccessor.of(field, fieldAccessor.getOrdinal()), (resolvedAccessor, oldTrieBuilder) -> { if (oldTrieBuilder == null) { return new FieldAccessTrieNodeBuilder(null, null, field.getFieldType()); 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 f273be7698..ec905da4b8 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 @@ -425,11 +425,12 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri @Nonnull Descriptors.FieldDescriptor.Type protoType, @Nonnull FieldDescriptorProto.Label protoLabel, @Nullable DescriptorProtos.FieldOptions fieldOptions, - boolean isNullable) { + boolean isNullable, + boolean preserveNames) { final var typeCode = TypeCode.fromProtobufFieldDescriptor(protoType, fieldOptions); if (protoLabel == FieldDescriptorProto.Label.LABEL_REPEATED) { // collection type - return fromProtoTypeToArray(descriptor, protoType, typeCode, fieldOptions, false); + return fromProtoTypeToArray(descriptor, protoType, typeCode, fieldOptions, false, preserveNames); } else if (typeCode.isPrimitive()) { final var fieldOptionMaybe = Optional.ofNullable(fieldOptions).map(f -> f.getExtension(RecordMetaDataOptionsProto.field)); if (fieldOptionMaybe.isPresent() && fieldOptionMaybe.get().hasVectorOptions()) { @@ -439,7 +440,7 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri return primitiveType(typeCode, isNullable); } else if (typeCode == TypeCode.ENUM) { final var enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor); - return Enum.fromProtoValues(isNullable, enumDescriptor.getValues()); + return preserveNames ? Enum.fromDescriptorPreservingNames(isNullable, enumDescriptor) : Enum.fromDescriptor(isNullable, enumDescriptor); } else if (typeCode == TypeCode.RECORD) { Objects.requireNonNull(descriptor); final var messageDescriptor = (Descriptors.Descriptor)descriptor; @@ -447,11 +448,12 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri // find TypeCode of array elements final var elementField = messageDescriptor.findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName()); final var elementTypeCode = TypeCode.fromProtobufFieldDescriptor(elementField.getType(), elementField.getOptions()); - return fromProtoTypeToArray(descriptor, protoType, elementTypeCode, elementField.getOptions(), true); + return fromProtoTypeToArray(descriptor, protoType, elementTypeCode, elementField.getOptions(), true, preserveNames); } else if (TupleFieldsProto.UUID.getDescriptor().equals(messageDescriptor)) { return Type.uuidType(isNullable); } else { - return Record.fromFieldDescriptorsMap(isNullable, Record.toFieldDescriptorMap(messageDescriptor.getFields())); + final Type.Record recordType = preserveNames ? Record.fromDescriptorPreservingName(messageDescriptor) : Record.fromDescriptor(messageDescriptor); + return recordType.withNullability(isNullable); } } @@ -469,7 +471,8 @@ private static Array fromProtoTypeToArray(@Nullable Descriptors.GenericDescripto @Nonnull Descriptors.FieldDescriptor.Type protoType, @Nonnull TypeCode typeCode, @Nullable DescriptorProtos.FieldOptions fieldOptions, - boolean isNullable) { + boolean isNullable, + boolean preserveNames) { if (typeCode.isPrimitive()) { final Type type; if (typeCode == TypeCode.VECTOR) { @@ -480,18 +483,27 @@ private static Array fromProtoTypeToArray(@Nullable Descriptors.GenericDescripto } return new Array(isNullable, type); } else if (typeCode == TypeCode.ENUM) { - final var enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor); - final var enumType = Enum.fromProtoValues(false, enumDescriptor.getValues()); + final Descriptors.EnumDescriptor enumDescriptor; + if (isNullable) { + // Unwrap the nullable array + enumDescriptor = ((Descriptors.Descriptor)Objects.requireNonNull(descriptor)).findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName()).getEnumType(); + } else { + enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor); + } + Objects.requireNonNull(enumDescriptor); + final var enumType = preserveNames ? Enum.fromDescriptorPreservingNames(false, enumDescriptor) : Enum.fromDescriptor(false, enumDescriptor); return new Array(isNullable, enumType); } else { + final Descriptors.Descriptor recordDescriptor; if (isNullable) { - Descriptors.Descriptor wrappedDescriptor = ((Descriptors.Descriptor)Objects.requireNonNull(descriptor)).findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName()).getMessageType(); - Objects.requireNonNull(wrappedDescriptor); - return new Array(true, fromProtoType(wrappedDescriptor, Descriptors.FieldDescriptor.Type.MESSAGE, FieldDescriptorProto.Label.LABEL_OPTIONAL, fieldOptions, false)); + // Unwrap the nullable array + recordDescriptor = ((Descriptors.Descriptor)Objects.requireNonNull(descriptor)).findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName()).getMessageType(); + protoType = Descriptors.FieldDescriptor.Type.MESSAGE; } else { - // case 2: any arbitrary sub message we don't understand - return new Array(false, fromProtoType(descriptor, protoType, FieldDescriptorProto.Label.LABEL_OPTIONAL, fieldOptions, false)); + recordDescriptor = (Descriptors.Descriptor) descriptor; } + Objects.requireNonNull(recordDescriptor); + return new Array(isNullable, fromProtoType(recordDescriptor, protoType, FieldDescriptorProto.Label.LABEL_OPTIONAL, fieldOptions, false, preserveNames)); } } @@ -1743,6 +1755,8 @@ class Enum implements Type { final List enumValues; @Nullable final String name; + @Nullable + final String storageName; /** * Memoized hash function. @@ -1750,17 +1764,19 @@ class Enum implements Type { @Nonnull private final Supplier hashFunctionSupplier = Suppliers.memoize(this::computeHashCode); - public Enum(final boolean isNullable, - @Nullable final List enumValues) { - this(isNullable, enumValues, null); + private Enum(final boolean isNullable, + @Nullable final List enumValues) { + this(isNullable, enumValues, null, null); } public Enum(final boolean isNullable, @Nullable final List enumValues, - @Nullable final String name) { + @Nullable final String name, + @Nullable final String storageName) { this.isNullable = isNullable; this.enumValues = enumValues; this.name = name; + this.storageName = storageName; } @Override @@ -1789,7 +1805,10 @@ public boolean isNullable() { @Nonnull @Override public Enum withNullability(final boolean newIsNullable) { - return new Enum(newIsNullable, enumValues, name); + if (newIsNullable == isNullable()) { + return this; + } + return new Enum(newIsNullable, enumValues, name, storageName); } @Nullable @@ -1797,16 +1816,21 @@ public String getName() { return name; } + @Nullable + public String getStorageName() { + return storageName; + } + @Override public void defineProtoType(@Nonnull final TypeRepository.Builder typeRepositoryBuilder) { Verify.verify(!isErased()); - final var typeName = name == null ? ProtoUtils.uniqueTypeName() : name; + final var typeName = storageName == null ? ProtoUtils.uniqueTypeName() : storageName; final var enumDescriptorProtoBuilder = DescriptorProtos.EnumDescriptorProto.newBuilder(); enumDescriptorProtoBuilder.setName(typeName); for (final var enumValue : Objects.requireNonNull(enumValues)) { enumDescriptorProtoBuilder.addValue(DescriptorProtos.EnumValueDescriptorProto.newBuilder() - .setName(enumValue.getName()) + .setName(enumValue.getStorageName()) .setNumber(enumValue.getNumber())); } @@ -1887,19 +1911,26 @@ public static > Enum forJavaEnum(@Nonnull final Clas T[] enumConstants = enumClass.getEnumConstants(); for (int i = 0; i < enumConstants.length; i++) { final var enumConstant = enumConstants[i]; - enumValuesBuilder.add(new EnumValue(enumConstant.name(), i)); + enumValuesBuilder.add(EnumValue.from(enumConstant.name(), i)); } - return new Enum(false, enumValuesBuilder.build(), null); + return new Enum(false, enumValuesBuilder.build(), null, null); } - private static Enum fromProtoValues(boolean isNullable, @Nonnull List values) { - return new Enum(isNullable, enumValuesFromProto(values), null); + @Nonnull + public static Enum fromDescriptor(boolean isNullable, @Nonnull Descriptors.EnumDescriptor enumDescriptor) { + return Enum.fromValues(isNullable, enumValuesFromProto(enumDescriptor.getValues())); } + @Nonnull + public static Enum fromDescriptorPreservingNames(boolean isNullable, @Nonnull Descriptors.EnumDescriptor enumDescriptor) { + return new Type.Enum(isNullable, enumValuesFromProto(enumDescriptor.getValues()), ProtoUtils.toUserIdentifier(enumDescriptor.getName()), enumDescriptor.getName()); + } + + @Nonnull public static List enumValuesFromProto(@Nonnull final List enumValueDescriptors) { return enumValueDescriptors .stream() - .map(enumValueDescriptor -> new EnumValue(enumValueDescriptor.getName(), enumValueDescriptor.getNumber())) + .map(enumValueDescriptor -> new EnumValue(ProtoUtils.toUserIdentifier(enumValueDescriptor.getName()), enumValueDescriptor.getName(), enumValueDescriptor.getNumber())) .collect(ImmutableList.toImmutableList()); } @@ -1914,6 +1945,9 @@ public PEnumType toProto(@Nonnull final PlanSerializationContext serializationCo if (name != null) { enumTypeProtoBuilder.setName(name); } + if (storageName != null && !Objects.equals(storageName, name)) { + enumTypeProtoBuilder.setStorageName(storageName); + } return enumTypeProtoBuilder.build(); } @@ -1933,8 +1967,19 @@ public static Enum fromProto(@Nonnull final PlanSerializationContext serializati } final ImmutableList enumValues = enumValuesBuilder.build(); Verify.verify(!enumValues.isEmpty()); - return new Enum(enumTypeProto.getIsNullable(), enumValues, - PlanSerialization.getFieldOrNull(enumTypeProto, PEnumType::hasName, PEnumType::getName)); + String name = PlanSerialization.getFieldOrNull(enumTypeProto, PEnumType::hasName, PEnumType::getName); + String storageName = enumTypeProto.hasStorageName() ? enumTypeProto.getStorageName() : name; + return new Enum(enumTypeProto.getIsNullable(), enumValues, name, storageName); + } + + @Nonnull + public static Type.Enum fromValues(boolean isNullable, @Nonnull List enumValues) { + return new Type.Enum(isNullable, enumValues); + } + + @Nonnull + public static Type.Enum fromValuesWithName(@Nonnull String name, boolean isNullable, @Nonnull List enumValues) { + return new Type.Enum(isNullable, enumValues, name, ProtoUtils.toProtoBufCompliantName(name)); } /** @@ -1962,10 +2007,13 @@ public Enum fromProto(@Nonnull final PlanSerializationContext serializationConte public static class EnumValue implements PlanSerializable { @Nonnull final String name; + @Nonnull + final String storageName; final int number; - public EnumValue(@Nonnull final String name, final int number) { + EnumValue(@Nonnull final String name, @Nonnull String storageName, final int number) { this.name = name; + this.storageName = storageName; this.number = number; } @@ -1974,6 +2022,11 @@ public String getName() { return name; } + @Nonnull + public String getStorageName() { + return storageName; + } + public int getNumber() { return number; } @@ -2004,14 +2057,27 @@ public String toString() { @Nonnull @Override public PEnumType.PEnumValue toProto(@Nonnull final PlanSerializationContext serializationContext) { - return PEnumType.PEnumValue.newBuilder().setName(name).setNumber(number).build(); + PEnumType.PEnumValue.Builder enumValueBuilder = PEnumType.PEnumValue.newBuilder() + .setName(name) + .setNumber(number); + if (!Objects.equals(storageName, name)) { + enumValueBuilder.setStorageName(storageName); + } + return enumValueBuilder.build(); } @Nonnull @SuppressWarnings("unused") public static EnumValue fromProto(@Nonnull final PlanSerializationContext serializationContext, @Nonnull final PEnumType.PEnumValue enumValueProto) { - return new EnumValue(enumValueProto.getName(), enumValueProto.getNumber()); + final String name = enumValueProto.getName(); + final String storageName = enumValueProto.hasStorageName() ? enumValueProto.getStorageName() : name; + return new EnumValue(name, storageName, enumValueProto.getNumber()); + } + + @Nonnull + public static EnumValue from(@Nonnull String name, int number) { + return new EnumValue(name, ProtoUtils.toProtoBufCompliantName(name), number); } } } @@ -2022,6 +2088,8 @@ public static EnumValue fromProto(@Nonnull final PlanSerializationContext serial class Record implements Type, Erasable { @Nullable private final String name; + @Nullable + private final String storageName; /** * indicates whether the {@link Record} type instance is nullable or not. @@ -2068,7 +2136,7 @@ private int computeHashCode() { * @param normalizedFields The list of {@link Record} {@link Field}s. */ protected Record(final boolean isNullable, @Nullable final List normalizedFields) { - this(null, isNullable, normalizedFields); + this(null, null, isNullable, normalizedFields); } /** @@ -2077,8 +2145,9 @@ protected Record(final boolean isNullable, @Nullable final List normalize * @param isNullable True if the record type is nullable, otherwise false. * @param normalizedFields The list of {@link Record} {@link Field}s. */ - protected Record(@Nullable final String name, final boolean isNullable, @Nullable final List normalizedFields) { + protected Record(@Nullable final String name, @Nullable final String storageName, final boolean isNullable, @Nullable final List normalizedFields) { this.name = name; + this.storageName = storageName; this.isNullable = isNullable; this.fields = normalizedFields; this.fieldNameFieldMapSupplier = Suppliers.memoize(this::computeFieldNameFieldMap); @@ -2109,12 +2178,12 @@ public Record withNullability(final boolean newIsNullable) { if (isNullable == newIsNullable) { return this; } - return new Record(name, newIsNullable, fields); + return new Record(name, storageName, newIsNullable, fields); } @Nonnull public Record withName(@Nonnull final String name) { - return new Record(name, isNullable, fields); + return new Record(name, ProtoUtils.toProtoBufCompliantName(name), isNullable, fields); } @Nullable @@ -2122,6 +2191,11 @@ public String getName() { return name; } + @Nullable + public String getStorageName() { + return storageName; + } + /** * Returns the list of {@link Record} {@link Field}s. * @return the list of {@link Record} {@link Field}s. @@ -2228,13 +2302,13 @@ public boolean isErased() { @Override public void defineProtoType(final TypeRepository.Builder typeRepositoryBuilder) { Objects.requireNonNull(fields); - final var typeName = name == null ? ProtoUtils.uniqueTypeName() : name; + final var typeName = storageName == null ? ProtoUtils.uniqueTypeName() : storageName; final var recordMsgBuilder = DescriptorProto.newBuilder(); recordMsgBuilder.setName(typeName); for (final var field : fields) { final var fieldType = field.getFieldType(); - final var fieldName = field.getFieldName(); + final var fieldName = field.getFieldStorageName(); fieldType.addProtoField(typeRepositoryBuilder, recordMsgBuilder, field.getFieldIndex(), fieldName, @@ -2335,6 +2409,9 @@ public PRecordType toProto(@Nonnull final PlanSerializationContext serialization if (name != null) { recordTypeProtoBuilder.setName(name); } + if (!Objects.equals(name, storageName) && storageName != null) { + recordTypeProtoBuilder.setStorageName(storageName); + } recordTypeProtoBuilder.setIsNullable(isNullable); for (final Field field : Objects.requireNonNull(fields)) { @@ -2368,7 +2445,9 @@ public static Record fromProto(@Nonnull final PlanSerializationContext serializa fieldsBuilder.add(Field.fromProto(serializationContext, recordTypeProto.getFields(i))); } final ImmutableList fields = fieldsBuilder.build(); - type = new Record(recordTypeProto.hasName() ? recordTypeProto.getName() : null, recordTypeProto.getIsNullable(), fields); + final String name = recordTypeProto.hasName() ? recordTypeProto.getName() : null; + final String storageName = recordTypeProto.hasStorageName() ? recordTypeProto.getStorageName() : name; + type = new Record(name, storageName, recordTypeProto.getIsNullable(), fields); serializationContext.registerReferenceIdForRecordType(type, referenceId); return type; } @@ -2408,7 +2487,7 @@ public static Record fromFields(final boolean isNullable, @Nonnull final List fields) { - return new Record(name, isNullable, normalizeFields(fields)); + return new Record(name, ProtoUtils.toProtoBufCompliantName(name), isNullable, normalizeFields(fields)); } /** @@ -2436,21 +2515,17 @@ public static Record fromFieldDescriptorsMap(@Nonnull final Map fieldDescriptorMap) { + return fromFields(isNullable, fieldsFromDescriptorMap(fieldDescriptorMap, false)); + } + + @Nonnull + private static List fieldsFromDescriptorMap(@Nonnull final Map fieldDescriptorMap, boolean preserveNames) { final var fieldsBuilder = ImmutableList.builder(); for (final var entry : Objects.requireNonNull(fieldDescriptorMap).entrySet()) { final var fieldDescriptor = entry.getValue(); - final var fieldOptions = fieldDescriptor.getOptions(); - fieldsBuilder.add( - new Field(fromProtoType(getTypeSpecificDescriptor(fieldDescriptor), - fieldDescriptor.getType(), - fieldDescriptor.toProto().getLabel(), - fieldOptions, - !fieldDescriptor.isRequired()), - Optional.of(entry.getKey()), - Optional.of(fieldDescriptor.getNumber()))); + fieldsBuilder.add(Field.fromDescriptor(fieldDescriptor, preserveNames)); } - - return fromFields(isNullable, fieldsBuilder.build()); + return fieldsBuilder.build(); } /** @@ -2464,6 +2539,12 @@ public static Record fromDescriptor(final Descriptors.Descriptor descriptor) { return fromFieldDescriptorsMap(toFieldDescriptorMap(descriptor.getFields())); } + @Nonnull + public static Record fromDescriptorPreservingName(final Descriptors.Descriptor descriptor) { + return new Record(ProtoUtils.toUserIdentifier(descriptor.getName()), descriptor.getName(), false, + fieldsFromDescriptorMap(toFieldDescriptorMap(descriptor.getFields()), true)); + } + /** * Translates a list of {@link com.google.protobuf.Descriptors.FieldDescriptor}s to a mapping between field name * and the field itself. @@ -2521,10 +2602,14 @@ private static List normalizeFields(@Nullable final List fields) { ? Optional.empty() : Optional.of(fieldName)) .orElse("_" + i); + final var fieldStorageName = + field.getFieldStorageNameOptional() + .orElseGet(() -> ProtoUtils.toProtoBufCompliantName(explicitFieldName)); fieldToBeAdded = new Field(field.getFieldType(), Optional.of(explicitFieldName), - Optional.of(i + 1)); + Optional.of(i + 1), + Optional.of(fieldStorageName)); } if (!(fieldNamesSeen.add(fieldToBeAdded.getFieldName()))) { @@ -2559,6 +2644,9 @@ public static class Field implements Comparable, PlanSerializable { @Nonnull private final Optional fieldIndexOptional; + @Nonnull + private final Optional fieldStorageNameOptional; + /** * Memoized hash function. */ @@ -2576,10 +2664,11 @@ private int computeHashFunction() { * @param fieldNameOptional The field name. * @param fieldIndexOptional The field index. */ - protected Field(@Nonnull final Type fieldType, @Nonnull final Optional fieldNameOptional, @Nonnull Optional fieldIndexOptional) { + protected Field(@Nonnull final Type fieldType, @Nonnull final Optional fieldNameOptional, @Nonnull Optional fieldIndexOptional, @Nonnull Optional fieldStorageNameOptional) { this.fieldType = fieldType; this.fieldNameOptional = fieldNameOptional; this.fieldIndexOptional = fieldIndexOptional; + this.fieldStorageNameOptional = fieldStorageNameOptional; } /** @@ -2592,8 +2681,11 @@ public Type getFieldType() { } /** - * Returns the field name. - * @return The field name. + * Returns the field name if set. This should be the name of the field as the user would refer to it. + * This may not be set if the user has used un-named fields, in which case names based on the field + * index will be generated. + * + * @return The field name if set. */ @Nonnull public Optional getFieldNameOptional() { @@ -2601,14 +2693,42 @@ public Optional getFieldNameOptional() { } /** - * Returns the field name. + * Returns the field name. If the underlying {@link Optional} is not set, this will throw an error. + * * @return The field name. + * @see #getFieldStorageNameOptional() + * @throws RecordCoreException if the field is not set */ @Nonnull public String getFieldName() { return getFieldNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); } + /** + * Returns the name of the underlying field in protobuf storage if set. This can differ from the user-visible + * field name if, for example, there are characters in there are fields that need to be adjusted in order + * to produce a valid protobuf identifier. + * + * @return The protobuf field name used to serialize this field if set. + * @see ProtoUtils#toProtoBufCompliantName(String) for the escaping used + */ + @Nonnull + public Optional getFieldStorageNameOptional() { + return fieldStorageNameOptional; + } + + /** + * Returns the name of the underlying field in protobuf storage if set. If the underlying {@link Optional} + * is not set, this will throw an error. + * + * @return The protobuf field name used to serialize this field. + * @see #getFieldStorageNameOptional() + */ + @Nonnull + public String getFieldStorageName() { + return getFieldStorageNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); + } + /** * Returns the field index. * @return The field index. @@ -2626,28 +2746,13 @@ public int getFieldIndex() { return getFieldIndexOptional().orElseThrow(() -> new RecordCoreException("field index should have been set")); } - /** - * Returns a new field with a new name. - * @param newName The new name. - * @return if the name is different from the current field name, returns a new {@code Field} with the new name, - * the same {@link Type}, and index, otherwise it returns {@code this} {@link Field}. - */ - @Nonnull - public Field withName(@Nonnull final String newName) { - if (fieldNameOptional.map(fieldName -> fieldName.equals(newName)).orElse(false)) { - return this; - } else { - return Field.of(getFieldType(), Optional.of(newName), getFieldIndexOptional()); - } - } - @Nonnull public Field withNullability(boolean newNullability) { if (getFieldType().isNullable() == newNullability) { return this; } var newFieldType = getFieldType().withNullability(newNullability); - return new Field(newFieldType, fieldNameOptional, fieldIndexOptional); + return new Field(newFieldType, fieldNameOptional, fieldIndexOptional, fieldStorageNameOptional); } @Nonnull @@ -2690,14 +2795,35 @@ public PRecordType.PField toProto(@Nonnull final PlanSerializationContext serial fieldProtoBuilder.setFieldType(fieldType.toTypeProto(serializationContext)); fieldNameOptional.ifPresent(fieldProtoBuilder::setFieldName); fieldIndexOptional.ifPresent(fieldProtoBuilder::setFieldIndex); + fieldStorageNameOptional.ifPresent(storageFieldName -> { + if (!fieldProtoBuilder.getFieldName().equals(storageFieldName)) { + fieldProtoBuilder.setFieldStorageName(storageFieldName); + } + }); return fieldProtoBuilder.build(); } + @Nonnull + private static Field fromDescriptor(@Nonnull Descriptors.FieldDescriptor fieldDescriptor, boolean preserveNames) { + final Type fieldType = Type.fromProtoType(Type.getTypeSpecificDescriptor(fieldDescriptor), + fieldDescriptor.getType(), + fieldDescriptor.toProto().getLabel(), + fieldDescriptor.getOptions(), + !fieldDescriptor.isRequired(), + preserveNames); + return new Field(fieldType, + Optional.of(ProtoUtils.toUserIdentifier(fieldDescriptor.getName())), + Optional.of(fieldDescriptor.getNumber()), + Optional.of(fieldDescriptor.getName())); + } + @Nonnull public static Field fromProto(@Nonnull final PlanSerializationContext serializationContext, @Nonnull final PRecordType.PField fieldProto) { - return new Field(Type.fromTypeProto(serializationContext, Objects.requireNonNull(fieldProto.getFieldType())), - fieldProto.hasFieldName() ? Optional.of(fieldProto.getFieldName()) : Optional.empty(), - fieldProto.hasFieldIndex() ? Optional.of(fieldProto.getFieldIndex()) : Optional.empty()); + final Type fieldType = Type.fromTypeProto(serializationContext, Objects.requireNonNull(fieldProto.getFieldType())); + final Optional fieldNameOptional = fieldProto.hasFieldName() ? Optional.of(fieldProto.getFieldName()) : Optional.empty(); + final Optional fieldIndexOptional = fieldProto.hasFieldIndex() ? Optional.of(fieldProto.getFieldIndex()) : Optional.empty(); + final Optional storageFieldNameOptional = fieldProto.hasFieldStorageName() ? Optional.of(fieldProto.getFieldStorageName()) : fieldNameOptional; + return new Field(fieldType, fieldNameOptional, fieldIndexOptional, storageFieldNameOptional); } /** @@ -2709,7 +2835,7 @@ public static Field fromProto(@Nonnull final PlanSerializationContext serializat * @return a new field */ public static Field of(@Nonnull final Type fieldType, @Nonnull final Optional fieldNameOptional, @Nonnull Optional fieldIndexOptional) { - return new Field(fieldType, fieldNameOptional, fieldIndexOptional); + return new Field(fieldType, fieldNameOptional, fieldIndexOptional, fieldNameOptional.map(ProtoUtils::toProtoBufCompliantName)); } /** @@ -2720,7 +2846,7 @@ public static Field of(@Nonnull final Type fieldType, @Nonnull final Optional fieldNameOptional) { - return new Field(fieldType, fieldNameOptional, Optional.empty()); + return new Field(fieldType, fieldNameOptional, Optional.empty(), fieldNameOptional.map(ProtoUtils::toProtoBufCompliantName)); } /** @@ -2730,7 +2856,7 @@ public static Field of(@Nonnull final Type fieldType, @Nonnull final Optional> computeFieldNames(@Nonnull final List fieldAccessors) { return fieldAccessors.stream() - .map(accessor -> Optional.ofNullable(accessor.getName())) + .map(accessor -> accessor.getField().getFieldStorageNameOptional()) .collect(ImmutableList.toImmutableList()); } @@ -550,8 +551,8 @@ private static List computeFieldTypes(@Nonnull final List= 0); - this.name = name; + this.field = field; this.ordinal = ordinal; - this.type = type; } @Nullable public String getName() { - return name; + return field.getFieldNameOptional().orElse(null); } public int getOrdinal() { return ordinal; } + @Nonnull + public Field getField() { + return field; + } + @Nonnull public Type getType() { - return Objects.requireNonNull(type); + return Objects.requireNonNull(field.getFieldType()); } @Override @@ -682,18 +683,21 @@ public int hashCode() { @Nonnull @Override public String toString() { - return name + ';' + ordinal + ';' + type; + return getName() + ';' + ordinal + ';' + getType(); } @Nonnull @Override public PResolvedAccessor toProto(@Nonnull final PlanSerializationContext serializationContext) { PResolvedAccessor.Builder builder = PResolvedAccessor.newBuilder(); - builder.setName(name); + // Older serialization: write out the name, ordinal, and type manually + builder.setName(field.getFieldName()); builder.setOrdinal(ordinal); - if (type != null) { - builder.setType(type.toTypeProto(serializationContext)); + if (field.getFieldType() != null) { + builder.setType(getType().toTypeProto(serializationContext)); } + // Newer serialization: write that information in a nested field + builder.setField(field.toProto(serializationContext)); return builder.build(); } @@ -706,22 +710,37 @@ public static ResolvedAccessor fromProto(@Nonnull PlanSerializationContext seria } else { type = null; } - return new ResolvedAccessor(resolvedAccessorProto.getName(), resolvedAccessorProto.getOrdinal(), type); + + final Field field; + if (resolvedAccessorProto.hasField()) { + // Newer serialization: use a single nested field. If both are set, we need to deserialize + // the field after reading the type information, as the type will be cached in the + // serialization context + field = Field.fromProto(serializationContext, resolvedAccessorProto.getField()); + } else { + // Older serialization: get the name and type information from separate fields + field = Field.of(Objects.requireNonNull(type), Optional.of(resolvedAccessorProto.getName())); + } + return new ResolvedAccessor(field, resolvedAccessorProto.getOrdinal()); } @Nonnull public static ResolvedAccessor of(@Nonnull final Field field, final int ordinal) { - return of(field.getFieldNameOptional().orElse(null), ordinal, field.getFieldType()); + return new ResolvedAccessor(field, ordinal); } @Nonnull public static ResolvedAccessor of(@Nullable final String fieldName, final int ordinalFieldNumber, @Nonnull final Type type) { - return new ResolvedAccessor(fieldName, ordinalFieldNumber, type); + final Field field = Field.of(type, Optional.ofNullable(fieldName)); + return new ResolvedAccessor(field, ordinalFieldNumber); } @Nonnull - public static ResolvedAccessor of(@Nullable final String fieldName, final int ordinalFieldNumber) { - return new ResolvedAccessor(fieldName, ordinalFieldNumber, null); + public static ResolvedAccessor of(@Nonnull final Type.Record recordType, @Nonnull final String fieldName, final int ordinalFieldNumber) { + final Map fieldNameMap = recordType.getFieldNameFieldMap(); + Field field = fieldNameMap.get(fieldName); + SemanticException.check(field != null, SemanticException.ErrorCode.RECORD_DOES_NOT_CONTAIN_FIELD); + return new ResolvedAccessor(field, ordinalFieldNumber); } } 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 8f6eb9150c..31adbdf6af 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 @@ -41,6 +41,7 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.MessageHelpers.CoercionTrieNode; import com.apple.foundationdb.record.query.plan.serialization.PlanSerialization; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.google.auto.service.AutoService; import com.google.common.base.Suppliers; @@ -144,9 +145,15 @@ public static PhysicalOperator fromProto(@Nonnull final PlanSerializationContext @Nonnull public static Descriptors.EnumValueDescriptor stringToEnumValue(Descriptors.EnumDescriptor enumDescriptor, String value) { - final var maybeValue = enumDescriptor.findValueByName(value); + Descriptors.EnumValueDescriptor maybeValue = null; + for (Descriptors.EnumValueDescriptor valueDescriptor : enumDescriptor.getValues()) { + if (ProtoUtils.toUserIdentifier(valueDescriptor.getName()).equals(value)) { + maybeValue = valueDescriptor; + break; + } + } SemanticException.check(maybeValue != null, SemanticException.ErrorCode.INVALID_ENUM_VALUE, value); - return maybeValue; + return Objects.requireNonNull(maybeValue); } public static UUID stringToUuidValue(String value) { diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Values.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Values.java index 76572d10e3..9270026ff6 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Values.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Values.java @@ -111,8 +111,7 @@ public static List primitiveAccessorsForType(@Nonnull final Type type, for (int i = 0; i < fields.size(); i++) { final var field = fields.get(i); final var singleStepPath = - FieldValue.FieldPath.ofSingle(FieldValue.ResolvedAccessor.of( - field.getFieldNameOptional().orElse(null), i, field.getFieldType())); + FieldValue.FieldPath.ofSingle(FieldValue.ResolvedAccessor.of(field, i)); primitiveAccessorsForType(field.getFieldType(), () -> FieldValue.ofFieldsAndFuseIfPossible(baseValueSupplier.get(), singleStepPath)) .forEach(orderingValuesBuilder::add); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/CompensateRecordConstructorRule.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/CompensateRecordConstructorRule.java index c7d0c8cce1..e8c4a1e3a4 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/CompensateRecordConstructorRule.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/simplification/CompensateRecordConstructorRule.java @@ -81,7 +81,7 @@ public void onMatch(@Nonnull final ValueComputationRuleCall INVALID_START_SEQUENCES = List.of(".", "$", DOUBLE_UNDERSCORE_ESCAPE, DOLLAR_ESCAPE, DOT_ESCAPE); + + private static final Pattern VALID_PROTOBUF_COMPLIANT_NAME_PATTERN = Pattern.compile("^[A-Za-z_][A-Za-z0-9_]*$"); + private ProtoUtils() { } + @Nonnull + public static String toProtoBufCompliantName(final String name) { + if (INVALID_START_SEQUENCES.stream().anyMatch(name::startsWith)) { + throw new InvalidNameException("name cannot start with " + INVALID_START_SEQUENCES); + } + String translated; + if (name.startsWith("__")) { + translated = "__" + translateSpecialCharacters(name.substring(2)); + } else { + if (name.isEmpty()) { + throw new InvalidNameException("name cannot be empty string"); + } + translated = translateSpecialCharacters(name); + } + checkValidProtoBufCompliantName(translated); + return translated; + } + + public static void checkValidProtoBufCompliantName(String name) { + if (!VALID_PROTOBUF_COMPLIANT_NAME_PATTERN.matcher(name).matches()) { + throw new InvalidNameException(name + " it not a valid protobuf identifier"); + } + } + + @Nonnull + private static String translateSpecialCharacters(final String userIdentifier) { + return userIdentifier.replace("__", DOUBLE_UNDERSCORE_ESCAPE).replace("$", DOLLAR_ESCAPE).replace(".", DOT_ESCAPE); + } + + public static String toUserIdentifier(String protoIdentifier) { + return protoIdentifier.replace(DOT_ESCAPE, ".").replace(DOLLAR_ESCAPE, "$").replace(DOUBLE_UNDERSCORE_ESCAPE, "__"); + } + /** * Generates a JVM-wide unique type name. * @return a unique type name. @@ -103,4 +148,11 @@ public String toString() { return getName(); } } + + @SuppressWarnings("serial") + public static class InvalidNameException extends MetaDataException { + public InvalidNameException(@Nonnull final String msg, @Nullable final Object... keyValues) { + super(msg, keyValues); + } + } } 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 49dc07faf9..d4a8f0ad61 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 @@ -90,11 +90,13 @@ message PType { message PEnumValue { optional string name = 1; optional int32 number = 2; + optional string storage_name = 3; } optional bool is_nullable = 1; repeated PEnumValue enum_values = 2; optional string name = 3; // referential name -- not used in the planner + optional string storage_name = 4; } message PRecordType { @@ -102,12 +104,14 @@ message PType { optional PType field_type = 1; optional string field_name = 2; optional int32 field_index = 3; + optional string field_storage_name = 4; } optional int32 reference_id = 1; optional string name = 2; // referential name -- not used in the planner optional bool is_nullable = 3; repeated PField fields = 4; + optional string storage_name = 5; } message PRelationType { @@ -482,6 +486,7 @@ message PFieldPath { optional string name = 1; optional int32 ordinal = 2; optional PType type = 3; + optional PType.PRecordType.PField field = 4; } repeated PResolvedAccessor field_accessors = 1; } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBSimpleQueryGraphTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBSimpleQueryGraphTest.java index 5cf5b6325c..5f70d95ecc 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBSimpleQueryGraphTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBSimpleQueryGraphTest.java @@ -828,24 +828,24 @@ void testMediumJoinTypeEvolutionIdentical() { // var childrenMap = fieldAccessesRestaurantRecord.getChildrenMap(); Assertions.assertNotNull(childrenMap); - var childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of("name", 1)); + var childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of(restaurantReviewerType, "name", 1)); Assertions.assertNotNull(childFieldAccesses); var leafType = childFieldAccesses.getValue(); Assertions.assertNotNull(leafType); Assertions.assertEquals(leafType, Type.primitiveType(Type.TypeCode.STRING, true)); - childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of("reviews", 2)); + childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of(restaurantRecordType, "reviews", 2)); Assertions.assertNotNull(childFieldAccesses); childrenMap = fieldAccessesRestaurantRecord.getChildrenMap(); Assertions.assertNotNull(childrenMap); - childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of("reviewer", 0)); + childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of(reviewsType, "reviewer", 0)); Assertions.assertNotNull(childFieldAccesses); leafType = childFieldAccesses.getValue(); Assertions.assertNotNull(leafType); Assertions.assertEquals(leafType, Type.primitiveType(Type.TypeCode.LONG, false)); childrenMap = fieldAccessesRestaurantRecord.getChildrenMap(); - childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of("rest_no", 0)); + childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of(restaurantRecordType, "rest_no", 0)); Assertions.assertNotNull(childFieldAccesses); leafType = childFieldAccesses.getValue(); Assertions.assertNotNull(leafType); @@ -861,13 +861,13 @@ void testMediumJoinTypeEvolutionIdentical() { // childrenMap = fieldAccessesRestaurantReviewer.getChildrenMap(); Assertions.assertNotNull(childrenMap); - childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of("name", 1)); + childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of(restaurantReviewerType, "name", 1)); Assertions.assertNotNull(childFieldAccesses); leafType = childFieldAccesses.getValue(); Assertions.assertNotNull(leafType); Assertions.assertEquals(leafType, Type.primitiveType(Type.TypeCode.STRING, false)); - childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of("id", 0)); + childFieldAccesses = childrenMap.get(FieldValue.ResolvedAccessor.of(restaurantReviewerType, "id", 0)); Assertions.assertNotNull(childFieldAccesses); leafType = childFieldAccesses.getValue(); Assertions.assertNotNull(leafType); 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 87cbbe3424..39599c9c43 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 @@ -75,12 +75,12 @@ */ 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 Type.Enum ENUM_TYPE_FOR_TEST = Type.Enum.fromValuesWithName("enumTestType", false, List.of( + Type.Enum.EnumValue.from("SPADES", 0), + Type.Enum.EnumValue.from("HEARTS", 1), + Type.Enum.EnumValue.from("DIAMONDS", 2), + Type.Enum.EnumValue.from("CLUBS", 3) + )); private static final TypeRepository.Builder typeRepositoryBuilder = TypeRepository.newBuilder().setName("foo").setPackage("a.b.c") .addTypeIfNeeded(ENUM_TYPE_FOR_TEST); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeTest.java index 27ff21f4ab..1d3ed6b1e2 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeTest.java @@ -20,7 +20,6 @@ package com.apple.foundationdb.record.query.plan.cascades; -import com.apple.foundationdb.record.PlanHashable; import com.apple.foundationdb.record.PlanSerializationContext; import com.apple.foundationdb.record.TestRecords1Proto; import com.apple.foundationdb.record.TestRecords2Proto; @@ -29,16 +28,25 @@ import com.apple.foundationdb.record.TestRecords4WrapperProto; import com.apple.foundationdb.record.TestRecordsUuidProto; import com.apple.foundationdb.record.TupleFieldsProto; +import com.apple.foundationdb.record.TypeTestProto; +import com.apple.foundationdb.record.planprotos.PType; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; -import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.record.util.RandomUtil; +import com.apple.foundationdb.record.util.pair.Pair; +import com.apple.test.BooleanSource; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Streams; import com.google.protobuf.ByteString; import com.google.protobuf.DescriptorProtos; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import com.google.protobuf.Message; +import org.assertj.core.api.AutoCloseableSoftAssertions; +import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -46,6 +54,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -54,6 +63,7 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -61,8 +71,11 @@ import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; +import static org.assertj.core.api.Assertions.assertThat; + /** * Tests for synthesizing a protobuf descriptor from a {@link Type} object and lifting a Java object type into an * equivalent {@link Type}. @@ -231,14 +244,13 @@ public Stream provideArguments(final ExtensionContext conte return Stream.of( // Typed objects - Arguments.of(LiteralValue.ofScalar(false), LiteralValue.ofScalar(false).getResultType()), - Arguments.of(LiteralValue.ofScalar(42), LiteralValue.ofScalar(42).getResultType()), - Arguments.of(LiteralValue.ofScalar(42.1d), LiteralValue.ofScalar(42.1d).getResultType()), - Arguments.of(LiteralValue.ofScalar(42.2f), LiteralValue.ofScalar(42.2f).getResultType()), - Arguments.of(LiteralValue.ofScalar(43L), LiteralValue.ofScalar(43L).getResultType()), - Arguments.of(LiteralValue.ofScalar("foo"), LiteralValue.ofScalar("foo").getResultType()), - Arguments.of(LiteralValue.ofScalar(ByteString.copyFrom("bar", Charset.defaultCharset().name())), - LiteralValue.ofScalar(ByteString.copyFrom("bar", Charset.defaultCharset().name())).getResultType()), + Arguments.of(LiteralValue.ofScalar(false), Type.primitiveType(Type.TypeCode.BOOLEAN, false)), + Arguments.of(LiteralValue.ofScalar(42), Type.primitiveType(Type.TypeCode.INT, false)), + Arguments.of(LiteralValue.ofScalar(42.1d), Type.primitiveType(Type.TypeCode.DOUBLE, false)), + Arguments.of(LiteralValue.ofScalar(42.2f), Type.primitiveType(Type.TypeCode.FLOAT, false)), + Arguments.of(LiteralValue.ofScalar(43L), Type.primitiveType(Type.TypeCode.LONG, false)), + Arguments.of(LiteralValue.ofScalar("foo"), Type.primitiveType(Type.TypeCode.STRING, false)), + Arguments.of(LiteralValue.ofScalar(ByteString.copyFrom("bar", Charset.defaultCharset().name())), Type.primitiveType(Type.TypeCode.BYTES, false)), // Primitives @@ -297,14 +309,668 @@ public Stream provideArguments(final ExtensionContext conte @ParameterizedTest(name = "[{index} Java object {0}, Expected type {1}]") @ArgumentsSource(TypesProvider.class) void testTypeLifting(@Nullable final Object object, @Nonnull final Type expectedType) { - Assertions.assertEquals(expectedType, Type.fromObject(object)); + final Type typeFromObject = Type.fromObject(object); + Assertions.assertEquals(expectedType, typeFromObject); + } + + @Nonnull + static Stream enumTypes() { + return Stream.of( + Type.Enum.fromValues(false, List.of(Type.Enum.EnumValue.from("A", 0), Type.Enum.EnumValue.from("B", 1), Type.Enum.EnumValue.from("C", 2))), + Type.Enum.fromValues(false, List.of(Type.Enum.EnumValue.from("A", 0), Type.Enum.EnumValue.from("B", 1), Type.Enum.EnumValue.from("C", 3))), + Type.Enum.fromValues(false, List.of(Type.Enum.EnumValue.from("A", 0), Type.Enum.EnumValue.from("B", 1), Type.Enum.EnumValue.from("D", 2))), + Type.Enum.fromValues(false, List.of(Type.Enum.EnumValue.from("A..", 0), Type.Enum.EnumValue.from("B__", 1), Type.Enum.EnumValue.from("__C$", 2))) + ); + } + + @Nonnull + static Stream recordTypes() { + return Stream.of( + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.LONG), Optional.empty()))), + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.LONG), Optional.of("a")))), + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.LONG), Optional.of("b")))), + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of("a")))), + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of("b")))), + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.empty(), Optional.of(1)))), + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of("a"), Optional.of(2)))), + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of("b"), Optional.of(2)))), + Type.Record.fromFields(List.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of("a.b"), Optional.of(2)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(Type.Record.fromFields(List.of( + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING, true), Optional.of("b"), Optional.of(2))) + ), Optional.of("a"), Optional.of(1)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(Type.Record.fromFields(List.of( + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING, false), Optional.of("b"), Optional.of(2))) + ), Optional.of("a"), Optional.of(1)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.LONG), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of("b"), Optional.of(3)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.LONG, false), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of("b"), Optional.of(3)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.LONG), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING, false), Optional.of("b"), Optional.of(3)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(new Type.Array(false, Type.primitiveType(Type.TypeCode.LONG, false)), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(new Type.Array(false, Type.primitiveType(Type.TypeCode.STRING, false)), Optional.of("b"), Optional.of(3)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(new Type.Array(true, Type.primitiveType(Type.TypeCode.LONG, false)), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(new Type.Array(false, Type.primitiveType(Type.TypeCode.STRING, false)), Optional.of("b"), Optional.of(3)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(new Type.Array(true, Type.primitiveType(Type.TypeCode.LONG, false)), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(new Type.Array(true, Type.primitiveType(Type.TypeCode.STRING, false)), Optional.of("b"), Optional.of(3)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(new Type.Array(false, Type.primitiveType(Type.TypeCode.LONG, true)), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(new Type.Array(false, Type.primitiveType(Type.TypeCode.STRING, false)), Optional.of("b"), Optional.of(3)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(new Type.Array(false, Type.primitiveType(Type.TypeCode.LONG, true)), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(new Type.Array(false, Type.primitiveType(Type.TypeCode.STRING, true)), Optional.of("b"), Optional.of(3)))), + Type.Record.fromFields(List.of( + Type.Record.Field.of(Type.primitiveType(Type.TypeCode.LONG), Optional.of("a"), Optional.of(1)), + Type.Record.Field.of(Type.Enum.fromValues(false, List.of(Type.Enum.EnumValue.from("A", 0), Type.Enum.EnumValue.from("B", 1))), Optional.of("b"), Optional.of(3)))) + ); + } + + @Nonnull + static Stream types() { + Stream primitiveTypes = Stream.of(Type.TypeCode.values()) + .filter(Type.TypeCode::isPrimitive) + .filter(code -> code != Type.TypeCode.VECTOR) + .map(Type::primitiveType); + + Stream otherTypes = Stream.of( + new Type.AnyRecord(false), + Type.uuidType(false), + Type.Vector.of(false, 16, 500), + Type.Vector.of(false, 32, 500), + Type.Vector.of(false, 16, 1000), + Type.Vector.of(false, 32, 1000) + ); + + Stream nullableAndNonNullableTypes = Stream.concat( + Stream.of(Type.any(), Type.nullType(), Type.noneType()), + Streams.concat(primitiveTypes, otherTypes, enumTypes(), recordTypes()).flatMap(t -> Stream.of(t.withNullability(false), t.withNullability(true))) + ); + + return nullableAndNonNullableTypes + .flatMap(t -> Stream.of(t, new Type.Array(false, t), new Type.Array(true, t), new Type.Relation(t))); + } + + @Nonnull + static Stream typesWithIndex() { + final List typeList = types().collect(Collectors.toList()); + return IntStream.range(0, typeList.size()) + .mapToObj(index -> Arguments.of(index, typeList.get(index))); + } + + @ParameterizedTest(name = "[{index} serialization of {0}]") + @MethodSource("types") + void testSerialization(@Nonnull final Type type) { + PlanSerializationContext serializationContext = PlanSerializationContext.newForCurrentMode(); + final PType typeProto = type.toTypeProto(serializationContext); + + PlanSerializationContext deserializationContext = PlanSerializationContext.newForCurrentMode(); + final Type recreatedType = Type.fromTypeProto(deserializationContext, typeProto); + Assertions.assertEquals(type, recreatedType); + } + + @ParameterizedTest(name = "[{index} nullability of {0}]") + @MethodSource("types") + void testNullability(@Nonnull final Type type) { + if (type instanceof Type.None || type instanceof Type.Relation) { + // None and Relational are special and are always not nullable + Assertions.assertSame(type, type.notNullable()); + Assertions.assertThrows(VerifyException.class, type::nullable); + return; + } + + final Type nullableType = type.nullable(); + if (type.isNullable()) { + Assertions.assertSame(nullableType, type); + } else { + Assertions.assertNotEquals(nullableType, type); + } + + if (type instanceof Type.Any || type instanceof Type.Null) { + // These types do not have a not-nullable variation + Assertions.assertThrows(Throwable.class, type::notNullable); + return; + } + + final Type notNullableType = type.notNullable(); + if (type.isNullable()) { + Assertions.assertNotEquals(notNullableType, type); + } else { + Assertions.assertSame(notNullableType, type); + } + + // Converting the type back and forth from nullable and not nullable should + // produce the original type + Assertions.assertTrue(nullableType.isNullable()); + Assertions.assertFalse(notNullableType.isNullable()); + Assertions.assertEquals(nullableType, notNullableType.nullable()); + Assertions.assertEquals(notNullableType, nullableType.notNullable()); + } + + @ParameterizedTest(name = "pairwiseEquality[{index} {1}]") + @MethodSource("typesWithIndex") + void pairwiseEquality(int index, @Nonnull Type type) { + final List typeList = types().collect(Collectors.toList()); + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + // Check this type for equality/inequality against items in the list. It should only be equal to the + // item at the same position. + // Note that we could have one test case for each pair of types, bun then we'd have thousands and + // thousands of tests added to the report, which clogs things up. + final PType typeProto = type.toTypeProto(PlanSerializationContext.newForCurrentMode()); + for (int i = 0; i < typeList.size(); i++) { + final Type otherType = typeList.get(i); + final PType otherTypeProto = otherType.toTypeProto(PlanSerializationContext.newForCurrentMode()); + + if (i == index) { + softly.assertThat(type) + .isEqualTo(otherType) + .hasSameHashCodeAs(otherType); + softly.assertThat(typeProto) + .isEqualTo(otherTypeProto); + } else { + softly.assertThat(type) + .isNotEqualTo(otherType); + softly.assertThat(typeProto) + .isNotEqualTo(otherTypeProto); + } + } + } + } + + enum EnumForTesting { + ALPHA, + BRAVO, + __CHARLIE, + DELTA__1, } @Test - void testAnyRecordSerialization() { - PlanSerializationContext serializationContext = new PlanSerializationContext(DefaultPlanSerializationRegistry.INSTANCE, - PlanHashable.CURRENT_FOR_CONTINUATION); - Type.AnyRecord r1 = new Type.AnyRecord(false); - Assertions.assertEquals(r1, Type.AnyRecord.fromProto(serializationContext, r1.toProto(serializationContext))); + void createEnumFromJavaEnum() { + final Type.Enum fromJava = Type.Enum.forJavaEnum(EnumForTesting.class); + assertThat(fromJava) + .isEqualTo(Type.Enum.fromValues(false, List.of( + // Java enum values assigned field positions starting with zero + Type.Enum.EnumValue.from("ALPHA", 0), + Type.Enum.EnumValue.from("BRAVO", 1), + Type.Enum.EnumValue.from("__CHARLIE", 2), + Type.Enum.EnumValue.from("DELTA__1", 3)))); + assertThat(fromJava.getName()) + .isNull(); + assertThat(fromJava.getStorageName()) + .isNull(); + assertThat(fromJava.getEnumValues().stream().map(Type.Enum.EnumValue::getStorageName).collect(Collectors.toList())) + .containsExactly("ALPHA", "BRAVO", "__CHARLIE", "DELTA__01"); + } + + @Nonnull + static Stream enumTypesWithNames() { + return Stream.of(Pair.of(null, null), + Pair.of("myEnumType", "myEnumType"), + Pair.of("__myEnumType", "__myEnumType"), + Pair.of("__myEnum.Type", "__myEnum__2Type"), + Pair.of("__myEnum$Type", "__myEnum__1Type"), + Pair.of("__myEnum__Type", "__myEnum__0Type") + ).flatMap(namePair -> enumTypes().map(enumType -> { + if (namePair.getLeft() == null) { + return Arguments.of(enumType, namePair.getRight()); + } else { + return Arguments.of(Type.Enum.fromValuesWithName(namePair.getLeft(), enumType.isNullable(), enumType.getEnumValues()), namePair.getRight()); + } + })); + } + + @ParameterizedTest(name = "createEnumProtobuf[{0} storageName={1}]") + @MethodSource("enumTypesWithNames") + void createEnumProtobuf(@Nonnull Type.Enum enumType, @Nullable String expectedStorageName) { + final TypeRepository.Builder typeBuilder = TypeRepository.newBuilder(); + enumType.defineProtoType(typeBuilder); + typeBuilder.build(); + final TypeRepository repository = typeBuilder.build(); + + final String enumTypeName = repository.getProtoTypeName(enumType); + assertThat(repository.getEnumTypes()) + .containsExactly(enumTypeName); + if (expectedStorageName == null) { + assertThat(enumType.getName()) + .isNull(); + assertThat(enumType.getStorageName()) + .isNull(); + assertThat(enumTypeName) + .isNotNull(); + } else { + assertThat(expectedStorageName) + .isEqualTo(enumTypeName) + .isEqualTo(enumType.getStorageName()); + } + final Descriptors.EnumDescriptor enumDescriptor = repository.getEnumDescriptor(enumType); + assertThat(enumDescriptor) + .isNotNull() + .isSameAs(repository.getEnumDescriptor(enumTypeName)); + assertThat(enumDescriptor.getName()) + .isEqualTo(enumTypeName); + + final Type.Enum fromProto = Type.Enum.fromDescriptor(enumType.isNullable(), enumDescriptor); + assertThat(fromProto) + .isEqualTo(enumType); + } + + @ParameterizedTest(name = "enumEqualsIgnoresName[{0}]") + @MethodSource("enumTypes") + void enumEqualsIgnoresName(@Nonnull Type.Enum enumType) { + final Type.Enum typeWithName1 = Type.Enum.fromValuesWithName("name_one", enumType.isNullable(), enumType.getEnumValues()); + final Type.Enum typeWithName2 = Type.Enum.fromValuesWithName("name_two", enumType.isNullable(), enumType.getEnumValues()); + + assertThat(typeWithName1.getName()) + .isNotEqualTo(typeWithName2.getName()); + assertThat(typeWithName1) + .isEqualTo(typeWithName2) + .hasSameHashCodeAs(typeWithName2); + } + + @Nonnull + static Stream recordTypesWithNames() { + return Stream.of(Pair.of(null, null), + Pair.of("myEnumType", "myEnumType"), + Pair.of("__myEnumType", "__myEnumType"), + Pair.of("__myEnum.Type", "__myEnum__2Type"), + Pair.of("__myEnum$Type", "__myEnum__1Type"), + Pair.of("__myEnum__Type", "__myEnum__0Type") + ).flatMap(namePair -> recordTypes().map(recordType -> { + if (namePair.getLeft() == null) { + return Arguments.of(recordType, namePair.getRight()); + } else { + return Arguments.of(recordType.withName(namePair.getLeft()), namePair.getRight()); + } + })); + } + + @ParameterizedTest(name = "createRecordProtobuf[{0} storageName={1}]") + @MethodSource("recordTypesWithNames") + void createRecordProtobuf(@Nonnull Type.Record recordType, @Nullable String expectedStorageName) { + final TypeRepository.Builder typeBuilder = TypeRepository.newBuilder(); + recordType.defineProtoType(typeBuilder); + typeBuilder.build(); + final TypeRepository repository = typeBuilder.build(); + + final String recordTypeName = repository.getProtoTypeName(recordType); + assertThat(repository.getMessageTypes()) + .contains(recordTypeName); + if (expectedStorageName == null) { + assertThat(recordType.getName()) + .isNull(); + assertThat(recordType.getStorageName()) + .isNull(); + assertThat(recordTypeName) + .isNotNull(); + } else { + assertThat(recordTypeName) + .isEqualTo(expectedStorageName) + .isEqualTo(recordType.getStorageName()); + } + final Descriptors.Descriptor messageDescriptor = repository.getMessageDescriptor(recordType); + assertThat(messageDescriptor) + .isNotNull() + .isSameAs(repository.getMessageDescriptor(recordTypeName)); + assertThat(messageDescriptor.getName()) + .isEqualTo(recordTypeName); + + final Type.Record fromDescriptor = Type.Record.fromDescriptor(messageDescriptor).withNullability(recordType.isNullable()); + assertThat(fromDescriptor.getName()) + .as("storage name of record not included in type from message descriptor") + .isNull(); + assertThat(fromDescriptor) + .isEqualTo(adjustFieldsForDescriptorParsing(recordType)); + } + + @Nonnull + private Type.Record adjustFieldsForDescriptorParsing(@Nonnull Type.Record recordType) { + // There are a number of changes that happen to a type when we create a protobuf descriptor for it that + // fail to round trip. These may be bugs, but we can at least assert that everything except for these + // components are preserved + final ImmutableList.Builder newFields = ImmutableList.builderWithExpectedSize(recordType.getFields().size()); + for (Type.Record.Field field : recordType.getFields()) { + Type fieldType = field.getFieldType(); + if (fieldType instanceof Type.Array) { + // Array types retain their nullability as there are separate. However, they make their own element types not nullable + Type elementType = ((Type.Array)fieldType).getElementType(); + if (elementType instanceof Type.Record) { + elementType = adjustFieldsForDescriptorParsing((Type.Record) elementType); + } + elementType = elementType.withNullability(false); + fieldType = new Type.Array(fieldType.isNullable(), elementType); + } else if (fieldType instanceof Type.Record) { + // We need to recursively apply operations to record field types + fieldType = adjustFieldsForDescriptorParsing((Type.Record) fieldType).withNullability(true); + } else { + // By default, the field is nullable. This is because the generated descriptor uses + // LABEL_OPTIONAL on each type, so we make the field nullable + fieldType = fieldType.withNullability(true); + } + newFields.add(Type.Record.Field.of(fieldType, field.getFieldNameOptional(), field.getFieldIndexOptional())); + } + return Type.Record.fromFields(recordType.isNullable(), newFields.build()); + } + + @ParameterizedTest(name = "recordEqualsIgnoresName[{0}]") + @MethodSource("recordTypes") + void recordEqualsIgnoresName(@Nonnull Type.Record recordType) { + final Type.Record typeWithName1 = recordType.withName("name_one"); + final Type.Record typeWithName2 = recordType.withName("name_two"); + + assertThat(typeWithName1.getName()) + .isNotEqualTo(typeWithName2.getName()); + assertThat(typeWithName1) + .isEqualTo(typeWithName2) + .hasSameHashCodeAs(typeWithName2); + } + + @ParameterizedTest(name = "updateFieldNullability[{0}]") + @MethodSource("recordTypes") + void updateFieldNullability(@Nonnull Type.Record recordType) { + for (Type.Record.Field field : recordType.getFields()) { + final Type fieldType = field.getFieldType(); + + // Validate that the non-nullable field type matches the not-nullable version of the field + final Type.Record.Field nonNullableField = field.withNullability(false); + assertThat(nonNullableField.getFieldType()) + .isEqualTo(fieldType.notNullable()) + .isNotEqualTo(fieldType.nullable()) + .matches(Type::isNotNullable); + + // Validate that the nullable field type matches the nullable version of the field + final Type.Record.Field nullableField = field.withNullability(true); + assertThat(nullableField.getFieldType()) + .isEqualTo(fieldType.nullable()) + .isNotEqualTo(fieldType.notNullable()) + .matches(Type::isNullable); + + // All other components of the field should remain the same + assertThat(field.getFieldIndexOptional()) + .isEqualTo(nullableField.getFieldIndexOptional()) + .isEqualTo(nonNullableField.getFieldIndexOptional()); + assertThat(field.getFieldNameOptional()) + .isEqualTo(nullableField.getFieldNameOptional()) + .isEqualTo(nonNullableField.getFieldNameOptional()); + assertThat(field.getFieldStorageNameOptional()) + .isEqualTo(nullableField.getFieldStorageNameOptional()) + .isEqualTo(nonNullableField.getFieldStorageNameOptional()); + } + } + + /** + * Validate all primitive types can be parsed as field elements of a type. If this test fails after introducing + * a new primitive type, update the Protobuf definition in {@code type_test.proto} or exclude it from consideration + * if its type should not automatically be inferred for some reason. + */ + @Test + void testPrimitivesFromDescriptor() { + final Descriptors.Descriptor descriptor = TypeTestProto.PrimitiveFields.getDescriptor(); + final Type.Record type = Type.Record.fromDescriptor(descriptor); + + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + for (Type.TypeCode typeCode : Type.TypeCode.values()) { + if (!typeCode.isPrimitive() || typeCode == Type.TypeCode.NULL || typeCode == Type.TypeCode.UNKNOWN || typeCode == Type.TypeCode.VERSION || typeCode == Type.TypeCode.VECTOR) { + continue; + } + Type primitiveType = Type.primitiveType(typeCode); + final String nameBase = typeCode.name().toLowerCase(Locale.ROOT) + "_field"; + + // Should contain nullable, not-nullable, nullable array, and not-nullable array fields + assertContainsField(softly, type, nameBase, primitiveType.nullable(), descriptor); + assertContainsField(softly, type, "req_" + nameBase, primitiveType.notNullable(), descriptor); + assertContainsField(softly, type, "arr_" + nameBase, new Type.Array(true, primitiveType.notNullable()), descriptor); + assertContainsField(softly, type, "req_arr_" + nameBase, new Type.Array(false, primitiveType.notNullable()), descriptor); + } + + // Ensure all fields have been picked up + final List fieldDescriptors = descriptor.getFields(); + for (Descriptors.FieldDescriptor fieldDescriptor : fieldDescriptors) { + softly.assertThat(type.getFieldNameFieldMap()) + .containsKey(fieldDescriptor.getName()); + } + } + } + + /** + * Simple case of resolving fields from a protobuf descriptor. None of the field names require any messaging for escape sequences. + */ + @ParameterizedTest(name = "testSimpleFromDescriptor[preserveNames={0}]") + @BooleanSource + void testSimpleFromDescriptor(boolean preserveNames) { + final Descriptors.Descriptor descriptor = TypeTestProto.Type1.getDescriptor(); + final Type.Record type = preserveNames ? Type.Record.fromDescriptorPreservingName(descriptor) : Type.Record.fromDescriptor(descriptor); + + final Descriptors.Descriptor outerDescriptor = TypeTestProto.Type2.getDescriptor(); + final Type.Record outerType = preserveNames ? Type.Record.fromDescriptorPreservingName(outerDescriptor) : Type.Record.fromDescriptor(outerDescriptor); + + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + assertContainsField(softly, type, "alpha", Type.primitiveType(Type.TypeCode.STRING, true), descriptor); + assertContainsField(softly, type, "beta", Type.primitiveType(Type.TypeCode.LONG, false), descriptor); + assertContainsField(softly, type, "uuid_field", Type.UUID_NULL_INSTANCE, descriptor); + assertTypeNames(softly, type, descriptor, preserveNames); + assertTypeNames(softly, roundTrip(type), descriptor, preserveNames); + + assertContainsField(softly, outerType, "alpha", type.nullable(), outerDescriptor); + assertContainsField(softly, outerType, "beta", type.notNullable(), outerDescriptor); + final Type.Enum enumType = Type.Enum.fromValues(true, + List.of(Type.Enum.EnumValue.from("ALPHA", 0), Type.Enum.EnumValue.from("BRAVO", 1), Type.Enum.EnumValue.from("CHARLIE", 3), Type.Enum.EnumValue.from("DELTA", 4))); + assertContainsField(softly, outerType, "gamma", enumType, outerDescriptor); + assertContainsField(softly, outerType, "delta", new Type.Array(true, type.notNullable()), outerDescriptor); + assertContainsField(softly, outerType, "epsilon", new Type.Array(false, type.notNullable()), outerDescriptor); + assertContainsField(softly, outerType, "zeta", new Type.Array(true, enumType.notNullable()), outerDescriptor); + assertContainsField(softly, outerType, "eta", new Type.Array(false, enumType.notNullable()), outerDescriptor); + assertTypeNames(softly, outerType, outerDescriptor, preserveNames); + assertTypeNames(softly, roundTrip(outerType), outerDescriptor, preserveNames); + } + } + + /** + * Test where a type descriptor has some field names that require escaping. In this case, all the field translations + * are reversible, so it doesn't matter if the name is preserved from the protobuf file or re-calculated using + * {@link com.apple.foundationdb.record.util.ProtoUtils#toProtoBufCompliantName(String)}. + */ + @ParameterizedTest(name = "testEscapesFromDescriptor[preserveNames={0}]") + @BooleanSource + void testEscapesFromDescriptor(boolean preserveNames) { + final Descriptors.Descriptor descriptor = TypeTestProto.Type1__0Escaped.getDescriptor(); + final Type.Record type = preserveNames ? Type.Record.fromDescriptorPreservingName(descriptor) : Type.Record.fromDescriptor(descriptor); + + final Descriptors.Descriptor outerDescriptor = TypeTestProto.Type2__0Escaped.getDescriptor(); + final Type.Record outerType = preserveNames ? Type.Record.fromDescriptorPreservingName(outerDescriptor) : Type.Record.fromDescriptor(outerDescriptor); + + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + assertContainsField(softly, type, "alpha$", Type.primitiveType(Type.TypeCode.STRING, true), descriptor.findFieldByName("alpha__1")); + assertContainsField(softly, type, "beta.", Type.primitiveType(Type.TypeCode.LONG, false), descriptor.findFieldByName("beta__2")); + assertContainsField(softly, type, "uuid__field", Type.UUID_NULL_INSTANCE, descriptor.findFieldByName("uuid__0field")); + assertTypeNames(softly, type, descriptor, preserveNames); + assertTypeNames(softly, roundTrip(type), descriptor, preserveNames); + + assertContainsField(softly, outerType, "alpha$", type.nullable(), outerDescriptor.findFieldByName("alpha__1")); + assertContainsField(softly, outerType, "beta.", type.notNullable(), outerDescriptor.findFieldByName("beta__2")); + final Type.Enum enumType = Type.Enum.fromValues(true, + List.of(Type.Enum.EnumValue.from("ALPHA__", 0), Type.Enum.EnumValue.from("BRAVO$", 1), Type.Enum.EnumValue.from("CHARLIE.", 4), Type.Enum.EnumValue.from("DELTA__3", 3))); + assertContainsField(softly, outerType, "gamma__", enumType, outerDescriptor.findFieldByName("gamma__0")); + assertContainsField(softly, outerType, "delta.array", new Type.Array(true, type.notNullable()), outerDescriptor.findFieldByName("delta__2array")); + assertContainsField(softly, outerType, "epsilon.array", new Type.Array(false, type.notNullable()), outerDescriptor.findFieldByName("epsilon__2array")); + assertContainsField(softly, outerType, "zeta.array", new Type.Array(true, enumType.notNullable()), outerDescriptor.findFieldByName("zeta__2array")); + assertContainsField(softly, outerType, "eta.array", new Type.Array(false, enumType.notNullable()), outerDescriptor.findFieldByName("eta__2array")); + assertTypeNames(softly, outerType, outerDescriptor, preserveNames); + assertTypeNames(softly, roundTrip(outerType), outerDescriptor, preserveNames); + } + } + + /** + * Test where a type descriptor has some field names that require escaping, but the original fields are imperfect. + * The escaped names which are stored are not reversible back to the original field names. This can happen + * if we have an unescaped double-underscore in the field. To make sure the storage names that come back from, say, + * placing the field in a type repository, we need to preserve the original field names. + */ + @ParameterizedTest(name = "testEscapesMalformedFromDescriptor[preserveNames={0}]") + @BooleanSource + void testEscapesMalformedFromDescriptor(boolean preserveNames) { + final Descriptors.Descriptor descriptor = TypeTestProto.Type1__EscapingNotReversible.getDescriptor(); + final Type.Record type = preserveNames ? Type.Record.fromDescriptorPreservingName(descriptor) : Type.Record.fromDescriptor(descriptor); + + final Descriptors.Descriptor outerDescriptor = TypeTestProto.Type2__EscapedNotReversible.getDescriptor(); + final Type.Record outerType = preserveNames ? Type.Record.fromDescriptorPreservingName(outerDescriptor) : Type.Record.fromDescriptor(outerDescriptor); + + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + assertContainsField(softly, type, "alpha$__blah", Type.primitiveType(Type.TypeCode.STRING, true), descriptor.findFieldByName("alpha__1__blah")); + assertContainsField(softly, type, "beta.__", Type.primitiveType(Type.TypeCode.LONG, false), descriptor.findFieldByName("beta__2__")); + assertContainsField(softly, type, "uuid__$field", Type.UUID_NULL_INSTANCE, descriptor.findFieldByName("uuid____1field")); + assertTypeNames(softly, type, descriptor, preserveNames); + assertTypeNames(softly, roundTrip(type), descriptor, preserveNames); + + assertContainsField(softly, outerType, "alpha__blah", type.nullable(), outerDescriptor.findFieldByName("alpha__blah")); + assertContainsField(softly, outerType, "beta.__", type.notNullable(), outerDescriptor.findFieldByName("beta__2__")); + final Type.Enum enumType = Type.Enum.fromValues(true, + List.of(Type.Enum.EnumValue.from("ALPHA__", 0), Type.Enum.EnumValue.from("BRAVO_$", 1), Type.Enum.EnumValue.from("CHARLIE__.", 7), Type.Enum.EnumValue.from("DELTA__3", 4))); + assertContainsField(softly, outerType, "gamma__", enumType, outerDescriptor.findFieldByName("gamma__")); + assertContainsField(softly, outerType, "delta__array", new Type.Array(true, type.notNullable()), outerDescriptor.findFieldByName("delta__array")); + assertContainsField(softly, outerType, "epsilon__array", new Type.Array(false, type.notNullable()), outerDescriptor.findFieldByName("epsilon__array")); + assertTypeNames(softly, outerType, outerDescriptor, preserveNames); + assertTypeNames(softly, roundTrip(outerType), outerDescriptor, preserveNames); + } + } + + private static void assertContainsField(@Nonnull SoftAssertions softly, @Nonnull Type.Record recordType, @Nonnull String fieldName, @Nonnull Type fieldType, @Nonnull Descriptors.Descriptor messageDescriptor) { + final Descriptors.FieldDescriptor fieldDescriptor = messageDescriptor.findFieldByName(fieldName); + softly.assertThat(fieldDescriptor) + .as("field %s not found in descriptor", fieldDescriptor) + .isNotNull(); + assertContainsField(softly, recordType, fieldName, fieldType, fieldDescriptor); + } + + private static void assertContainsField(@Nonnull SoftAssertions softly, @Nonnull Type.Record recordType, @Nonnull String fieldName, @Nonnull Type fieldType, @Nullable Descriptors.FieldDescriptor fieldDescriptor) { + final Map fieldMap = recordType.getFieldNameFieldMap(); + softly.assertThat(fieldMap) + .as("should have had field %s", fieldName) + .containsKey(fieldName); + final Type.Record.Field field = fieldMap.get(fieldName); + if (field == null) { + return; + } + softly.assertThat(field.getFieldType()) + .as("field %s had unexpected type", fieldName) + .isEqualTo(fieldType); + softly.assertThat(field.getFieldName()) + .as("field %s had unexpected name", fieldName) + .isEqualTo(fieldName); + if (fieldDescriptor == null) { + softly.fail("cannot compare with null field descriptor for field %s", fieldName); + } else { + softly.assertThat(field.getFieldStorageName()) + .as("field %s had unexpected storage name", fieldName) + .isEqualTo(fieldDescriptor.getName()); + if (field.getFieldType() instanceof Type.Enum) { + Type.Enum fieldEnumType = (Type.Enum) field.getFieldType(); + final List storageNames = fieldEnumType.getEnumValues().stream().map(Type.Enum.EnumValue::getStorageName).collect(Collectors.toList()); + final List expectedStorageNames = fieldDescriptor.getEnumType().getValues().stream().map(Descriptors.EnumValueDescriptor::getName).collect(Collectors.toList()); + softly.assertThat(storageNames) + .as("field %s enum type should have same names as stored enum", fieldName) + .isEqualTo(expectedStorageNames); + + final List numbers = fieldEnumType.getEnumValues().stream().map(Type.Enum.EnumValue::getNumber).collect(Collectors.toList()); + final List expectedNumbers = fieldDescriptor.getEnumType().getValues().stream().map(Descriptors.EnumValueDescriptor::getNumber).collect(Collectors.toList()); + softly.assertThat(numbers) + .as("field %s enum type should have same numbers as stored enum", fieldName) + .isEqualTo(expectedNumbers); + } + } + } + + private static void assertTypeNames(@Nonnull SoftAssertions softly, @Nonnull Type.Record recordType, @Nonnull Descriptors.Descriptor descriptor, boolean preserveNames) { + if (preserveNames) { + softly.assertThat(recordType.getName()) + .isEqualTo(ProtoUtils.toUserIdentifier(descriptor.getName())); + softly.assertThat(recordType.getStorageName()) + .isEqualTo(descriptor.getName()); + } else { + softly.assertThat(recordType.getName()) + .isNull(); + softly.assertThat(recordType.getStorageName()) + .isNull(); + + } + for (Type.Record.Field field : recordType.getFields()) { + Descriptors.FieldDescriptor fieldDescriptor = descriptor.findFieldByName(field.getFieldStorageName()); + softly.assertThat(fieldDescriptor) + .as("field descriptor not found for field %s", field) + .isNotNull(); + if (fieldDescriptor == null) { + // Can't proceed further + return; + } + + // Extract the element from arrays. If the type is not nullable, we just replace the field + // type with its element type. If the field is nullable, we need to also replace the + // field descriptor so it points to the underlying values. + Type fieldType = field.getFieldType(); + if (fieldType instanceof Type.Array) { + Type.Array fieldArrayType = (Type.Array) fieldType; + fieldType = fieldArrayType.getElementType(); + if (fieldArrayType.isNullable()) { + fieldDescriptor = fieldDescriptor.getMessageType().findFieldByName("values"); + } + softly.assertThat(fieldDescriptor.isRepeated()) + .as("array field %s should be over repeated field", field) + .isTrue(); + } + + // Look to make sure enum and record names are preserved (or not) + if (fieldType instanceof Type.Enum) { + final Type.Enum fieldEnumType = (Type.Enum) fieldType; + if (preserveNames) { + final Descriptors.EnumDescriptor fieldEnumDescriptor = fieldDescriptor.getEnumType(); + softly.assertThat(fieldEnumType.getName()) + .isEqualTo(ProtoUtils.toUserIdentifier(fieldEnumDescriptor.getName())); + softly.assertThat(fieldEnumType.getStorageName()) + .isEqualTo(fieldEnumDescriptor.getName()); + } else { + softly.assertThat(fieldEnumType.getName()) + .isNull(); + softly.assertThat(fieldEnumType.getStorageName()) + .isNull(); + } + } else if (fieldType instanceof Type.Record) { + final Type.Record fieldRecordType = (Type.Record) fieldType; + final Descriptors.Descriptor fieldTypeDescriptor = fieldDescriptor.getMessageType(); + if (preserveNames) { + softly.assertThat(fieldRecordType.getName()) + .isEqualTo(ProtoUtils.toUserIdentifier(fieldTypeDescriptor.getName())); + softly.assertThat(fieldRecordType.getStorageName()) + .isEqualTo(fieldTypeDescriptor.getName()); + } else { + softly.assertThat(fieldRecordType.getName()) + .isNull(); + softly.assertThat(fieldRecordType.getStorageName()) + .isNull(); + } + + // Recurse down + assertTypeNames(softly, fieldRecordType, fieldTypeDescriptor, preserveNames); + } + } + } + + /** + * Send a type to Protobuf and back. This allows us to check that certain features are preserved even in the face + * of type serialization/deserialization. + * + * @param type the type to serialize and deserialize + * @return the deserialized type + */ + @SuppressWarnings("unchecked") + @Nonnull + private static T roundTrip(@Nonnull T type) { + return (T) Type.fromTypeProto(PlanSerializationContext.newForCurrentMode(), + type.toTypeProto(PlanSerializationContext.newForCurrentMode())); } } 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 index 754bdf6d89..d7ec0db814 100644 --- 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 @@ -51,14 +51,14 @@ */ public class RecordConstructorValueTest { - private static final String[] suites = new String[] {"SPADES", "HEARTS", "DIAMONDS", "CLUBS"}; + private static final String[] suits = 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)); + for (var i = 0; i < suits.length; i++) { + enumValues.add(Type.Enum.EnumValue.from(suits[i], i)); } - return new Type.Enum(false, enumValues, "enumType"); + return Type.Enum.fromValuesWithName("enumType", false, enumValues); } @Test @@ -118,8 +118,8 @@ public void deepCopyMessageWithRepeatedEnumValueTest() { 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]))); + for (int i = suits.length - 1; i >= 0; i--) { + messageBuilder.addRepeatedField(enumFieldSrc, Objects.requireNonNull(typeRepoSrc.getEnumValue("enumType", suits[i]))); } var message = messageBuilder.build(); @@ -145,8 +145,8 @@ public void deepCopyEnumValueArrayTest() { 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]))); + for (int i = suits.length - 1; i >= 0; i--) { + list.add(Objects.requireNonNull(typeRepoSrc.getEnumValue("enumType", suits[i]))); } var copied = RecordConstructorValue.deepCopyIfNeeded(typeRepoTarget, arrayType, list); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/util/ProtoUtilsTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/util/ProtoUtilsTest.java new file mode 100644 index 0000000000..9d8cdf3573 --- /dev/null +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/util/ProtoUtilsTest.java @@ -0,0 +1,117 @@ +/* + * ProtoUtilsTest.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 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.util; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Unit tests of the {@link ProtoUtils} class. + */ +class ProtoUtilsTest { + + static Stream protobufCompliantNameTestArguments() { + return Stream.of( + Arguments.of("__", "__"), + Arguments.of("_", "_"), + Arguments.of("$", null), + Arguments.of(".", null), + Arguments.of("__hello", "__hello"), + Arguments.of("__$hello", "____1hello"), + Arguments.of("__$hello", "____1hello"), + Arguments.of("__.hello", "____2hello"), + Arguments.of("____hello", "____0hello"), + Arguments.of("__$h$e$l$l$o", "____1h__1e__1l__1l__1o"), + Arguments.of("__0", null), + Arguments.of("__0hello", null), + Arguments.of("__1", null), + Arguments.of("__1hello", null), + Arguments.of("__2", null), + Arguments.of("__2hello", null), + Arguments.of("__4", "__4"), + Arguments.of("__$$$$", "____1__1__1__1"), + Arguments.of("______", "____0__0"), + Arguments.of("__4hello", "__4hello"), + Arguments.of("$", null), + Arguments.of("$hello", null), + Arguments.of(".", null), + Arguments.of(".hello", null), + Arguments.of("h__e__l__l__o", "h__0e__0l__0l__0o"), + Arguments.of("h.e.l.l.o", "h__2e__2l__2l__2o"), + Arguments.of("h$e$l$l$o", "h__1e__1l__1l__1o"), + Arguments.of("1hello", null), + Arguments.of("डेटाबेस", null) + ); + } + + @ParameterizedTest + @MethodSource("protobufCompliantNameTestArguments") + void protobufCompliantNameTest(@Nonnull String userIdentifier, @Nullable String protobufIdentifier) { + if (protobufIdentifier != null) { + final String actual = ProtoUtils.toProtoBufCompliantName(userIdentifier); + assertThat(actual) + .isEqualTo(protobufIdentifier); + final String reTranslated = ProtoUtils.toUserIdentifier(actual); + assertThat(reTranslated) + .isEqualTo(userIdentifier); + } else { + assertThatThrownBy(() -> ProtoUtils.toProtoBufCompliantName(userIdentifier)) + .isInstanceOf(ProtoUtils.InvalidNameException.class); + } + } + + @Test + void uniqueTypeName() { + final String name1 = ProtoUtils.uniqueTypeName(); + final String name2 = ProtoUtils.uniqueTypeName(); + assertThat(name1) + .isNotEqualTo(name2); + assertThatCode(() -> ProtoUtils.checkValidProtoBufCompliantName(name1)) + .doesNotThrowAnyException(); + assertThatCode(() -> ProtoUtils.checkValidProtoBufCompliantName(name2)) + .doesNotThrowAnyException(); + } + + @Test + void uniqueOtherName() { + final String name1 = ProtoUtils.uniqueName("blah"); + final String name2 = ProtoUtils.uniqueName("blah"); + assertThat(name1) + .startsWith("blah") + .isNotEqualTo(name2); + assertThat(name2) + .startsWith("blah"); + assertThatCode(() -> ProtoUtils.checkValidProtoBufCompliantName(name1)) + .doesNotThrowAnyException(); + assertThatCode(() -> ProtoUtils.checkValidProtoBufCompliantName(name2)) + .doesNotThrowAnyException(); + } +} diff --git a/fdb-record-layer-core/src/test/proto/type_test.proto b/fdb-record-layer-core/src/test/proto/type_test.proto new file mode 100644 index 0000000000..2ac1a8ee3a --- /dev/null +++ b/fdb-record-layer-core/src/test/proto/type_test.proto @@ -0,0 +1,174 @@ +/* + * type_test.proto + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 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. + */ + +syntax = "proto2"; + +package com.apple.foundationdb.record.type_test; + +option java_package = "com.apple.foundationdb.record"; +option java_outer_classname = "TypeTestProto"; + +import "tuple_fields.proto"; + +message PrimitiveFields { + // Optional variants: + optional bool boolean_field = 1; + optional bytes bytes_field = 2; + optional double double_field = 3; + optional float float_field = 4; + optional int32 int_field = 5; + optional int64 long_field = 6; + optional string string_field = 7; + + // Required variants: + required bool req_boolean_field = 101; + required bytes req_bytes_field = 102; + required double req_double_field = 103; + required float req_float_field = 104; + required int32 req_int_field = 105; + required int64 req_long_field = 106; + required string req_string_field = 107; + + // Nullable arrays + message BooleanArray { + repeated bool values = 1; + } + optional BooleanArray arr_boolean_field = 201; + message BytesArray { + repeated bytes values = 1; + } + optional BytesArray arr_bytes_field = 202; + message DoubleArray { + repeated double values = 1; + } + optional DoubleArray arr_double_field = 203; + message FloatArray { + repeated float values = 1; + } + optional FloatArray arr_float_field = 204; + message IntArray { + repeated int32 values = 1; + } + optional IntArray arr_int_field = 205; + message LongArray { + repeated int64 values = 1; + } + optional LongArray arr_long_field = 206; + message StringArray { + repeated string values = 1; + } + optional StringArray arr_string_field = 207; + + // Non-nullable arrays: + repeated bool req_arr_boolean_field = 301; + repeated bytes req_arr_bytes_field = 302; + repeated double req_arr_double_field = 303; + repeated float req_arr_float_field = 304; + repeated int32 req_arr_int_field = 305; + repeated int64 req_arr_long_field = 306; + repeated string req_arr_string_field = 307; +} + +message Type1 { + optional string alpha = 1; + required int64 beta = 2; + optional UUID uuid_field = 4; +} + +message Type2 { + optional Type1 alpha = 1; + required Type1 beta = 5; + + enum T2Enum { + ALPHA = 0; + BRAVO = 1; + CHARLIE = 3; + DELTA = 4; + } + optional T2Enum gamma = 3; + + message Type1Array { + repeated Type1 values = 1; + } + optional Type1Array delta = 6; + repeated Type1 epsilon = 4; + + message T2EnumArray { + repeated T2Enum values = 1; + } + optional T2EnumArray zeta = 7; + repeated T2Enum eta = 8; +} + +message Type1__0Escaped { + optional string alpha__1 = 1; + required int64 beta__2 = 2; + optional UUID uuid__0field = 5; +} + +message Type2__0Escaped { + optional Type1__0Escaped alpha__1 = 1; + required Type1__0Escaped beta__2 = 6; + + enum T2Enum__0Escaped { + ALPHA__0 = 0; + BRAVO__1 = 1; + CHARLIE__2 = 4; + DELTA__03 = 3; + } + optional T2Enum__0Escaped gamma__0 = 3; + + message Type1__2Array { + repeated Type1__0Escaped values = 1; + } + optional Type1__2Array delta__2array = 4; + repeated Type1__0Escaped epsilon__2array = 5; + + message T2Enum__0EscapedArray { + repeated T2Enum__0Escaped values = 1; + } + optional T2Enum__0EscapedArray zeta__2array = 7; + repeated T2Enum__0Escaped eta__2array = 8; +} + +message Type1__EscapingNotReversible { + optional string alpha__1__blah = 1; + required int64 beta__2__ = 2; + optional UUID uuid____1field = 6; +} + +message Type2__EscapedNotReversible { + optional Type1__EscapingNotReversible alpha__blah = 1; + required Type1__EscapingNotReversible beta__2__ = 7; + + enum T2__EnumEscapedNotReversible { + ALPHA__ = 0; + BRAVO___1 = 1; + CHARLIE____2 = 7; + DELTA__3 = 4; + } + optional T2__EnumEscapedNotReversible gamma__ = 3; + + message Type1__Array { + repeated Type1__EscapingNotReversible values = 1; + } + optional Type1__Array delta__array = 4; + repeated Type1__EscapingNotReversible epsilon__array = 5; +} diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/metadata/DataType.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/metadata/DataType.java index 239d351347..bdc1a27f7c 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/metadata/DataType.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/metadata/DataType.java @@ -1072,6 +1072,23 @@ public int getNumber() { public String toString() { return name; } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final EnumValue enumValue = (EnumValue)object; + return number == enumValue.number && Objects.equals(name, enumValue.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, number); + } } @Override diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java index 6edc85fe4c..5b3d2364bc 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java @@ -20,6 +20,7 @@ package com.apple.foundationdb.relational.api; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.InvalidColumnReferenceException; import com.apple.foundationdb.relational.api.exceptions.RelationalException; @@ -211,7 +212,7 @@ public String getString(int oneBasedPosition) throws SQLException { } else if (o instanceof Enum) { return ((Enum) o).name(); } else if (o instanceof Descriptors.EnumValueDescriptor) { - return ((Descriptors.EnumValueDescriptor) o).getName(); + return ProtoUtils.toUserIdentifier(((Descriptors.EnumValueDescriptor) o).getName()); } else if (o instanceof ByteString) { return ((ByteString) o).toString(); } else { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/AbstractRow.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/AbstractRow.java index 088618b635..bf0274bb32 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/AbstractRow.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/AbstractRow.java @@ -20,6 +20,7 @@ package com.apple.foundationdb.relational.recordlayer; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.tuple.ByteArrayUtil2; import com.apple.foundationdb.tuple.Tuple; import com.apple.foundationdb.relational.api.Row; @@ -91,7 +92,7 @@ public String getString(int position) throws InvalidTypeException, InvalidColumn if (o instanceof Enum) { return ((Enum) o).name(); } else if (o instanceof Descriptors.EnumValueDescriptor) { - return ((Descriptors.EnumValueDescriptor) o).getName(); + return ProtoUtils.toUserIdentifier(((Descriptors.EnumValueDescriptor) o).getName()); } else if (!(o instanceof String)) { throw new InvalidTypeException("Value <" + o + "> cannot be cast to a String"); } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/MessageTuple.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/MessageTuple.java index b2eb275661..e29bef5948 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/MessageTuple.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/MessageTuple.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.record.RecordMetaDataOptionsProto; import com.apple.foundationdb.record.TupleFieldsProto; import com.apple.foundationdb.record.metadata.expressions.TupleFieldsHelper; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.record.util.VectorUtils; import com.apple.foundationdb.relational.api.exceptions.InvalidColumnReferenceException; import com.google.protobuf.ByteString; @@ -81,7 +82,7 @@ public static Object sanitizeField(@Nonnull final Object field, @Nonnull final D return TupleFieldsHelper.fromProto((Message) field, TupleFieldsProto.UUID.getDescriptor()); } if (field instanceof Descriptors.EnumValueDescriptor) { - return ((Descriptors.EnumValueDescriptor) field).getName(); + return ProtoUtils.toUserIdentifier(((Descriptors.EnumValueDescriptor) field).getName()); } if (field instanceof ByteString) { final var byteString = (ByteString) field; diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/DataTypeUtils.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/DataTypeUtils.java index f29d51158f..7799db0db3 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/DataTypeUtils.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/DataTypeUtils.java @@ -21,13 +21,12 @@ package com.apple.foundationdb.relational.recordlayer.metadata; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; - import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -35,7 +34,6 @@ import java.util.List; import java.util.Locale; import java.util.Optional; -import java.util.UUID; import java.util.stream.Collectors; @API(API.Status.EXPERIMENTAL) @@ -76,31 +74,20 @@ public static DataType toRelationalType(@Nonnull final Type type) { case RECORD: final var record = (Type.Record) type; final var columns = record.getFields().stream().map(field -> DataType.StructType.Field.from(field.getFieldName(), toRelationalType(field.getFieldType()), field.getFieldIndex())).collect(Collectors.toList()); - return DataType.StructType.from(record.getName() == null ? toProtoBufCompliantName(UUID.randomUUID().toString()) : record.getName(), columns, record.isNullable()); + return DataType.StructType.from(record.getName() == null ? ProtoUtils.uniqueTypeName() : record.getName(), columns, record.isNullable()); case ARRAY: final var asArray = (Type.Array) type; return DataType.ArrayType.from(toRelationalType(Assert.notNullUnchecked(asArray.getElementType())), asArray.isNullable()); case ENUM: final var asEnum = (Type.Enum) type; final var enumValues = asEnum.getEnumValues().stream().map(v -> DataType.EnumType.EnumValue.of(v.getName(), v.getNumber())).collect(Collectors.toList()); - return DataType.EnumType.from(asEnum.getName() == null ? toProtoBufCompliantName(UUID.randomUUID().toString()) : asEnum.getName(), enumValues, asEnum.isNullable()); + return DataType.EnumType.from(asEnum.getName() == null ? ProtoUtils.uniqueName("") : asEnum.getName(), enumValues, asEnum.isNullable()); default: Assert.failUnchecked(String.format(Locale.ROOT, "unexpected type %s", type)); return null; // make compiler happy. } } - @Nonnull - private static String toProtoBufCompliantName(@Nonnull final String input) { - Assert.thatUnchecked(input.length() > 0); - final var modified = input.replace("-", "_"); - final char c = input.charAt(0); - if (c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) { - return modified; - } - return "id" + modified; - } - /** * Converts a given Relational {@link DataType} into a corresponding Record Layer {@link Type}. * @@ -132,8 +119,8 @@ public static Type toRecordLayerType(@Nonnull final DataType type) { return new Type.Array(asArray.isNullable(), toRecordLayerType(asArray.getElementType())); case ENUM: final var asEnum = (DataType.EnumType) type; - final List enumValues = asEnum.getValues().stream().map(v -> new Type.Enum.EnumValue(v.getName(), v.getNumber())).collect(Collectors.toList()); - return new Type.Enum(asEnum.isNullable(), enumValues, asEnum.getName()); + final List enumValues = asEnum.getValues().stream().map(v -> Type.Enum.EnumValue.from(v.getName(), v.getNumber())).collect(Collectors.toList()); + return Type.Enum.fromValuesWithName(asEnum.getName(), asEnum.isNullable(), enumValues); case VECTOR: final var vectorType = (DataType.VectorType)type; final var precision = vectorType.getPrecision(); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumn.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumn.java index 776580b37e..f6d1d1719c 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumn.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumn.java @@ -27,6 +27,7 @@ import com.apple.foundationdb.relational.util.Assert; import javax.annotation.Nonnull; +import java.util.Objects; @API(API.Status.EXPERIMENTAL) public class RecordLayerColumn implements Column { @@ -60,6 +61,28 @@ public int getIndex() { return index; } + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final RecordLayerColumn that = (RecordLayerColumn)object; + return index == that.index && Objects.equals(name, that.name) && Objects.equals(dataType, that.dataType); + } + + @Override + public int hashCode() { + return Objects.hash(name, dataType, index); + } + + @Override + public String toString() { + return name + ": " + dataType + " = " + index; + } + public static final class Builder { private String name; private DataType dataType; diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java index 00095bc203..68aebe2f3d 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java @@ -21,13 +21,13 @@ package com.apple.foundationdb.relational.recordlayer.metadata; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.RecordMetaDataProto; import com.apple.foundationdb.record.metadata.IndexOptions; import com.apple.foundationdb.record.metadata.expressions.KeyExpression; +import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.relational.api.metadata.Index; import com.apple.foundationdb.relational.util.Assert; - import com.google.common.collect.ImmutableMap; import javax.annotation.Nonnull; @@ -41,6 +41,9 @@ public final class RecordLayerIndex implements Index { @Nonnull private final String tableName; + @Nonnull + private final String tableStorageName; + private final String indexType; @Nonnull @@ -56,12 +59,14 @@ public final class RecordLayerIndex implements Index { private final RecordMetaDataProto.Predicate predicate; private RecordLayerIndex(@Nonnull final String tableName, + @Nonnull final String tableStorageName, @Nonnull final String indexType, @Nonnull final String name, @Nonnull final KeyExpression keyExpression, @Nullable final RecordMetaDataProto.Predicate predicate, @Nonnull final Map options) { this.tableName = tableName; + this.tableStorageName = tableStorageName; this.indexType = indexType; this.name = name; this.keyExpression = keyExpression; @@ -75,6 +80,11 @@ public String getTableName() { return tableName; } + @Nonnull + public String getTableStorageName() { + return tableStorageName; + } + @Nonnull @Override public String getIndexType() { @@ -114,11 +124,12 @@ public Map getOptions() { } @Nonnull - public static RecordLayerIndex from(@Nonnull final String tableName, @Nonnull final com.apple.foundationdb.record.metadata.Index index) { + public static RecordLayerIndex from(@Nonnull final String tableName, @Nonnull String tableStorageName, @Nonnull final com.apple.foundationdb.record.metadata.Index index) { final var indexProto = index.toProto(); return newBuilder().setName(index.getName()) .setIndexType(index.getType()) .setTableName(tableName) + .setTableStorageName(tableStorageName) .setKeyExpression(index.getRootExpression()) .setPredicate(indexProto.hasPredicate() ? indexProto.getPredicate() : null) .setOptions(index.getOptions()) @@ -149,6 +160,7 @@ public int hashCode() { public static class Builder { private String tableName; + private String tableStorageName; private String indexType; private String name; private KeyExpression keyExpression; @@ -164,6 +176,18 @@ public Builder setTableName(String tableName) { return this; } + @Nonnull + public Builder setTableStorageName(String tableStorageName) { + this.tableStorageName = tableStorageName; + return this; + } + + @Nonnull + public Builder setTableType(@Nonnull Type.Record tableType) { + return setTableName(tableType.getName()) + .setTableStorageName(tableType.getStorageName()); + } + @Nonnull public Builder setIndexType(String indexType) { this.indexType = indexType; @@ -223,9 +247,12 @@ public Builder setOption(@Nonnull final String optionKey, boolean optionValue) { public RecordLayerIndex build() { Assert.notNullUnchecked(name, "index name is not set"); Assert.notNullUnchecked(tableName, "table name is not set"); + if (tableStorageName == null) { + tableStorageName = ProtoUtils.toProtoBufCompliantName(tableName); + } Assert.notNullUnchecked(indexType, "index type is not set"); Assert.notNullUnchecked(keyExpression, "index key expression is not set"); - return new RecordLayerIndex(tableName, indexType, name, keyExpression, predicate, + return new RecordLayerIndex(tableName, tableStorageName, indexType, name, keyExpression, predicate, optionsBuilder == null ? ImmutableMap.of() : optionsBuilder.build()); } } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerSchemaTemplate.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerSchemaTemplate.java index f21fb0198c..82e9e793d5 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerSchemaTemplate.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerSchemaTemplate.java @@ -570,9 +570,10 @@ public Builder setCachedMetadata(@Nonnull final RecordMetaData metadata) { } @Nonnull - public RecordLayerTable findTable(@Nonnull final String name) { - Assert.thatUnchecked(tables.containsKey(name), ErrorCode.UNDEFINED_TABLE, "could not find '%s'", name); - return tables.get(name); + public RecordLayerTable findTableByStorageName(@Nonnull final String storageName) { + return tables.values().stream().filter(t -> t.getType().getStorageName().equals(storageName)) + .findAny() + .orElseThrow(() -> Assert.failUnchecked(ErrorCode.UNDEFINED_TABLE, "could not find '" + storageName + "'")); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerTable.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerTable.java index 1e43195d90..632059339a 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerTable.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerTable.java @@ -24,9 +24,11 @@ import com.apple.foundationdb.record.metadata.Key; import com.apple.foundationdb.record.metadata.expressions.EmptyKeyExpression; +import com.apple.foundationdb.record.metadata.expressions.FieldKeyExpression; import com.apple.foundationdb.record.metadata.expressions.KeyExpression; import com.apple.foundationdb.record.metadata.expressions.ThenKeyExpression; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.Table; @@ -38,8 +40,10 @@ import com.google.protobuf.DescriptorProtos; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -289,7 +293,7 @@ public Builder setPrimaryKey(@Nonnull final KeyExpression primaryKey) { @Nonnull public Builder addPrimaryKeyPart(@Nonnull final List primaryKeyPart) { - primaryKeyParts.add(toKeyExpression(primaryKeyPart)); + primaryKeyParts.add(toKeyExpression(record, primaryKeyPart)); return this; } @@ -306,20 +310,48 @@ public Builder addColumns(@Nonnull final Collection columns) } @Nonnull - private static KeyExpression toKeyExpression(@Nonnull final List fields) { + private static KeyExpression toKeyExpression(@Nullable Type.Record type, @Nonnull final List fields) { Assert.thatUnchecked(!fields.isEmpty()); - return toKeyExpression(fields, 0); + return toKeyExpression(type, fields.iterator()); } @Nonnull - private static KeyExpression toKeyExpression(@Nonnull final List fields, int i) { - Assert.thatUnchecked(0 <= i && i < fields.size()); - if (i == fields.size() - 1) { - return Key.Expressions.field(fields.get(i)); + private static KeyExpression toKeyExpression(@Nullable Type.Record type, @Nonnull final Iterator fields) { + Assert.thatUnchecked(fields.hasNext()); + String fieldName = fields.next(); + Type.Record.Field field = getFieldDefinition(type, fieldName); + final FieldKeyExpression expression = Key.Expressions.field(getFieldStorageName(field, fieldName)); + if (fields.hasNext()) { + Type.Record fieldType = getFieldRecordType(type, field); + return expression.nest(toKeyExpression(fieldType, fields)); + } else { + return expression; } - return Key.Expressions.field(fields.get(i)).nest(toKeyExpression(fields, i + 1)); } + @Nullable + private static Type.Record.Field getFieldDefinition(@Nullable Type.Record type, @Nonnull String fieldName) { + return type == null ? null : type.getFieldNameFieldMap().get(fieldName); + } + + @Nonnull + private static String getFieldStorageName(@Nullable Type.Record.Field field, @Nonnull String fieldName) { + return field == null ? ProtoUtils.toProtoBufCompliantName(fieldName) : field.getFieldStorageName(); + } + + @Nullable + private static Type.Record getFieldRecordType(@Nullable Type.Record recordType, @Nullable Type.Record.Field field) { + if (field == null) { + return null; + } + Type fieldType = field.getFieldType(); + if (!(fieldType instanceof Type.Record)) { + Assert.failUnchecked(ErrorCode.INVALID_COLUMN_REFERENCE, "Field '" + field.getFieldName() + "' on type '" + (recordType == null ? "UNKNONW" : recordType.getName()) + "' is not a struct"); + } + return (Type.Record) fieldType; + } + + @Nonnull private KeyExpression getPrimaryKey() { if (primaryKeyParts.isEmpty()) { return EmptyKeyExpression.EMPTY; diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/FileDescriptorSerializer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/FileDescriptorSerializer.java index 062877905d..68457e8a86 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/FileDescriptorSerializer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/FileDescriptorSerializer.java @@ -91,20 +91,20 @@ public void visit(@Nonnull Metadata metadata) { @Override public void visit(@Nonnull final Table table) { Assert.thatUnchecked(table instanceof RecordLayerTable); - final var recordLayerTable = (RecordLayerTable) table; - final var type = recordLayerTable.getType(); - final var typeDescriptor = registerTypeDescriptors(type); - final var generations = recordLayerTable.getGenerations(); + final RecordLayerTable recordLayerTable = (RecordLayerTable) table; + final Type.Record type = recordLayerTable.getType(); + final String typeDescriptor = registerTypeDescriptors(type); + final Map generations = recordLayerTable.getGenerations(); checkTableGenerations(generations); int fieldCounter = 0; // add the table as an entry in the final 'RecordTypeUnion' entry of the record store metadata. There is one // field for each generation of the RecordLayerTable. - for (var version : generations.entrySet()) { + for (Map.Entry version : generations.entrySet()) { final var tableEntryInUnionDescriptor = DescriptorProtos.FieldDescriptorProto.newBuilder() .setNumber(version.getKey()) - .setName(recordLayerTable.getName() + "_" + (fieldCounter++)) + .setName(typeDescriptor + "_" + (fieldCounter++)) .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE) .setTypeName(typeDescriptor) .setOptions(version.getValue()) diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataDeserializer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataDeserializer.java index 25966e4c3b..f24c446da7 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataDeserializer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataDeserializer.java @@ -21,13 +21,13 @@ package com.apple.foundationdb.relational.recordlayer.metadata.serde; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.RecordMetaData; import com.apple.foundationdb.record.metadata.RecordType; -import com.apple.foundationdb.record.query.plan.cascades.UserDefinedMacroFunction; import com.apple.foundationdb.record.query.plan.cascades.RawSqlFunction; import com.apple.foundationdb.record.query.plan.cascades.UserDefinedFunction; +import com.apple.foundationdb.record.query.plan.cascades.UserDefinedMacroFunction; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerIndex; @@ -37,15 +37,17 @@ import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerView; import com.apple.foundationdb.relational.recordlayer.query.LogicalOperator; import com.apple.foundationdb.relational.util.Assert; - import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableList; +import com.google.protobuf.Descriptors; import javax.annotation.Nonnull; import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @@ -77,24 +79,25 @@ private RecordLayerSchemaTemplate.Builder deserializeRecordMetaData() { // deserialization of other descriptors that can never be used by the user. final var unionDescriptor = recordMetaData.getUnionDescriptor(); - final var registeredTypes = unionDescriptor.getFields(); - final var schemaTemplateBuilder = RecordLayerSchemaTemplate.newBuilder() + final List registeredTypes = unionDescriptor.getFields(); + final RecordLayerSchemaTemplate.Builder schemaTemplateBuilder = RecordLayerSchemaTemplate.newBuilder() .setStoreRowVersions(recordMetaData.isStoreRecordVersions()) .setEnableLongRows(recordMetaData.isSplitLongRecords()) .setIntermingleTables(!recordMetaData.primaryKeyHasRecordTypePrefix()); - final var nameToTableBuilder = new HashMap(); - for (final var registeredType : registeredTypes) { + final Map nameToTableBuilder = new HashMap<>(); + for (final Descriptors.FieldDescriptor registeredType : registeredTypes) { switch (registeredType.getType()) { case MESSAGE: - final var name = registeredType.getMessageType().getName(); - if (!nameToTableBuilder.containsKey(name)) { - nameToTableBuilder.put(name, generateTableBuilder(name)); + final String storageName = registeredType.getMessageType().getName(); + final String userName = ProtoUtils.toUserIdentifier(storageName); + if (!nameToTableBuilder.containsKey(userName)) { + nameToTableBuilder.put(userName, generateTableBuilder(userName, storageName)); } - nameToTableBuilder.get(name).addGeneration(registeredType.getNumber(), registeredType.getOptions()); + nameToTableBuilder.get(userName).addGeneration(registeredType.getNumber(), registeredType.getOptions()); break; case ENUM: // todo (yhatem) this is temporary, we rely on rec layer type system to deserialize protobuf descriptors. - final var recordLayerType = new Type.Enum(false, Type.Enum.enumValuesFromProto(registeredType.getEnumType().getValues()), registeredType.getName()); + final var recordLayerType = Type.Enum.fromDescriptorPreservingNames(false, registeredType.getEnumType()); schemaTemplateBuilder.addAuxiliaryType((DataType.Named) DataTypeUtils.toRelationalType(recordLayerType)); break; default: @@ -127,38 +130,19 @@ private RecordLayerSchemaTemplate.Builder deserializeRecordMetaData() { } @Nonnull - private RecordLayerTable.Builder generateTableBuilder(@Nonnull final String tableName) { - return generateTableBuilder(recordMetaData.getRecordType(tableName)); - } + private RecordLayerTable.Builder generateTableBuilder(@Nonnull final String userName, @Nonnull final String storageName) { + final RecordType recordType = recordMetaData.getRecordType(storageName); - @Nonnull - private RecordLayerTable.Builder generateTableBuilder(@Nonnull final RecordType recordType) { // todo (yhatem) we rely on the record type for deserialization from ProtoBuf for now, later on // we will avoid this step by having our own deserializers. - final var recordLayerType = Type.Record.fromFieldsWithName(recordType.getName(), false, Type.Record.fromDescriptor(recordType.getDescriptor()).getFields()); // todo (yhatem) this is hacky and must be cleaned up. We need to understand the actually field types so we can take decisions - // on higher level based on these types (wave3). - if (recordLayerType.getFields().stream().anyMatch(f -> f.getFieldType().isRecord())) { - ImmutableList.Builder newFields = ImmutableList.builder(); - for (int i = 0; i < recordLayerType.getFields().size(); i++) { - final var protoField = recordType.getDescriptor().getFields().get(i); - final var field = recordLayerType.getField(i); - if (field.getFieldType().isRecord()) { - Type.Record r = Type.Record.fromFieldsWithName(protoField.getMessageType().getName(), field.getFieldType().isNullable(), ((Type.Record) field.getFieldType()).getFields()); - newFields.add(Type.Record.Field.of(r, field.getFieldNameOptional(), field.getFieldIndexOptional())); - } else { - newFields.add(field); - } - } - return RecordLayerTable.Builder - .from(Type.Record.fromFieldsWithName(recordType.getName(), false, newFields.build())) - .setPrimaryKey(recordType.getPrimaryKey()) - .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(recordType.getName(), index)).collect(Collectors.toSet())); - } + // on higher level based on these types (wave3). + final var recordLayerType = Type.Record.fromDescriptorPreservingName(recordType.getDescriptor()); return RecordLayerTable.Builder .from(recordLayerType) + .setName(userName) .setPrimaryKey(recordType.getPrimaryKey()) - .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(recordType.getName(), index)).collect(Collectors.toSet())); + .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(Objects.requireNonNull(recordLayerType.getName()), Objects.requireNonNull(recordLayerType.getStorageName()), index)).collect(Collectors.toSet())); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataSerializer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataSerializer.java index 621d53d73c..11b80a071c 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataSerializer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataSerializer.java @@ -66,7 +66,7 @@ public void visit(@Nonnull Table table) { Assert.thatUnchecked(table instanceof RecordLayerTable); final var recLayerTable = (RecordLayerTable) table; final KeyExpression keyExpression = recLayerTable.getPrimaryKey(); - final RecordTypeBuilder recordType = getBuilder().getRecordType(table.getName()); + final RecordTypeBuilder recordType = getBuilder().getRecordType(recLayerTable.getType().getStorageName()); recordType.setRecordTypeKey(recordTypeCounter++); recordType.setPrimaryKey(keyExpression); } @@ -79,8 +79,8 @@ public void visit(@Nonnull com.apple.foundationdb.relational.api.metadata.Index // have a version that matches the schema template's version // See: TODO (Relational index misses version information) Assert.thatUnchecked(index instanceof RecordLayerIndex); - final var recLayerIndex = (RecordLayerIndex) index; - getBuilder().addIndex(index.getTableName(), + final RecordLayerIndex recLayerIndex = (RecordLayerIndex) index; + getBuilder().addIndex(recLayerIndex.getTableStorageName(), new Index(index.getName(), recLayerIndex.getKeyExpression(), index.getIndexType(), diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java index 14c10f53c5..8695c4a9e3 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java @@ -67,10 +67,10 @@ import com.apple.foundationdb.record.query.plan.cascades.values.VersionValue; import com.apple.foundationdb.record.query.plan.planning.BooleanPredicateNormalizer; import com.apple.foundationdb.record.util.pair.NonnullPair; -import com.apple.foundationdb.record.util.pair.Pair; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerIndex; +import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.NullableArrayUtils; import com.google.common.base.Verify; @@ -143,10 +143,13 @@ private IndexGenerator(@Nonnull RelationalExpression relationalExpression, boole } @Nonnull - public RecordLayerIndex generate(@Nonnull String indexName, boolean isUnique, @Nonnull Type.Record tableType, boolean containsNullableArray) { + public RecordLayerIndex generate(@Nonnull RecordLayerSchemaTemplate.Builder schemaTemplateBuilder, @Nonnull String indexName, boolean isUnique, boolean containsNullableArray) { + final String recordTypeName = getRecordTypeName(); + // Have to use the storage name here because the index generator uses it + final Type.Record tableType = schemaTemplateBuilder.findTableByStorageName(recordTypeName).getType(); final var indexBuilder = RecordLayerIndex.newBuilder() .setName(indexName) - .setTableName(getRecordTypeName()) + .setTableType(tableType) .setUnique(isUnique); collectQuantifiers(relationalExpression); @@ -529,7 +532,7 @@ private KeyExpression toKeyExpression(@Nonnull Value value) { return VersionKeyExpression.VERSION; } else if (value instanceof FieldValue) { FieldValue fieldValue = (FieldValue) value; - return toKeyExpression(fieldValue.getFieldPath().getFieldAccessors().stream().map(acc -> Pair.of(acc.getName(), acc.getType())).collect(toList())); + return toKeyExpression(fieldValue.getFieldPath().getFieldAccessors().iterator()); } else if (value instanceof ArithmeticValue) { var children = value.getChildren(); var builder = ImmutableList.builder(); @@ -561,15 +564,16 @@ private static KeyExpression toKeyExpression(@Nonnull FieldValueTrieNode trieNod Assert.thatUnchecked(!trieNode.getChildrenMap().isEmpty()); final var childrenMap = trieNode.getChildrenMap(); - final var exprConstituents = childrenMap.keySet().stream().map(key -> { - final var expr = toKeyExpression(Objects.requireNonNull(key.getName()), key.getType()); - final var value = childrenMap.get(key); - if (value.getChildrenMap() != null) { - return expr.nest(toKeyExpression(value, orderingFunctions)); - } else if (orderingFunctions.containsKey(value.getValue())) { - return function(orderingFunctions.get(value.getValue()), expr); + final var exprConstituents = childrenMap.entrySet().stream().map(nodeEntry -> { + final FieldValue.ResolvedAccessor accessor = nodeEntry.getKey(); + final FieldValueTrieNode node = nodeEntry.getValue(); + final FieldKeyExpression fieldExpr = toFieldKeyExpression(accessor.getField()); + if (node.getChildrenMap() != null) { + return fieldExpr.nest(toKeyExpression(node, orderingFunctions)); + } else if (orderingFunctions.containsKey(node.getValue())) { + return function(orderingFunctions.get(node.getValue()), fieldExpr); } else { - return expr; + return fieldExpr; } }).map(v -> (KeyExpression) v).collect(toList()); if (exprConstituents.size() == 1) { @@ -647,17 +651,16 @@ private static final class AnnotatedAccessor extends FieldValue.ResolvedAccessor private final int marker; - private AnnotatedAccessor(@Nonnull Type fieldType, - @Nullable String fieldName, - int fieldOrdinal, + private AnnotatedAccessor(@Nonnull Type.Record.Field field, + int ordinal, int marker) { - super(fieldName, fieldOrdinal, fieldType); + super(field, ordinal); this.marker = marker; } @Nonnull public static AnnotatedAccessor of(@Nonnull FieldValue.ResolvedAccessor resolvedAccessor, int marker) { - return new AnnotatedAccessor(resolvedAccessor.getType(), resolvedAccessor.getName(), resolvedAccessor.getOrdinal(), marker); + return new AnnotatedAccessor(resolvedAccessor.getField(), resolvedAccessor.getOrdinal(), marker); } @Override @@ -739,23 +742,20 @@ private Value dereference(@Nonnull Value value) { } @Nonnull - private KeyExpression toKeyExpression(@Nonnull List> fields) { - return toKeyExpression(fields, 0); - } - - @Nonnull - private KeyExpression toKeyExpression(@Nonnull List> fields, int index) { - Assert.thatUnchecked(!fields.isEmpty()); - final var field = fields.get(index); - final var keyExpression = toKeyExpression(field.getLeft(), field.getRight()); - if (index + 1 < fields.size()) { - return keyExpression.nest(toKeyExpression(fields, index + 1)); + private KeyExpression toKeyExpression(@Nonnull Iterator resolvedAccessors) { + Assert.thatUnchecked(resolvedAccessors.hasNext(), "cannot resolve empty list"); + final Type.Record.Field field = resolvedAccessors.next().getField(); + final FieldKeyExpression fieldExpression = toFieldKeyExpression(field); + if (resolvedAccessors.hasNext()) { + KeyExpression childExpression = toKeyExpression(resolvedAccessors); + return fieldExpression.nest(childExpression); + } else { + return fieldExpression; } - return keyExpression; } @Nonnull - public String getRecordTypeName() { + private String getRecordTypeName() { final var expressionRefs = relationalExpressions.stream() .filter(r -> r instanceof LogicalTypeFilterExpression) .map(r -> (LogicalTypeFilterExpression) r) @@ -767,12 +767,14 @@ public String getRecordTypeName() { } @Nonnull - private static FieldKeyExpression toKeyExpression(@Nonnull String name, @Nonnull Type type) { - Assert.notNullUnchecked(name); - final var fanType = type.getTypeCode() == Type.TypeCode.ARRAY ? + private static FieldKeyExpression toFieldKeyExpression(@Nonnull Type.Record.Field fieldType) { + Assert.notNullUnchecked(fieldType.getFieldStorageName()); + final var fanType = fieldType.getFieldType().getTypeCode() == Type.TypeCode.ARRAY ? KeyExpression.FanType.FanOut : KeyExpression.FanType.None; - return field(name, fanType); + // At this point, we need to use the storage field name as that will be the name referenced + // in Protobuf storage + return field(fieldType.getFieldStorageName(), fanType); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java index 5db9f27378..0f596a8750 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java @@ -232,15 +232,17 @@ public static LogicalOperator newOperatorWithPreservedExpressionNames(@Nonnull O public static LogicalOperator generateTableAccess(@Nonnull Identifier tableId, @Nonnull Set indexAccessHints, @Nonnull SemanticAnalyzer semanticAnalyzer) { - final var tableNames = semanticAnalyzer.getAllTableNames(); + final Set tableNames = semanticAnalyzer.getAllTableStorageNames(); semanticAnalyzer.validateIndexes(tableId, indexAccessHints); final var scanExpression = Quantifier.forEach(Reference.initialOf( new FullUnorderedScanExpression(tableNames, new Type.AnyRecord(false), new AccessHints(indexAccessHints.toArray(new AccessHint[0]))))); final var table = semanticAnalyzer.getTable(tableId); - final var type = Assert.castUnchecked(table, RecordLayerTable.class).getType(); - final var typeFilterExpression = new LogicalTypeFilterExpression(ImmutableSet.of(tableId.getName()), scanExpression, type); + final Type.Record type = Assert.castUnchecked(table, RecordLayerTable.class).getType(); + final String storageName = type.getStorageName(); + Assert.thatUnchecked(storageName != null, "storage name for table access must not be null"); + final var typeFilterExpression = new LogicalTypeFilterExpression(ImmutableSet.of(storageName), scanExpression, type); final var resultingQuantifier = Quantifier.forEach(Reference.initialOf(typeFilterExpression)); final ImmutableList.Builder attributesBuilder = ImmutableList.builder(); int colCount = 0; @@ -470,10 +472,10 @@ public static LogicalOperator generateSort(@Nonnull LogicalOperator logicalOpera @Nonnull public static LogicalOperator generateInsert(@Nonnull LogicalOperator insertSource, @Nonnull Table target) { - final var targetType = Assert.castUnchecked(target, RecordLayerTable.class).getType(); + final Type.Record targetType = Assert.castUnchecked(target, RecordLayerTable.class).getType(); final var insertExpression = new InsertExpression(Assert.castUnchecked(insertSource.getQuantifier(), Quantifier.ForEach.class), - target.getName(), + Assert.notNullUnchecked(targetType.getStorageName(), "target type for insert must have set storage name"), targetType); final var resultingQuantifier = Quantifier.forEach(Reference.initialOf(insertExpression)); final var output = Expressions.fromQuantifier(resultingQuantifier); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java index d5adc8953c..7db30bce17 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/PlanGenerator.java @@ -36,6 +36,7 @@ import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; import com.apple.foundationdb.record.query.plan.plans.RecordQueryPlan; import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; @@ -239,6 +240,8 @@ private Plan generatePhysicalPlanForCompilableStatement(@Nonnull AstNormalize planContext.getConstantActionFactory(), planContext.getDbUri(), caseSensitive) .generateLogicalPlan(ast.getParseTree())); return maybePlan.optimize(planner, planContext, currentPlanHashMode); + } catch (ProtoUtils.InvalidNameException ine) { + throw new RelationalException(ine.getMessage(), ErrorCode.INVALID_NAME, ine).toUncheckedWrappedException(); } catch (MetaDataException mde) { // we need a better way for translating error codes between record layer and Relational SQL error codes throw new RelationalException(mde.getMessage(), ErrorCode.SYNTAX_OR_ACCESS_VIOLATION, mde).toUncheckedWrappedException(); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java index f8d99a9cd0..c551f202d7 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java @@ -58,6 +58,7 @@ import com.apple.foundationdb.relational.api.metadata.View; import com.apple.foundationdb.relational.generated.RelationalParser; import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; +import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerTable; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerView; import com.apple.foundationdb.relational.recordlayer.query.functions.SqlFunctionCatalog; import com.apple.foundationdb.relational.recordlayer.query.functions.WithPlanGenerationSideEffects; @@ -274,9 +275,12 @@ public void validateOrderByColumns(@Nonnull Iterable orderBys } @Nonnull - public Set getAllTableNames() { + public Set getAllTableStorageNames() { try { - return metadataCatalog.getTables().stream().map(Metadata::getName).collect(ImmutableSet.toImmutableSet()); + return metadataCatalog.getTables().stream() + .map(table -> Assert.castUnchecked(table, RecordLayerTable.class)) + .map(table -> Assert.notNullUnchecked(table.getType().getStorageName())) + .collect(ImmutableSet.toImmutableSet()); } catch (RelationalException e) { throw e.toUncheckedWrappedException(); } @@ -616,7 +620,7 @@ public DataType lookupType(@Nonnull final ParsedTypeInfo parsedTypeInfo, final var typeName = Assert.notNullUnchecked(parsedTypeInfo.getCustomType()).getName(); final var maybeFound = dataTypeProvider.apply(typeName); // if we cannot find the type now, mark it, we will try to resolve it later on via a second pass. - type = maybeFound.orElseGet(() -> DataType.UnresolvedType.of(typeName, isNullable)); + type = maybeFound.map(dataType -> dataType.withNullable(isNullable)).orElseGet(() -> DataType.UnresolvedType.of(typeName, isNullable)); } else { final var primitiveType = Assert.notNullUnchecked(parsedTypeInfo.getPrimitiveTypeContext()); if (primitiveType.vectorType() != null) { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java index 4e5145aca1..6602be62a5 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java @@ -204,9 +204,8 @@ public RecordLayerIndex visitIndexDefinition(@Nonnull RelationalParser.IndexDefi final var useLegacyBasedExtremumEver = ctx.indexAttributes() != null && ctx.indexAttributes().indexAttribute().stream().anyMatch(attribute -> attribute.LEGACY_EXTREMUM_EVER() != null); final var isUnique = ctx.UNIQUE() != null; final var generator = IndexGenerator.from(viewPlan, useLegacyBasedExtremumEver); - final var table = metadataBuilder.findTable(generator.getRecordTypeName()); Assert.thatUnchecked(viewPlan instanceof LogicalSortExpression, ErrorCode.INVALID_COLUMN_REFERENCE, "Cannot create index and order by an expression that is not present in the projection list"); - return generator.generate(indexId.getName(), isUnique, table.getType(), containsNullableArray); + return generator.generate(metadataBuilder, indexId.getName(), isUnique, containsNullableArray); } @Nonnull @@ -265,7 +264,7 @@ public ProceduralPlan visitCreateSchemaTemplateStatement(@Nonnull RelationalPars } structClauses.build().stream().map(this::visitStructDefinition).map(RecordLayerTable::getDatatype).forEach(metadataBuilder::addAuxiliaryType); tableClauses.build().stream().map(this::visitTableDefinition).forEach(metadataBuilder::addTable); - final var indexes = indexClauses.build().stream().map(this::visitIndexDefinition).collect(ImmutableList.toImmutableList()); + final List indexes = indexClauses.build().stream().map(this::visitIndexDefinition).collect(ImmutableList.toImmutableList()); // TODO: this is currently relying on the lexical order of the function to resolve function dependencies which // is limited. sqlInvokedFunctionClauses.build().forEach(functionClause -> { @@ -276,7 +275,7 @@ public ProceduralPlan visitCreateSchemaTemplateStatement(@Nonnull RelationalPars final var view = getViewMetadata(viewClause, metadataBuilder.build()); metadataBuilder.addView(view); }); - for (final var index : indexes) { + for (final RecordLayerIndex index : indexes) { final var table = metadataBuilder.extractTable(index.getTableName()); final var tableWithIndex = RecordLayerTable.Builder.from(table).addIndex(index).build(); metadataBuilder.addTable(tableWithIndex); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java index 9d7a45dd49..4989946f6e 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java @@ -462,11 +462,11 @@ public LogicalOperator visitInsertStatementValueValues(@Nonnull RelationalParser @Nonnull @Override public LogicalOperator visitUpdateStatement(@Nonnull RelationalParser.UpdateStatementContext ctx) { - final var tableId = visitFullId(ctx.tableName().fullId()); - final var semanticAnalyzer = getDelegate().getSemanticAnalyzer(); - final var table = semanticAnalyzer.getTable(tableId); - final var tableType = Assert.castUnchecked(table, RecordLayerTable.class).getType(); - final var tableAccess = getDelegate().getLogicalOperatorCatalog().lookupTableAccess(tableId, semanticAnalyzer); + final Identifier tableId = visitFullId(ctx.tableName().fullId()); + final SemanticAnalyzer semanticAnalyzer = getDelegate().getSemanticAnalyzer(); + final RecordLayerTable table = Assert.castUnchecked(semanticAnalyzer.getTable(tableId), RecordLayerTable.class); + final Type.Record tableType = table.getType(); + final LogicalOperator tableAccess = getDelegate().getLogicalOperatorCatalog().lookupTableAccess(tableId, semanticAnalyzer); getDelegate().pushPlanFragment().setOperator(tableAccess); final var output = Expressions.ofSingle(semanticAnalyzer.expandStar(Optional.empty(), getDelegate().getLogicalOperators())); @@ -475,16 +475,16 @@ public LogicalOperator visitUpdateStatement(@Nonnull RelationalParser.UpdateStat final var updateSource = LogicalOperator.generateSimpleSelect(output, getDelegate().getLogicalOperators(), whereMaybe, Optional.of(tableId), ImmutableSet.of(), false); getDelegate().getCurrentPlanFragment().setOperator(updateSource); - final var transformMapBuilder = ImmutableMap.builder(); - for (final var updatedElementCtx : ctx.updatedElement()) { - final var targetAndUpdateExpressions = visitUpdatedElement(updatedElementCtx).asList(); - final var target = Assert.castUnchecked(targetAndUpdateExpressions.get(0).getUnderlying(), FieldValue.class).getFieldPath(); - final var update = targetAndUpdateExpressions.get(1).getUnderlying(); + final ImmutableMap.Builder transformMapBuilder = ImmutableMap.builder(); + for (final RelationalParser.UpdatedElementContext updatedElementCtx : ctx.updatedElement()) { + final List targetAndUpdateExpressions = visitUpdatedElement(updatedElementCtx).asList(); + final FieldValue.FieldPath target = Assert.castUnchecked(targetAndUpdateExpressions.get(0).getUnderlying(), FieldValue.class).getFieldPath(); + final Value update = targetAndUpdateExpressions.get(1).getUnderlying(); transformMapBuilder.put(target, update); } final var updateExpression = new UpdateExpression(Assert.castUnchecked(updateSource.getQuantifier(), Quantifier.ForEach.class), - table.getName(), + Assert.notNullUnchecked(tableType.getStorageName(), "Update target type must have storage type name available"), tableType, transformMapBuilder.build()); final var updateQuantifier = Quantifier.forEach(Reference.initialOf(updateExpression)); @@ -513,10 +513,10 @@ public LogicalOperator visitUpdateStatement(@Nonnull RelationalParser.UpdateStat @Override public LogicalOperator visitDeleteStatement(@Nonnull RelationalParser.DeleteStatementContext ctx) { Assert.thatUnchecked(ctx.limitClause() == null, "limit is not supported"); - final var tableId = visitFullId(ctx.tableName().fullId()); - final var semanticAnalyzer = getDelegate().getSemanticAnalyzer(); - final var table = semanticAnalyzer.getTable(tableId); - final var tableAccess = getDelegate().getLogicalOperatorCatalog().lookupTableAccess(tableId, semanticAnalyzer); + final Identifier tableId = visitFullId(ctx.tableName().fullId()); + final SemanticAnalyzer semanticAnalyzer = getDelegate().getSemanticAnalyzer(); + final RecordLayerTable table = Assert.castUnchecked(semanticAnalyzer.getTable(tableId), RecordLayerTable.class); + final LogicalOperator tableAccess = getDelegate().getLogicalOperatorCatalog().lookupTableAccess(tableId, semanticAnalyzer); getDelegate().pushPlanFragment().setOperator(tableAccess); final var output = Expressions.ofSingle(semanticAnalyzer.expandStar(Optional.empty(), getDelegate().getLogicalOperators())); @@ -524,7 +524,7 @@ public LogicalOperator visitDeleteStatement(@Nonnull RelationalParser.DeleteStat Optional whereMaybe = ctx.whereExpr() == null ? Optional.empty() : Optional.of(visitWhereExpr(ctx.whereExpr())); final var deleteSource = LogicalOperator.generateSimpleSelect(output, getDelegate().getLogicalOperators(), whereMaybe, Optional.of(tableId), ImmutableSet.of(), false); - final var deleteExpression = new DeleteExpression(Assert.castUnchecked(deleteSource.getQuantifier(), Quantifier.ForEach.class), table.getName()); + final var deleteExpression = new DeleteExpression(Assert.castUnchecked(deleteSource.getQuantifier(), Quantifier.ForEach.class), table.getType().getStorageName()); final var deleteQuantifier = Quantifier.forEach(Reference.initialOf(deleteExpression)); final var resultingDelete = LogicalOperator.newUnnamedOperator(Expressions.fromQuantifier(deleteQuantifier), deleteQuantifier); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/TypeUtils.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/TypeUtils.java index 45f297724d..15fb164325 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/TypeUtils.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/TypeUtils.java @@ -75,7 +75,7 @@ final var record = (Type.Record)input; Optional.of(recordField.getFieldIndex())); newlyNamedFields.add(newField); } - return record.getName() == null ? Type.Record.fromFieldsWithName(record.getName(), record.isNullable(), newlyNamedFields.build()) + return record.getName() != null ? Type.Record.fromFieldsWithName(record.getName(), record.isNullable(), newlyNamedFields.build()) : Type.Record.fromFields(record.isNullable(), newlyNamedFields.build()); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java index a6d3871aa0..6164015b59 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java @@ -20,23 +20,35 @@ package com.apple.foundationdb.relational.api.ddl; +import com.apple.foundationdb.record.RecordMetaData; import com.apple.foundationdb.record.expressions.RecordKeyExpressionProto; +import com.apple.foundationdb.record.metadata.Key; +import com.apple.foundationdb.record.metadata.MetaDataValidator; +import com.apple.foundationdb.record.metadata.RecordType; import com.apple.foundationdb.record.metadata.expressions.KeyExpression; import com.apple.foundationdb.record.metadata.expressions.ThenKeyExpression; +import com.apple.foundationdb.record.provider.foundationdb.IndexMaintainerFactoryRegistryImpl; +import com.apple.foundationdb.record.query.plan.cascades.RawSqlFunction; +import com.apple.foundationdb.record.query.plan.cascades.UserDefinedFunction; +import com.apple.foundationdb.record.query.plan.cascades.UserDefinedMacroFunction; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.api.metadata.DataType; import com.apple.foundationdb.relational.api.metadata.Index; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.api.metadata.Table; +import com.apple.foundationdb.relational.api.metadata.View; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.apple.foundationdb.relational.recordlayer.RecordContextTransaction; import com.apple.foundationdb.relational.recordlayer.RelationalConnectionRule; import com.apple.foundationdb.relational.recordlayer.Utils; import com.apple.foundationdb.relational.recordlayer.ddl.AbstractMetadataOperationsFactory; import com.apple.foundationdb.relational.recordlayer.ddl.NoOpMetadataOperationsFactory; +import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerColumn; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerIndex; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; +import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerTable; import com.apple.foundationdb.relational.recordlayer.metric.RecordLayerMetricCollector; import com.apple.foundationdb.relational.recordlayer.query.Plan; import com.apple.foundationdb.relational.recordlayer.query.PreparedParams; @@ -46,7 +58,9 @@ import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.TestSchemas; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Descriptors; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; @@ -66,6 +80,8 @@ import java.sql.Types; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.IntPredicate; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -339,6 +355,133 @@ public ConstantAction getSaveSchemaTemplateConstantAction(@Nonnull SchemaTemplat }); } + /** + * Validate that Protobuf escaping on a schema template by looking at the produced meta-data. + * This works with the tests in {@code valid-identifiers.yamsql}, which validate actual query semantics + * on such a meta-data. This test allows us to validate which parts of the meta-data are actually + * translated (it should only be things that get turned into Protobuf identifiers, like message types, + * field names, and enum values) and which parts are preserved (like function, view, and index names). + * + * @throws Exception from generating the schema-template + */ + @Test + void translateNames() throws Exception { + final String stmt = "CREATE SCHEMA TEMPLATE test_template " + + "CREATE TYPE AS STRUCT \"a.b$c__struct\" (\"___a$\" bigint, \"_b.x\" string, \"c__\" bigint) " + + "CREATE TYPE AS ENUM \"a.b$c__enum\" ('___A$', '_B.X', 'C__') " + + "CREATE TABLE \"__4a.b$c__table\"(\"__h__s\" \"a.b$c__struct\", \"_x.y\" bigint, \"enum.field\" \"a.b$c__enum\", primary key (\"__h__s\".\"_b.x\")) " + + "CREATE INDEX \"a.b$c__index\" AS SELECT \"_x.y\", \"__h__s\".\"___a$\", \"__h__s\".\"c__\" FROM \"__4a.b$c__table\" ORDER BY \"_x.y\", \"__h__s\".\"___a$\" " + + "CREATE VIEW \"a.b$c__view\" AS SELECT \"__h__s\".\"___a$\" AS \"f__00\" FROM \"__4a.b$c__table\" WHERE \"_x.y\" > 4 " + + "CREATE FUNCTION \"a.b$c__function\"(in \"__param__int\" bigint, in \"__param__enum\" TYPE \"a.b$c__enum\") " + + " AS SELECT \"__h__s\".\"___a$\" AS \"f__00\" FROM \"__4a.b$c__table\" WHERE \"_x.y\" > \"__param__int\" AND \"enum.field\" = \"__param__enum\" " + + "CREATE FUNCTION \"a.b$c__macro_function\"(in \"__in__4a.b$c__table\" TYPE \"__4a.b$c__table\") RETURNS string AS \"__in__4a.b$c__table\".\"__h__s\".\"_b.x\" "; + + shouldWorkWithInjectedFactory(stmt, new AbstractMetadataOperationsFactory() { + @Nonnull + @Override + public ConstantAction getSaveSchemaTemplateConstantAction(@Nonnull final SchemaTemplate template, @Nonnull final Options templateProperties) { + try { + // Assert all the user-visible names look like the user identifiers in the schema + + Set tables = template.getTables(); + Assertions.assertEquals(1, tables.size(), () -> "tables " + tables + " should have only one element"); + + final Table table = Iterables.getOnlyElement(tables); + Assertions.assertEquals("__4a.b$c__table", table.getName()); + + final DataType.StructType structType = DataType.StructType.from("a.b$c__struct", List.of( + DataType.StructType.Field.from("___a$", DataType.LongType.nullable(), 1), + DataType.StructType.Field.from("_b.x", DataType.StringType.nullable(), 2), + DataType.StructType.Field.from("c__", DataType.LongType.nullable(), 3) + ), true); + final DataType.EnumType enumType = DataType.EnumType.from("a.b$c__enum", List.of( + DataType.EnumType.EnumValue.of("___A$", 0), + DataType.EnumType.EnumValue.of("_B.X", 1), + DataType.EnumType.EnumValue.of("C__", 2) + ), true); + Assertions.assertEquals(List.of( + RecordLayerColumn.newBuilder().setName("__h__s").setDataType(structType).setIndex(1).build(), + RecordLayerColumn.newBuilder().setName("_x.y").setDataType(DataType.LongType.nullable()).setIndex(2).build(), + RecordLayerColumn.newBuilder().setName("enum.field").setDataType(enumType).setIndex(3).build() + ), table.getColumns()); + + Set indexes = table.getIndexes(); + Assertions.assertEquals(1, indexes.size(), () -> "indexes " + indexes + " for table " + table.getName() + " should have only one element"); + Index index = Iterables.getOnlyElement(indexes); + Assertions.assertEquals("a.b$c__index", index.getName()); + Assertions.assertEquals("__4a.b$c__table", index.getTableName()); + + Set views = template.getViews(); + Assertions.assertEquals(1, views.size(), () -> "views " + views + " should have only one element"); + View view = Iterables.getOnlyElement(views); + Assertions.assertEquals("a.b$c__view", view.getName()); + + template.findInvokedRoutineByName("a.b$c__function") + .orElseGet(() -> Assertions.fail("could not find function a.b$c__function")); + template.findInvokedRoutineByName("a.b$c__macro_function") + .orElseGet(() -> Assertions.fail("could not find function a.b$c__macro_function")); + + // Assert all the internal fields are using escaped protobuf identifiers + + Assertions.assertInstanceOf(RecordLayerTable.class, table); + final RecordLayerTable recordLayerTable = (RecordLayerTable) table; + Assertions.assertEquals(Key.Expressions.concat(Key.Expressions.recordType(), Key.Expressions.field("__h__0s").nest("_b__2x")), recordLayerTable.getPrimaryKey()); + + Assertions.assertInstanceOf(RecordLayerIndex.class, index); + final RecordLayerIndex recordLayerIndex = (RecordLayerIndex) index; + Assertions.assertEquals(Key.Expressions.keyWithValue(Key.Expressions.concat( + Key.Expressions.field("_x__2y"), + Key.Expressions.field("__h__0s").nest( + Key.Expressions.concatenateFields("___a__1", "c__0") + ) + ), 2), + recordLayerIndex.getKeyExpression()); + + Assertions.assertInstanceOf(RecordLayerSchemaTemplate.class, template); + final RecordLayerSchemaTemplate recordLayerSchemaTemplate = (RecordLayerSchemaTemplate) template; + final RecordMetaData metaData = recordLayerSchemaTemplate.toRecordMetadata(); + + Assertions.assertFalse(metaData.getRecordTypes().containsKey("__4a.b$c__table"), () -> "meta-data should not contain unescaped table name " + table.getName()); + Assertions.assertTrue(metaData.getRecordTypes().containsKey("__4a__2b__1c__0table"), () -> "meta-data should contain unescaped table name of " + table.getName()); + final RecordType recordType = metaData.getRecordType("__4a__2b__1c__0table"); + final Descriptors.Descriptor typeDescriptor = recordType.getDescriptor(); + Assertions.assertEquals("__4a__2b__1c__0table", typeDescriptor.getName()); + Assertions.assertEquals(List.of("__h__0s", "_x__2y", "enum__2field"), typeDescriptor.getFields().stream().map(Descriptors.FieldDescriptor::getName).collect(Collectors.toList())); + final Descriptors.Descriptor structDescriptor = typeDescriptor.findFieldByName("__h__0s").getMessageType(); + Assertions.assertEquals("a__2b__1c__0struct", structDescriptor.getName()); + Assertions.assertEquals(List.of("___a__1", "_b__2x", "c__0"), structDescriptor.getFields().stream().map(Descriptors.FieldDescriptor::getName).collect(Collectors.toList())); + final Descriptors.EnumDescriptor enumDescriptor = typeDescriptor.findFieldByName("enum__2field").getEnumType(); + Assertions.assertEquals("a__2b__1c__0enum", enumDescriptor.getName()); + Assertions.assertEquals(List.of("___A__1", "_B__2X", "C__0"), enumDescriptor.getValues().stream().map(Descriptors.EnumValueDescriptor::getName).collect(Collectors.toList())); + + var metaDataIndex = metaData.getIndex(index.getName()); + Assertions.assertEquals("a.b$c__index", metaDataIndex.getName()); // Index name is _not_ translated + Assertions.assertEquals(recordLayerIndex.getKeyExpression(), metaDataIndex.getRootExpression()); // key expression is already validated as translated + + final Map viewMap = metaData.getViewMap(); + Assertions.assertTrue(viewMap.containsKey("a.b$c__view"), "should contain function a.b$c__view without escaping name"); + + final Map functionMap = metaData.getUserDefinedFunctionMap(); + Assertions.assertTrue(functionMap.containsKey("a.b$c__function"), "should contain function a.b$c__function without escaping name"); + final UserDefinedFunction sqlFunction = functionMap.get("a.b$c__function"); + Assertions.assertInstanceOf(RawSqlFunction.class, sqlFunction); + Assertions.assertTrue(functionMap.containsKey("a.b$c__macro_function"), "should contain function a.b$c__macro_function without escaping name"); + final UserDefinedFunction macroFunction = functionMap.get("a.b$c__macro_function"); + Assertions.assertInstanceOf(UserDefinedMacroFunction.class, macroFunction); + + // Validates that referenced fields and types all line up + final MetaDataValidator validator = new MetaDataValidator(metaData, IndexMaintainerFactoryRegistryImpl.instance()); + Assertions.assertDoesNotThrow(validator::validate, "Meta-data validation should complete successfully"); + } catch (RelationalException e) { + return Assertions.fail(e); + } + + return txn -> { + }; + } + }); + } + @Nonnull private static Stream invalidVectorTypes() { return Stream.of( diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumnTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumnTests.java new file mode 100644 index 0000000000..e7ccaf6aed --- /dev/null +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumnTests.java @@ -0,0 +1,136 @@ +/* + * RecordLayerColumnTests.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 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.relational.recordlayer.metadata; + +import com.apple.foundationdb.relational.api.metadata.DataType; +import org.assertj.core.api.AutoCloseableSoftAssertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.annotation.Nonnull; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link RecordLayerColumn}. + */ +class RecordLayerColumnTests { + private static final RecordLayerColumn BLAH_COLUMN = RecordLayerColumn.newBuilder() + .setName("blah") + .setDataType(DataType.LongType.nullable()) + .setIndex(1) + .build(); + + @Test + void basicCopy() { + final RecordLayerColumn copy = RecordLayerColumn.newBuilder() + .setName(BLAH_COLUMN.getName()) + .setDataType(BLAH_COLUMN.getDataType()) + .setIndex(BLAH_COLUMN.getIndex()) + .build(); + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(copy) + .hasToString("blah: long ∪ ∅ = 1"); + softly.assertThat(BLAH_COLUMN) + .hasToString("blah: long ∪ ∅ = 1"); + softly.assertThat(copy.getName()) + .isEqualTo("blah"); + softly.assertThat(copy.getDataType()) + .isEqualTo(DataType.LongType.nullable()); + softly.assertThat(copy.getIndex()) + .isOne(); + } + } + + @Nonnull + static Stream testDiffersFromBlah() { + return Stream.of( + Arguments.of( + // Different name + RecordLayerColumn.newBuilder() + .setName("blag") + .setDataType(BLAH_COLUMN.getDataType()) + .setIndex(BLAH_COLUMN.getIndex()) + .build(), + "blag: long ∪ ∅ = 1" + ), + Arguments.of( + // Different type 1 + RecordLayerColumn.newBuilder() + .setName(BLAH_COLUMN.getName()) + .setDataType(DataType.LongType.notNullable()) + .setIndex(BLAH_COLUMN.getIndex()) + .build(), + "blah: long = 1" + ), + Arguments.of( + // Different type 2 + RecordLayerColumn.newBuilder() + .setName(BLAH_COLUMN.getName()) + .setDataType(DataType.StringType.nullable()) + .setIndex(BLAH_COLUMN.getIndex()) + .build(), + "blah: string ∪ ∅ = 1" + ), + Arguments.of( + // Different index + RecordLayerColumn.newBuilder() + .setName(BLAH_COLUMN.getName()) + .setDataType(BLAH_COLUMN.getDataType()) + .setIndex(BLAH_COLUMN.getIndex() + 1) + .build(), + "blah: long ∪ ∅ = 2" + ) + ); + } + + @ParameterizedTest(name = "testDiffersFromBlah[{1}]") + @MethodSource + void testDiffersFromBlah(@Nonnull RecordLayerColumn differentColumn, @Nonnull String toString) { + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(differentColumn) + .hasToString(toString) + .isNotEqualTo(BLAH_COLUMN) + .doesNotHaveSameHashCodeAs(BLAH_COLUMN) + .matches(c -> !BLAH_COLUMN.getName().equals(c.getName()) || !BLAH_COLUMN.getDataType().equals(c.getDataType()) || BLAH_COLUMN.getIndex() != c.getIndex()); + } + } + + @Test + void fromStructField() { + final RecordLayerColumn columnFromStructType = RecordLayerColumn.from(DataType.StructType.Field.from("blah", DataType.LongType.nullable(), 1)); + assertThat(columnFromStructType) + .hasToString("blah: long ∪ ∅ = 1"); + + final RecordLayerColumn columnFromBuilder = RecordLayerColumn.newBuilder() + .setName(columnFromStructType.getName()) + .setDataType(columnFromStructType.getDataType()) + .setIndex(columnFromStructType.getIndex()) + .build(); + assertThat(columnFromBuilder) + .hasToString("blah: long ∪ ∅ = 1") + .isEqualTo(columnFromStructType) + .hasSameHashCodeAs(columnFromStructType); + } +} diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java index d9faabf372..22ad220205 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java @@ -22,8 +22,10 @@ import com.apple.foundationdb.record.RecordMetaData; import com.apple.foundationdb.record.RecordStoreState; +import com.apple.foundationdb.record.metadata.Index; import com.apple.foundationdb.record.metadata.IndexTypes; import com.apple.foundationdb.record.metadata.Key; +import com.apple.foundationdb.record.metadata.RecordTypeBuilder; import com.apple.foundationdb.record.metadata.expressions.KeyExpression; import com.apple.foundationdb.record.provider.foundationdb.IndexMaintainerFactoryRegistryImpl; import com.apple.foundationdb.record.query.plan.cascades.RawSqlFunction; @@ -46,7 +48,9 @@ import com.apple.foundationdb.relational.util.Assert; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Descriptors; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; @@ -332,6 +336,113 @@ void deserializationNestedTypesPreservesNamesCorrectly() { Assertions.assertEquals("Subtype", typeName); } + + @Test + void deserializationTranslatesUserDefinedNameCorrectly() { + final var metaDataBuilder = RecordMetaData.newBuilder(); + metaDataBuilder.setRecords(createEscapedRecordTypesDescriptor()); + RecordTypeBuilder typeBuilder = metaDataBuilder.getRecordType("Foo__0Bar__1Baz__2End"); + final var primaryKey = Key.Expressions.concat(Key.Expressions.recordType(), Key.Expressions.field("id")); + typeBuilder.setPrimaryKey(primaryKey); + typeBuilder.setRecordTypeKey(1L); + metaDataBuilder.addIndex(typeBuilder, new Index("Foo__Bar$Baz.End$$a__b$c.d", "a__0b__1c__2d")); + final RecordMetaData metaData = metaDataBuilder.build(); + final var actualSchemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaData, "TestSchemaTemplate", metaData.getVersion()); + + // the RecordLayerSchemaTemplate deserializer translates proto fields to user-defined names. + final var expectedTableName = "Foo__Bar$Baz.End"; + Assertions.assertEquals(1, actualSchemaTemplate.getTables().size()); + final var tableMaybe = actualSchemaTemplate.findTableByName(expectedTableName); + Assertions.assertTrue(tableMaybe.isPresent()); + final var actualTable = tableMaybe.get(); + final var actualRecordTable = Assertions.assertInstanceOf(RecordLayerTable.class, actualTable); + Assertions.assertEquals("Foo__0Bar__1Baz__2End", actualRecordTable.getType().getStorageName()); + + final var expectedTable = RecordLayerTable.newBuilder(false) + .setName(expectedTableName) + .addColumn(RecordLayerColumn.newBuilder() + .setName("id") + .setDataType(DataType.Primitives.NULLABLE_LONG.type()) + .build()) + .addColumn(RecordLayerColumn.newBuilder() + .setName("a__b$c.d") + .setDataType(DataType.Primitives.NULLABLE_LONG.type()) + .build()) + .addColumn(RecordLayerColumn.newBuilder() + .setName("otherField") + .setDataType(DataType.Primitives.NULLABLE_STRING.type()) + .build()) + .setPrimaryKey(primaryKey) + .build(); + Assertions.assertEquals(expectedTable.getName(), actualRecordTable.getName()); + Assertions.assertEquals(expectedTable.getColumns(), actualRecordTable.getColumns()); + Assertions.assertEquals(expectedTable.getPrimaryKey(), actualRecordTable.getPrimaryKey()); + + final var actualIndexes = actualTable.getIndexes(); + Assertions.assertEquals(1, actualIndexes.size(), () -> "actual indexes: " + actualIndexes + " should have size 1"); + final var actualIndex = Iterables.getOnlyElement(actualIndexes); + Assertions.assertEquals("Foo__Bar$Baz.End$$a__b$c.d", actualIndex.getName()); + Assertions.assertEquals(expectedTableName, actualIndex.getTableName()); + Assertions.assertInstanceOf(RecordLayerIndex.class, actualIndex); + final var actualRecordLayerIndex = (RecordLayerIndex) actualIndex; + Assertions.assertEquals(Key.Expressions.field("a__0b__1c__2d"), actualRecordLayerIndex.getKeyExpression()); + Assertions.assertEquals("Foo__0Bar__1Baz__2End", actualRecordLayerIndex.getTableStorageName()); + } + + @Test + void deserializeTemplateWithMalformedNamesCorrectly() { + final var metaDataBuilder = RecordMetaData.newBuilder(); + metaDataBuilder.setRecords(createRecordTypesDescriptorWithMalformedEscaping()); + RecordTypeBuilder typeBuilder = metaDataBuilder.getRecordType("_Foo__Bar__1Baz"); + final var primaryKey = Key.Expressions.concat(Key.Expressions.recordType(), Key.Expressions.field("id")); + typeBuilder.setPrimaryKey(primaryKey); + typeBuilder.setRecordTypeKey(1L); + metaDataBuilder.addIndex(typeBuilder, new Index("_Foo__Bar__1Baz$a__b$c.d", "a__b__1c__2d")); + final RecordMetaData metaData = metaDataBuilder.build(); + final var actualSchemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaData, "TestSchemaTemplate", metaData.getVersion()); + + // the RecordLayerSchemaTemplate deserializer translates proto fields to user-defined names as best it can. + // Note that if we went back the other way, the table name we'd have gotten back would have a type name of + // "Foo__0Bar__1Baz" + final var expectedTableName = "_Foo__Bar$Baz"; + Assertions.assertEquals(1, actualSchemaTemplate.getTables().size()); + final var tableMaybe = actualSchemaTemplate.findTableByName(expectedTableName); + Assertions.assertTrue(tableMaybe.isPresent()); + final var actualTable = tableMaybe.get(); + final var actualRecordTable = Assertions.assertInstanceOf(RecordLayerTable.class, actualTable); + Assertions.assertEquals("_Foo__Bar__1Baz", actualRecordTable.getType().getStorageName()); + + final var expectedTable = RecordLayerTable.newBuilder(false) + .setName(expectedTableName) + .addColumn(RecordLayerColumn.newBuilder() + .setName("id") + .setDataType(DataType.Primitives.NULLABLE_LONG.type()) + .build()) + .addColumn(RecordLayerColumn.newBuilder() + .setName("a__b$c.d") + .setDataType(DataType.Primitives.NULLABLE_LONG.type()) + .build()) + .addColumn(RecordLayerColumn.newBuilder() + .setName("otherField") + .setDataType(DataType.Primitives.NULLABLE_STRING.type()) + .build()) + .setPrimaryKey(primaryKey) + .build(); + Assertions.assertEquals(expectedTable.getName(), actualRecordTable.getName()); + Assertions.assertEquals(expectedTable.getColumns(), actualRecordTable.getColumns()); + Assertions.assertEquals(expectedTable.getPrimaryKey(), actualRecordTable.getPrimaryKey()); + + final var actualIndexes = actualTable.getIndexes(); + Assertions.assertEquals(1, actualIndexes.size(), () -> "actual indexes: " + actualIndexes + " should have size 1"); + final var actualIndex = Iterables.getOnlyElement(actualIndexes); + Assertions.assertEquals("_Foo__Bar__1Baz$a__b$c.d", actualIndex.getName()); + Assertions.assertEquals(expectedTableName, actualIndex.getTableName()); + Assertions.assertInstanceOf(RecordLayerIndex.class, actualIndex); + final var actualRecordLayerIndex = (RecordLayerIndex) actualIndex; + Assertions.assertEquals(Key.Expressions.field("a__b__1c__2d"), actualRecordLayerIndex.getKeyExpression()); + Assertions.assertEquals("_Foo__Bar__1Baz", actualRecordLayerIndex.getTableStorageName()); + } + @Test void findTableByNameWorksCorrectly() { final var sampleRecordSchemaTemplate = RecordLayerSchemaTemplate.newBuilder() @@ -817,6 +928,98 @@ void testFindViewByNameReturnsEmpty() { Assertions.assertFalse(viewOpt.isPresent()); } + @Nonnull + private static Descriptors.FileDescriptor createEscapedRecordTypesDescriptor() { + DescriptorProtos.FileDescriptorProto fileDescriptorProto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setName("test_schema_with_escaping.proto") + .setPackage("com.apple.foundationdb.record.test1") + .setSyntax("proto2") + .addMessageType(DescriptorProtos.DescriptorProto.newBuilder() + .setName("Foo__0Bar__1Baz__2End") + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64) + .setName("id") + .setNumber(1) + ) + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64) + .setName("a__0b__1c__2d") + .setNumber(2) + ) + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_STRING) + .setName("otherField") + .setNumber(3) + ) + ) + .addMessageType(DescriptorProtos.DescriptorProto.newBuilder() + .setName("RecordTypeUnion") + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE) + .setTypeName("Foo__0Bar__1Baz__2End") + .setName("_Foo__0Bar__1Baz__2End") + .setNumber(1) + ) + ) + .build(); + + try { + return Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[0]); + } catch (Descriptors.DescriptorValidationException e) { + return Assertions.fail("unable to build file descriptor", e); + } + } + + @Nonnull + private static Descriptors.FileDescriptor createRecordTypesDescriptorWithMalformedEscaping() { + DescriptorProtos.FileDescriptorProto fileDescriptorProto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setName("test_schema_with_malformed_escaping.proto") + .setPackage("com.apple.foundationdb.record.test1") + .setSyntax("proto2") + .addMessageType(DescriptorProtos.DescriptorProto.newBuilder() + .setName("_Foo__Bar__1Baz") + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64) + .setName("id") + .setNumber(1) + ) + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64) + .setName("a__b__1c__2d") + .setNumber(2) + ) + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_STRING) + .setName("otherField") + .setNumber(3) + ) + ) + .addMessageType(DescriptorProtos.DescriptorProto.newBuilder() + .setName("RecordTypeUnion") + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE) + .setTypeName("_Foo__Bar__1Baz") + .setName("__Foo__Bar__1Baz") + .setNumber(1) + ) + ) + .build(); + + try { + return Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[0]); + } catch (Descriptors.DescriptorValidationException e) { + return Assertions.fail("unable to build file descriptor", e); + } + } + private static final class RecordMetadataDeserializerWithPeekingFunctionSupplier extends RecordMetadataDeserializer { @Nonnull @@ -874,4 +1077,6 @@ public T clock(@Nonnull RelationalMetric.RelationalEvent event, IndexMaintainerFactoryRegistryImpl.instance(), Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()); } } + + } diff --git a/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/RelationalStructAssert.java b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/RelationalStructAssert.java index a5d6e92039..98208de5a0 100644 --- a/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/RelationalStructAssert.java +++ b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/RelationalStructAssert.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.annotation.API; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; @@ -442,7 +443,7 @@ public RelationalStructAssert hasValue(String columnName, Object value) { } else if (object instanceof Array) { ArrayAssert.assertThat((Array) object).isEqualTo(value); } else if (object instanceof Descriptors.EnumValueDescriptor) { - Assertions.assertThat(((Descriptors.EnumValueDescriptor) object).getName()).isEqualTo(value); + Assertions.assertThat(ProtoUtils.toUserIdentifier(((Descriptors.EnumValueDescriptor) object).getName())).isEqualTo(value); } else { Assertions.assertThat(object).isEqualTo(value); } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java index 0babee8e5f..36c7cdd668 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java @@ -148,13 +148,15 @@ private Object executeStatementAndCheckCacheIfNeeded(@Nonnull Statement s, final preMetricCollector.getCountsForCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) : 0; final var toReturn = executeStatementAndCheckForceContinuations(s, statementHasQuery, queryString, connection, maxRows); final var postMetricCollector = connection.getMetricCollector(); - final var postValue = postMetricCollector.hasCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) ? - postMetricCollector.getCountsForCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) : 0; - final var planFound = preMetricCollector != postMetricCollector ? postValue == 1 : postValue == preValue + 1; - if (!planFound) { - reportTestFailure("‼️ Expected to retrieve the plan from the cache at line " + lineNumber); - } else { - logger.debug("🎁 Retrieved the plan from the cache!"); + if (postMetricCollector != null) { + final var postValue = postMetricCollector.hasCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) ? + postMetricCollector.getCountsForCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) : 0; + final var planFound = preMetricCollector != postMetricCollector ? postValue == 1 : postValue == preValue + 1; + if (!planFound) { + reportTestFailure("‼️ Expected to retrieve the plan from the cache at line " + lineNumber); + } else { + logger.debug("🎁 Retrieved the plan from the cache!"); + } } return toReturn; } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/ExportSchemaTemplateUtil.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/ExportSchemaTemplateUtil.java new file mode 100644 index 0000000000..adf24085e1 --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/ExportSchemaTemplateUtil.java @@ -0,0 +1,69 @@ +/* + * ExportSchemaTemplate.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 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.relational.yamltests.utils; + +import com.apple.foundationdb.record.RecordMetaData; +import com.apple.foundationdb.record.RecordMetaDataProto; +import com.google.protobuf.util.JsonFormat; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +/** + * Utility to generate a JSON export of a schema template. The output of this file can then be commited + * to the repository and then the referenced by a test using the {@code lode schema template} directive. + * This allows the user to test a schema template that either wouldn't be generated from the DDL or to + * verify that a certain serialized meta-data will be interpreted as expected at runtime. + */ +public class ExportSchemaTemplateUtil { + private ExportSchemaTemplateUtil() { + } + + /** + * Export a {@link RecordMetaData} object to a file. This will overwrite an existing file with + * the new meta-data if set. + * + * @param metaData meta-data to export + * @param exportLocation path to export location + * @throws IOException any problem encountered writing the file + */ + public static void export(@Nonnull RecordMetaData metaData, @Nonnull Path exportLocation) throws IOException { + final RecordMetaDataProto.MetaData metaDataProto = metaData.toProto(); + + // Ensure parent directory exists + Path parentDir = exportLocation.getParent(); + if (parentDir != null) { + Files.createDirectories(parentDir); + } + + // Convert protobuf to human-readable JSON + String json = JsonFormat.printer() + .print(metaDataProto); + + // Write to file, overwriting if exists + Files.writeString(exportLocation, json, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } +} diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/package-info.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/package-info.java new file mode 100644 index 0000000000..371d497397 --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/package-info.java @@ -0,0 +1,24 @@ +/* + * package-info.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 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. + */ + +/** + * Utility methods to be used in conjunction with the YAML testing framework. + */ +package com.apple.foundationdb.relational.yamltests.utils; diff --git a/yaml-tests/src/test/java/MetaDataExportUtilityTests.java b/yaml-tests/src/test/java/MetaDataExportUtilityTests.java new file mode 100644 index 0000000000..e349a18a7f --- /dev/null +++ b/yaml-tests/src/test/java/MetaDataExportUtilityTests.java @@ -0,0 +1,82 @@ +/* + * MetaDataExportHelper.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 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. + */ + +import com.apple.foundationdb.record.RecordMetaData; +import com.apple.foundationdb.record.RecordMetaDataBuilder; +import com.apple.foundationdb.record.metadata.Index; +import com.apple.foundationdb.record.metadata.Key; +import com.apple.foundationdb.record.metadata.View; +import com.apple.foundationdb.record.query.plan.cascades.RawSqlFunction; +import com.apple.foundationdb.relational.yamltests.generated.identifierstests.IdentifiersTestProto; +import com.apple.foundationdb.relational.yamltests.utils.ExportSchemaTemplateUtil; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.nio.file.Path; + +/** + * Test utility methods that can be used to set up custom meta-data definitions for YAML tests. + * + *

+ * The idea here is that we may have a test that wants to control its meta-data more than other tests. + * For example, it may want to ensure that some choice is made that would be legal for a {@link RecordMetaData} + * but that the schema template generator may not make. One common case for this is backwards compatibility: + * if we change the schema template to meta-data code path, we may want to make sure that we can can still deserialize + * meta-data objects in the old format. + *

+ * + *

+ * To assist with this, there's functionality in the YAML framework that allows the user to specify a JSON file + * which contains the serialized Protobuf meta-data. These methods allow the user to create a {@link RecordMetaData} + * using a {@link RecordMetaDataBuilder} or some other method, and then create + * such a JSON file. The user should then commit the output of running these tests. If an existing test file + * needs to be updated, it is usually the case that the user should update this file and regenerate the meta-data + * rather than manually update the file. + *

+ */ +@Disabled("for updating test files that should be checked in") +class MetaDataExportUtilityTests { + + private static void exportMetaData(@Nonnull RecordMetaData metaData, @Nonnull String name) throws IOException { + Path path = Path.of("src", "test", "resources", name); + ExportSchemaTemplateUtil.export(metaData, path); + } + + @Test + void createValidIdentifiersMetaData() throws IOException { + final RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder().setRecords(IdentifiersTestProto.getDescriptor()); + metaDataBuilder.getUnionDescriptor().getFields().forEach( f -> { + final String typeName = f.getMessageType().getName(); + metaDataBuilder.getRecordType(typeName).setPrimaryKey(Key.Expressions.concat(Key.Expressions.recordType(), Key.Expressions.field("ID"))); + }); + + metaDataBuilder.addIndex(metaDataBuilder.getRecordType("T2"), new Index("T2$T2.COL1", "T2__1COL1")); + metaDataBuilder.addIndex(metaDataBuilder.getRecordType("___T6__2__UNESCAPED"), new Index("T6$COL2", "__T6__2COL2__VALUE")); + metaDataBuilder.addIndex(metaDataBuilder.getRecordType("___T6__2__UNESCAPED"), new Index("T6$ENUM2", "T6__1__ENUM_2")); + metaDataBuilder.addUserDefinedFunction(new RawSqlFunction("__func__T3$col2", + "CREATE FUNCTION \"__func__T3$col2\"(in \"x$\" bigint) AS select \"__T3$COL1\" as \"c.1\", \"__T3$COL3\" as \"c.2\" from \"__T3\" WHERE \"__T3$COL2\" = \"x$\"")); + metaDataBuilder.addView(new View("T4$view", + "select \"T4.COL1\" AS \"c__1\", \"T4.COL2\" AS \"c__2\" from T4 where \"T4.COL1\" > 0 and \"T4.COL2\" > 0")); + final RecordMetaData metaData = metaDataBuilder.build(); + exportMetaData(metaData, "valid_identifiers_metadata.json"); + } +} diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index fef3bf8948..96df1e13e8 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -342,6 +342,18 @@ public void castTests(YamlTest.Runner runner) throws Exception { runner.runYamsql("cast-tests.yamsql"); } + /** + * Tests that validate the way that identifiers get translated back and forth from Protobuf. + * + * @param runner test runner to use + * @throws Exception any exceptions during the test + * @see MetaDataExportUtilityTests#createValidIdentifiersMetaData() for how the custom meta-data is generated + */ + @TestTemplate + public void validIdentifierTests(YamlTest.Runner runner) throws Exception { + runner.runYamsql("valid-identifiers.yamsql"); + } + @TestTemplate public void vectorTests(YamlTest.Runner runner) throws Exception { runner.runYamsql("vector.yamsql"); diff --git a/yaml-tests/src/test/proto/identifiers.proto b/yaml-tests/src/test/proto/identifiers.proto new file mode 100644 index 0000000000..9be753c948 --- /dev/null +++ b/yaml-tests/src/test/proto/identifiers.proto @@ -0,0 +1,100 @@ +/* + * identifiers.proto + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2021-2025 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. + */ + +syntax = "proto3"; + +// make sure to use this package naming convention: +// com.apple.foundationdb.relational.yamltests.generated. +// adding "generated" is important to exclude the generated Java file from Jacoco reports. +// suffixing the namespace with your test name is important for segregating similarly-named +// generated-Java-classes (such as RecordTypeUnion) into separate packages, otherwise you +// get an error from `protoc`. +package com.apple.foundationdb.relational.yamltests.generated.identifierstests; + +option java_outer_classname = "IdentifiersTestProto"; + +import "record_metadata_options.proto"; + +message T1 { + int64 ID = 1; + int64 T1__2COL1 = 2; + int64 T1__2COL2 = 3; +} + +message T2 { + int64 ID = 1; + int64 T2__1COL1 = 2; + int64 T2__1COL2 = 3; +} + +enum __T3__2ENUM { + T3__2E__2A = 0; + T3__2E__2B = 1; + T3__2E__2C = 2; +} + +message __T3 { + int64 ID = 1; + int64 __T3__1COL1 = 2; + int64 __T3__1COL2 = 3; + optional __T3__2ENUM __T3__1COL3 = 4; +} + +message internal { + int64 a = 1; + int64 b = 2; +} + +message T4 { + int64 ID = 1; + internal ___hidden = 2; + int64 T4__2COL1 = 3; + int64 T4__2COL2 = 4; +} + +message T5__UNESCAPED { + int64 ID = 1; + int64 T5__COL1 = 2; + int64 __T5__COL2 = 3; +} + +enum T6__2__ENUM__0 { + A = 0; + B__0 = 1; + C__ = 2; + __D__2__ = 3; +} + +message ___T6__2__UNESCAPED { + int64 ID = 1; + int64 T6__1__COL1__0 = 2; + int64 __T6__2COL2__VALUE = 3; + optional T6__2__ENUM__0 T6__1__ENUM_1 = 4; + optional T6__2__ENUM__0 T6__1__ENUM_2 = 5; +} + +message RecordTypeUnion { + T1 _T1 = 1; + T2 _T2 = 2; + __T3 ___T3 = 3; + T4 _T4 = 4; + T5__UNESCAPED _T5__UNESCAPED = 5; + ___T6__2__UNESCAPED ____T6__2__UNESCAPED = 6; +} diff --git a/yaml-tests/src/test/resources/in-predicate.metrics.binpb b/yaml-tests/src/test/resources/in-predicate.metrics.binpb index e79481991c..f48e3cef86 100644 --- a/yaml-tests/src/test/resources/in-predicate.metrics.binpb +++ b/yaml-tests/src/test/resources/in-predicate.metrics.binpb @@ -137,4 +137,78 @@ b rankDir=LR; 2 -> 3 [ color="red" style="invis" ]; } +} +s + unnamed-2fEXPLAIN select id from array_table where exists (select 1 from array_table.fruits f where f = 'apple') +C /("0Ɖ8.@ACOVERING(FRUITS [EQUALS @c18] -> [ID: KEY[2]]) | MAP (_.ID AS ID) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q67.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS @c18]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 [ label=<
Index
FRUITS
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q67> label="q67" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +G + unnamed-2:EXPLAIN select id from array_table where 'apple' in fruits +w ˅(0d8&@KSCAN(<,>) | TFILTER ARRAY_TABLE | FILTER @c6 IN _.FRUITS | MAP (_.ID AS ID)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q36.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Predicate Filter
WHERE @c6 IN q2.FRUITS
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 [ label=<
Type Filter
WHERE record IS [ARRAY_TABLE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [ARRAY_TABLE, TA]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q29> label="q29" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q36> label="q36" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +}# +o + unnamed-2bEXPLAIN select id from array_table where exists (select 1 from array_table.numbers n where n = 10)" + +g (0@8"@SCAN(<,>) | TFILTER ARRAY_TABLE | FLATMAP q0 -> { EXPLODE q0.NUMBERS | FILTER _ EQUALS promote(@c18 AS LONG) | MAP (@c9 AS _0) | DEFAULT NULL | FILTER _ NOT_NULL AS q1 RETURN (q0.ID AS ID) }!digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Nested Loop Join
FLATMAP (q2.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Type Filter
WHERE record IS [ARRAY_TABLE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [ARRAY_TABLE, TA]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Predicate Filter
WHERE q8 NOT_NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ]; + 6 [ label=<
Value Computation
FIRST $q8 OR NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ]; + 7 [ label=<
Value Computation
MAP (@c9 AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ]; + 8 [ label=<
Predicate Filter
WHERE q4 EQUALS promote(@c18 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG)" ]; + 9 [ label=<
Value Computation
EXPLODE q2.NUMBERS
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG)" ]; + 3 -> 2 [ label=< q40> label="q40" 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=< q2> label="q2" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 5 [ label=< q8> label="q8" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 6 [ label=< q8> label="q8" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 8 -> 7 [ label=< q36> label="q36" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 9 -> 8 [ label=< q4> label="q4" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 1 [ label=< q8> label="q8" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + { + rank=same; + rankDir=LR; + 2 -> 5 [ color="red" style="invis" ]; + } +} +C + unnamed-26EXPLAIN select id from array_table where 10 in numbers +ԁw (0c8&@]SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER promote(@c6 AS LONG) IN _.NUMBERS | MAP (_.ID AS ID)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q36.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Predicate Filter
WHERE promote(@c6 AS LONG) IN q2.NUMBERS
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 [ label=<
Type Filter
WHERE record IS [ARRAY_TABLE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [ARRAY_TABLE, TA]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q29> label="q29" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q36> label="q36" 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/in-predicate.metrics.yaml b/yaml-tests/src/test/resources/in-predicate.metrics.yaml index 87fbc30f19..1b93134187 100644 --- a/yaml-tests/src/test/resources/in-predicate.metrics.yaml +++ b/yaml-tests/src/test/resources/in-predicate.metrics.yaml @@ -81,3 +81,49 @@ unnamed-2: insert_time_ms: 10 insert_new_count: 134 insert_reused_count: 8 +- query: EXPLAIN select id from array_table where exists (select 1 from array_table.fruits + f where f = 'apple') + explain: 'COVERING(FRUITS [EQUALS @c18] -> [ID: KEY[2]]) | MAP (_.ID AS ID)' + task_count: 472 + task_total_time_ms: 141 + transform_count: 137 + transform_time_ms: 100 + transform_yield_count: 34 + insert_time_ms: 6 + insert_new_count: 46 + insert_reused_count: 2 +- query: EXPLAIN select id from array_table where 'apple' in fruits + explain: SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER @c6 IN _.FRUITS | MAP (_.ID + AS ID) + task_count: 400 + task_total_time_ms: 34 + transform_count: 119 + transform_time_ms: 10 + transform_yield_count: 25 + insert_time_ms: 1 + insert_new_count: 38 + insert_reused_count: 4 +- query: EXPLAIN select id from array_table where exists (select 1 from array_table.numbers + n where n = 10) + explain: SCAN(<,>) | TFILTER ARRAY_TABLE | FLATMAP q0 -> { EXPLODE q0.NUMBERS + | FILTER _ EQUALS promote(@c18 AS LONG) | MAP (@c9 AS _0) | DEFAULT NULL | + FILTER _ NOT_NULL AS q1 RETURN (q0.ID AS ID) } + task_count: 367 + task_total_time_ms: 21 + transform_count: 103 + transform_time_ms: 5 + transform_yield_count: 23 + insert_time_ms: 1 + insert_new_count: 34 + insert_reused_count: 2 +- query: EXPLAIN select id from array_table where 10 in numbers + explain: SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER promote(@c6 AS LONG) IN _.NUMBERS + | MAP (_.ID AS ID) + task_count: 400 + task_total_time_ms: 35 + transform_count: 119 + transform_time_ms: 9 + transform_yield_count: 25 + insert_time_ms: 1 + insert_new_count: 38 + insert_reused_count: 4 diff --git a/yaml-tests/src/test/resources/in-predicate.yamsql b/yaml-tests/src/test/resources/in-predicate.yamsql index 64eb0a6c5b..7563b05061 100644 --- a/yaml-tests/src/test/resources/in-predicate.yamsql +++ b/yaml-tests/src/test/resources/in-predicate.yamsql @@ -21,8 +21,9 @@ schema_template: create type as struct ts(sa bigint, sb bigint) create type as struct fruit_type(name string, color string) create table ta(a bigint, b bigint, c double, d boolean, e string, f ts, primary key(a)) - create table array_table(id bigint, fruits string array, numbers bigint array, fruit_records fruit_type array, primary key(id)) create index f1 as select f.sa, f.sb from ta order by f.sa, f.sb + create table array_table(id bigint, fruits string array, numbers bigint array, fruit_records fruit_type array, primary key(id)) + create index fruits as select fruits from array_table --- setup: steps: @@ -189,10 +190,19 @@ test_block: # constant LONG value matched against IN list of LONG values. - query: select a from ta where 1 in (1, 2, 3) - unorderedResult: [{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}] + - + # Test alternate IN-like predicate with primitive array column - string in string array + - query: select id from array_table where exists (select 1 from array_table.fruits f where f = 'apple') + - supported_version: 4.8.1.0 + - explain: "COVERING(FRUITS [EQUALS @c18] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - unorderedResult: [{1}, {3}] - # Test IN predicate with primitive array column - string in string array - query: select id from array_table where 'apple' in fruits - supported_version: 4.8.1.0 + # Note: this should be able to match the "fruits" index like the above query, but it does not + # See: https://github.com/FoundationDB/fdb-record-layer/issues/3777 + - explain: "SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER @c6 IN _.FRUITS | MAP (_.ID AS ID)" - unorderedResult: [{1}, {3}] - # Test IN predicate with primitive array column - multiple matches @@ -204,10 +214,18 @@ test_block: - query: select id from array_table where 'pineapple' in fruits - supported_version: 4.8.1.0 - result: [] + - + # Test alternate IN-like predicate with primitive array column - int in int array + - query: select id from array_table where exists (select 1 from array_table.numbers n where n = 10) + - supported_version: 4.8.1.0 + - explain: "SCAN(<,>) | TFILTER ARRAY_TABLE | FLATMAP q0 -> { EXPLODE q0.NUMBERS | FILTER _ EQUALS promote(@c18 AS LONG) | MAP (@c9 AS _0) | DEFAULT NULL | FILTER _ NOT_NULL AS q1 RETURN (q0.ID AS ID) }" + - unorderedResult: [{1}, {3}] - # Test IN predicate with primitive array column - bigint in bigint array - query: select id from array_table where 10 in numbers - supported_version: 4.8.1.0 + # Note: as there is no index on the numbers field, this is the expected plan. Ideally, the previous query would also have the same plan as this one + - explain: "SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER promote(@c6 AS LONG) IN _.NUMBERS | MAP (_.ID AS ID)" - unorderedResult: [{1}, {3}] - # Test IN predicate with primitive array column - bigint no match diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb new file mode 100644 index 0000000000..5716e92ad2 --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -0,0 +1,1074 @@ + + + all-testsEXPLAIN select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.3" = 20) +b K(50ҩ8E@sCOVERING(foo.table$nested.repeated.idx.field.1.3 [EQUALS promote(@c25 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q110.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS promote(@c25 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 [ label=<
Index
foo.table$nested.repeated.idx.field.1.3
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q110> label="q110" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-testsEXPLAIN select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.2"."level2$field.1" = 91) + (50փ8E@uCOVERING(foo.table$nested.repeated.idx.field.1.2.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q110.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS promote(@c27 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 [ label=<
Index
foo.table$nested.repeated.idx.field.1.2.1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q110> label="q110" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-testsEXPLAIN select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as b, b."level1$field.1" where "level2$array.field.1" = 10) + (E0Ƭ8`@ uCOVERING(foo.table$nested.repeated.idx.field.1.1.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q123.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS promote(@c27 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 [ label=<
Index
foo.table$nested.repeated.idx.field.1.1.1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q123> label="q123" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +r + all-testseEXPLAIN select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" where id = 1 +·r (!084@COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q49.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$field.1)" ]; + 2 [ label=<
Predicate Filter
WHERE q45.ID EQUALS promote(@c12 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2 AS level0.field1)" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2 AS level0.field1)" ]; + 4 [ label=<
Index
foo.table$nested.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2 AS level0.field1)" ]; + 3 -> 2 [ label=< q45> label="q45" 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=< q49> label="q49" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-teststEXPLAIN select "foo.table$repeated".id from "foo.table$repeated" where 'foo' IN "foo.table$repeated"."field.1$array" +y (0^8&@]SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER @c8 IN _.field.1$array | MAP (_.ID AS ID)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q46.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Predicate Filter
WHERE @c8 IN q2.field.1$array
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2table__1repeated]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1repeated, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q39> label="q39" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q46> label="q46" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-testsEXPLAIN select "foo.table$repeated".id from "foo.table$repeated" where exists (select 1 from "foo.table$repeated"."field.1$array" r where r = 'foo') + ($0)8.@OCOVERING(foo.table$repeated.2 [EQUALS @c20] -> [ID: KEY[2]]) | MAP (_.ID AS ID) +digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q77.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS @c20]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 [ label=<
Index
foo.table$repeated.2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q77> label="q77" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +i + all-tests\EXPLAIN select t."field.1$array" from "foo.table$repeated" as t where 3 IN t."field.0$array" +ךy ۲(028&@SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER promote(@c10 AS LONG) IN _.field.0$array | MAP (_.field.1$array AS field.1$array)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q46.field.1$array AS field.1$array)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(ARRAY(STRING) AS field.1$array)" ]; + 2 [ label=<
Predicate Filter
WHERE promote(@c10 AS LONG) IN q2.field.0$array
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2table__1repeated]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1repeated, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q39> label="q39" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q46> label="q46" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-tests|EXPLAIN select t."field.1$array" from "foo.table$repeated" as t where exists (select 1 from t."field.0$array" r where r = 3) + ("058+@cISCAN(foo.table$repeated.1 [EQUALS promote(@c22 AS LONG)]) | MAP (_.field.1$array AS field.1$array) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2.field.1$array AS field.1$array)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(ARRAY(STRING) AS field.1$array)" ]; + 2 [ label=<
Index Scan
comparisons: [EQUALS promote(@c22 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 [ label=<
Index
foo.table$repeated.1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} += + all-tests0EXPLAIN select "foo.tableA".* from "foo.tableA"; + +j (10;8-@COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q66.foo.tableA.A1 AS foo.tableA.A1, q66.foo.tableA.A2 AS foo.tableA.A2, q66.foo.tableA.A3 AS foo.tableA.A3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 2 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ 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" ]; +} +0 + all-tests#EXPLAIN select * from "foo.tableA"; + j (10@8-@COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q66.foo.tableA.A1 AS foo.tableA.A1, q66.foo.tableA.A2 AS foo.tableA.A2, q66.foo.tableA.A3 AS foo.tableA.A3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 2 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ 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" ]; +} +A + all-tests4EXPLAIN select "_$$$".* from "foo.tableA" as "_$$$"; +j (1028-@COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q66.foo.tableA.A1 AS foo.tableA.A1, q66.foo.tableA.A2 AS foo.tableA.A2, q66.foo.tableA.A3 AS foo.tableA.A3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 2 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ 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" ]; +} +m + all-tests`EXPLAIN select "foo.tableA.A2", sum("foo.tableA.A1") from "foo.tableA" group by "foo.tableA.A2"; + u (,0;8$@nAISCAN(foo.tableA.idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP (_._0 AS foo.tableA.A2, _._1 AS _1) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q6._0 AS foo.tableA.A2, q6._1 AS _1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A2, LONG AS _1)" ]; + 2 [ label=<
Index Scan
scan type: BY_GROUP
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0, LONG AS _1)" ]; + 3 [ label=<
Index
foo.tableA.idx2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ 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" ]; +} + + all-testssEXPLAIN select * from "foo.tableA", "foo.tableB" where "foo.tableA"."foo.tableA.A1" = "foo.tableB"."foo.tableB.B1"; +ͬ (E0n8k@SCAN(<,>) | TFILTER foo__2tableB | FLATMAP q0 -> { ISCAN(foo.tableA.idx [EQUALS q0.foo.tableB.B1]) AS q1 RETURN (q1.foo.tableA.A1 AS foo.tableA.A1, q1.foo.tableA.A2 AS foo.tableA.A2, q1.foo.tableA.A3 AS foo.tableA.A3, q0.foo.tableB.B1 AS foo.tableB.B1, q0.foo.tableB.B2 AS foo.tableB.B2, q0.foo.tableB.B3 AS foo.tableB.B3) }digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Nested Loop Join
FLATMAP (q2.foo.tableA.A1 AS foo.tableA.A1, q2.foo.tableA.A2 AS foo.tableA.A2, q2.foo.tableA.A3 AS foo.tableA.A3, q6.foo.tableB.B1 AS foo.tableB.B1, q6.foo.tableB.B2 AS foo.tableB.B2, q6.foo.tableB.B3 AS foo.tableB.B3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3, LONG AS foo.tableB.B1, LONG AS foo.tableB.B2, LONG AS S1, LONG AS S2 AS foo.tableB.B3)" ]; + 2 [ label=<
Type Filter
WHERE record IS [foo__2tableB]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableB.B1, LONG AS foo.tableB.B2, LONG AS S1, LONG AS S2 AS foo.tableB.B3)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Index Scan
comparisons: [EQUALS q6.foo.tableB.B1]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 6 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ label=< q53> label="q53" 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=< q6> label="q6" color="gray20" style="solid" 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" ]; + 5 -> 1 [ label=< q2> label="q2" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + { + rank=same; + rankDir=LR; + 2 -> 5 [ color="red" style="invis" ]; + } +} +m + all-tests`EXPLAIN select "foo$tableC$C2", sum("foo$tableC$C1") from "foo$tableC" group by "foo$tableC$C2"; +쭍g ("08@nAISCAN(foo$tableC$idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP (_._0 AS foo$tableC$C2, _._1 AS _1) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q6._0 AS foo$tableC$C2, q6._1 AS _1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo$tableC$C2, LONG AS _1)" ]; + 2 [ label=<
Index Scan
scan type: BY_GROUP
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0, LONG AS _1)" ]; + 3 [ label=<
Index
foo$tableC$idx2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo$tableC$C1, LONG AS foo$tableC$C2, LONG AS foo$tableC$C3)" ]; + 3 -> 2 [ 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" ]; +} +C + all-tests6EXPLAIN select "__foo__tableD".* from "__foo__tableD"; +ԧP (#08 @COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q51.__foo__tableD$D1 AS __foo__tableD$D1, q51.__foo__tableD$D2 AS __foo__tableD$D2, q51.__foo__tableD$D3 AS __foo__tableD$D3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 2 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 [ label=<
Index
__foo__tableD$idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q51> label="q51" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +a + all-testsTEXPLAIN select "__foo__tableD".* from "__foo__tableD" where "__foo__tableD$D3" >= 2; +c ('080@COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | FILTER _.__foo__tableD$D3 GREATER_THAN_OR_EQUALS promote(@c11 AS LONG) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q59.__foo__tableD$D1 AS __foo__tableD$D1, q59.__foo__tableD$D2 AS __foo__tableD$D2, q59.__foo__tableD$D3 AS __foo__tableD$D3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 2 [ label=<
Predicate Filter
WHERE q55.__foo__tableD$D3 GREATER_THAN_OR_EQUALS promote(@c11 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 4 [ label=<
Index
__foo__tableD$idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 -> 2 [ label=< q55> label="q55" 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=< q59> label="q59" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +T + all-testsGEXPLAIN select "__foo__tableD"."__foo__tableD$D1" from "__foo__tableD"; +@ (08@COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q51.__foo__tableD$D1 AS __foo__tableD$D1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1)" ]; + 2 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 [ label=<
Index
__foo__tableD$idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q51> label="q51" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +D + all-tests7EXPLAIN select "__foo__tableD$D1" from "__foo__tableD"; +Ǽ@ (08@COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q51.__foo__tableD$D1 AS __foo__tableD$D1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1)" ]; + 2 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 [ label=<
Index
__foo__tableD$idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q51> label="q51" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +} + all-testspEXPLAIN select "__foo__tableD".* from "__foo__tableD" where "__foo__tableD$D3" >= 2 order by "__foo__tableD$D1"; +R (!08#@COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | FILTER _.__foo__tableD$D3 GREATER_THAN_OR_EQUALS promote(@c11 AS LONG) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q48.__foo__tableD$D1 AS __foo__tableD$D1, q48.__foo__tableD$D2 AS __foo__tableD$D2, q48.__foo__tableD$D3 AS __foo__tableD$D3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 2 [ label=<
Predicate Filter
WHERE q44.__foo__tableD$D3 GREATER_THAN_OR_EQUALS promote(@c11 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 4 [ label=<
Index
__foo__tableD$idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __foo__tableD$D1, LONG AS __foo__tableD$D2, LONG AS __foo__tableD$D3)" ]; + 3 -> 2 [ label=< q44> label="q44" 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=< q48> label="q48" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-testsEXPLAIN select "βήτα__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" as "f__e" where "alpha__f"("f__e"."foo.tableE.E3") = 5 += (08@~SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c22 AS LONG) | MAP (_.foo.tableE.E3.S2 AS ___h.1)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.foo.tableE.E3.S2 AS ___h.1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ___h.1)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.foo.tableE.E3.S1 EQUALS promote(@c22 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2tableE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-testsEXPLAIN select "alpha__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" as "f__e" where "βήτα__f"("f__e"."foo.tableE.E3") >= 50 += (08@SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S2 GREATER_THAN_OR_EQUALS promote(@c23 AS LONG) | MAP (_.foo.tableE.E3.S1 AS ___h.1)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.foo.tableE.E3.S1 AS ___h.1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ___h.1)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.foo.tableE.E3.S2 GREATER_THAN_OR_EQUALS promote(@c23 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2tableE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +}( +8 + all-tests+EXPLAIN select * from "__$func3"(10, 1, 1);' + + (n08@ SCAN(<,>) | TFILTER my__1adjacency__1list | FILTER _.me LESS_THAN promote(@c6 AS LONG) AND _.my__parent EQUALS promote(@c8 AS LONG) | FLATMAP q0 -> { SCAN(<,>) | TFILTER my__1adjacency__1list | FILTER _.me EQUALS promote(@c8 AS LONG) AS q1 RETURN (q0.me AS _0, q0.my__parent AS _1, q1.me AS _2, q1.my__parent AS _3) }$digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Nested Loop Join
FLATMAP (q50.me AS _0, q50.my__parent AS _1, q61.me AS _2, q61.my__parent AS _3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0, LONG AS _1, LONG AS _2, LONG AS _3)" ]; + 2 [ label=<
Predicate Filter
WHERE q50.me LESS_THAN promote(@c6 AS LONG) AND q50.my__parent EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS me, LONG AS my__parent)" ]; + 3 [ label=<
Type Filter
WHERE record IS [my__1adjacency__1list]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS me, LONG AS my__parent)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 6 [ label=<
Predicate Filter
WHERE q61.me EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS me, LONG AS my__parent)" ]; + 7 [ label=<
Type Filter
WHERE record IS [my__1adjacency__1list]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS me, LONG AS my__parent)" ]; + 8 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 9 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q50> label="q50" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q201> label="q201" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q50> label="q50" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 6 [ label=< q61> label="q61" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 8 -> 7 [ label=< q197> label="q197" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 9 -> 8 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 1 [ label=< q61> label="q61" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + { + rank=same; + rankDir=LR; + 2 -> 6 [ color="red" style="invis" ]; + } +} +- + all-tests EXPLAIN select * from "$yay"(5); +ӭZ (0'83@zSCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$x.id)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q54.foo.tableE.E1 AS _$x.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$x.id)" ]; + 2 [ label=<
Predicate Filter
WHERE q12.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2tableE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q12> label="q12" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q54> label="q54" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +/ + all-tests"EXPLAIN select * from "__2yay"(6); +ՇZ (0&83@zSCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$y.id)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q54.foo.tableE.E1 AS _$y.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$y.id)" ]; + 2 [ label=<
Predicate Filter
WHERE q12.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2tableE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q12> label="q12" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q54> label="q54" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +8 + all-tests+EXPLAIN select * from "नमस्त"(4); +ǞZ (0Ϙ'83@zSCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$z.id)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q54.foo.tableE.E1 AS _$z.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$z.id)" ]; + 2 [ label=<
Predicate Filter
WHERE q12.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2tableE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q12> label="q12" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q54> label="q54" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +0 + all-tests#EXPLAIN select * from "$yay__view"; +R ŝ(0Ç8@SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 4 | MAP (_.foo.tableE.E1 AS _$x.id) | MAP (_._$x.id AS _$x.id)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q6._$x.id AS _$x.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$x.id)" ]; + 2 [ label=<
Value Computation
MAP (q32.foo.tableE.E1 AS _$x.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$x.id)" ]; + 3 [ label=<
Predicate Filter
WHERE q2.foo.tableE.E3.S1 EQUALS 4
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 4 [ label=<
Type Filter
WHERE record IS [foo__2tableE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 5 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 6 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q32> label="q32" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ label=< q25> label="q25" 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" ]; +} +2 + all-tests%EXPLAIN select * from "__2yay__view"; +R }(08@SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 5 | MAP (_.foo.tableE.E1 AS _$y.id) | MAP (_._$y.id AS _$y.id)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q6._$y.id AS _$y.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$y.id)" ]; + 2 [ label=<
Value Computation
MAP (q32.foo.tableE.E1 AS _$y.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$y.id)" ]; + 3 [ label=<
Predicate Filter
WHERE q2.foo.tableE.E3.S1 EQUALS 5
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 4 [ label=<
Type Filter
WHERE record IS [foo__2tableE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 5 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 6 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q32> label="q32" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ label=< q25> label="q25" 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" ]; +} +; + all-tests.EXPLAIN select * from "வணக்கம்"; +R ِ(08@SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 6 | MAP (_.foo.tableE.E1 AS _$z.id) | MAP (_._$z.id AS _$z.id)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q6._$z.id AS _$z.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$z.id)" ]; + 2 [ label=<
Value Computation
MAP (q32.foo.tableE.E1 AS _$z.id)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _$z.id)" ]; + 3 [ label=<
Predicate Filter
WHERE q2.foo.tableE.E3.S1 EQUALS 6
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 4 [ label=<
Type Filter
WHERE record IS [foo__2tableE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableE.E1, ARRAY(LONG) AS foo.tableE.E2, LONG AS S1, LONG AS S2 AS foo.tableE.E3)" ]; + 5 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 6 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q32> label="q32" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ label=< q25> label="q25" 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" ]; +} +^ + all-testsQEXPLAIN select * from values (1, 2, 3), (4, 5, 6) as "_$$$$"("_$$$", "_$$", "_$") +tD ޚ (08 @EXPLODE array((@c6 AS _$$$, @c8 AS _$$, @c10 AS _$), (@c14 AS _$$$, @c16 AS _$$, @c18 AS _$)) | MAP (_._$$$ AS _$$$, _._$$ AS _$$, _._$ AS _$)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q0._$$$ AS _$$$, q0._$$ AS _$$, q0._$ AS _$)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _$$$, INT AS _$$, INT AS _$)" ]; + 2 [ label=<
Value Computation
EXPLODE array((@c6 AS _$$$, @c8 AS _$$, @c10 AS _$), (@c14 AS _$$$, @c16 AS _$$, @c18 AS _$))
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _$$$, INT AS _$$, INT AS _$)" ]; + 2 -> 1 [ label=< q0> label="q0" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +n + all-testsaEXPLAIN select struct "x$$" ("foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3") from "foo.tableA" + (20!86@COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP ((_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) AS _0) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP ((q66.foo.tableA.A1 AS foo.tableA.A1, q66.foo.tableA.A2 AS foo.tableA.A2, q66.foo.tableA.A3 AS foo.tableA.A3) AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS _0)" ]; + 2 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ 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" ]; +} +K + all-tests>EXPLAIN select struct "x$$" ("foo.tableA".*) from "foo.tableA" +K ٓ(%08#@*ISCAN(foo.tableA.idx3 <,>) | MAP (_ AS _0) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2 AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS _0)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Index
foo.tableA.idx3
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +j + all-tests]EXPLAIN select struct "x$$" ("foo.tableA.A2" + "foo.tableA.A1" as "__$$__") from "foo.tableA" +I (%0'8#@VISCAN(foo.tableA.idx3 <,>) | MAP ((_.foo.tableA.A2 + _.foo.tableA.A1 AS __$$__) AS _0) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP ((q2.foo.tableA.A2 + q2.foo.tableA.A1 AS __$$__) AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __$$__ AS _0)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Index
foo.tableA.idx3
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +3 + all-tests&EXPLAIN select * from "foo.enum.type"; +H ž(08@ ISCAN(foo.enum.type$enum__1 <,>)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 [ label=<
Index
foo.enum.type$enum__1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +U + all-testsHEXPLAIN select * from "foo.enum.type" where "enum_type.enum__1" = 'B$C'; + +S (08%@cISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
comparisons: [EQUALS promote(@c8 AS ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)>)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 [ label=<
Index
foo.enum.type$enum__1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +U + all-testsHEXPLAIN select * from "foo.enum.type" where "enum_type.enum__1" = 'C.D'; + +S (08%@cISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
comparisons: [EQUALS promote(@c8 AS ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)>)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 [ label=<
Index
foo.enum.type$enum__1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + +S + all-testsFEXPLAIN select * from "foo.enum.type" where "enum_type.enum__1" = 'A'; + +S (08%@cISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
comparisons: [EQUALS promote(@c8 AS ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)>)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 [ label=<
Index
foo.enum.type$enum__1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +U + all-testsHEXPLAIN select * from "foo.enum.type" where "enum_type.enum__2" = 'B$C'; +Q (08%@ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.enum_type.enum__2 EQUALS promote(@c8 AS ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)>)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 3 [ label=<
Index
foo.enum.type$enum__1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +U + all-testsHEXPLAIN select * from "foo.enum.type" where "enum_type.enum__2" = 'C.D'; +Q (08%@ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.enum_type.enum__2 EQUALS promote(@c8 AS ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)>)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 3 [ label=<
Index
foo.enum.type$enum__1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +S + all-testsFEXPLAIN select * from "foo.enum.type" where "enum_type.enum__2" = 'A'; +Q (08%@ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.enum_type.enum__2 EQUALS promote(@c8 AS ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)>)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 3 [ label=<
Index
foo.enum.type$enum__1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS enum_type.id, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__1, ENUM<A(0), B$C(1), C.D(2), E__F(3), __G$H(4)> AS enum_type.enum__2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +k +update-delete-statementsOEXPLAIN UPDATE "foo.tableA" SET "foo.tableA.A2" = 100 WHERE "foo.tableA.A1" = 1 + (?0J8`@COVERING(foo.tableA.idx [EQUALS promote(@c10 AS LONG)] -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | DISTINCT BY PK | FETCH | UPDATE foo__2tableAdigraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Modification
UPDATE
> color="black" shape="plain" style="filled" fillcolor="lightcoral" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS old, LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS new)" ]; + 2 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Unordered Primary Key Distinct
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 4 [ label=<
Covering Index Scan
comparisons: [EQUALS promote(@c10 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 5 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 6 [ label=<
Primary Storage
foo__2tableA
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS old, LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS new)" ]; + 3 -> 2 [ label=< q142> label="q142" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q140> label="q140" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="none" arrowtail="normal" dir="both" ]; + { + rank=same; + rankDir=LR; + 2 -> 6 [ color="red" style="invis" ]; + } +} + +update-delete-statementsoEXPLAIN UPDATE "foo.tableA" SET "foo.tableA.A2" = 100 WHERE "foo.tableA.A1" > 1 RETURNING "new"."foo.tableA.A1" + + (<0:8d@COVERING(foo.tableA.idx [[GREATER_THAN promote(@c10 AS LONG)]] -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | DISTINCT BY PK | FETCH | UPDATE foo__2tableA | MAP (_.new.foo.tableA.A1 AS foo.tableA.A1)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q6.new.foo.tableA.A1 AS foo.tableA.A1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1)" ]; + 2 [ label=<
Modification
UPDATE
> color="black" shape="plain" style="filled" fillcolor="lightcoral" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS old, LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS new)" ]; + 3 [ label=<
Fetch Records
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 4 [ label=<
Primary Storage
foo__2tableA
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS old, LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3 AS new)" ]; + 5 [ label=<
Unordered Primary Key Distinct
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="12" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 6 [ label=<
Covering Index Scan
comparisons: [[GREATER_THAN promote(@c10 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 7 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="none" arrowtail="normal" dir="both" ]; + 5 -> 3 [ label=< q145> label="q145" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 5 [ label=< q143> label="q143" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 6 [ 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" ]; + { + rank=same; + rankDir=LR; + 3 -> 4 [ color="red" style="invis" ]; + } +} + +update-delete-statementsxEXPLAIN DELETE FROM "foo.tableA" WHERE "foo.tableA.A1" = 1 RETURNING "foo.tableA.A1" + "foo.tableA.A2" + "foo.tableA.A3" +č (A0*8_@~ISCAN(foo.tableA.idx [EQUALS promote(@c7 AS LONG)]) | DELETE | MAP (_.foo.tableA.A1 + _.foo.tableA.A2 + _.foo.tableA.A3 AS _0)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q6.foo.tableA.A1 + q6.foo.tableA.A2 + q6.foo.tableA.A3 AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS _0)" ]; + 2 [ label=<
Modification
DELETE
> color="black" shape="plain" style="filled" fillcolor="lightcoral" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Index Scan
comparisons: [EQUALS promote(@c7 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 4 [ label=<
Primary Storage
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="12" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 5 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="none" arrowtail="normal" dir="both" ]; + 5 -> 3 [ 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" ]; + { + rank=same; + rankDir=LR; + 3 -> 4 [ color="red" style="invis" ]; + } +} +X +update-delete-statementsModificationDELETE> color="black" shape="plain" style="filled" fillcolor="lightcoral" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 2 [ label=<
Index Scan
comparisons: [EQUALS promote(@c7 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 [ label=<
Index
foo.tableA.idx3
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 4 [ label=<
Primary Storage
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="12" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="none" arrowtail="normal" dir="both" ]; + { + rank=same; + rankDir=LR; + 2 -> 4 [ color="red" style="invis" ]; + } +} +& + +unnamed-12EXPLAIN SELECT * FROM T1 +ރ. ͆(0 8@USCAN(<,>) | TFILTER T1 | MAP (_.ID AS ID, _.T1.COL1 AS T1.COL1, _.T1.COL2 AS T1.COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2.ID AS ID, q2.T1.COL1 AS T1.COL1, q2.T1.COL2 AS T1.COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T1.COL1, LONG AS T1.COL2)" ]; + 2 [ label=<
Type Filter
WHERE record IS [T1]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T1.COL1, LONG AS T1.COL2)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q19> label="q19" 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=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +: + +unnamed-12,EXPLAIN SELECT * FROM T1 WHERE "T1.COL2" > 3 +4 (08@SCAN(<,>) | TFILTER T1 | FILTER _.T1.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.T1.COL1 AS T1.COL1, _.T1.COL2 AS T1.COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.T1.COL1 AS T1.COL1, q26.T1.COL2 AS T1.COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T1.COL1, LONG AS T1.COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T1.COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T1.COL1, LONG AS T1.COL2)" ]; + 3 [ label=<
Type Filter
WHERE record IS [T1]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T1.COL1, LONG AS T1.COL2)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +B + +unnamed-124EXPLAIN SELECT "T1.COL2" FROM T1 WHERE "T1.COL2" > 3 += (08@hSCAN(<,>) | TFILTER T1 | FILTER _.T1.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T1.COL2 AS T1.COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.T1.COL2 AS T1.COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS T1.COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T1.COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T1.COL1, LONG AS T1.COL2)" ]; + 3 [ label=<
Type Filter
WHERE record IS [T1]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T1.COL1, LONG AS T1.COL2)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +& + +unnamed-12EXPLAIN SELECT * FROM T2 +H (08@ISCAN(T2$T2.COL1 <,>)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T2$COL1, LONG AS T2$COL2)" ]; + 2 [ label=<
Index
T2$T2.COL1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T2$COL1, LONG AS T2$COL2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +: + +unnamed-12,EXPLAIN SELECT * FROM T2 WHERE "T2$COL2" > 8 +ʃQ (0 8%@JISCAN(T2$T2.COL1 <,>) | FILTER _.T2$COL2 GREATER_THAN promote(@c8 AS LONG) +digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.T2$COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T2$COL1, LONG AS T2$COL2)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T2$COL1, LONG AS T2$COL2)" ]; + 3 [ label=<
Index
T2$T2.COL1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T2$COL1, LONG AS T2$COL2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +B + +unnamed-124EXPLAIN SELECT "T2$COL2" FROM T2 WHERE "T2$COL2" > 8 + +b (0,8'@gISCAN(T2$T2.COL1 <,>) | FILTER _.T2$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T2$COL2 AS T2$COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q43.T2$COL2 AS T2$COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS T2$COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T2$COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T2$COL1, LONG AS T2$COL2)" ]; + 3 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T2$COL1, LONG AS T2$COL2)" ]; + 4 [ label=<
Index
T2$T2.COL1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T2$COL1, LONG AS T2$COL2)" ]; + 3 -> 2 [ label=< q2> label="q2" 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=< q43> label="q43" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +* + +unnamed-12EXPLAIN SELECT * FROM "__T3" +. `(0͆8@ySCAN(<,>) | TFILTER __T3 | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2, _.__T3$COL3 AS __T3$COL3)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2.ID AS ID, q2.__T3$COL1 AS __T3$COL1, q2.__T3$COL2 AS __T3$COL2, q2.__T3$COL3 AS __T3$COL3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 2 [ label=<
Type Filter
WHERE record IS [__T3]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q19> label="q19" 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=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +A + +unnamed-123EXPLAIN SELECT * FROM "__T3" WHERE "__T3$COL2" > 13 +4 ɹk(08@SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2, _.__T3$COL3 AS __T3$COL3)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.__T3$COL1 AS __T3$COL1, q26.__T3$COL2 AS __T3$COL2, q26.__T3$COL3 AS __T3$COL3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.__T3$COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [__T3]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +K + +unnamed-12=EXPLAIN SELECT "__T3$COL2" FROM "__T3" WHERE "__T3$COL2" > 13 += (0ї8@pSCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.__T3$COL2 AS __T3$COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.__T3$COL2 AS __T3$COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __T3$COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.__T3$COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [__T3]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +9 + +unnamed-12+EXPLAIN SELECT * FROM "__func__T3$col2"(13) +Z  (0/83@xSCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 EQUALS promote(@c6 AS LONG) | MAP (_.__T3$COL1 AS c.1, _.__T3$COL3 AS c.2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q54.__T3$COL1 AS c.1, q54.__T3$COL3 AS c.2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS c.1, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS c.2)" ]; + 2 [ label=<
Predicate Filter
WHERE q12.__T3$COL2 EQUALS promote(@c6 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [__T3]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q12> label="q12" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q54> label="q54" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +& + +unnamed-12EXPLAIN SELECT * FROM T4 +. (08@oSCAN(<,>) | TFILTER T4 | MAP (_.ID AS ID, _.___hidden AS ___hidden, _.T4.COL1 AS T4.COL1, _.T4.COL2 AS T4.COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2.ID AS ID, q2.___hidden AS ___hidden, q2.T4.COL1 AS T4.COL1, q2.T4.COL2 AS T4.COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 2 [ label=<
Type Filter
WHERE record IS [T4]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q19> label="q19" 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=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +; + +unnamed-12-EXPLAIN SELECT * FROM T4 WHERE "T4.COL2" > 18 +Բ4 w(0 +8@SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.___hidden AS ___hidden, _.T4.COL1 AS T4.COL1, _.T4.COL2 AS T4.COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.___hidden AS ___hidden, q26.T4.COL1 AS T4.COL1, q26.T4.COL2 AS T4.COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T4.COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 3 [ label=<
Type Filter
WHERE record IS [T4]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +C + +unnamed-125EXPLAIN SELECT "T4.COL2" FROM T4 WHERE "T4.COL2" > 18 += (08@hSCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T4.COL2 AS T4.COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.T4.COL2 AS T4.COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS T4.COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T4.COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 3 [ label=<
Type Filter
WHERE record IS [T4]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +- + +unnamed-12EXPLAIN SELECT * FROM "T4$view" +ΓR (0&8@SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL1 GREATER_THAN 0 AND _.T4.COL2 GREATER_THAN 0 | MAP (_.T4.COL1 AS c__1, _.T4.COL2 AS c__2) | MAP (_.c__1 AS c__1, _.c__2 AS c__2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q6.c__1 AS c__1, q6.c__2 AS c__2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS c__1, LONG AS c__2)" ]; + 2 [ label=<
Value Computation
MAP (q32.T4.COL1 AS c__1, q32.T4.COL2 AS c__2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS c__1, LONG AS c__2)" ]; + 3 [ label=<
Predicate Filter
WHERE q2.T4.COL1 GREATER_THAN 0 AND q2.T4.COL2 GREATER_THAN 0
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 4 [ label=<
Type Filter
WHERE record IS [T4]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 5 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 6 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q32> label="q32" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ label=< q25> label="q25" 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" ]; +} +4 + +unnamed-12&EXPLAIN SELECT "___hidden"."a" FROM T4 +) ( 08@1SCAN(<,>) | TFILTER T4 | MAP (_.___hidden.a AS a)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2.___hidden.a AS a)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS a)" ]; + 2 [ label=<
Type Filter
WHERE record IS [T4]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS a, LONG AS b AS ___hidden, LONG AS T4.COL1, LONG AS T4.COL2)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q19> label="q19" 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=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +1 + +unnamed-12#EXPLAIN SELECT * FROM T5__UNESCAPED +) v( 0 +8 @!SCAN(<,>) | TFILTER T5__UNESCAPED +digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 2 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +E + +unnamed-127EXPLAIN SELECT * FROM T5__UNESCAPED WHERE T5__COL1 = 10 +0 (0/8@QSCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 AS LONG)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.T5__COL1 EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 2 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q19> label="q19" 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=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +T + +unnamed-12FEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE T5__COL1 = 10 +Յ= (08@SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.__T5__COL2 AS __T5__COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T5__COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T5__COL1 EQUALS promote(@c10 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 3 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +I + +unnamed-12;EXPLAIN SELECT * FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 +0 (08@VSCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c8 AS LONG)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.__T5__COL2 LESS_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 2 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q19> label="q19" 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=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +X + +unnamed-12JEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 += ҋ(0ψ8@SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.__T5__COL2 AS __T5__COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T5__COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.__T5__COL2 LESS_THAN promote(@c10 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 3 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +7 + +unnamed-12)EXPLAIN SELECT * FROM "___T6.__UNESCAPED" +b ˅(+08*@ISCAN(T6$ENUM2 <,>)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +P + +unnamed-12BEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 +n (,0986@FISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.T6$__COL1__ EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +e + +unnamed-12WEXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 +˛ + ()0x89@ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q60.ID AS ID, q60.__T6.COL2__VALUE AS __T6.COL2__VALUE)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T6$__COL1__ EQUALS promote(@c10 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 4 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ label=< q2> label="q2" 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=< q60> label="q60" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + +U + +unnamed-12GEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 +èr (-0!86@1ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
comparisons: [[LESS_THAN promote(@c8 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +j + +unnamed-12\EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + (/0M8B@COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q92.ID AS ID, q92.__T6.COL2__VALUE AS __T6.COL2__VALUE)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [[LESS_THAN promote(@c10 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q92> label="q92" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +V + +unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + +n Ш(,0B86@gISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +f + +unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + Ӊ()0:89@ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q60.ID AS ID, q60.T6$__ENUM_2 AS T6$__ENUM_2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 4 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ label=< q2> label="q2" 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=< q60> label="q60" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + +V + +unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + + p (-0-86@NISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)]) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
comparisons: [EQUALS promote(@c8 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +f + +unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_1" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' +  (+0X8;@ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2.ID AS ID, q2.T6$__ENUM_1 AS T6$__ENUM_1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1)" ]; + 2 [ label=<
Index Scan
comparisons: [EQUALS promote(@c10 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" 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/valid-identifiers.metrics.yaml b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml new file mode 100644 index 0000000000..0d327ab246 --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -0,0 +1,834 @@ +all-tests: +- query: EXPLAIN select t.id from "foo.table$nested.repeated" as t where exists + (select * from t."level0.field1" as x where x."level1$field.3" = 20) + explain: 'COVERING(foo.table$nested.repeated.idx.field.1.3 [EQUALS promote(@c25 + AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)' + task_count: 667 + task_total_time_ms: 206 + transform_count: 203 + transform_time_ms: 158 + transform_yield_count: 53 + insert_time_ms: 6 + insert_new_count: 69 + insert_reused_count: 5 +- query: EXPLAIN select t.id from "foo.table$nested.repeated" as t where exists + (select * from t."level0.field1" as x where x."level1$field.2"."level2$field.1" + = 91) + explain: 'COVERING(foo.table$nested.repeated.idx.field.1.2.1 [EQUALS promote(@c27 + AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)' + task_count: 667 + task_total_time_ms: 34 + transform_count: 203 + transform_time_ms: 19 + transform_yield_count: 53 + insert_time_ms: 2 + insert_new_count: 69 + insert_reused_count: 5 +- query: EXPLAIN select t.id from "foo.table$nested.repeated" as t where exists + (select * from t."level0.field1" as b, b."level1$field.1" where "level2$array.field.1" + = 10) + explain: 'COVERING(foo.table$nested.repeated.idx.field.1.1.1 [EQUALS promote(@c27 + AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)' + task_count: 914 + task_total_time_ms: 64 + transform_count: 322 + transform_time_ms: 38 + transform_yield_count: 69 + insert_time_ms: 4 + insert_new_count: 96 + insert_reused_count: 9 +- query: EXPLAIN select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" + where id = 1 + explain: 'COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: + [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) + | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)' + task_count: 474 + task_total_time_ms: 46 + transform_count: 114 + transform_time_ms: 27 + transform_yield_count: 33 + insert_time_ms: 3 + insert_new_count: 52 + insert_reused_count: 6 +- query: EXPLAIN select "foo.table$repeated".id from "foo.table$repeated" where + 'foo' IN "foo.table$repeated"."field.1$array" + explain: SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER @c8 IN _.field.1$array + | MAP (_.ID AS ID) + task_count: 404 + task_total_time_ms: 15 + transform_count: 121 + transform_time_ms: 8 + transform_yield_count: 27 + insert_time_ms: 1 + insert_new_count: 38 + insert_reused_count: 4 +- query: EXPLAIN select "foo.table$repeated".id from "foo.table$repeated" where + exists (select 1 from "foo.table$repeated"."field.1$array" r where r = 'foo') + explain: 'COVERING(foo.table$repeated.2 [EQUALS @c20] -> [ID: KEY[2]]) | MAP (_.ID + AS ID)' + task_count: 476 + task_total_time_ms: 14 + transform_count: 139 + transform_time_ms: 6 + transform_yield_count: 36 + insert_time_ms: 0 + insert_new_count: 46 + insert_reused_count: 2 +- query: EXPLAIN select t."field.1$array" from "foo.table$repeated" as t where 3 + IN t."field.0$array" + explain: SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER promote(@c10 AS LONG) + IN _.field.0$array | MAP (_.field.1$array AS field.1$array) + task_count: 404 + task_total_time_ms: 10 + transform_count: 121 + transform_time_ms: 5 + transform_yield_count: 27 + insert_time_ms: 0 + insert_new_count: 38 + insert_reused_count: 4 +- query: EXPLAIN select t."field.1$array" from "foo.table$repeated" as t where exists + (select 1 from t."field.0$array" r where r = 3) + explain: ISCAN(foo.table$repeated.1 [EQUALS promote(@c22 AS LONG)]) | MAP (_.field.1$array + AS field.1$array) + task_count: 448 + task_total_time_ms: 17 + transform_count: 135 + transform_time_ms: 10 + transform_yield_count: 34 + insert_time_ms: 0 + insert_new_count: 43 + insert_reused_count: 2 +- query: EXPLAIN select "foo.tableA".* from "foo.tableA"; + explain: 'COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: + KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, + _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)' + task_count: 441 + task_total_time_ms: 21 + transform_count: 106 + transform_time_ms: 11 + transform_yield_count: 49 + insert_time_ms: 0 + insert_new_count: 45 + insert_reused_count: 5 +- query: EXPLAIN select * from "foo.tableA"; + explain: 'COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: + KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, + _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)' + task_count: 441 + task_total_time_ms: 20 + transform_count: 106 + transform_time_ms: 10 + transform_yield_count: 49 + insert_time_ms: 1 + insert_new_count: 45 + insert_reused_count: 5 +- query: EXPLAIN select "_$$$".* from "foo.tableA" as "_$$$"; + explain: 'COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: + KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, + _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)' + task_count: 441 + task_total_time_ms: 16 + transform_count: 106 + transform_time_ms: 8 + transform_yield_count: 49 + insert_time_ms: 0 + insert_new_count: 45 + insert_reused_count: 5 +- query: EXPLAIN select "foo.tableA.A2", sum("foo.tableA.A1") from "foo.tableA" + group by "foo.tableA.A2"; + explain: 'AISCAN(foo.tableA.idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) + | MAP (_._0 AS foo.tableA.A2, _._1 AS _1)' + task_count: 408 + task_total_time_ms: 26 + transform_count: 117 + transform_time_ms: 19 + transform_yield_count: 44 + insert_time_ms: 0 + insert_new_count: 36 + insert_reused_count: 3 +- query: EXPLAIN select * from "foo.tableA", "foo.tableB" where "foo.tableA"."foo.tableA.A1" + = "foo.tableB"."foo.tableB.B1"; + explain: SCAN(<,>) | TFILTER foo__2tableB | FLATMAP q0 -> { ISCAN(foo.tableA.idx + [EQUALS q0.foo.tableB.B1]) AS q1 RETURN (q1.foo.tableA.A1 AS foo.tableA.A1, + q1.foo.tableA.A2 AS foo.tableA.A2, q1.foo.tableA.A3 AS foo.tableA.A3, q0.foo.tableB.B1 + AS foo.tableB.B1, q0.foo.tableB.B2 AS foo.tableB.B2, q0.foo.tableB.B3 AS foo.tableB.B3) + } + task_count: 798 + task_total_time_ms: 34 + transform_count: 214 + transform_time_ms: 17 + transform_yield_count: 69 + insert_time_ms: 1 + insert_new_count: 107 + insert_reused_count: 8 +- query: EXPLAIN select "foo$tableC$C2", sum("foo$tableC$C1") from "foo$tableC" + group by "foo$tableC$C2"; + explain: 'AISCAN(foo$tableC$idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) + | MAP (_._0 AS foo$tableC$C2, _._1 AS _1)' + task_count: 336 + task_total_time_ms: 10 + transform_count: 103 + transform_time_ms: 6 + transform_yield_count: 34 + insert_time_ms: 0 + insert_new_count: 24 + insert_reused_count: 1 +- query: EXPLAIN select "__foo__tableD".* from "__foo__tableD"; + explain: 'COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: + KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, + _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)' + task_count: 332 + task_total_time_ms: 7 + transform_count: 80 + transform_time_ms: 3 + transform_yield_count: 35 + insert_time_ms: 0 + insert_new_count: 32 + insert_reused_count: 3 +- query: EXPLAIN select "__foo__tableD".* from "__foo__tableD" where "__foo__tableD$D3" + >= 2; + explain: 'COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: + KEY[1], __foo__0tableD__1D3: KEY[2]]) | FILTER _.__foo__tableD$D3 GREATER_THAN_OR_EQUALS + promote(@c11 AS LONG) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 + AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)' + task_count: 458 + task_total_time_ms: 9 + transform_count: 99 + transform_time_ms: 4 + transform_yield_count: 39 + insert_time_ms: 0 + insert_new_count: 48 + insert_reused_count: 3 +- query: EXPLAIN select "__foo__tableD"."__foo__tableD$D1" from "__foo__tableD"; + explain: 'COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: + KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1)' + task_count: 262 + task_total_time_ms: 5 + transform_count: 64 + transform_time_ms: 3 + transform_yield_count: 29 + insert_time_ms: 0 + insert_new_count: 28 + insert_reused_count: 4 +- query: EXPLAIN select "__foo__tableD$D1" from "__foo__tableD"; + explain: 'COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: + KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1)' + task_count: 262 + task_total_time_ms: 7 + transform_count: 64 + transform_time_ms: 4 + transform_yield_count: 29 + insert_time_ms: 0 + insert_new_count: 28 + insert_reused_count: 4 +- query: EXPLAIN select "__foo__tableD".* from "__foo__tableD" where "__foo__tableD$D3" + >= 2 order by "__foo__tableD$D1"; + explain: 'COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: + KEY[1], __foo__0tableD__1D3: KEY[2]]) | FILTER _.__foo__tableD$D3 GREATER_THAN_OR_EQUALS + promote(@c11 AS LONG) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 + AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)' + task_count: 355 + task_total_time_ms: 7 + transform_count: 82 + transform_time_ms: 3 + transform_yield_count: 33 + insert_time_ms: 0 + insert_new_count: 35 + insert_reused_count: 2 +- query: EXPLAIN select "βήτα__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" + as "f__e" where "alpha__f"("f__e"."foo.tableE.E3") = 5 + explain: SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c22 + AS LONG) | MAP (_.foo.tableE.E3.S2 AS ___h.1) + task_count: 230 + task_total_time_ms: 5 + transform_count: 61 + transform_time_ms: 2 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 21 + insert_reused_count: 2 +- query: EXPLAIN select "alpha__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" + as "f__e" where "βήτα__f"("f__e"."foo.tableE.E3") >= 50 + explain: SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S2 GREATER_THAN_OR_EQUALS + promote(@c23 AS LONG) | MAP (_.foo.tableE.E3.S1 AS ___h.1) + task_count: 230 + task_total_time_ms: 3 + transform_count: 61 + transform_time_ms: 2 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 21 + insert_reused_count: 2 +- query: EXPLAIN select * from "__$func3"(10, 1, 1); + explain: SCAN(<,>) | TFILTER my__1adjacency__1list | FILTER _.me LESS_THAN promote(@c6 + AS LONG) AND _.my__parent EQUALS promote(@c8 AS LONG) | FLATMAP q0 -> { SCAN(<,>) + | TFILTER my__1adjacency__1list | FILTER _.me EQUALS promote(@c8 AS LONG) + AS q1 RETURN (q0.me AS _0, q0.my__parent AS _1, q1.me AS _2, q1.my__parent + AS _3) } + task_count: 1405 + task_total_time_ms: 43 + transform_count: 301 + transform_time_ms: 17 + transform_yield_count: 110 + insert_time_ms: 5 + insert_new_count: 289 + insert_reused_count: 13 +- query: EXPLAIN select * from "$yay"(5); + explain: SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 + AS LONG) | MAP (_.foo.tableE.E1 AS _$x.id) + task_count: 382 + task_total_time_ms: 9 + transform_count: 90 + transform_time_ms: 3 + transform_yield_count: 28 + insert_time_ms: 0 + insert_new_count: 51 + insert_reused_count: 2 +- query: EXPLAIN select * from "__2yay"(6); + explain: SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 + AS LONG) | MAP (_.foo.tableE.E1 AS _$y.id) + task_count: 382 + task_total_time_ms: 10 + transform_count: 90 + transform_time_ms: 6 + transform_yield_count: 28 + insert_time_ms: 0 + insert_new_count: 51 + insert_reused_count: 2 +- query: EXPLAIN select * from "नमस्त"(4); + explain: SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 + AS LONG) | MAP (_.foo.tableE.E1 AS _$z.id) + task_count: 382 + task_total_time_ms: 8 + transform_count: 90 + transform_time_ms: 3 + transform_yield_count: 28 + insert_time_ms: 0 + insert_new_count: 51 + insert_reused_count: 2 +- query: EXPLAIN select * from "$yay__view"; + explain: SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 4 + | MAP (_.foo.tableE.E1 AS _$x.id) | MAP (_._$x.id AS _$x.id) + task_count: 322 + task_total_time_ms: 5 + transform_count: 82 + transform_time_ms: 2 + transform_yield_count: 19 + insert_time_ms: 0 + insert_new_count: 28 + insert_reused_count: 2 +- query: EXPLAIN select * from "__2yay__view"; + explain: SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 5 + | MAP (_.foo.tableE.E1 AS _$y.id) | MAP (_._$y.id AS _$y.id) + task_count: 322 + task_total_time_ms: 4 + transform_count: 82 + transform_time_ms: 2 + transform_yield_count: 19 + insert_time_ms: 0 + insert_new_count: 28 + insert_reused_count: 2 +- query: EXPLAIN select * from "வணக்கம்"; + explain: SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 6 + | MAP (_.foo.tableE.E1 AS _$z.id) | MAP (_._$z.id AS _$z.id) + task_count: 322 + task_total_time_ms: 5 + transform_count: 82 + transform_time_ms: 2 + transform_yield_count: 19 + insert_time_ms: 0 + insert_new_count: 28 + insert_reused_count: 2 +- query: EXPLAIN select * from values (1, 2, 3), (4, 5, 6) as "_$$$$"("_$$$", "_$$", + "_$") + explain: EXPLODE array((@c6 AS _$$$, @c8 AS _$$, @c10 AS _$), (@c14 AS _$$$, @c16 + AS _$$, @c18 AS _$)) | MAP (_._$$$ AS _$$$, _._$$ AS _$$, _._$ AS _$) + task_count: 116 + task_total_time_ms: 1 + transform_count: 27 + transform_time_ms: 0 + transform_yield_count: 6 + insert_time_ms: 0 + insert_new_count: 9 + insert_reused_count: 0 +- query: EXPLAIN select struct "x$$" ("foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3") + from "foo.tableA" + explain: 'COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: + KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP ((_.foo.tableA.A1 AS foo.tableA.A1, + _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) AS _0)' + task_count: 523 + task_total_time_ms: 11 + transform_count: 138 + transform_time_ms: 5 + transform_yield_count: 50 + insert_time_ms: 0 + insert_new_count: 54 + insert_reused_count: 3 +- query: EXPLAIN select struct "x$$" ("foo.tableA".*) from "foo.tableA" + explain: ISCAN(foo.tableA.idx3 <,>) | MAP (_ AS _0) + task_count: 301 + task_total_time_ms: 7 + transform_count: 75 + transform_time_ms: 4 + transform_yield_count: 37 + insert_time_ms: 0 + insert_new_count: 35 + insert_reused_count: 6 +- query: EXPLAIN select struct "x$$" ("foo.tableA.A2" + "foo.tableA.A1" as "__$$__") + from "foo.tableA" + explain: ISCAN(foo.tableA.idx3 <,>) | MAP ((_.foo.tableA.A2 + _.foo.tableA.A1 + AS __$$__) AS _0) + task_count: 301 + task_total_time_ms: 7 + transform_count: 73 + transform_time_ms: 4 + transform_yield_count: 37 + insert_time_ms: 0 + insert_new_count: 35 + insert_reused_count: 6 +- query: EXPLAIN select * from "foo.enum.type"; + explain: ISCAN(foo.enum.type$enum__1 <,>) + task_count: 296 + task_total_time_ms: 5 + transform_count: 72 + transform_time_ms: 2 + transform_yield_count: 29 + insert_time_ms: 0 + insert_new_count: 29 + insert_reused_count: 3 +- query: EXPLAIN select * from "foo.enum.type" where "enum_type.enum__1" = 'B$C'; + explain: ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) + task_count: 367 + task_total_time_ms: 9 + transform_count: 83 + transform_time_ms: 5 + transform_yield_count: 31 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 3 +- query: EXPLAIN select * from "foo.enum.type" where "enum_type.enum__1" = 'C.D'; + explain: ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) + task_count: 367 + task_total_time_ms: 9 + transform_count: 83 + transform_time_ms: 5 + transform_yield_count: 31 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 3 +- query: EXPLAIN select * from "foo.enum.type" where "enum_type.enum__1" = 'A'; + explain: ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) + task_count: 367 + task_total_time_ms: 9 + transform_count: 83 + transform_time_ms: 5 + transform_yield_count: 31 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 3 +- query: EXPLAIN select * from "foo.enum.type" where "enum_type.enum__2" = 'B$C'; + explain: ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS + promote(@c8 AS ENUM) + task_count: 351 + task_total_time_ms: 6 + transform_count: 81 + transform_time_ms: 2 + transform_yield_count: 30 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 2 +- query: EXPLAIN select * from "foo.enum.type" where "enum_type.enum__2" = 'C.D'; + explain: ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS + promote(@c8 AS ENUM) + task_count: 351 + task_total_time_ms: 6 + transform_count: 81 + transform_time_ms: 2 + transform_yield_count: 30 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 2 +- query: EXPLAIN select * from "foo.enum.type" where "enum_type.enum__2" = 'A'; + explain: ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS + promote(@c8 AS ENUM) + task_count: 351 + task_total_time_ms: 6 + transform_count: 81 + transform_time_ms: 2 + transform_yield_count: 30 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 2 +update-delete-statements: +- query: EXPLAIN UPDATE "foo.tableA" SET "foo.tableA.A2" = 100 WHERE "foo.tableA.A1" + = 1 + explain: 'COVERING(foo.tableA.idx [EQUALS promote(@c10 AS LONG)] -> [foo__2tableA__2A1: + KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | DISTINCT + BY PK | FETCH | UPDATE foo__2tableA' + task_count: 849 + task_total_time_ms: 15 + transform_count: 159 + transform_time_ms: 6 + transform_yield_count: 63 + insert_time_ms: 1 + insert_new_count: 96 + insert_reused_count: 3 +- query: EXPLAIN UPDATE "foo.tableA" SET "foo.tableA.A2" = 100 WHERE "foo.tableA.A1" + > 1 RETURNING "new"."foo.tableA.A1" + explain: 'COVERING(foo.tableA.idx [[GREATER_THAN promote(@c10 AS LONG)]] -> [foo__2tableA__2A1: + KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | DISTINCT + BY PK | FETCH | UPDATE foo__2tableA | MAP (_.new.foo.tableA.A1 AS foo.tableA.A1)' + task_count: 897 + task_total_time_ms: 21 + transform_count: 170 + transform_time_ms: 7 + transform_yield_count: 60 + insert_time_ms: 0 + insert_new_count: 100 + insert_reused_count: 3 +- query: EXPLAIN DELETE FROM "foo.tableA" WHERE "foo.tableA.A1" = 1 RETURNING "foo.tableA.A1" + + "foo.tableA.A2" + "foo.tableA.A3" + explain: ISCAN(foo.tableA.idx [EQUALS promote(@c7 AS LONG)]) | DELETE | MAP (_.foo.tableA.A1 + + _.foo.tableA.A2 + _.foo.tableA.A3 AS _0) + task_count: 832 + task_total_time_ms: 13 + transform_count: 164 + transform_time_ms: 5 + transform_yield_count: 65 + insert_time_ms: 0 + insert_new_count: 95 + insert_reused_count: 3 +- query: EXPLAIN DELETE FROM "foo.tableA" WHERE "foo.tableA.A2" = 100 + explain: ISCAN(foo.tableA.idx3 [EQUALS promote(@c7 AS LONG)]) | DELETE + task_count: 636 + task_total_time_ms: 11 + transform_count: 134 + transform_time_ms: 4 + transform_yield_count: 54 + insert_time_ms: 0 + insert_new_count: 76 + insert_reused_count: 3 +unnamed-12: +- query: EXPLAIN SELECT * FROM T1 + explain: SCAN(<,>) | TFILTER T1 | MAP (_.ID AS ID, _.T1.COL1 AS T1.COL1, _.T1.COL2 + AS T1.COL2) + task_count: 187 + task_total_time_ms: 6 + transform_count: 46 + transform_time_ms: 2 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 16 + insert_reused_count: 1 +- query: EXPLAIN SELECT * FROM T1 WHERE "T1.COL2" > 3 + explain: SCAN(<,>) | TFILTER T1 | FILTER _.T1.COL2 GREATER_THAN promote(@c8 AS + LONG) | MAP (_.ID AS ID, _.T1.COL1 AS T1.COL1, _.T1.COL2 AS T1.COL2) + task_count: 218 + task_total_time_ms: 9 + transform_count: 52 + transform_time_ms: 3 + transform_yield_count: 16 + insert_time_ms: 0 + insert_new_count: 20 + insert_reused_count: 1 +- query: EXPLAIN SELECT "T1.COL2" FROM T1 WHERE "T1.COL2" > 3 + explain: SCAN(<,>) | TFILTER T1 | FILTER _.T1.COL2 GREATER_THAN promote(@c8 AS + LONG) | MAP (_.T1.COL2 AS T1.COL2) + task_count: 230 + task_total_time_ms: 5 + transform_count: 61 + transform_time_ms: 2 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 21 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM T2 + explain: ISCAN(T2$T2.COL1 <,>) + task_count: 296 + task_total_time_ms: 12 + transform_count: 72 + transform_time_ms: 4 + transform_yield_count: 29 + insert_time_ms: 0 + insert_new_count: 29 + insert_reused_count: 3 +- query: EXPLAIN SELECT * FROM T2 WHERE "T2$COL2" > 8 + explain: ISCAN(T2$T2.COL1 <,>) | FILTER _.T2$COL2 GREATER_THAN promote(@c8 AS + LONG) + task_count: 351 + task_total_time_ms: 17 + transform_count: 81 + transform_time_ms: 5 + transform_yield_count: 30 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 2 +- query: EXPLAIN SELECT "T2$COL2" FROM T2 WHERE "T2$COL2" > 8 + explain: ISCAN(T2$T2.COL1 <,>) | FILTER _.T2$COL2 GREATER_THAN promote(@c8 AS + LONG) | MAP (_.T2$COL2 AS T2$COL2) + task_count: 375 + task_total_time_ms: 21 + transform_count: 98 + transform_time_ms: 12 + transform_yield_count: 28 + insert_time_ms: 0 + insert_new_count: 39 + insert_reused_count: 4 +- query: EXPLAIN SELECT * FROM "__T3" + explain: SCAN(<,>) | TFILTER __T3 | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, + _.__T3$COL2 AS __T3$COL2, _.__T3$COL3 AS __T3$COL3) + task_count: 187 + task_total_time_ms: 5 + transform_count: 46 + transform_time_ms: 1 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 16 + insert_reused_count: 1 +- query: EXPLAIN SELECT * FROM "__T3" WHERE "__T3$COL2" > 13 + explain: SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 + AS LONG) | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2, + _.__T3$COL3 AS __T3$COL3) + task_count: 218 + task_total_time_ms: 3 + transform_count: 52 + transform_time_ms: 1 + transform_yield_count: 16 + insert_time_ms: 0 + insert_new_count: 20 + insert_reused_count: 1 +- query: EXPLAIN SELECT "__T3$COL2" FROM "__T3" WHERE "__T3$COL2" > 13 + explain: SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 + AS LONG) | MAP (_.__T3$COL2 AS __T3$COL2) + task_count: 230 + task_total_time_ms: 8 + transform_count: 61 + transform_time_ms: 3 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 21 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM "__func__T3$col2"(13) + explain: SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 EQUALS promote(@c6 AS LONG) + | MAP (_.__T3$COL1 AS c.1, _.__T3$COL3 AS c.2) + task_count: 382 + task_total_time_ms: 13 + transform_count: 90 + transform_time_ms: 5 + transform_yield_count: 28 + insert_time_ms: 0 + insert_new_count: 51 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM T4 + explain: SCAN(<,>) | TFILTER T4 | MAP (_.ID AS ID, _.___hidden AS ___hidden, _.T4.COL1 + AS T4.COL1, _.T4.COL2 AS T4.COL2) + task_count: 187 + task_total_time_ms: 5 + transform_count: 46 + transform_time_ms: 2 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 16 + insert_reused_count: 1 +- query: EXPLAIN SELECT * FROM T4 WHERE "T4.COL2" > 18 + explain: SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS + LONG) | MAP (_.ID AS ID, _.___hidden AS ___hidden, _.T4.COL1 AS T4.COL1, _.T4.COL2 + AS T4.COL2) + task_count: 218 + task_total_time_ms: 4 + transform_count: 52 + transform_time_ms: 1 + transform_yield_count: 16 + insert_time_ms: 0 + insert_new_count: 20 + insert_reused_count: 1 +- query: EXPLAIN SELECT "T4.COL2" FROM T4 WHERE "T4.COL2" > 18 + explain: SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS + LONG) | MAP (_.T4.COL2 AS T4.COL2) + task_count: 230 + task_total_time_ms: 7 + transform_count: 61 + transform_time_ms: 2 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 21 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM "T4$view" + explain: SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL1 GREATER_THAN 0 AND _.T4.COL2 + GREATER_THAN 0 | MAP (_.T4.COL1 AS c__1, _.T4.COL2 AS c__2) | MAP (_.c__1 + AS c__1, _.c__2 AS c__2) + task_count: 322 + task_total_time_ms: 11 + transform_count: 82 + transform_time_ms: 3 + transform_yield_count: 19 + insert_time_ms: 0 + insert_new_count: 28 + insert_reused_count: 2 +- query: EXPLAIN SELECT "___hidden"."a" FROM T4 + explain: SCAN(<,>) | TFILTER T4 | MAP (_.___hidden.a AS a) + task_count: 159 + task_total_time_ms: 9 + transform_count: 41 + transform_time_ms: 4 + transform_yield_count: 13 + insert_time_ms: 0 + insert_new_count: 15 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM T5__UNESCAPED + explain: SCAN(<,>) | TFILTER T5__UNESCAPED + task_count: 159 + task_total_time_ms: 7 + transform_count: 41 + transform_time_ms: 1 + transform_yield_count: 13 + insert_time_ms: 0 + insert_new_count: 13 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM T5__UNESCAPED WHERE T5__COL1 = 10 + explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 + AS LONG) + task_count: 187 + task_total_time_ms: 10 + transform_count: 48 + transform_time_ms: 4 + transform_yield_count: 14 + insert_time_ms: 0 + insert_new_count: 17 + insert_reused_count: 2 +- query: EXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE T5__COL1 = 10 + explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 + AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2) + task_count: 230 + task_total_time_ms: 6 + transform_count: 61 + transform_time_ms: 2 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 21 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 + explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c8 + AS LONG) + task_count: 187 + task_total_time_ms: 10 + transform_count: 48 + transform_time_ms: 3 + transform_yield_count: 14 + insert_time_ms: 0 + insert_new_count: 17 + insert_reused_count: 2 +- query: EXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2" < + 10 + explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c10 + AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2) + task_count: 230 + task_total_time_ms: 11 + transform_count: 61 + transform_time_ms: 4 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 21 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" + explain: ISCAN(T6$ENUM2 <,>) + task_count: 405 + task_total_time_ms: 15 + transform_count: 98 + transform_time_ms: 6 + transform_yield_count: 43 + insert_time_ms: 0 + insert_new_count: 42 + insert_reused_count: 5 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 + explain: ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) + task_count: 484 + task_total_time_ms: 16 + transform_count: 110 + transform_time_ms: 6 + transform_yield_count: 44 + insert_time_ms: 0 + insert_new_count: 54 + insert_reused_count: 3 +- query: EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" + = 10 + explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) + | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) + task_count: 520 + task_total_time_ms: 21 + transform_count: 136 + transform_time_ms: 8 + transform_yield_count: 41 + insert_time_ms: 1 + insert_new_count: 57 + insert_reused_count: 6 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + explain: ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]]) + task_count: 500 + task_total_time_ms: 15 + transform_count: 114 + transform_time_ms: 7 + transform_yield_count: 45 + insert_time_ms: 0 + insert_new_count: 54 + insert_reused_count: 4 +- query: EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" + < 10 + explain: 'COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], + __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)' + task_count: 608 + task_total_time_ms: 17 + transform_count: 147 + transform_time_ms: 7 + transform_yield_count: 47 + insert_time_ms: 1 + insert_new_count: 66 + insert_reused_count: 6 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + explain: ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM) + task_count: 484 + task_total_time_ms: 22 + transform_count: 110 + transform_time_ms: 9 + transform_yield_count: 44 + insert_time_ms: 1 + insert_new_count: 54 + insert_reused_count: 3 +- query: EXPLAIN SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" + = '__D.__' + explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2) + task_count: 520 + task_total_time_ms: 14 + transform_count: 136 + transform_time_ms: 5 + transform_yield_count: 41 + insert_time_ms: 0 + insert_new_count: 57 + insert_reused_count: 6 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + explain: ISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)]) + task_count: 500 + task_total_time_ms: 28 + transform_count: 112 + transform_time_ms: 11 + transform_yield_count: 45 + insert_time_ms: 0 + insert_new_count: 54 + insert_reused_count: 4 +- query: EXPLAIN SELECT ID, "T6$__ENUM_1" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" + = '__D.__' + explain: ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) + | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1) + task_count: 549 + task_total_time_ms: 23 + transform_count: 139 + transform_time_ms: 10 + transform_yield_count: 43 + insert_time_ms: 1 + insert_new_count: 59 + insert_reused_count: 5 diff --git a/yaml-tests/src/test/resources/valid-identifiers.yamsql b/yaml-tests/src/test/resources/valid-identifiers.yamsql new file mode 100644 index 0000000000..7b6a82ba5f --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -0,0 +1,850 @@ +# +# valid-identifiers.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 type as struct "nested.type$level2" ("level2$field.1" bigint, "level2$field.2" bigint) + create type as struct "nested.type$level1" ("level1$field.1" "nested.type$level2", "level1$field.2" "nested.type$level2") + create table "foo.table$nested" (id bigint, "level0.field1" "nested.type$level1", primary key(id)) + create index "foo.table$nested.idx" as select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" order by "level0.field1"."level1$field.1"."level2$field.1" + + create type as struct "nested.repeated.type$level2" ("level2$array.field.1" bigint, "level2$field.2" bigint) + create type as struct "nested.repeated.type$level1" ("level1$field.1" "nested.repeated.type$level2" array, "level1$field.2" "nested.type$level2", "level1$field.3" bigint) + create table "foo.table$nested.repeated" (id bigint, "level0.field1" "nested.repeated.type$level1" array, primary key(id)) + create index "foo.table$nested.repeated.idx.field.1.3" as select "nested$level1"."level1$field.3" from "foo.table$nested.repeated" as "level0$t", "level0$t"."level0.field1" AS "nested$level1" + create index "foo.table$nested.repeated.idx.field.1.2.1" as select "nested$level1"."level1$field.2"."level2$field.1" from "foo.table$nested.repeated" as "level0$t", "level0$t"."level0.field1" AS "nested$level1" + create index "foo.table$nested.repeated.idx.field.1.1.1" as select "nested$level2"."level2$array.field.1" from "foo.table$nested.repeated" as "level0$t", "level0$t"."level0.field1" AS "nested$level1", "nested$level1"."level1$field.1" AS "nested$level2" + + create table "foo.table$repeated" (id bigint, "field.0$array" bigint array, "field.1$array" string array, primary key (id)) + create index "foo.table$repeated.1" as select t."field.0$array" from "foo.table$repeated" AS t + create index "foo.table$repeated.2" as select t."field.1$array" from "foo.table$repeated" AS t + + create type as struct "foo.struct"(S1 bigint, S2 bigint) + create table "foo.tableA"("foo.tableA.A1" bigint, "foo.tableA.A2" bigint, "foo.tableA.A3" bigint, primary key("foo.tableA.A1")) + create index "foo.tableA.idx" as select "foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3" FROM "foo.tableA" order by "foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3" + create index "foo.tableA.idx2" as select sum("foo.tableA.A1") FROM "foo.tableA" group by "foo.tableA.A2" + create index "foo.tableA.idx3" as select "foo.tableA.A2" FROM "foo.tableA" order by "foo.tableA.A2" + + create table "foo.tableB"("foo.tableB.B1" bigint, "foo.tableB.B2" bigint, "foo.tableB.B3" "foo.struct", primary key("foo.tableB.B1")) + + create table "foo$tableC"("foo$tableC$C1" bigint, "foo$tableC$C2" bigint, "foo$tableC$C3" bigint, primary key("foo$tableC$C1")) + create index "foo$tableC$idx" as select "foo$tableC$C1", "foo$tableC$C2", "foo$tableC$C3" FROM "foo$tableC" order by "foo$tableC$C1", "foo$tableC$C2", "foo$tableC$C3" + create index "foo$tableC$idx2" as select sum("foo$tableC$C1") FROM "foo$tableC" group by "foo$tableC$C2" + + create table "__foo__tableD"("__foo__tableD$D1" bigint, "__foo__tableD$D2" bigint, "__foo__tableD$D3" bigint, primary key("__foo__tableD$D1")) + create index "__foo__tableD$idx" as select "__foo__tableD$D1", "__foo__tableD$D2", "__foo__tableD$D3" FROM "__foo__tableD" order by "__foo__tableD$D1", "__foo__tableD$D2", "__foo__tableD$D3" + create index "__foo__tableD$idx2" as select sum("__foo__tableD$D1") FROM "__foo__tableD" group by "__foo__tableD$D2" + + create table "foo.tableE"("foo.tableE.E1" bigint, "foo.tableE.E2" bigint array, "foo.tableE.E3" "foo.struct", primary key("foo.tableE.E1")) + + create function "$yay" (in "_1$blah" bigint) + as select "foo.tableE"."foo.tableE.E1" as "_$x.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = "_1$blah" + create function "__2yay" (in "_2$blah" bigint) + as select "foo.tableE"."foo.tableE.E1" as "_$y.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = "_2$blah" + create function "नमस्त" (in "x.y" bigint) + as select "foo.tableE"."foo.tableE.E1" as "_$z.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = "x.y" + + create function "alpha__f" (in "__in__foo.struct" TYPE "foo.struct") RETURNS bigint AS "__in__foo.struct".S1 + create function "βήτα__f" (in "__in__foo.struct" TYPE "foo.struct") RETURNS bigint AS "__in__foo.struct".S2 + + create view "$yay__view" + as select "foo.tableE"."foo.tableE.E1" as "_$x.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = 4 + create view "__2yay__view" + as select "foo.tableE"."foo.tableE.E1" as "_$y.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = 5 + create view "வணக்கம்" + as select "foo.tableE"."foo.tableE.E1" as "_$z.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = 6 + + create table "my$adjacency$list"("me" bigint, "my__parent" bigint, primary key("me")) + create function "__$func1" ( in "__$func1$A" bigint, in "__$func1$B" bigint ) + as select "me" as "__A", "my__parent" as "__B" from "my$adjacency$list" where "me" < "__$func1$A" and "my$adjacency$list"."my__parent" = "__$func1$B" + create function "__$func2" ( "__$func2$A" bigint ) + as select "me" as "__A", "my__parent" as "__B" from "my$adjacency$list" where "me" = "__$func2$A" + create function "__$func3" ( in "__$func3$A" bigint, in "__$func3$B" bigint, in "__$func3$C" bigint) as select "__..f1"."__A", "__..f1"."__B", "__..f2"."__A", "__..f2"."__B" from "__$func1"("__$func3$A", "__$func3$B") "__..f1", "__$func2"("__$func3$C") "__..f2" + + create type as enum "foo.enum"('A', 'B$C', 'C.D', 'E__F', '__G$H') + create table "foo.enum.type"("enum_type.id" bigint, "enum_type.enum__1" "foo.enum", "enum_type.enum__2" "foo.enum", primary key ("enum_type.id")) + + create index "foo.enum.type$enum__1" as select "enum_type.enum__1" from "foo.enum.type" + +--- +setup: + steps: + - query: insert into "foo.table$nested" + values (1, ((10, 20), (30, 40))), + (2, ((100, 200), (300, 400))), + (3, ((1000, 2000), (3000, 4000))) + - query: insert into "foo.table$nested.repeated" + values (1, [([(10, 20), (30, 40), (50, 60)], (90, 100), 10), ([(11, 21), (31, 41), (51, 61)], (91, 101), 11)]), + (2, [([(100, 200), (300, 400), (500, 600)], (900, 1000), 20), ([(101, 201), (301, 401), (501, 601)], (901, 1001), 21)]) + - query: insert into "foo.table$repeated" + values (1, [ 1, 2, 3], ['foo', 'bar', 'baz']), + (2, [10, 20, 30], ['foo', 'quox']) + - query: insert into "foo.tableA" + values (1, 10, 1), + (2, 10, 2) + - query: insert into "foo.tableB" + values (1, 20, (4, 40)), + (2, 20, (5, 50)), + (3, 20, (6, 60)) + - query: insert into "foo.tableE" + values (1, [1, 2, 3], (4, 40)), + (2, [2, 3, 4], (5, 50)), + (3, [3, 4, 5], (6, 60)) + - query: insert into "foo$tableC" + values (1, 20, 1), + (2, 20, 2), + (3, 20, 3), + (4, 20, 4) + - query: insert into "__foo__tableD" + values (1, 20, 1), + (2, 20, 2), + (3, 20, 3), + (4, 20, 4) + - query: insert into "my$adjacency$list" + values (1, -1), + (2, 1), + (3, 1), + (4, 1), + (5, 2), + (6, 2) + + # Note: the insert below flips the order of the enum__1 and enum__2 columns + - query: insert into "foo.enum.type"("enum_type.id", "enum_type.enum__2", "enum_type.enum__1") + values ( 1, 'B$C', 'A'), + ( 2, 'B$C', 'B$C'), + ( 3, 'B$C', 'C.D'), + ( 4, 'B$C', 'E__F'), + ( 5, 'B$C', '__G$H'), + ( 6, 'A', 'B$C'), + ( 7, 'C.D', 'B$C'), + ( 8, 'E__F', 'B$C'), + ( 9, '__G$H', 'B$C'), + (10, 'B$C', null), + (11, null, 'B$C') +--- +test_block: + name: insert-explicit-columns + preset: single_repetition_ordered + tests: + - + - query: insert into "foo.tableA" ("foo.tableA.A3", "foo.tableA.A1", "foo.tableA.A2") values (3, 3, 10) + - count: 1 +--- +test_block: + name: all-tests + tests: + - + # select from nested repeated table + - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.3" = 20) + - explain: "COVERING(foo.table$nested.repeated.idx.field.1.3 [EQUALS promote(@c25 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - result: [{2}] + - + # select a nested field field on a nested repeated struct + - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.2"."level2$field.1" = 91) + - explain: "COVERING(foo.table$nested.repeated.idx.field.1.2.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - result: [{1}] + - + # select from deeply nested repeated table + - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as b, b."level1$field.1" where "level2$array.field.1" = 10) + - explain: "COVERING(foo.table$nested.repeated.idx.field.1.1.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - result: [{1}] + - + # select from nested table + - query: select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" where id = 1 + - explain: "COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)" + - result: [{10}] + - + # select with predicates on repeated scalar fields + - query: select "foo.table$repeated".id from "foo.table$repeated" where 'foo' IN "foo.table$repeated"."field.1$array" + # Ideally, this would use the index "foo.table$repeated.2". It appears to be a matching problem unrelated to the identifier choices + # See: https://github.com/FoundationDB/fdb-record-layer/issues/3777 + - explain: "SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER @c8 IN _.field.1$array | MAP (_.ID AS ID)" + - result: [{1}, {2}] + - + # select with predicates on repeated scalar fields + - query: select "foo.table$repeated".id from "foo.table$repeated" where exists (select 1 from "foo.table$repeated"."field.1$array" r where r = 'foo') + - explain: "COVERING(foo.table$repeated.2 [EQUALS @c20] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - result: [{1}, {2}] + - + # select repeated scalar fields + - query: select t."field.1$array" from "foo.table$repeated" as t where 3 IN t."field.0$array" + # Ideally, this would use the index "foo.table$repeated.1". It appears to be a matching problem unrelated to the identifier choices + # See: https://github.com/FoundationDB/fdb-record-layer/issues/3777 + - explain: "SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER promote(@c10 AS LONG) IN _.field.0$array | MAP (_.field.1$array AS field.1$array)" + - result: [{["foo", "bar", "baz"]}] + - + # select repeated scalar fields + - query: select t."field.1$array" from "foo.table$repeated" as t where exists (select 1 from t."field.0$array" r where r = 3) + - explain: "ISCAN(foo.table$repeated.1 [EQUALS promote(@c22 AS LONG)]) | MAP (_.field.1$array AS field.1$array)" + - result: [{["foo", "bar", "baz"]}] + - + # qualified star + - query: select "foo.tableA".* from "foo.tableA"; + - explain: "COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)" + - result: [{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1}, {"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}, {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}] + - + # non-qualified star + - query: select * from "foo.tableA"; + - explain: "COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)" + - result: [{"foo.tableA.A1": 1 , 10, 1}, {"foo.tableA.A1": 2, 10, 2}, {"foo.tableA.A1": 3, 10, 3}] + - + # aliased star + - query: select "_$$$".* from "foo.tableA" as "_$$$"; + - explain: "COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)" + - result: [{"foo.tableA.A1": 1 , 10, 1}, {"foo.tableA.A1": 2, 10, 2}, {"foo.tableA.A1": 3, 10, 3}] + - + # with predicate + - query: select "foo.tableA".* from "foo.tableA" where "foo.tableA.A3" >= 2; + - result: [{"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}, {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}] + - + # qualified select element + - query: select "foo.tableA"."foo.tableA.A1" from "foo.tableA"; + - result: [{"foo.tableA.A1": 1}, {"foo.tableA.A1": 2}, {"foo.tableA.A1": 3}] + - + # with select element alias + - query: select "foo.tableA"."foo.tableA.A1" AS "__.__." from "foo.tableA"; + - result: [{"__.__.": 1}, {"__.__.": 2}, {"__.__.": 3}] + - + # with select element alias prefixed with . + - query: select "foo.tableA"."foo.tableA.A1" AS ".__." from "foo.tableA"; + - error: "42602" + - + # with select element alias prefixed with $ + - query: select "foo.tableA"."foo.tableA.A1" AS "$__." from "foo.tableA"; + - error: "42602" + - + # with select element alias with non-supported characters + - query: select "foo.tableA"."foo.tableA.A1" AS "उपनाम" from "foo.tableA"; + - error: "42602" + - + # multi-level CTEs + - query: with "A$_$__$$" as (with "A$__$" as (select "foo.tableA.A1" as "A$" from "foo.tableA") select * from "A$__$") select * from "A$_$__$$" + - result: [{"A$": 1}, {"A$": 2}, {"A$": 3}] + - + # CTEs with column aliases + - query: with "A$__$" ("__$$a", "__$$b", "__$$c") as (select "foo.tableA".* from "foo.tableA") select * from "A$__$" + - result: [{"__$$a": 1, "__$$b": 10, "__$$c": 1}, {"__$$a": 2, "__$$b": 10, "__$$c": 2}, {"__$$a": 3, "__$$b": 10, "__$$c": 3}] + - + # with recursive CTE + - query: with recursive "x$__$" as ( + select "me", "my__parent", 0 as "__level__" from "my$adjacency$list" where "me" = 5 + union all + select "x$"."me", "x$"."my__parent", "x$$".y as "__level__" from "my$adjacency$list" as "x$", (select "me", "my__parent", "__level__" + 1 as y from "x$__$") as "x$$" where "x$$"."my__parent" = "x$"."me") + traversal order pre_order + select "me", "my__parent", "__level__" from "x$__$" + - result: [{"me": 5, "my__parent": 2, "__level__": 0}, {"me": 2, "my__parent": 1, "__level__": 1}, {"me": 1, "my__parent": -1, "__level__": 2}] + - + # recursive CTE with column aliases + - query: with recursive "_$__$" ("__a", "__b", "__c") as ( + select "me", "my__parent", 0 as "__level__" from "my$adjacency$list" where "me" = 5 + union all + select "_$"."me", "_$"."my__parent", "_$$".y as "__level__" from "my$adjacency$list" as "_$", (select "me", "my__parent", "__level__" + 1 as y from "_$__$") as "_$$" where "_$$"."my__parent" = "_$"."me") + traversal order pre_order + select "__a", "__b", "__c" from "_$__$" + - result: [{"__a": 5, "__b": 2, "__c": 0}, {"__a": 2, "__b": 1, "__c": 1}, {"__a": 1, "__b": -1, "__c": 2}] + - + # with non-qualified select element + - query: select "foo.tableA.A1" from "foo.tableA"; + - result: [{"foo.tableA.A1": 1}, {"foo.tableA.A1": 2}, {"foo.tableA.A1": 3}] + - + # order by + - query: select "foo.tableA".* from "foo.tableA" where "foo.tableA.A3" >= 2 order by "foo.tableA.A1"; + - result: [{"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}, {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}] + - + # order by alias + - query: select "foo.tableA.A1" as "_______" from "foo.tableA" where "foo.tableA.A3" >= 2 order by "_______"; + - result: [{"_______": 2}, {"_______": 3}] + - + # group by + - query: select "foo.tableA.A2", sum("foo.tableA.A1") from "foo.tableA" group by "foo.tableA.A2"; + - explain: "AISCAN(foo.tableA.idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP (_._0 AS foo.tableA.A2, _._1 AS _1)" + - result: [{"foo.tableA.A2": 10, 6}] + - + # group by alias + - query: select "__$__" from "foo.tableA" group by "foo.tableA.A2" as "__$__"; + - result: [{10}] + - + # star over simple join + - query: select * from "foo.tableA", "foo.tableB" where "foo.tableA"."foo.tableA.A1" = "foo.tableB"."foo.tableB.B1"; + - explain: "SCAN(<,>) | TFILTER foo__2tableB | FLATMAP q0 -> { ISCAN(foo.tableA.idx [EQUALS q0.foo.tableB.B1]) AS q1 RETURN (q1.foo.tableA.A1 AS foo.tableA.A1, q1.foo.tableA.A2 AS foo.tableA.A2, q1.foo.tableA.A3 AS foo.tableA.A3, q0.foo.tableB.B1 AS foo.tableB.B1, q0.foo.tableB.B2 AS foo.tableB.B2, q0.foo.tableB.B3 AS foo.tableB.B3) }" + - result: [{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1, "foo.tableB.B1": 1, "foo.tableB.B2": 20, "foo.tableB.B3": {4, 40}}, + {"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2, "foo.tableB.B1": 2, "foo.tableB.B2": 20, "foo.tableB.B3": {5, 50}}, + {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3, "foo.tableB.B1": 3, "foo.tableB.B2": 20, "foo.tableB.B3": {6, 60}}] + - + # qualified star over simple join + - query: select "foo.tableA".*, "foo.tableB".* from "foo.tableA", "foo.tableB" where "foo.tableA"."foo.tableA.A1" = "foo.tableB"."foo.tableB.B1"; + - result: [{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1, "foo.tableB.B1": 1, "foo.tableB.B2": 20, "foo.tableB.B3": {4, 40}}, + {"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2, "foo.tableB.B1": 2, "foo.tableB.B2": 20, "foo.tableB.B3": {5, 50}}, + {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3, "foo.tableB.B1": 3, "foo.tableB.B2": 20, "foo.tableB.B3": {6, 60}}] + - + - query: select "foo$tableC".* from "foo$tableC"; + - result: [{"foo$tableC$C1": 1 , "foo$tableC$C2": 20, "foo$tableC$C3": 1}, {"foo$tableC$C1": 2, "foo$tableC$C2": 20, "foo$tableC$C3": 2}, {"foo$tableC$C1": 3, "foo$tableC$C2": 20, "foo$tableC$C3": 3}, {"foo$tableC$C1": 4, "foo$tableC$C2": 20, "foo$tableC$C3": 4}] + - + - query: select "foo$tableC".* from "foo$tableC" where "foo$tableC$C3" >= 2; + - result: [{"foo$tableC$C1": 2, "foo$tableC$C2": 20, "foo$tableC$C3": 2}, {"foo$tableC$C1": 3, "foo$tableC$C2": 20, "foo$tableC$C3": 3}, {"foo$tableC$C1": 4, "foo$tableC$C2": 20, "foo$tableC$C3": 4}] + - + - query: select "foo$tableC"."foo$tableC$C1" from "foo$tableC"; + - result: [{"foo$tableC$C1": 1}, {"foo$tableC$C1": 2}, {"foo$tableC$C1": 3}, {"foo$tableC$C1": 4}] + - + - query: select "foo$tableC$C1" from "foo$tableC"; + - result: [{"foo$tableC$C1": 1}, {"foo$tableC$C1": 2}, {"foo$tableC$C1": 3}, {"foo$tableC$C1": 4}] + - + - query: select "foo$tableC".* from "foo$tableC" where "foo$tableC$C3" >= 2 order by "foo$tableC$C1"; + - result: [{"foo$tableC$C1": 2, "foo$tableC$C2": 20, "foo$tableC$C3": 2}, {"foo$tableC$C1": 3, "foo$tableC$C2": 20, "foo$tableC$C3": 3}, {"foo$tableC$C1": 4, "foo$tableC$C2": 20, "foo$tableC$C3": 4}] + - + - query: select "foo$tableC$C2", sum("foo$tableC$C1") from "foo$tableC" group by "foo$tableC$C2"; + - explain: "AISCAN(foo$tableC$idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP (_._0 AS foo$tableC$C2, _._1 AS _1)" + - result: [{"foo$tableC$C2": 20, 10}] + - + - query: select "__foo__tableD".* from "__foo__tableD"; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)" + - result: [{"__foo__tableD$D1": 1 , "__foo__tableD$D2": 20, "__foo__tableD$D3": 1}, {"__foo__tableD$D1": 2, "__foo__tableD$D2": 20, "__foo__tableD$D3": 2}, {"__foo__tableD$D1": 3, "__foo__tableD$D2": 20, "__foo__tableD$D3": 3}, {"__foo__tableD$D1": 4, "__foo__tableD$D2": 20, "__foo__tableD$D3": 4}] + - + - query: select "__foo__tableD".* from "__foo__tableD" where "__foo__tableD$D3" >= 2; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | FILTER _.__foo__tableD$D3 GREATER_THAN_OR_EQUALS promote(@c11 AS LONG) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)" + - result: [{"__foo__tableD$D1": 2, "__foo__tableD$D2": 20, "__foo__tableD$D3": 2}, {"__foo__tableD$D1": 3, "__foo__tableD$D2": 20, "__foo__tableD$D3": 3}, {"__foo__tableD$D1": 4, "__foo__tableD$D2": 20, "__foo__tableD$D3": 4}] + - + - query: select "__foo__tableD"."__foo__tableD$D1" from "__foo__tableD"; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1)" + - result: [{"__foo__tableD$D1": 1}, {"__foo__tableD$D1": 2}, {"__foo__tableD$D1": 3}, {"__foo__tableD$D1": 4}] + - + - query: select "__foo__tableD$D1" from "__foo__tableD"; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1)" + - result: [{"__foo__tableD$D1": 1}, {"__foo__tableD$D1": 2}, {"__foo__tableD$D1": 3}, {"__foo__tableD$D1": 4}] + - + - query: select "__foo__tableD".* from "__foo__tableD" where "__foo__tableD$D3" >= 2 order by "__foo__tableD$D1"; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | FILTER _.__foo__tableD$D3 GREATER_THAN_OR_EQUALS promote(@c11 AS LONG) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)" + - result: [{"__foo__tableD$D1": 2, "__foo__tableD$D2": 20, "__foo__tableD$D3": 2}, {"__foo__tableD$D1": 3, "__foo__tableD$D2": 20, "__foo__tableD$D3": 3}, {"__foo__tableD$D1": 4, "__foo__tableD$D2": 20, "__foo__tableD$D3": 4}] + - + - query: select sum("__foo__tableD$D1") from "__foo__tableD" group by "__foo__tableD$D2"; + - result: [{10}] + - + # fanned-out array + - query: select "foo.tableE.__array_elements" from "foo.tableE", "foo.tableE"."foo.tableE.E2" as "foo.tableE.__array_elements" where "foo.tableE.E3".S1 = 5; + - result: [{2}, {3}, {4}] + + # macro functions + - + - query: select "βήτα__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" as "f__e" where "alpha__f"("f__e"."foo.tableE.E3") = 5 + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c22 AS LONG) | MAP (_.foo.tableE.E3.S2 AS ___h.1)" + - result: [ {"___h.1": 50 }] + - + - query: select "alpha__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" as "f__e" where "βήτα__f"("f__e"."foo.tableE.E3") >= 50 + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S2 GREATER_THAN_OR_EQUALS promote(@c23 AS LONG) | MAP (_.foo.tableE.E3.S1 AS ___h.1)" + - result: [ {"___h.1": 5 }, {"___h.1": 6 }] + + # functions + - + - query: select * from "__$func3"(10, 1, 1); + - explain: "SCAN(<,>) | TFILTER my__1adjacency__1list | FILTER _.me LESS_THAN promote(@c6 AS LONG) AND _.my__parent EQUALS promote(@c8 AS LONG) | FLATMAP q0 -> { SCAN(<,>) | TFILTER my__1adjacency__1list | FILTER _.me EQUALS promote(@c8 AS LONG) AS q1 RETURN (q0.me AS _0, q0.my__parent AS _1, q1.me AS _2, q1.my__parent AS _3) }" + - result: [{"_0": 2, "_1": 1, "_2": 1, "_3": -1}, {"_0": 3, "_1": 1, "_2": 1, "_3": -1}, {"_0": 4, "_1": 1, "_2": 1, "_3": -1}] + - + - query: select * from "$yay"(5); + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$x.id)" + - result: [{"_$x.id": 2}] + - + - query: select * from "__2yay"(6); + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$y.id)" + - result: [{"_$y.id": 3}] + - + - query: select * from "नमस्त"(4); + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$z.id)" + - result: [{"_$z.id": 1}] + + # views + - + - query: select * from "$yay__view"; + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 4 | MAP (_.foo.tableE.E1 AS _$x.id) | MAP (_._$x.id AS _$x.id)" + - result: [{"_$x.id": 1}] + - + - query: select * from "__2yay__view"; + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 5 | MAP (_.foo.tableE.E1 AS _$y.id) | MAP (_._$y.id AS _$y.id)" + - result: [{"_$y.id": 2}] + - + - query: select * from "வணக்கம்"; + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 6 | MAP (_.foo.tableE.E1 AS _$z.id) | MAP (_._$z.id AS _$z.id)" + - result: [{"_$z.id": 3}] + - + # inline table definition + - query: select * from values (1, 2, 3), (4, 5, 6) as "_$$$$"("_$$$", "_$$", "_$") + - explain: "EXPLODE array((@c6 AS _$$$, @c8 AS _$$, @c10 AS _$), (@c14 AS _$$$, @c16 AS _$$, @c18 AS _$)) | MAP (_._$$$ AS _$$$, _._$$ AS _$$, _._$ AS _$)" + - result: [{"_$$$": 1, "_$$": 2, "_$": 3}, { "_$$$": 4, "_$$": 5, "_$": 6}] + - + # named record construction + - query: select struct "x$$" ("foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3") from "foo.tableA" + - explain: "COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP ((_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) AS _0)" + - result: [{{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1}}, {{"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}}, {{"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}}] + - + # named record construction with uid star + - query: select struct "x$$" ("foo.tableA".*) from "foo.tableA" + - explain: "ISCAN(foo.tableA.idx3 <,>) | MAP (_ AS _0)" + - result: [{{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1}}, {{"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}}, {{"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}}] + - + # named record construction with aliased expressions + - query: select struct "x$$" ("foo.tableA.A2" + "foo.tableA.A1" as "__$$__") from "foo.tableA" + - explain: "ISCAN(foo.tableA.idx3 <,>) | MAP ((_.foo.tableA.A2 + _.foo.tableA.A1 AS __$$__) AS _0)" + - result: [{{"__$$__": 11}}, {{"__$$__": 12}}, {{"__$$__": 13}}] + + # enums + - + - query: select * from "foo.enum.type"; + - explain: "ISCAN(foo.enum.type$enum__1 <,>)" + - unorderedResult: [ + {"enum_type.id": 1, "enum_type.enum__1": "A", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 2, "enum_type.enum__1": "B$C", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 3, "enum_type.enum__1": "C.D", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 4, "enum_type.enum__1": "E__F", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 5, "enum_type.enum__1": "__G$H", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 6, "enum_type.enum__1": "B$C", "enum_type.enum__2": "A"}, + {"enum_type.id": 7, "enum_type.enum__1": "B$C", "enum_type.enum__2": "C.D"}, + {"enum_type.id": 8, "enum_type.enum__1": "B$C", "enum_type.enum__2": "E__F"}, + {"enum_type.id": 9, "enum_type.enum__1": "B$C", "enum_type.enum__2": "__G$H"}, + {"enum_type.id": 10, "enum_type.enum__1": !null _, "enum_type.enum__2": "B$C"}, + {"enum_type.id": 11, "enum_type.enum__1": "B$C", "enum_type.enum__2": !null _}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__1" = 'B$C'; + - explain: "ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)])" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"enum_type.id": 2, "enum_type.enum__1": "B$C", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 6, "enum_type.enum__1": "B$C", "enum_type.enum__2": "A"}, + {"enum_type.id": 7, "enum_type.enum__1": "B$C", "enum_type.enum__2": "C.D"}, + {"enum_type.id": 8, "enum_type.enum__1": "B$C", "enum_type.enum__2": "E__F"}, + {"enum_type.id": 9, "enum_type.enum__1": "B$C", "enum_type.enum__2": "__G$H"}, + {"enum_type.id": 11, "enum_type.enum__1": "B$C", "enum_type.enum__2": !null _}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__1" = 'C.D'; + - explain: "ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)])" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"enum_type.id": 3, "enum_type.enum__1": "C.D", "enum_type.enum__2": "B$C"}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__1" = 'A'; + - explain: "ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)])" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"enum_type.id": 1, "enum_type.enum__1": "A", "enum_type.enum__2": "B$C"}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__2" = 'B$C'; + - explain: "ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)" + - unorderedResult: [ + {"enum_type.id": 1, "enum_type.enum__1": "A", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 2, "enum_type.enum__1": "B$C", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 3, "enum_type.enum__1": "C.D", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 4, "enum_type.enum__1": "E__F", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 5, "enum_type.enum__1": "__G$H", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 10, "enum_type.enum__1": !null _, "enum_type.enum__2": "B$C"}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__2" = 'C.D'; + - explain: "ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)" + - result: [ + {"enum_type.id": 7, "enum_type.enum__1": "B$C", "enum_type.enum__2": "C.D"}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__2" = 'A'; + - explain: "ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)" + - result: [ + {"enum_type.id": 6, "enum_type.enum__1": "B$C", "enum_type.enum__2": "A"}, + ] +--- +test_block: + name: update-delete-statements + preset: single_repetition_ordered + tests: + - + - query: UPDATE "foo.tableA" SET "foo.tableA.A2" = 100 WHERE "foo.tableA.A1" = 1 + - explain: "COVERING(foo.tableA.idx [EQUALS promote(@c10 AS LONG)] -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | DISTINCT BY PK | FETCH | UPDATE foo__2tableA" + - count: 1 + - + - query: UPDATE "foo.tableA" SET "foo.tableA.A2" = 100 WHERE "foo.tableA.A1" > 1 RETURNING "new"."foo.tableA.A1" + - explain: "COVERING(foo.tableA.idx [[GREATER_THAN promote(@c10 AS LONG)]] -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | DISTINCT BY PK | FETCH | UPDATE foo__2tableA | MAP (_.new.foo.tableA.A1 AS foo.tableA.A1)" + - result: [{"foo.tableA.A1" : 2}, {"foo.tableA.A1" : 3}] + - + - query: DELETE FROM "foo.tableA" WHERE "foo.tableA.A1" = 1 RETURNING "foo.tableA.A1" + "foo.tableA.A2" + "foo.tableA.A3" + - explain: "ISCAN(foo.tableA.idx [EQUALS promote(@c7 AS LONG)]) | DELETE | MAP (_.foo.tableA.A1 + _.foo.tableA.A2 + _.foo.tableA.A3 AS _0)" + - result: [{102}] + - + - query: DELETE FROM "foo.tableA" WHERE "foo.tableA.A2" = 100 + - explain: "ISCAN(foo.tableA.idx3 [EQUALS promote(@c7 AS LONG)]) | DELETE" + - count: 2 +--- +setup: + connect: "jdbc:embed:/__SYS?schema=CATALOG" + steps: + - query: drop schema template if exists "टेम्पलेट" + - query: create schema template "टेम्पलेट" create table T1(a1 bigint, primary key(a1)) +--- +test_block: + connect: "jdbc:embed:/__SYS?schema=CATALOG" + tests: + - + - query: select count(*) from "TEMPLATES" where template_name = 'टेम्पलेट' + - result: [{1}] +--- +setup: + connect: "jdbc:embed:/__SYS?schema=CATALOG" + steps: + - query: drop schema template if exists test_template_with_invalid_identifiers +--- +test_block: + connect: "jdbc:embed:/__SYS?schema=CATALOG" + preset: single_repetition_ordered + tests: + - + # invalid table name + - query: create schema template test_template_with_invalid_identifiers + create table "$yay"(id2 bigint, col6 s2 ARRAY, primary key(id2)) + - error: "42602" + - + # invalid table name + - query: create schema template test_template_with_invalid_identifiers + create table "नमस्ते"(id2 bigint, col6 s2 ARRAY, primary key(id2)) + - error: "42602" + - + # invalid column name + - query: create schema template test_template_with_invalid_identifiers + create table T1("$yay" bigint, col6 s2 ARRAY, primary key(id2)) + - error: "42602" + - + # invalid column name + - query: create schema template test_template_with_invalid_identifiers + create table T1("नमस्ते" bigint, col6 s2 ARRAY, primary key(id2)) + - error: "42602" + - + # invalid struct name + - query: create schema template test_template_with_invalid_identifiers + CREATE TYPE AS STRUCT "$yay"(S1 bigint, S2 bigint) + create table T1(id2 bigint, col6 "$yay", primary key(id2)) + - error: "42602" + - + # invalid struct name + - query: create schema template test_template_with_invalid_identifiers + CREATE TYPE AS STRUCT "नमस्ते"(S1 bigint, S2 bigint) + create table T1(id2 bigint, col6 "नमस्ते", primary key(id2)) + - error: "42602" + - + # invalid function argument + - query: create schema template test_template_with_invalid_identifiers + create table T1(id2 bigint, id3 bigint, primary key(id2)) + create function func (in "$yay" bigint) as SELECT id3 from T1 where id2 > "$yay" + - error: "42602" + - + # invalid function argument + - query: create schema template test_template_with_invalid_identifiers + create table T1(id2 bigint, id3 bigint, primary key(id2)) + create function func (in "नमस्ते" bigint) as SELECT id3 from T1 where id2 > "नमस्ते" + - error: "42602" +--- +setup: + connect: "jdbc:embed:/__SYS?schema=CATALOG" + steps: + - query: drop schema template if exists IDENTIFIERS_PROTO_TEMPLATE + - query: drop database if exists /FRL/IDENTIFIERS_PROTO_YAML + - query: create database /FRL/IDENTIFIERS_PROTO_YAML + # See: MetaDataExportUtilityTests.createValidIdentifiersMetaData for how this file was created + - load schema template: IDENTIFIERS_PROTO_TEMPLATE from src/test/resources/valid_identifiers_metadata.json + - query: create schema /FRL/IDENTIFIERS_PROTO_YAML/test with template IDENTIFIERS_PROTO_TEMPLATE + - set schema state: "{\"name\": \"TEST\", \"database_id\": \"/FRL/IDENTIFIERS_PROTO_YAML\", \"template_name\": \"IDENTIFIERS_PROTO_TEMPLATE\", \"store_info\" : {\"formatVersion\": 2}}"# This does not work entirely, but theoretically should be possible! +--- +setup: + connect: "jdbc:embed:/FRL/IDENTIFIERS_PROTO_YAML?schema=TEST" + steps: + - query: insert into T1 + values (1, 10, 1), + (2, 10, 2), + (3, 10, 3), + (4, 10, 4), + (5, 10, 5) + - query: insert into T2 + values (6, 10, 6), + (7, 10, 7), + (8, 10, 8), + (9, 10, 9), + (10, 10, 10) + - query: insert into "__T3" + values (11, 10, 11, null), + (12, 10, 12, 'T3.E.A'), + (13, 10, 13, 'T3.E.B'), + (14, 10, 14, 'T3.E.C'), + (15, 10, 15, 'T3.E.A') + - query: insert into T4 + values (16, (11, 16), 10, 16), + (17, (11, 17), 10, 17), + (18, (11, 18), 10, 18), + (19, (11, 19), 10, 19), + (20, (22, 20), 10, 20), + (21, (22, 20), -1, 5), + (22, (22, 20), 5, -1), + (23, (22, 20), -1, -1) + - query: INSERT INTO T5__UNESCAPED + VALUES (24, 10, 16), + (25, 10, 17), + (26, 10, 18), + (27, 10, 19), + (28, 10, 20), + (29, -1, 5), + (30, 5, -1), + (31, -1, -1) + - query: INSERT INTO "___T6.__UNESCAPED" + VALUES (32, 10, 16, 'A', 'A'), + (33, 10, 17, 'B__', 'B__'), + (34, 10, 18, 'C__', 'C__'), + (35, 10, 19, '__D.__', '__D.__'), + (36, 10, 20, 'B__', 'A'), + (37, -1, 5, 'C__', 'B__'), + (38, 5, -1, '__D.__', 'C__'), + (39, -1, -1, 'A', '__D.__') +--- +test_block: + connect: "jdbc:embed:/FRL/IDENTIFIERS_PROTO_YAML?schema=TEST" + tests: + - + - query: SELECT * FROM T1 + - explain: "SCAN(<,>) | TFILTER T1 | MAP (_.ID AS ID, _.T1.COL1 AS T1.COL1, _.T1.COL2 AS T1.COL2)" + - result: [{"ID": 1, "T1.COL1": 10, "T1.COL2" : 1}, {"ID": 2, "T1.COL1": 10, "T1.COL2" : 2}, {"ID": 3, "T1.COL1": 10, "T1.COL2" : 3}, {"ID": 4, "T1.COL1": 10, "T1.COL2" : 4}, {"ID": 5, "T1.COL1": 10, "T1.COL2" : 5}] + - + - query: SELECT * FROM T1 WHERE "T1.COL2" > 3 + - explain: "SCAN(<,>) | TFILTER T1 | FILTER _.T1.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.T1.COL1 AS T1.COL1, _.T1.COL2 AS T1.COL2)" + - result: [{"ID": 4, "T1.COL1": 10, "T1.COL2" : 4}, {"ID": 5, "T1.COL1": 10, "T1.COL2" : 5}] + - + - query: SELECT "T1.COL2" FROM T1 WHERE "T1.COL2" > 3 + - explain: "SCAN(<,>) | TFILTER T1 | FILTER _.T1.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T1.COL2 AS T1.COL2)" + - result: [{4}, {5}] + - + - query: SELECT * FROM T2 + - explain: "ISCAN(T2$T2.COL1 <,>)" + - result: [{"ID": 6, "T2$COL1": 10, "T2$COL2" : 6}, {"ID": 7, "T2$COL1": 10, "T2$COL2" : 7}, {"ID": 8, "T2$COL1": 10, "T2$COL2" : 8}, {"ID": 9, "T2$COL1": 10, "T2$COL2" : 9}, {"ID": 10, "T2$COL1": 10, "T2$COL2" : 10}] + - + - query: SELECT * FROM T2 WHERE "T2$COL2" > 8 + - explain: "ISCAN(T2$T2.COL1 <,>) | FILTER _.T2$COL2 GREATER_THAN promote(@c8 AS LONG)" + - result: [{"ID": 9, "T2$COL1": 10, "T2$COL2" : 9}, {"ID": 10, "T2$COL1": 10, "T2$COL2" : 10}] + - + - query: SELECT "T2$COL2" FROM T2 WHERE "T2$COL2" > 8 + - explain: "ISCAN(T2$T2.COL1 <,>) | FILTER _.T2$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T2$COL2 AS T2$COL2)" + - result: [{9}, {10}] + - + - query: SELECT * FROM "__T3" + - explain: "SCAN(<,>) | TFILTER __T3 | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2, _.__T3$COL3 AS __T3$COL3)" + - result: [ + {"ID": 11, "__T3$COL1": 10, "__T3$COL2" : 11, "__T3$COL3": !null _}, + {"ID": 12, "__T3$COL1": 10, "__T3$COL2" : 12, "__T3$COL3": "T3.E.A"}, + {"ID": 13, "__T3$COL1": 10, "__T3$COL2" : 13, "__T3$COL3": "T3.E.B"}, + {"ID": 14, "__T3$COL1": 10, "__T3$COL2" : 14, "__T3$COL3": "T3.E.C"}, + {"ID": 15, "__T3$COL1": 10, "__T3$COL2" : 15, "__T3$COL3": "T3.E.A"}, + ] + - + - query: SELECT * FROM "__T3" WHERE "__T3$COL2" > 13 + - explain: "SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2, _.__T3$COL3 AS __T3$COL3)" + - result: [ + {"ID": 14, "__T3$COL1": 10, "__T3$COL2" : 14, "__T3$COL3": "T3.E.C"}, + {"ID": 15, "__T3$COL1": 10, "__T3$COL2" : 15, "__T3$COL3": "T3.E.A"}, + ] + - + - query: SELECT "__T3$COL2" FROM "__T3" WHERE "__T3$COL2" > 13 + - explain: "SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.__T3$COL2 AS __T3$COL2)" + - result: [{14}, {15}] + - + - query: SELECT * FROM "__func__T3$col2"(13) + - explain: "SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 EQUALS promote(@c6 AS LONG) | MAP (_.__T3$COL1 AS c.1, _.__T3$COL3 AS c.2)" + - result: [{ "c.1": 10, "c.2": "T3.E.B" }] + - + - query: SELECT * FROM T4 + - explain: "SCAN(<,>) | TFILTER T4 | MAP (_.ID AS ID, _.___hidden AS ___hidden, _.T4.COL1 AS T4.COL1, _.T4.COL2 AS T4.COL2)" + - result: [ + {"ID": 16, "___hidden": {11, 16}, "T4.COL1": 10, "T4.COL2" : 16}, + {"ID": 17, "___hidden": {11, 17}, "T4.COL1": 10, "T4.COL2" : 17}, + {"ID": 18, "___hidden": {11, 18}, "T4.COL1": 10, "T4.COL2" : 18}, + {"ID": 19, "___hidden": {11, 19}, "T4.COL1": 10, "T4.COL2" : 19}, + {"ID": 20, "___hidden": {22, 20}, "T4.COL1": 10, "T4.COL2" : 20}, + {"ID": 21, "___hidden": {22, 20}, "T4.COL1": -1, "T4.COL2" : 5}, + {"ID": 22, "___hidden": {22, 20}, "T4.COL1": 5, "T4.COL2" : -1}, + {"ID": 23, "___hidden": {22, 20}, "T4.COL1": -1, "T4.COL2" : -1}, + ] + - + - query: SELECT * FROM T4 WHERE "T4.COL2" > 18 + - explain: "SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.___hidden AS ___hidden, _.T4.COL1 AS T4.COL1, _.T4.COL2 AS T4.COL2)" + - result: [ + {"ID": 19, "___hidden": {11, 19}, "T4.COL1": 10, "T4.COL2" : 19}, + {"ID": 20, "___hidden": {22, 20}, "T4.COL1": 10, "T4.COL2" : 20}, + ] + - + - query: SELECT "T4.COL2" FROM T4 WHERE "T4.COL2" > 18 + - explain: "SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T4.COL2 AS T4.COL2)" + - result: [{19}, {20}] + - + - query: SELECT * FROM "T4$view" + - explain: "SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL1 GREATER_THAN 0 AND _.T4.COL2 GREATER_THAN 0 | MAP (_.T4.COL1 AS c__1, _.T4.COL2 AS c__2) | MAP (_.c__1 AS c__1, _.c__2 AS c__2)" + - result: [ + {"c__1": 10, "c__2": 16 }, + {"c__1": 10, "c__2": 17 }, + {"c__1": 10, "c__2": 18 }, + {"c__1": 10, "c__2": 19 }, + {"c__1": 10, "c__2": 20 }, + ] + - + - query: SELECT "___hidden"."a" FROM T4 + - explain: "SCAN(<,>) | TFILTER T4 | MAP (_.___hidden.a AS a)" + - result: [{11}, {11}, {11}, {11}, {22}, {22}, {22}, {22} ] + - + - query: SELECT * FROM T5__UNESCAPED + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED" + - result: [ + {"ID": 24, "T5__COL1": 10, "__T5__COL2" : 16}, + {"ID": 25, "T5__COL1": 10, "__T5__COL2" : 17}, + {"ID": 26, "T5__COL1": 10, "__T5__COL2" : 18}, + {"ID": 27, "T5__COL1": 10, "__T5__COL2" : 19}, + {"ID": 28, "T5__COL1": 10, "__T5__COL2" : 20}, + {"ID": 29, "T5__COL1": -1, "__T5__COL2" : 5}, + {"ID": 30, "T5__COL1": 5, "__T5__COL2" : -1}, + {"ID": 31, "T5__COL1": -1, "__T5__COL2" : -1}, + ] + - + - query: SELECT * FROM T5__UNESCAPED WHERE T5__COL1 = 10 + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 AS LONG)" + - result: [ + {"ID": 24, "T5__COL1": 10, "__T5__COL2" : 16}, + {"ID": 25, "T5__COL1": 10, "__T5__COL2" : 17}, + {"ID": 26, "T5__COL1": 10, "__T5__COL2" : 18}, + {"ID": 27, "T5__COL1": 10, "__T5__COL2" : 19}, + {"ID": 28, "T5__COL1": 10, "__T5__COL2" : 20}, + ] + - + - query: SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE T5__COL1 = 10 + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)" + - result: [ + {"ID": 24, "__T5__COL2" : 16}, + {"ID": 25, "__T5__COL2" : 17}, + {"ID": 26, "__T5__COL2" : 18}, + {"ID": 27, "__T5__COL2" : 19}, + {"ID": 28, "__T5__COL2" : 20}, + ] + - + - query: SELECT * FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c8 AS LONG)" + - result: [ + {"ID": 29, "T5__COL1": -1, "__T5__COL2" : 5}, + {"ID": 30, "T5__COL1": 5, "__T5__COL2" : -1}, + {"ID": 31, "T5__COL1": -1, "__T5__COL2" : -1}, + ] + - + - query: SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)" + - result: [ + {"ID": 29, "__T5__COL2" : 5}, + {"ID": 30, "__T5__COL2" : -1}, + {"ID": 31, "__T5__COL2" : -1}, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" + - explain: "ISCAN(T6$ENUM2 <,>)" + - unorderedResult: [ + {"ID": 32, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 16, "T6$__ENUM_1": 'A', "T6$__ENUM_2": 'A' }, + {"ID": 33, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 17, "T6$__ENUM_1": 'B__', "T6$__ENUM_2": 'B__' }, + {"ID": 34, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 18, "T6$__ENUM_1": 'C__', "T6$__ENUM_2": 'C__' }, + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": '__D.__' }, + {"ID": 36, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 20, "T6$__ENUM_1": 'B__', "T6$__ENUM_2": 'A' }, + {"ID": 37, "T6$__COL1__": -1, "__T6.COL2__VALUE" : 5, "T6$__ENUM_1": 'C__', "T6$__ENUM_2": 'B__' }, + {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": 'C__' }, + {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": 'A', "T6$__ENUM_2": '__D.__' }, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 + - explain: "ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG)" + - unorderedResult: [ + {"ID": 32, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 16, "T6$__ENUM_1": 'A', "T6$__ENUM_2": 'A' }, + {"ID": 33, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 17, "T6$__ENUM_1": 'B__', "T6$__ENUM_2": 'B__' }, + {"ID": 34, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 18, "T6$__ENUM_1": 'C__', "T6$__ENUM_2": 'C__' }, + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": '__D.__' }, + {"ID": 36, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 20, "T6$__ENUM_1": 'B__', "T6$__ENUM_2": 'A' }, + ] + - + - query: SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 + - explain: "ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)" + - unorderedResult: [ + {"ID": 32, "__T6.COL2__VALUE" : 16}, + {"ID": 33, "__T6.COL2__VALUE" : 17}, + {"ID": 34, "__T6.COL2__VALUE" : 18}, + {"ID": 35, "__T6.COL2__VALUE" : 19}, + {"ID": 36, "__T6.COL2__VALUE" : 20}, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + - explain: "ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])" + - result: [ + {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": 'C__' }, + {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": 'A', "T6$__ENUM_2": '__D.__' }, + {"ID": 37, "T6$__COL1__": -1, "__T6.COL2__VALUE" : 5, "T6$__ENUM_1": 'C__', "T6$__ENUM_2": 'B__' }, + ] + - + - query: SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + - explain: "COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)" + - result: [ + {"ID": 38, "__T6.COL2__VALUE" : -1}, + {"ID": 39, "__T6.COL2__VALUE" : -1}, + {"ID": 37, "__T6.COL2__VALUE" : 5}, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + - explain: "ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM)" + - unorderedResult: [ + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": '__D.__' }, + {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": 'C__' }, + ] + - + - query: SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + - explain: "ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2)" + - unorderedResult: [ + {"ID": 35, "T6$__ENUM_2": '__D.__' }, + {"ID": 38, "T6$__ENUM_2": 'C__' }, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + - explain: "ISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)])" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": '__D.__' }, + {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": 'A', "T6$__ENUM_2": '__D.__' }, + ] + - + - query: SELECT ID, "T6$__ENUM_1" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + - explain: "ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1)" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"ID": 35, "T6$__ENUM_1": '__D.__' }, + {"ID": 39, "T6$__ENUM_1": 'A' }, + ] +--- +setup: + connect: "jdbc:embed:/__SYS?schema=CATALOG" + steps: + - query: drop schema template IDENTIFIERS_PROTO_TEMPLATE + - query: drop database /FRL/IDENTIFIERS_PROTO_YAML +... diff --git a/yaml-tests/src/test/resources/valid_identifiers_metadata.json b/yaml-tests/src/test/resources/valid_identifiers_metadata.json new file mode 100644 index 0000000000..885f5f7589 --- /dev/null +++ b/yaml-tests/src/test/resources/valid_identifiers_metadata.json @@ -0,0 +1,391 @@ +{ + "records": { + "name": "identifiers.proto", + "package": "com.apple.foundationdb.relational.yamltests.generated.identifierstests", + "dependency": ["record_metadata_options.proto"], + "messageType": [{ + "name": "T1", + "field": [{ + "name": "ID", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T1__2COL1", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T1__2COL2", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }] + }, { + "name": "T2", + "field": [{ + "name": "ID", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T2__1COL1", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T2__1COL2", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }] + }, { + "name": "__T3", + "field": [{ + "name": "ID", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "__T3__1COL1", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "__T3__1COL2", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "__T3__1COL3", + "number": 4, + "label": "LABEL_OPTIONAL", + "type": "TYPE_ENUM", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.__T3__2ENUM", + "oneofIndex": 0, + "proto3Optional": true + }], + "oneofDecl": [{ + "name": "X__T3__1COL3" + }] + }, { + "name": "internal", + "field": [{ + "name": "a", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "b", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }] + }, { + "name": "T4", + "field": [{ + "name": "ID", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "___hidden", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.internal" + }, { + "name": "T4__2COL1", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T4__2COL2", + "number": 4, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }] + }, { + "name": "T5__UNESCAPED", + "field": [{ + "name": "ID", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T5__COL1", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "__T5__COL2", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }] + }, { + "name": "___T6__2__UNESCAPED", + "field": [{ + "name": "ID", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T6__1__COL1__0", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "__T6__2COL2__VALUE", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T6__1__ENUM_1", + "number": 4, + "label": "LABEL_OPTIONAL", + "type": "TYPE_ENUM", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T6__2__ENUM__0", + "oneofIndex": 0, + "proto3Optional": true + }, { + "name": "T6__1__ENUM_2", + "number": 5, + "label": "LABEL_OPTIONAL", + "type": "TYPE_ENUM", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T6__2__ENUM__0", + "oneofIndex": 1, + "proto3Optional": true + }], + "oneofDecl": [{ + "name": "_T6__1__ENUM_1" + }, { + "name": "_T6__1__ENUM_2" + }] + }, { + "name": "RecordTypeUnion", + "field": [{ + "name": "_T1", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T1" + }, { + "name": "_T2", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T2" + }, { + "name": "___T3", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.__T3" + }, { + "name": "_T4", + "number": 4, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T4" + }, { + "name": "_T5__UNESCAPED", + "number": 5, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T5__UNESCAPED" + }, { + "name": "____T6__2__UNESCAPED", + "number": 6, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.___T6__2__UNESCAPED" + }] + }], + "enumType": [{ + "name": "__T3__2ENUM", + "value": [{ + "name": "T3__2E__2A", + "number": 0 + }, { + "name": "T3__2E__2B", + "number": 1 + }, { + "name": "T3__2E__2C", + "number": 2 + }] + }, { + "name": "T6__2__ENUM__0", + "value": [{ + "name": "A", + "number": 0 + }, { + "name": "B__0", + "number": 1 + }, { + "name": "C__", + "number": 2 + }, { + "name": "__D__2__", + "number": 3 + }] + }], + "options": { + "javaOuterClassname": "IdentifiersTestProto" + }, + "syntax": "proto3" + }, + "indexes": [{ + "recordType": ["T2"], + "name": "T2$T2.COL1", + "rootExpression": { + "field": { + "fieldName": "T2__1COL1", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }, + "subspaceKey": "AlQyJFQyLkNPTDEA", + "lastModifiedVersion": 1, + "type": "value", + "addedVersion": 1 + }, { + "recordType": ["___T6__2__UNESCAPED"], + "name": "T6$COL2", + "rootExpression": { + "field": { + "fieldName": "__T6__2COL2__VALUE", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }, + "subspaceKey": "AlQ2JENPTDIA", + "lastModifiedVersion": 2, + "type": "value", + "addedVersion": 2 + }, { + "recordType": ["___T6__2__UNESCAPED"], + "name": "T6$ENUM2", + "rootExpression": { + "field": { + "fieldName": "T6__1__ENUM_2", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }, + "subspaceKey": "AlQ2JEVOVU0yAA==", + "lastModifiedVersion": 3, + "type": "value", + "addedVersion": 3 + }], + "recordTypes": [{ + "name": "T4", + "primaryKey": { + "then": { + "child": [{ + "recordTypeKey": { + } + }, { + "field": { + "fieldName": "ID", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }] + } + } + }, { + "name": "T5__UNESCAPED", + "primaryKey": { + "then": { + "child": [{ + "recordTypeKey": { + } + }, { + "field": { + "fieldName": "ID", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }] + } + } + }, { + "name": "__T3", + "primaryKey": { + "then": { + "child": [{ + "recordTypeKey": { + } + }, { + "field": { + "fieldName": "ID", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }] + } + } + }, { + "name": "T1", + "primaryKey": { + "then": { + "child": [{ + "recordTypeKey": { + } + }, { + "field": { + "fieldName": "ID", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }] + } + } + }, { + "name": "T2", + "primaryKey": { + "then": { + "child": [{ + "recordTypeKey": { + } + }, { + "field": { + "fieldName": "ID", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }] + } + } + }, { + "name": "___T6__2__UNESCAPED", + "primaryKey": { + "then": { + "child": [{ + "recordTypeKey": { + } + }, { + "field": { + "fieldName": "ID", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }] + } + } + }], + "splitLongRecords": false, + "version": 3, + "storeRecordVersions": false, + "userDefinedFunctions": [{ + "sqlFunction": { + "name": "__func__T3$col2", + "definition": "CREATE FUNCTION \"__func__T3$col2\"(in \"x$\" bigint) AS select \"__T3$COL1\" as \"c.1\", \"__T3$COL3\" as \"c.2\" from \"__T3\" WHERE \"__T3$COL2\" \u003d \"x$\"" + } + }], + "views": [{ + "name": "T4$view", + "definition": "select \"T4.COL1\" AS \"c__1\", \"T4.COL2\" AS \"c__2\" from T4 where \"T4.COL1\" \u003e 0 and \"T4.COL2\" \u003e 0" + }] +} \ No newline at end of file