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..a6de2f1fa0 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(); 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..fccff89881 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) 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..6fcb238726 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 @@ -1743,6 +1743,8 @@ class Enum implements Type { final List enumValues; @Nullable final String name; + @Nullable + final String storageName; /** * Memoized hash function. @@ -1750,17 +1752,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 +1793,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 +1804,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 +1899,21 @@ 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); } + @Nonnull private static Enum fromProtoValues(boolean isNullable, @Nonnull List values) { - return new Enum(isNullable, enumValuesFromProto(values), null); + return Enum.fromValues(isNullable, enumValuesFromProto(values)); } + @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 +1928,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 +1950,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 +1990,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 +2005,11 @@ public String getName() { return name; } + @Nonnull + public String getStorageName() { + return storageName; + } + public int getNumber() { return number; } @@ -2004,14 +2040,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 +2071,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 +2119,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 +2128,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 +2161,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 +2174,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 +2285,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 +2392,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 +2428,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 +2470,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)); } /** @@ -2446,8 +2508,9 @@ public static Record fromFieldDescriptorsMap(final boolean isNullable, @Nonnull fieldDescriptor.toProto().getLabel(), fieldOptions, !fieldDescriptor.isRequired()), - Optional.of(entry.getKey()), - Optional.of(fieldDescriptor.getNumber()))); + Optional.of(ProtoUtils.toUserIdentifier(entry.getKey())), + Optional.of(fieldDescriptor.getNumber()), + Optional.of(entry.getKey()))); } return fromFields(isNullable, fieldsBuilder.build()); @@ -2521,10 +2584,12 @@ private static List normalizeFields(@Nullable final List fields) { ? Optional.empty() : Optional.of(fieldName)) .orElse("_" + i); + final var storageFieldName = ProtoUtils.toProtoBufCompliantName(explicitFieldName); fieldToBeAdded = new Field(field.getFieldType(), Optional.of(explicitFieldName), - Optional.of(i + 1)); + Optional.of(i + 1), + Optional.of(storageFieldName)); } if (!(fieldNamesSeen.add(fieldToBeAdded.getFieldName()))) { @@ -2559,6 +2624,9 @@ public static class Field implements Comparable, PlanSerializable { @Nonnull private final Optional fieldIndexOptional; + @Nonnull + private final Optional fieldStorageNameOptional; + /** * Memoized hash function. */ @@ -2576,10 +2644,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; } /** @@ -2609,6 +2678,16 @@ public String getFieldName() { return getFieldNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); } + @Nonnull + public Optional getFieldStorageNameOptional() { + return fieldStorageNameOptional; + } + + @Nonnull + public String getFieldStorageName() { + return getFieldStorageNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); + } + /** * Returns the field index. * @return The field index. @@ -2647,7 +2726,7 @@ public Field withNullability(boolean newNullability) { return this; } var newFieldType = getFieldType().withNullability(newNullability); - return new Field(newFieldType, fieldNameOptional, fieldIndexOptional); + return new Field(newFieldType, fieldNameOptional, fieldIndexOptional, fieldStorageNameOptional); } @Nonnull @@ -2690,14 +2769,21 @@ 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 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 +2795,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 +2806,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 +2816,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..02e9e0662f 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,7 +145,7 @@ public static PhysicalOperator fromProto(@Nonnull final PlanSerializationContext @Nonnull public static Descriptors.EnumValueDescriptor stringToEnumValue(Descriptors.EnumDescriptor enumDescriptor, String value) { - final var maybeValue = enumDescriptor.findValueByName(value); + final var maybeValue = enumDescriptor.findValueByName(ProtoUtils.toProtoBufCompliantName(value)); SemanticException.check(maybeValue != null, SemanticException.ErrorCode.INVALID_ENUM_VALUE, value); return maybeValue; } 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..875a8ce2be 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,21 @@ import com.apple.foundationdb.record.TestRecords4WrapperProto; import com.apple.foundationdb.record.TestRecordsUuidProto; import com.apple.foundationdb.record.TupleFieldsProto; +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.RandomUtil; +import com.apple.foundationdb.record.util.pair.Pair; +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.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -46,6 +50,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; @@ -61,8 +66,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 +239,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 +304,398 @@ 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.fromValues(enumType.isNullable(), Type.Enum.enumValuesFromProto(enumDescriptor.getValues())); + 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 record) { + // 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(record.getFields().size()); + for (Type.Record.Field field : record.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(record.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()); + } } } 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-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..7571fef485 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 storageTableName; + 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 storageTableName, @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.storageTableName = storageTableName; this.indexType = indexType; this.name = name; this.keyExpression = keyExpression; @@ -75,6 +80,11 @@ public String getTableName() { return tableName; } + @Nonnull + public String getTableStorageName() { + return storageTableName; + } + @Nonnull @Override public String getIndexType() { @@ -149,6 +159,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 +175,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 +246,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..5bcd92f7b9 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 record, @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 '" + (record == null ? "UNKNONW" : record.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..25e2dc6cfc 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.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 = new Type.Enum(false, Type.Enum.enumValuesFromProto(registeredType.getEnumType().getValues()), ProtoUtils.toUserIdentifier(registeredType.getName()), registeredType.getName()); schemaTemplateBuilder.addAuxiliaryType((DataType.Named) DataTypeUtils.toRelationalType(recordLayerType)); break; default: @@ -127,15 +130,15 @@ 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) { + return generateTableBuilder(userName, recordMetaData.getRecordType(storageName)); } @Nonnull - private RecordLayerTable.Builder generateTableBuilder(@Nonnull final RecordType recordType) { + private RecordLayerTable.Builder generateTableBuilder(@Nonnull String userName, @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()); + final var recordLayerType = Type.Record.fromFieldsWithName(userName, 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())) { @@ -144,14 +147,14 @@ private RecordLayerTable.Builder generateTableBuilder(@Nonnull final RecordType 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()); + Type.Record r = Type.Record.fromFieldsWithName(ProtoUtils.toUserIdentifier(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())) + .from(Type.Record.fromFieldsWithName(userName, false, newFields.build())) .setPrimaryKey(recordType.getPrimaryKey()) .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(recordType.getName(), index)).collect(Collectors.toSet())); } 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..a080e3919c --- /dev/null +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumnTests.java @@ -0,0 +1,110 @@ +/* + * 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.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link RecordLayerColumn}. + */ +class RecordLayerColumnTests { + + @Test + void basicBuilder() { + final RecordLayerColumn column = RecordLayerColumn.newBuilder() + .setName("blah") + .setDataType(DataType.LongType.nullable()) + .setIndex(1) + .build(); + assertThat(column) + .hasToString("blah: long ∪ ∅ = 1"); + + final RecordLayerColumn copy = RecordLayerColumn.newBuilder() + .setName(column.getName()) + .setDataType(column.getDataType()) + .setIndex(column.getIndex()) + .build(); + assertThat(copy) + .hasToString("blah: long ∪ ∅ = 1") + .isEqualTo(column) + .hasSameHashCodeAs(column); + + final RecordLayerColumn differentName = RecordLayerColumn.newBuilder() + .setName("blag") + .setDataType(column.getDataType()) + .setIndex(column.getIndex()) + .build(); + assertThat(differentName) + .hasToString("blag: long ∪ ∅ = 1") + .isNotEqualTo(column) + .doesNotHaveSameHashCodeAs(column); + + final RecordLayerColumn differentType1 = RecordLayerColumn.newBuilder() + .setName(column.getName()) + .setDataType(DataType.LongType.notNullable()) + .setIndex(column.getIndex()) + .build(); + assertThat(differentType1) + .hasToString("blah: long = 1") + .isNotEqualTo(column) + .doesNotHaveSameHashCodeAs(column); + + final RecordLayerColumn differentType2 = RecordLayerColumn.newBuilder() + .setName(column.getName()) + .setDataType(DataType.StringType.nullable()) + .setIndex(column.getIndex()) + .build(); + assertThat(differentType2) + .hasToString("blah: string ∪ ∅ = 1") + .isNotEqualTo(column) + .doesNotHaveSameHashCodeAs(column); + + final RecordLayerColumn differentIndex = RecordLayerColumn.newBuilder() + .setName(column.getName()) + .setDataType(column.getDataType()) + .setIndex(column.getIndex() + 1) + .build(); + assertThat(differentIndex) + .hasToString("blah: long ∪ ∅ = 2") + .isNotEqualTo(column) + .doesNotHaveSameHashCodeAs(column); + } + + @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/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..555182fcbf --- /dev/null +++ b/yaml-tests/src/test/java/MetaDataExportUtilityTests.java @@ -0,0 +1,80 @@ +/* + * 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.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..db5442c2fa --- /dev/null +++ b/yaml-tests/src/test/proto/identifiers.proto @@ -0,0 +1,77 @@ +/* + * identifiers.proto + * + * 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. + */ + +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 RecordTypeUnion { + T1 _T1 = 1; + T2 _T2 = 2; + __T3 ___T3 = 3; + T4 _T4 = 4; +} 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..feb4110926 --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -0,0 +1,765 @@ + += + all-tests0EXPLAIN select "foo.tableA".* from "foo.tableA"; +ϊ5j &(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 (10e8-@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 Ǘ(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" ]; +} +m + all-tests`EXPLAIN select "foo.tableA.A2", sum("foo.tableA.A1") from "foo.tableA" group by "foo.tableA.A2"; + u (,0B8$@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"; +ٖ (E08k@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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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"; +@ (0ɔ8@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, 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, 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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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 (083@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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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 (083@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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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 u(08@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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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 u(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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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, my__1adjacency__1list, foo__2tableB, __foo__0tableD, foo__1tableC, 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 "_$$$$"("_$$$", "_$$", "_$") +tR (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 (%0ͻ 8#@*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 (0լ 8@ 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 +յ  (?0L8`@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" + (<008d@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, __T3, T1, T2]
> 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 Ȉ(0 8@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, __T3, T1, T2]
> 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, __T3, T1, T2]
> 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" +䯽. (08@_SCAN(<,>) | TFILTER __T3 | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2)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)
> 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)" ]; + 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)" ]; + 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, __T3, T1, T2]
> 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 (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)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)
> 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)" ]; + 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)" ]; + 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)" ]; + 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, __T3, T1, T2]
> 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 += Ȭ(028@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)" ]; + 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)" ]; + 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, __T3, T1, T2]
> 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 (0883@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, __T3, T1, T2]
> 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, __T3, T1, T2]
> 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 (08@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, __T3, T1, T2]
> 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 += (0Ά8@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, __T3, T1, T2]
> 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 (08@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, __T3, T1, T2]
> 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, __T3, T1, T2]
> 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" ]; +} \ 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..c140b60e50 --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -0,0 +1,580 @@ +all-tests: +- 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: 111 + transform_count: 106 + transform_time_ms: 81 + transform_yield_count: 49 + insert_time_ms: 2 + 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: 24 + transform_count: 106 + transform_time_ms: 12 + 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: 22 + transform_count: 106 + transform_time_ms: 10 + transform_yield_count: 49 + insert_time_ms: 2 + 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: 27 + transform_count: 117 + transform_time_ms: 20 + transform_yield_count: 44 + insert_time_ms: 1 + 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: 31 + transform_count: 214 + transform_time_ms: 16 + transform_yield_count: 69 + insert_time_ms: 2 + 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: 12 + transform_count: 103 + transform_time_ms: 8 + 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: 8 + transform_count: 80 + transform_time_ms: 4 + 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: 8 + 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: 2 + 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: 5 + transform_count: 64 + transform_time_ms: 2 + 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: 8 + transform_count: 82 + transform_time_ms: 5 + 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: 39 + transform_count: 301 + transform_time_ms: 14 + 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: 6 + transform_count: 90 + transform_time_ms: 2 + 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: 6 + 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 "नमस्त"(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: 6 + transform_count: 90 + transform_time_ms: 2 + 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: 4 + transform_count: 82 + transform_time_ms: 1 + 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: 1 + 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: 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 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: 12 + transform_count: 138 + transform_time_ms: 6 + 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: 3 + 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: 8 + transform_count: 83 + transform_time_ms: 4 + 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: 8 + transform_count: 83 + transform_time_ms: 4 + 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: 8 + transform_count: 83 + transform_time_ms: 4 + 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: 7 + transform_count: 81 + transform_time_ms: 3 + 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: 7 + transform_count: 81 + transform_time_ms: 3 + 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: 7 + transform_count: 81 + transform_time_ms: 3 + 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: 19 + transform_count: 159 + transform_time_ms: 8 + 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: 14 + transform_count: 170 + transform_time_ms: 5 + 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: 19 + transform_count: 164 + transform_time_ms: 6 + transform_yield_count: 65 + insert_time_ms: 1 + 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: 12 + transform_count: 134 + transform_time_ms: 5 + 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: 7 + 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: 6 + transform_count: 52 + transform_time_ms: 2 + 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: 10 + 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 T2 + explain: ISCAN(T2$T2.COL1 <,>) + task_count: 296 + task_total_time_ms: 8 + transform_count: 72 + transform_time_ms: 3 + 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: 13 + transform_count: 81 + transform_time_ms: 4 + 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: 8 + transform_count: 98 + transform_time_ms: 4 + 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) + task_count: 187 + task_total_time_ms: 7 + 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 "__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) + task_count: 218 + task_total_time_ms: 10 + 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 "__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: 11 + 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: 26 + 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 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: 9 + 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: 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 "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: 2 + 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: 8 + transform_count: 41 + transform_time_ms: 2 + transform_yield_count: 13 + insert_time_ms: 0 + insert_new_count: 15 + insert_reused_count: 2 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..0986c3e13a --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -0,0 +1,634 @@ +# +# 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 "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.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 + preset: single_repetition_ordered + tests: + - + # 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) +--- +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} ] +--- +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..0b2120942c --- /dev/null +++ b/yaml-tests/src/test/resources/valid_identifiers_metadata.json @@ -0,0 +1,247 @@ +{ + "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": "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" + }] + }], + "enumType": [{ + "name": "__T3__2ENUM", + "value": [{ + "name": "T3__2E__2A", + "number": 0 + }, { + "name": "T3__2E__2B", + "number": 1 + }, { + "name": "T3__2E__2C", + "number": 2 + }] + }], + "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 + }], + "recordTypes": [{ + "name": "T4", + "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" + } + }] + } + } + }], + "splitLongRecords": false, + "version": 1, + "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