From 3f583ee455da2b8236e3e6b0256fbf5ab9f60f7d Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Thu, 20 Nov 2025 16:11:20 +0000 Subject: [PATCH 01/12] Reapply "Reintroduce translation of identifiers to Protobuf compliant names (#3736)" (#3767) This reverts commit 7f1f4c95f3a5b9d0195cf4aa621bb4c5bdc41454. --- .../KeyExpressionExpansionVisitor.java | 3 +- .../cascades/ScalarTranslationVisitor.java | 3 +- .../CompatibleTypeEvolutionPredicate.java | 2 +- .../query/plan/cascades/typing/Type.java | 156 +++- .../plan/cascades/values/FieldValue.java | 67 +- .../plan/cascades/values/PromoteValue.java | 3 +- .../query/plan/cascades/values/Values.java | 3 +- .../CompensateRecordConstructorRule.java | 2 +- .../foundationdb/record/util/ProtoUtils.java | 52 ++ .../src/main/proto/record_query_plan.proto | 5 + .../query/FDBSimpleQueryGraphTest.java | 12 +- .../query/plan/cascades/BooleanValueTest.java | 12 +- .../record/query/plan/cascades/TypeTest.java | 423 +++++++++- .../values/RecordConstructorValueTest.java | 16 +- .../record/util/ProtoUtilsTest.java | 117 +++ .../relational/api/metadata/DataType.java | 17 + .../relational/api/RowStruct.java | 3 +- .../relational/recordlayer/AbstractRow.java | 3 +- .../relational/recordlayer/MessageTuple.java | 3 +- .../recordlayer/metadata/DataTypeUtils.java | 23 +- .../metadata/RecordLayerColumn.java | 23 + .../metadata/RecordLayerIndex.java | 32 +- .../metadata/RecordLayerSchemaTemplate.java | 7 +- .../metadata/RecordLayerTable.java | 48 +- .../serde/FileDescriptorSerializer.java | 12 +- .../serde/RecordMetadataDeserializer.java | 39 +- .../serde/RecordMetadataSerializer.java | 6 +- .../recordlayer/query/IndexGenerator.java | 70 +- .../recordlayer/query/LogicalOperator.java | 12 +- .../recordlayer/query/PlanGenerator.java | 3 + .../recordlayer/query/SemanticAnalyzer.java | 10 +- .../query/visitors/DdlVisitor.java | 7 +- .../query/visitors/QueryVisitor.java | 32 +- .../recordlayer/util/TypeUtils.java | 2 +- .../api/ddl/DdlStatementParsingTest.java | 143 ++++ .../metadata/RecordLayerColumnTests.java | 110 +++ .../utils/RelationalStructAssert.java | 3 +- .../yamltests/command/QueryExecutor.java | 16 +- .../utils/ExportSchemaTemplateUtil.java | 69 ++ .../yamltests/utils/package-info.java | 24 + .../test/java/MetaDataExportUtilityTests.java | 80 ++ .../src/test/java/YamlIntegrationTests.java | 12 + yaml-tests/src/test/proto/identifiers.proto | 77 ++ .../resources/valid-identifiers.metrics.binpb | 765 ++++++++++++++++++ .../resources/valid-identifiers.metrics.yaml | 580 +++++++++++++ .../test/resources/valid-identifiers.yamsql | 634 +++++++++++++++ .../resources/valid_identifiers_metadata.json | 247 ++++++ 47 files changed, 3753 insertions(+), 235 deletions(-) create mode 100644 fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/util/ProtoUtilsTest.java create mode 100644 fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumnTests.java create mode 100644 yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/ExportSchemaTemplateUtil.java create mode 100644 yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/package-info.java create mode 100644 yaml-tests/src/test/java/MetaDataExportUtilityTests.java create mode 100644 yaml-tests/src/test/proto/identifiers.proto create mode 100644 yaml-tests/src/test/resources/valid-identifiers.metrics.binpb create mode 100644 yaml-tests/src/test/resources/valid-identifiers.metrics.yaml create mode 100644 yaml-tests/src/test/resources/valid-identifiers.yamsql create mode 100644 yaml-tests/src/test/resources/valid_identifiers_metadata.json 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..a0da499c20 --- /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: 4.8.13.0 +--- +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 From f7cbe172bd48249d00d6de3199dd2303d9d4397e Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Fri, 21 Nov 2025 11:49:35 +0000 Subject: [PATCH 02/12] Handle malformed meta-data more gracefully This adds support for retaining the protobuf names more directly for types and fields. This can happen if the user has created a meta-data proto and used a strategy for naming that differs from the one that would have been generated by our own DML. The basic strategy is to: 1. Continue to always apply the `toProtoUtils` method to produce plausible user-generated names but 1. Retain the original protobuf name in the `Type` information and then use that to get the name used to access data in the field --- .../query/plan/cascades/typing/Type.java | 2 +- .../serde/RecordMetadataDeserializer.java | 8 +- .../test/java/MetaDataExportUtilityTests.java | 1 + yaml-tests/src/test/proto/identifiers.proto | 14 ++ .../resources/valid-identifiers.metrics.binpb | 141 ++++++++++++++++++ .../resources/valid-identifiers.metrics.yaml | 109 ++++++++++++++ .../test/resources/valid-identifiers.yamsql | 118 ++++++++++++++- .../resources/valid_identifiers_metadata.json | 96 +++++++++++- 8 files changed, 483 insertions(+), 6 deletions(-) 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 6fcb238726..0a663eb870 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 @@ -2128,7 +2128,7 @@ 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, @Nullable final String storageName, final boolean isNullable, @Nullable final List normalizedFields) { + public 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; 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 25e2dc6cfc..4b6e6b4e1f 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 @@ -138,7 +138,8 @@ private RecordLayerTable.Builder generateTableBuilder(@Nonnull final String user 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(userName, false, Type.Record.fromDescriptor(recordType.getDescriptor()).getFields()); + final var recordLayerType = new Type.Record(userName, recordType.getName(), false, + Type.Record.fromDescriptor(recordType.getDescriptor()).getFields()); // todo (yhatem) this is hacky and must be cleaned up. We need to understand the actually field types so we can take decisions // on higher level based on these types (wave3). if (recordLayerType.getFields().stream().anyMatch(f -> f.getFieldType().isRecord())) { @@ -147,14 +148,15 @@ private RecordLayerTable.Builder generateTableBuilder(@Nonnull String userName, final var protoField = recordType.getDescriptor().getFields().get(i); final var field = recordLayerType.getField(i); if (field.getFieldType().isRecord()) { - Type.Record r = Type.Record.fromFieldsWithName(ProtoUtils.toUserIdentifier(protoField.getMessageType().getName()), field.getFieldType().isNullable(), ((Type.Record) field.getFieldType()).getFields()); + final String fieldTypeName = protoField.getMessageType().getName(); + Type.Record r = new Type.Record(ProtoUtils.toUserIdentifier(fieldTypeName), fieldTypeName, 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(userName, false, newFields.build())) + .from(new Type.Record(userName, recordType.getName(), false, newFields.build())) .setPrimaryKey(recordType.getPrimaryKey()) .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(recordType.getName(), index)).collect(Collectors.toSet())); } diff --git a/yaml-tests/src/test/java/MetaDataExportUtilityTests.java b/yaml-tests/src/test/java/MetaDataExportUtilityTests.java index 555182fcbf..fb33ca765e 100644 --- a/yaml-tests/src/test/java/MetaDataExportUtilityTests.java +++ b/yaml-tests/src/test/java/MetaDataExportUtilityTests.java @@ -70,6 +70,7 @@ void createValidIdentifiersMetaData() throws IOException { }); metaDataBuilder.addIndex(metaDataBuilder.getRecordType("T2"), new Index("T2$T2.COL1", "T2__1COL1")); + metaDataBuilder.addIndex(metaDataBuilder.getRecordType("___T6__2__UNESCAPED"), new Index("T6$COL2", "__T6__2COL2__VALUE")); metaDataBuilder.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", diff --git a/yaml-tests/src/test/proto/identifiers.proto b/yaml-tests/src/test/proto/identifiers.proto index db5442c2fa..558f817e35 100644 --- a/yaml-tests/src/test/proto/identifiers.proto +++ b/yaml-tests/src/test/proto/identifiers.proto @@ -69,9 +69,23 @@ message T4 { int64 T4__2COL2 = 4; } +message T5__UNESCAPED { + int64 ID = 1; + int64 T5__COL1 = 2; + int64 __T5__COL2 = 3; +} + +message ___T6__2__UNESCAPED { + int64 ID = 1; + int64 T6__1__COL1__0 = 2; + int64 __T6__2COL2__VALUE = 3; +} + message RecordTypeUnion { T1 _T1 = 1; T2 _T2 = 2; __T3 ___T3 = 3; T4 _T4 = 4; + T5__UNESCAPED _T5__UNESCAPED = 5; + ___T6__2__UNESCAPED ____T6__2__UNESCAPED = 6; } diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb index feb4110926..0a9e10f97a 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -762,4 +762,145 @@ unnamed-12&EXPLAIN SELECT "___hidden"."a" FROM T4 3 -> 2 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +1 + +unnamed-12#EXPLAIN SELECT * FROM T5__UNESCAPED +ƛ) ( 08 @!SCAN(<,>) | TFILTER T5__UNESCAPED +digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 2 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +E + +unnamed-127EXPLAIN SELECT * FROM T5__UNESCAPED WHERE T5__COL1 = 10 +0 Ƌ(0 8@QSCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 AS LONG)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.T5__COL1 EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 2 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +T + +unnamed-12FEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE T5__COL1 = 10 +뒟= (08@SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.__T5__COL2 AS __T5__COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T5__COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T5__COL1 EQUALS promote(@c10 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 3 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +I + +unnamed-12;EXPLAIN SELECT * FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 +0 ͐(08@VSCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c8 AS LONG)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.__T5__COL2 LESS_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 2 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +X + +unnamed-12JEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 += (098@SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.__T5__COL2 AS __T5__COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T5__COL2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.__T5__COL2 LESS_THAN promote(@c10 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 3 [ label=<
Type Filter
WHERE record IS [T5__UNESCAPED]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T5__COL1, LONG AS __T5__COL2)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +7 + +unnamed-12)EXPLAIN SELECT * FROM "___T6.__UNESCAPED" +H (08@ISCAN(T6$COL2 <,>)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +P + +unnamed-12BEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 +Q (0,8%@EISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Predicate Filter
WHERE q2.T6$__COL1__ EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 3 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +e + +unnamed-12WEXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 +c ͦ(038'@ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q43.ID AS ID, q43.__T6.COL2__VALUE AS __T6.COL2__VALUE)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T6$__COL1__ EQUALS promote(@c10 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 3 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 4 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 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" ]; +} +U + +unnamed-12GEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 +ҴU ے(0'8%@1ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
comparisons: [[LESS_THAN promote(@c8 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +j + +unnamed-12\EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 +άn ("080@COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q65.ID AS ID, q65.__T6.COL2__VALUE AS __T6.COL2__VALUE)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [[LESS_THAN promote(@c10 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 3 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q65> label="q65" 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 index c140b60e50..6350c3734f 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -578,3 +578,112 @@ unnamed-12: insert_time_ms: 0 insert_new_count: 15 insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM T5__UNESCAPED + explain: SCAN(<,>) | TFILTER T5__UNESCAPED + task_count: 159 + task_total_time_ms: 7 + transform_count: 41 + transform_time_ms: 2 + transform_yield_count: 13 + insert_time_ms: 0 + insert_new_count: 13 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM T5__UNESCAPED WHERE T5__COL1 = 10 + explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 + AS LONG) + task_count: 187 + task_total_time_ms: 4 + transform_count: 48 + transform_time_ms: 2 + transform_yield_count: 14 + insert_time_ms: 0 + insert_new_count: 17 + insert_reused_count: 2 +- query: EXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE T5__COL1 = 10 + explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 + AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2) + task_count: 230 + task_total_time_ms: 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 T5__UNESCAPED WHERE "__T5__COL2" < 10 + explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c8 + AS LONG) + task_count: 187 + task_total_time_ms: 10 + transform_count: 48 + transform_time_ms: 4 + transform_yield_count: 14 + insert_time_ms: 0 + insert_new_count: 17 + insert_reused_count: 2 +- query: EXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2" < + 10 + explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c10 + AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2) + task_count: 230 + task_total_time_ms: 11 + transform_count: 61 + transform_time_ms: 4 + transform_yield_count: 15 + insert_time_ms: 0 + insert_new_count: 21 + insert_reused_count: 2 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" + explain: ISCAN(T6$COL2 <,>) + task_count: 296 + task_total_time_ms: 10 + 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 "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 + explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) + task_count: 351 + task_total_time_ms: 12 + transform_count: 81 + transform_time_ms: 5 + transform_yield_count: 30 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 2 +- query: EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" + = 10 + explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) + | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) + task_count: 375 + task_total_time_ms: 14 + transform_count: 99 + transform_time_ms: 4 + transform_yield_count: 28 + insert_time_ms: 0 + insert_new_count: 39 + insert_reused_count: 4 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + explain: ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]]) + task_count: 367 + task_total_time_ms: 15 + transform_count: 85 + transform_time_ms: 6 + transform_yield_count: 31 + insert_time_ms: 0 + insert_new_count: 37 + insert_reused_count: 3 +- query: EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" + < 10 + explain: 'COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], + __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)' + task_count: 463 + task_total_time_ms: 9 + transform_count: 110 + transform_time_ms: 3 + transform_yield_count: 34 + insert_time_ms: 0 + insert_new_count: 48 + insert_reused_count: 4 diff --git a/yaml-tests/src/test/resources/valid-identifiers.yamsql b/yaml-tests/src/test/resources/valid-identifiers.yamsql index a0da499c20..4da84af6f6 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.yamsql +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -18,7 +18,7 @@ # limitations under the License. --- options: - supported_version: 4.8.13.0 + supported_version: !current_version --- schema_template: CREATE TYPE AS STRUCT "foo.struct"(S1 bigint, S2 bigint) @@ -534,6 +534,24 @@ setup: (21, (22, 20), -1, 5), (22, (22, 20), 5, -1), (23, (22, 20), -1, -1) + - query: INSERT INTO T5__UNESCAPED + VALUES (24, 10, 16), + (25, 10, 17), + (26, 10, 18), + (27, 10, 19), + (28, 10, 20), + (29, -1, 5), + (30, 5, -1), + (31, -1, -1) + - query: INSERT INTO "___T6.__UNESCAPED" + VALUES (32, 10, 16), + (33, 10, 17), + (34, 10, 18), + (35, 10, 19), + (36, 10, 20), + (37, -1, 5), + (38, 5, -1), + (39, -1, -1) --- test_block: connect: "jdbc:embed:/FRL/IDENTIFIERS_PROTO_YAML?schema=TEST" @@ -625,6 +643,104 @@ test_block: - query: SELECT "___hidden"."a" FROM T4 - explain: "SCAN(<,>) | TFILTER T4 | MAP (_.___hidden.a AS a)" - result: [{11}, {11}, {11}, {11}, {22}, {22}, {22}, {22} ] + - + - query: SELECT * FROM T5__UNESCAPED + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED" + - result: [ + {"ID": 24, "T5__COL1": 10, "__T5__COL2" : 16}, + {"ID": 25, "T5__COL1": 10, "__T5__COL2" : 17}, + {"ID": 26, "T5__COL1": 10, "__T5__COL2" : 18}, + {"ID": 27, "T5__COL1": 10, "__T5__COL2" : 19}, + {"ID": 28, "T5__COL1": 10, "__T5__COL2" : 20}, + {"ID": 29, "T5__COL1": -1, "__T5__COL2" : 5}, + {"ID": 30, "T5__COL1": 5, "__T5__COL2" : -1}, + {"ID": 31, "T5__COL1": -1, "__T5__COL2" : -1}, + ] + - + - query: SELECT * FROM T5__UNESCAPED WHERE T5__COL1 = 10 + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 AS LONG)" + - result: [ + {"ID": 24, "T5__COL1": 10, "__T5__COL2" : 16}, + {"ID": 25, "T5__COL1": 10, "__T5__COL2" : 17}, + {"ID": 26, "T5__COL1": 10, "__T5__COL2" : 18}, + {"ID": 27, "T5__COL1": 10, "__T5__COL2" : 19}, + {"ID": 28, "T5__COL1": 10, "__T5__COL2" : 20}, + ] + - + - query: SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE T5__COL1 = 10 + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)" + - result: [ + {"ID": 24, "__T5__COL2" : 16}, + {"ID": 25, "__T5__COL2" : 17}, + {"ID": 26, "__T5__COL2" : 18}, + {"ID": 27, "__T5__COL2" : 19}, + {"ID": 28, "__T5__COL2" : 20}, + ] + - + - query: SELECT * FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c8 AS LONG)" + - result: [ + {"ID": 29, "T5__COL1": -1, "__T5__COL2" : 5}, + {"ID": 30, "T5__COL1": 5, "__T5__COL2" : -1}, + {"ID": 31, "T5__COL1": -1, "__T5__COL2" : -1}, + ] + - + - query: SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 + - explain: "SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)" + - result: [ + {"ID": 29, "__T5__COL2" : 5}, + {"ID": 30, "__T5__COL2" : -1}, + {"ID": 31, "__T5__COL2" : -1}, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" + - explain: "ISCAN(T6$COL2 <,>)" + - unorderedResult: [ + {"ID": 32, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 16}, + {"ID": 33, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 17}, + {"ID": 34, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 18}, + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19}, + {"ID": 36, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 20}, + {"ID": 37, "T6$__COL1__": -1, "__T6.COL2__VALUE" : 5}, + {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1}, + {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1}, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 + - explain: "ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG)" + - unorderedResult: [ + {"ID": 32, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 16}, + {"ID": 33, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 17}, + {"ID": 34, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 18}, + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19}, + {"ID": 36, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 20}, + ] + - + - query: SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 + - explain: "ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)" + - unorderedResult: [ + {"ID": 32, "__T6.COL2__VALUE" : 16}, + {"ID": 33, "__T6.COL2__VALUE" : 17}, + {"ID": 34, "__T6.COL2__VALUE" : 18}, + {"ID": 35, "__T6.COL2__VALUE" : 19}, + {"ID": 36, "__T6.COL2__VALUE" : 20}, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + - explain: "ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])" + - result: [ + {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1}, + {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1}, + {"ID": 37, "T6$__COL1__": -1, "__T6.COL2__VALUE" : 5}, + ] + - + - query: SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + - explain: "COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)" + - result: [ + {"ID": 38, "__T6.COL2__VALUE" : -1}, + {"ID": 39, "__T6.COL2__VALUE" : -1}, + {"ID": 37, "__T6.COL2__VALUE" : 5}, + ] --- setup: connect: "jdbc:embed:/__SYS?schema=CATALOG" diff --git a/yaml-tests/src/test/resources/valid_identifiers_metadata.json b/yaml-tests/src/test/resources/valid_identifiers_metadata.json index 0b2120942c..654e20e818 100644 --- a/yaml-tests/src/test/resources/valid_identifiers_metadata.json +++ b/yaml-tests/src/test/resources/valid_identifiers_metadata.json @@ -105,6 +105,42 @@ "label": "LABEL_OPTIONAL", "type": "TYPE_INT64" }] + }, { + "name": "T5__UNESCAPED", + "field": [{ + "name": "ID", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T5__COL1", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "__T5__COL2", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }] + }, { + "name": "___T6__2__UNESCAPED", + "field": [{ + "name": "ID", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "T6__1__COL1__0", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }, { + "name": "__T6__2COL2__VALUE", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_INT64" + }] }, { "name": "RecordTypeUnion", "field": [{ @@ -131,6 +167,18 @@ "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T4" + }, { + "name": "_T5__UNESCAPED", + "number": 5, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T5__UNESCAPED" + }, { + "name": "____T6__2__UNESCAPED", + "number": 6, + "label": "LABEL_OPTIONAL", + "type": "TYPE_MESSAGE", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.___T6__2__UNESCAPED" }] }], "enumType": [{ @@ -165,6 +213,20 @@ "lastModifiedVersion": 1, "type": "value", "addedVersion": 1 + }, { + "recordType": ["___T6__2__UNESCAPED"], + "name": "T6$COL2", + "rootExpression": { + "field": { + "fieldName": "__T6__2COL2__VALUE", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }, + "subspaceKey": "AlQ2JENPTDIA", + "lastModifiedVersion": 2, + "type": "value", + "addedVersion": 2 }], "recordTypes": [{ "name": "T4", @@ -182,6 +244,22 @@ }] } } + }, { + "name": "T5__UNESCAPED", + "primaryKey": { + "then": { + "child": [{ + "recordTypeKey": { + } + }, { + "field": { + "fieldName": "ID", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }] + } + } }, { "name": "__T3", "primaryKey": { @@ -230,9 +308,25 @@ }] } } + }, { + "name": "___T6__2__UNESCAPED", + "primaryKey": { + "then": { + "child": [{ + "recordTypeKey": { + } + }, { + "field": { + "fieldName": "ID", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }] + } + } }], "splitLongRecords": false, - "version": 1, + "version": 2, "storeRecordVersions": false, "userDefinedFunctions": [{ "sqlFunction": { From 0e76f6a59e0684f0215dc5af77cf094ff3652b63 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Fri, 21 Nov 2025 19:57:28 +0000 Subject: [PATCH 03/12] Add additional tests of the field name preservation logic and replace that code in the RecordMetadataDeserializer with logic in the Type system --- .../query/plan/cascades/typing/Type.java | 101 ++++--- .../plan/cascades/values/PromoteValue.java | 8 +- .../record/query/plan/cascades/TypeTest.java | 277 +++++++++++++++++- .../src/test/proto/type_test.proto | 174 +++++++++++ .../serde/RecordMetadataDeserializer.java | 32 +- .../test/java/MetaDataExportUtilityTests.java | 1 + yaml-tests/src/test/proto/identifiers.proto | 9 + .../resources/valid-identifiers.metrics.binpb | 117 ++++++-- .../resources/valid-identifiers.metrics.yaml | 121 +++++--- .../test/resources/valid-identifiers.yamsql | 84 ++++-- .../resources/valid_identifiers_metadata.json | 52 +++- 11 files changed, 809 insertions(+), 167 deletions(-) create mode 100644 fdb-record-layer-core/src/test/proto/type_test.proto 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 0a663eb870..3bd07206b7 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java @@ -425,11 +425,12 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri @Nonnull Descriptors.FieldDescriptor.Type protoType, @Nonnull FieldDescriptorProto.Label protoLabel, @Nullable DescriptorProtos.FieldOptions fieldOptions, - boolean isNullable) { + boolean isNullable, + boolean preserveNames) { final var typeCode = TypeCode.fromProtobufFieldDescriptor(protoType, fieldOptions); if (protoLabel == FieldDescriptorProto.Label.LABEL_REPEATED) { // collection type - return fromProtoTypeToArray(descriptor, protoType, typeCode, fieldOptions, false); + return fromProtoTypeToArray(descriptor, protoType, typeCode, fieldOptions, false, preserveNames); } else if (typeCode.isPrimitive()) { final var fieldOptionMaybe = Optional.ofNullable(fieldOptions).map(f -> f.getExtension(RecordMetaDataOptionsProto.field)); if (fieldOptionMaybe.isPresent() && fieldOptionMaybe.get().hasVectorOptions()) { @@ -439,7 +440,7 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri return primitiveType(typeCode, isNullable); } else if (typeCode == TypeCode.ENUM) { final var enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor); - return Enum.fromProtoValues(isNullable, enumDescriptor.getValues()); + return preserveNames ? Enum.fromDescriptorPreservingNames(isNullable, enumDescriptor) : Enum.fromDescriptor(isNullable, enumDescriptor); } else if (typeCode == TypeCode.RECORD) { Objects.requireNonNull(descriptor); final var messageDescriptor = (Descriptors.Descriptor)descriptor; @@ -447,11 +448,12 @@ private static Type fromProtoType(@Nullable Descriptors.GenericDescriptor descri // find TypeCode of array elements final var elementField = messageDescriptor.findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName()); final var elementTypeCode = TypeCode.fromProtobufFieldDescriptor(elementField.getType(), elementField.getOptions()); - return fromProtoTypeToArray(descriptor, protoType, elementTypeCode, elementField.getOptions(), true); + return fromProtoTypeToArray(descriptor, protoType, elementTypeCode, elementField.getOptions(), true, preserveNames); } else if (TupleFieldsProto.UUID.getDescriptor().equals(messageDescriptor)) { return Type.uuidType(isNullable); } else { - return Record.fromFieldDescriptorsMap(isNullable, Record.toFieldDescriptorMap(messageDescriptor.getFields())); + final Type.Record recordType = preserveNames ? Record.fromDescriptorPreservingName(messageDescriptor) : Record.fromDescriptor(messageDescriptor); + return recordType.withNullability(isNullable); } } @@ -469,7 +471,8 @@ private static Array fromProtoTypeToArray(@Nullable Descriptors.GenericDescripto @Nonnull Descriptors.FieldDescriptor.Type protoType, @Nonnull TypeCode typeCode, @Nullable DescriptorProtos.FieldOptions fieldOptions, - boolean isNullable) { + boolean isNullable, + boolean preserveNames) { if (typeCode.isPrimitive()) { final Type type; if (typeCode == TypeCode.VECTOR) { @@ -480,18 +483,27 @@ private static Array fromProtoTypeToArray(@Nullable Descriptors.GenericDescripto } return new Array(isNullable, type); } else if (typeCode == TypeCode.ENUM) { - final var enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor); - final var enumType = Enum.fromProtoValues(false, enumDescriptor.getValues()); + final Descriptors.EnumDescriptor enumDescriptor; + if (isNullable) { + // Unwrap the nullable array + enumDescriptor = ((Descriptors.Descriptor)Objects.requireNonNull(descriptor)).findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName()).getEnumType(); + } else { + enumDescriptor = (Descriptors.EnumDescriptor)Objects.requireNonNull(descriptor); + } + Objects.requireNonNull(enumDescriptor); + final var enumType = preserveNames ? Enum.fromDescriptorPreservingNames(false, enumDescriptor) : Enum.fromDescriptor(false, enumDescriptor); return new Array(isNullable, enumType); } else { + final Descriptors.Descriptor recordDescriptor; if (isNullable) { - Descriptors.Descriptor wrappedDescriptor = ((Descriptors.Descriptor)Objects.requireNonNull(descriptor)).findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName()).getMessageType(); - Objects.requireNonNull(wrappedDescriptor); - return new Array(true, fromProtoType(wrappedDescriptor, Descriptors.FieldDescriptor.Type.MESSAGE, FieldDescriptorProto.Label.LABEL_OPTIONAL, fieldOptions, false)); + // Unwrap the nullable array + recordDescriptor = ((Descriptors.Descriptor)Objects.requireNonNull(descriptor)).findFieldByName(NullableArrayTypeUtils.getRepeatedFieldName()).getMessageType(); + protoType = Descriptors.FieldDescriptor.Type.MESSAGE; } else { - // case 2: any arbitrary sub message we don't understand - return new Array(false, fromProtoType(descriptor, protoType, FieldDescriptorProto.Label.LABEL_OPTIONAL, fieldOptions, false)); + recordDescriptor = (Descriptors.Descriptor) descriptor; } + Objects.requireNonNull(recordDescriptor); + return new Array(isNullable, fromProtoType(recordDescriptor, protoType, FieldDescriptorProto.Label.LABEL_OPTIONAL, fieldOptions, false, preserveNames)); } } @@ -1905,8 +1917,13 @@ public static > Enum forJavaEnum(@Nonnull final Clas } @Nonnull - private static Enum fromProtoValues(boolean isNullable, @Nonnull List values) { - return Enum.fromValues(isNullable, enumValuesFromProto(values)); + public static Enum fromDescriptor(boolean isNullable, @Nonnull Descriptors.EnumDescriptor enumDescriptor) { + return Enum.fromValues(isNullable, enumValuesFromProto(enumDescriptor.getValues())); + } + + @Nonnull + public static Enum fromDescriptorPreservingNames(boolean isNullable, @Nonnull Descriptors.EnumDescriptor enumDescriptor) { + return new Type.Enum(isNullable, enumValuesFromProto(enumDescriptor.getValues()), ProtoUtils.toUserIdentifier(enumDescriptor.getName()), enumDescriptor.getName()); } @Nonnull @@ -2498,22 +2515,17 @@ public static Record fromFieldDescriptorsMap(@Nonnull final Map fieldDescriptorMap) { + return fromFields(isNullable, fieldsFromDescriptorMap(fieldDescriptorMap, false)); + } + + @Nonnull + private static List fieldsFromDescriptorMap(@Nonnull final Map fieldDescriptorMap, boolean preserveNames) { final var fieldsBuilder = ImmutableList.builder(); for (final var entry : Objects.requireNonNull(fieldDescriptorMap).entrySet()) { final var fieldDescriptor = entry.getValue(); - final var fieldOptions = fieldDescriptor.getOptions(); - fieldsBuilder.add( - new Field(fromProtoType(getTypeSpecificDescriptor(fieldDescriptor), - fieldDescriptor.getType(), - fieldDescriptor.toProto().getLabel(), - fieldOptions, - !fieldDescriptor.isRequired()), - Optional.of(ProtoUtils.toUserIdentifier(entry.getKey())), - Optional.of(fieldDescriptor.getNumber()), - Optional.of(entry.getKey()))); + fieldsBuilder.add(Field.fromDescriptor(fieldDescriptor, preserveNames)); } - - return fromFields(isNullable, fieldsBuilder.build()); + return fieldsBuilder.build(); } /** @@ -2527,6 +2539,12 @@ public static Record fromDescriptor(final Descriptors.Descriptor descriptor) { return fromFieldDescriptorsMap(toFieldDescriptorMap(descriptor.getFields())); } + @Nonnull + public static Record fromDescriptorPreservingName(final Descriptors.Descriptor descriptor) { + return new Record(ProtoUtils.toUserIdentifier(descriptor.getName()), descriptor.getName(), false, + fieldsFromDescriptorMap(toFieldDescriptorMap(descriptor.getFields()), true)); + } + /** * Translates a list of {@link com.google.protobuf.Descriptors.FieldDescriptor}s to a mapping between field name * and the field itself. @@ -2705,21 +2723,6 @@ public int getFieldIndex() { return getFieldIndexOptional().orElseThrow(() -> new RecordCoreException("field index should have been set")); } - /** - * Returns a new field with a new name. - * @param newName The new name. - * @return if the name is different from the current field name, returns a new {@code Field} with the new name, - * the same {@link Type}, and index, otherwise it returns {@code this} {@link Field}. - */ - @Nonnull - public Field withName(@Nonnull final String newName) { - if (fieldNameOptional.map(fieldName -> fieldName.equals(newName)).orElse(false)) { - return this; - } else { - return Field.of(getFieldType(), Optional.of(newName), getFieldIndexOptional()); - } - } - @Nonnull public Field withNullability(boolean newNullability) { if (getFieldType().isNullable() == newNullability) { @@ -2777,6 +2780,20 @@ public PRecordType.PField toProto(@Nonnull final PlanSerializationContext serial return fieldProtoBuilder.build(); } + @Nonnull + private static Field fromDescriptor(@Nonnull Descriptors.FieldDescriptor fieldDescriptor, boolean preserveNames) { + final Type fieldType = Type.fromProtoType(Type.getTypeSpecificDescriptor(fieldDescriptor), + fieldDescriptor.getType(), + fieldDescriptor.toProto().getLabel(), + fieldDescriptor.getOptions(), + !fieldDescriptor.isRequired(), + preserveNames); + return new Field(fieldType, + Optional.of(ProtoUtils.toUserIdentifier(fieldDescriptor.getName())), + Optional.of(fieldDescriptor.getNumber()), + Optional.of(fieldDescriptor.getName())); + } + @Nonnull public static Field fromProto(@Nonnull final PlanSerializationContext serializationContext, @Nonnull final PRecordType.PField fieldProto) { final Type fieldType = Type.fromTypeProto(serializationContext, Objects.requireNonNull(fieldProto.getFieldType())); 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 02e9e0662f..3960abe60c 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 @@ -145,7 +145,13 @@ public static PhysicalOperator fromProto(@Nonnull final PlanSerializationContext @Nonnull public static Descriptors.EnumValueDescriptor stringToEnumValue(Descriptors.EnumDescriptor enumDescriptor, String value) { - final var maybeValue = enumDescriptor.findValueByName(ProtoUtils.toProtoBufCompliantName(value)); + Descriptors.EnumValueDescriptor maybeValue = null; + for (Descriptors.EnumValueDescriptor valueDescriptor : enumDescriptor.getValues()) { + if (ProtoUtils.toUserIdentifier(valueDescriptor.getName()).equals(value)) { + maybeValue = valueDescriptor; + break; + } + } SemanticException.check(maybeValue != null, SemanticException.ErrorCode.INVALID_ENUM_VALUE, value); return maybeValue; } 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 875a8ce2be..3dfb725240 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 @@ -28,12 +28,15 @@ import com.apple.foundationdb.record.TestRecords4WrapperProto; import com.apple.foundationdb.record.TestRecordsUuidProto; import com.apple.foundationdb.record.TupleFieldsProto; +import com.apple.foundationdb.record.TypeTestProto; import com.apple.foundationdb.record.planprotos.PType; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository; import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; +import com.apple.foundationdb.record.util.ProtoUtils; import com.apple.foundationdb.record.util.RandomUtil; import com.apple.foundationdb.record.util.pair.Pair; +import com.apple.test.BooleanSource; import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; import com.google.common.collect.Streams; @@ -43,6 +46,7 @@ import com.google.protobuf.DynamicMessage; import com.google.protobuf.Message; import org.assertj.core.api.AutoCloseableSoftAssertions; +import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -59,6 +63,7 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -551,7 +556,7 @@ void createEnumProtobuf(@Nonnull Type.Enum enumType, @Nullable String expectedSt assertThat(enumDescriptor.getName()) .isEqualTo(enumTypeName); - final Type.Enum fromProto = Type.Enum.fromValues(enumType.isNullable(), Type.Enum.enumValuesFromProto(enumDescriptor.getValues())); + final Type.Enum fromProto = Type.Enum.fromDescriptor(enumType.isNullable(), enumDescriptor); assertThat(fromProto) .isEqualTo(enumType); } @@ -698,4 +703,274 @@ void updateFieldNullability(@Nonnull Type.Record recordType) { .isEqualTo(nonNullableField.getFieldStorageNameOptional()); } } + + /** + * Validate all primitive types can be parsed as field elements of a type. If this test fails after introducing + * a new primitive type, update the Protobuf definition in {@code type_test.proto} or exclude it from consideration + * if its type should not automatically be inferred for some reason. + */ + @Test + void testPrimitivesFromDescriptor() { + final Descriptors.Descriptor descriptor = TypeTestProto.PrimitiveFields.getDescriptor(); + final Type.Record type = Type.Record.fromDescriptor(descriptor); + + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + for (Type.TypeCode typeCode : Type.TypeCode.values()) { + if (!typeCode.isPrimitive() || typeCode == Type.TypeCode.NULL || typeCode == Type.TypeCode.UNKNOWN || typeCode == Type.TypeCode.VERSION || typeCode == Type.TypeCode.VECTOR) { + continue; + } + Type primitiveType = Type.primitiveType(typeCode); + final String nameBase = typeCode.name().toLowerCase(Locale.ROOT) + "_field"; + + // Should contain nullable, not-nullable, nullable array, and not-nullable array fields + assertContainsField(softly, type, nameBase, primitiveType.nullable(), descriptor); + assertContainsField(softly, type, "req_" + nameBase, primitiveType.notNullable(), descriptor); + assertContainsField(softly, type, "arr_" + nameBase, new Type.Array(true, primitiveType.notNullable()), descriptor); + assertContainsField(softly, type, "req_arr_" + nameBase, new Type.Array(false, primitiveType.notNullable()), descriptor); + } + + // Ensure all fields have been picked up + final List fieldDescriptors = descriptor.getFields(); + for (Descriptors.FieldDescriptor fieldDescriptor : fieldDescriptors) { + softly.assertThat(type.getFieldNameFieldMap()) + .containsKey(fieldDescriptor.getName()); + } + } + } + + /** + * Simple case of resolving fields from a protobuf descriptor. None of the field names require any messaging for escape sequences. + */ + @ParameterizedTest(name = "testSimpleFromDescriptor[preserveNames={0}]") + @BooleanSource + void testSimpleFromDescriptor(boolean preserveNames) { + final Descriptors.Descriptor descriptor = TypeTestProto.Type1.getDescriptor(); + final Type.Record type = preserveNames ? Type.Record.fromDescriptorPreservingName(descriptor) : Type.Record.fromDescriptor(descriptor); + + final Descriptors.Descriptor outerDescriptor = TypeTestProto.Type2.getDescriptor(); + final Type.Record outerType = preserveNames ? Type.Record.fromDescriptorPreservingName(outerDescriptor) : Type.Record.fromDescriptor(outerDescriptor); + + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + assertContainsField(softly, type, "alpha", Type.primitiveType(Type.TypeCode.STRING, true), descriptor); + assertContainsField(softly, type, "beta", Type.primitiveType(Type.TypeCode.LONG, false), descriptor); + assertContainsField(softly, type, "uuid_field", Type.UUID_NULL_INSTANCE, descriptor); + assertTypeNames(softly, type, descriptor, preserveNames); + assertTypeNames(softly, roundTrip(type), descriptor, preserveNames); + + assertContainsField(softly, outerType, "alpha", type.nullable(), outerDescriptor); + assertContainsField(softly, outerType, "beta", type.notNullable(), outerDescriptor); + final Type.Enum enumType = Type.Enum.fromValues(true, + List.of(Type.Enum.EnumValue.from("ALPHA", 0), Type.Enum.EnumValue.from("BRAVO", 1), Type.Enum.EnumValue.from("CHARLIE", 3), Type.Enum.EnumValue.from("DELTA", 4))); + assertContainsField(softly, outerType, "gamma", enumType, outerDescriptor); + assertContainsField(softly, outerType, "delta", new Type.Array(true, type.notNullable()), outerDescriptor); + assertContainsField(softly, outerType, "epsilon", new Type.Array(false, type.notNullable()), outerDescriptor); + assertContainsField(softly, outerType, "zeta", new Type.Array(true, enumType.notNullable()), outerDescriptor); + assertContainsField(softly, outerType, "eta", new Type.Array(false, enumType.notNullable()), outerDescriptor); + assertTypeNames(softly, outerType, outerDescriptor, preserveNames); + assertTypeNames(softly, roundTrip(outerType), outerDescriptor, preserveNames); + } + } + + /** + * Test where a type descriptor has some field names that require escaping. In this case, all the field translations + * are reversible, so it doesn't matter if the name is preserved from the protobuf file or re-calculated using + * {@link com.apple.foundationdb.record.util.ProtoUtils#toProtoBufCompliantName(String)}. + */ + @ParameterizedTest(name = "testEscapesFromDescriptor[preserveNames={0}]") + @BooleanSource + void testEscapesFromDescriptor(boolean preserveNames) { + final Descriptors.Descriptor descriptor = TypeTestProto.Type1__0Escaped.getDescriptor(); + final Type.Record type = preserveNames ? Type.Record.fromDescriptorPreservingName(descriptor) : Type.Record.fromDescriptor(descriptor); + + final Descriptors.Descriptor outerDescriptor = TypeTestProto.Type2__0Escaped.getDescriptor(); + final Type.Record outerType = preserveNames ? Type.Record.fromDescriptorPreservingName(outerDescriptor) : Type.Record.fromDescriptor(outerDescriptor); + + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + assertContainsField(softly, type, "alpha$", Type.primitiveType(Type.TypeCode.STRING, true), descriptor.findFieldByName("alpha__1")); + assertContainsField(softly, type, "beta.", Type.primitiveType(Type.TypeCode.LONG, false), descriptor.findFieldByName("beta__2")); + assertContainsField(softly, type, "uuid__field", Type.UUID_NULL_INSTANCE, descriptor.findFieldByName("uuid__0field")); + assertTypeNames(softly, type, descriptor, preserveNames); + assertTypeNames(softly, roundTrip(type), descriptor, preserveNames); + + assertContainsField(softly, outerType, "alpha$", type.nullable(), outerDescriptor.findFieldByName("alpha__1")); + assertContainsField(softly, outerType, "beta.", type.notNullable(), outerDescriptor.findFieldByName("beta__2")); + final Type.Enum enumType = Type.Enum.fromValues(true, + List.of(Type.Enum.EnumValue.from("ALPHA__", 0), Type.Enum.EnumValue.from("BRAVO$", 1), Type.Enum.EnumValue.from("CHARLIE.", 4), Type.Enum.EnumValue.from("DELTA__3", 3))); + assertContainsField(softly, outerType, "gamma__", enumType, outerDescriptor.findFieldByName("gamma__0")); + assertContainsField(softly, outerType, "delta.array", new Type.Array(true, type.notNullable()), outerDescriptor.findFieldByName("delta__2array")); + assertContainsField(softly, outerType, "epsilon.array", new Type.Array(false, type.notNullable()), outerDescriptor.findFieldByName("epsilon__2array")); + assertContainsField(softly, outerType, "zeta.array", new Type.Array(true, enumType.notNullable()), outerDescriptor.findFieldByName("zeta__2array")); + assertContainsField(softly, outerType, "eta.array", new Type.Array(false, enumType.notNullable()), outerDescriptor.findFieldByName("eta__2array")); + assertTypeNames(softly, outerType, outerDescriptor, preserveNames); + assertTypeNames(softly, roundTrip(outerType), outerDescriptor, preserveNames); + } + } + + /** + * Test where a type descriptor has some field names that require escaping, but the original fields are imperfect. + * The escaped names which are stored are not reversible back to the original field names. This can happen + * if we have an unescaped double-underscore in the field. To make sure the storage names that come back from, say, + * placing the field in a type repository, we need to preserve the original field names. + */ + @ParameterizedTest(name = "testEscapesMalformedFromDescriptor[preserveNames={0}]") + @BooleanSource + void testEscapesMalformedFromDescriptor(boolean preserveNames) { + final Descriptors.Descriptor descriptor = TypeTestProto.Type1__EscapingNotReversible.getDescriptor(); + final Type.Record type = preserveNames ? Type.Record.fromDescriptorPreservingName(descriptor) : Type.Record.fromDescriptor(descriptor); + + final Descriptors.Descriptor outerDescriptor = TypeTestProto.Type2__EscapedNotReversible.getDescriptor(); + final Type.Record outerType = preserveNames ? Type.Record.fromDescriptorPreservingName(outerDescriptor) : Type.Record.fromDescriptor(outerDescriptor); + + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + assertContainsField(softly, type, "alpha$__blah", Type.primitiveType(Type.TypeCode.STRING, true), descriptor.findFieldByName("alpha__1__blah")); + assertContainsField(softly, type, "beta.__", Type.primitiveType(Type.TypeCode.LONG, false), descriptor.findFieldByName("beta__2__")); + assertContainsField(softly, type, "uuid__$field", Type.UUID_NULL_INSTANCE, descriptor.findFieldByName("uuid____1field")); + assertTypeNames(softly, type, descriptor, preserveNames); + assertTypeNames(softly, roundTrip(type), descriptor, preserveNames); + + assertContainsField(softly, outerType, "alpha__blah", type.nullable(), outerDescriptor.findFieldByName("alpha__blah")); + assertContainsField(softly, outerType, "beta.__", type.notNullable(), outerDescriptor.findFieldByName("beta__2__")); + final Type.Enum enumType = Type.Enum.fromValues(true, + List.of(Type.Enum.EnumValue.from("ALPHA__", 0), Type.Enum.EnumValue.from("BRAVO_$", 1), Type.Enum.EnumValue.from("CHARLIE__.", 7), Type.Enum.EnumValue.from("DELTA__3", 4))); + assertContainsField(softly, outerType, "gamma__", enumType, outerDescriptor.findFieldByName("gamma__")); + assertContainsField(softly, outerType, "delta__array", new Type.Array(true, type.notNullable()), outerDescriptor.findFieldByName("delta__array")); + assertContainsField(softly, outerType, "epsilon__array", new Type.Array(false, type.notNullable()), outerDescriptor.findFieldByName("epsilon__array")); + assertTypeNames(softly, outerType, outerDescriptor, preserveNames); + assertTypeNames(softly, roundTrip(outerType), outerDescriptor, preserveNames); + } + } + + private static void assertContainsField(@Nonnull SoftAssertions softly, @Nonnull Type.Record recordType, @Nonnull String fieldName, @Nonnull Type fieldType, @Nonnull Descriptors.Descriptor messageDescriptor) { + final Descriptors.FieldDescriptor fieldDescriptor = messageDescriptor.findFieldByName(fieldName); + softly.assertThat(fieldDescriptor) + .as("field %s not found in descriptor", fieldDescriptor) + .isNotNull(); + assertContainsField(softly, recordType, fieldName, fieldType, fieldDescriptor); + } + + private static void assertContainsField(@Nonnull SoftAssertions softly, @Nonnull Type.Record recordType, @Nonnull String fieldName, @Nonnull Type fieldType, @Nullable Descriptors.FieldDescriptor fieldDescriptor) { + final Map fieldMap = recordType.getFieldNameFieldMap(); + softly.assertThat(fieldMap) + .as("should have had field %s", fieldName) + .containsKey(fieldName); + final Type.Record.Field field = fieldMap.get(fieldName); + if (field == null) { + return; + } + softly.assertThat(field.getFieldType()) + .as("field %s had unexpected type", fieldName) + .isEqualTo(fieldType); + softly.assertThat(field.getFieldName()) + .as("field %s had unexpected name", fieldName) + .isEqualTo(fieldName); + if (fieldDescriptor == null) { + softly.fail("cannot compare with null field descriptor for field %s", fieldName); + } else { + softly.assertThat(field.getFieldStorageName()) + .as("field %s had unexpected storage name", fieldName) + .isEqualTo(fieldDescriptor.getName()); + if (field.getFieldType() instanceof Type.Enum) { + Type.Enum fieldEnumType = (Type.Enum) field.getFieldType(); + final List storageNames = fieldEnumType.getEnumValues().stream().map(Type.Enum.EnumValue::getStorageName).collect(Collectors.toList()); + final List expectedStorageNames = fieldDescriptor.getEnumType().getValues().stream().map(Descriptors.EnumValueDescriptor::getName).collect(Collectors.toList()); + softly.assertThat(storageNames) + .as("field %s enum type should have same names as stored enum", fieldName) + .isEqualTo(expectedStorageNames); + + final List numbers = fieldEnumType.getEnumValues().stream().map(Type.Enum.EnumValue::getNumber).collect(Collectors.toList()); + final List expectedNumbers = fieldDescriptor.getEnumType().getValues().stream().map(Descriptors.EnumValueDescriptor::getNumber).collect(Collectors.toList()); + softly.assertThat(numbers) + .as("field %s enum type should have same numbers as stored enum", fieldName) + .isEqualTo(expectedNumbers); + } + } + } + + private static void assertTypeNames(@Nonnull SoftAssertions softly, @Nonnull Type.Record recordType, @Nonnull Descriptors.Descriptor descriptor, boolean preserveNames) { + if (preserveNames) { + softly.assertThat(recordType.getName()) + .isEqualTo(ProtoUtils.toUserIdentifier(descriptor.getName())); + softly.assertThat(recordType.getStorageName()) + .isEqualTo(descriptor.getName()); + } else { + softly.assertThat(recordType.getName()) + .isNull(); + softly.assertThat(recordType.getStorageName()) + .isNull(); + + } + for (Type.Record.Field field : recordType.getFields()) { + Type fieldType = field.getFieldType(); + Descriptors.FieldDescriptor fieldDescriptor = descriptor.findFieldByName(field.getFieldStorageName()); + softly.assertThat(fieldDescriptor) + .as("field descriptor not found for field %s", field) + .isNotNull(); + if (fieldDescriptor == null) { + // Can't proceed further + return; + } + + // Extract the element from arrays. If the type is not nullable, we just replace the field + // type with its element type. If the field is nullable, we need to also replace the + // field descriptor so it points to the underlying values. + if (fieldType instanceof Type.Array) { + Type.Array fieldArrayType = (Type.Array) fieldType; + fieldType = fieldArrayType.getElementType(); + if (fieldArrayType.isNullable()) { + fieldDescriptor = fieldDescriptor.getMessageType().findFieldByName("values"); + } + softly.assertThat(fieldDescriptor.isRepeated()) + .as("array field %s should be over repeated field", field) + .isTrue(); + } + + // Look to make sure enum and record names are preserved (or not) + if (fieldType instanceof Type.Enum) { + final Type.Enum fieldEnumType = (Type.Enum) fieldType; + if (preserveNames) { + final Descriptors.EnumDescriptor fieldEnumDescriptor = fieldDescriptor.getEnumType(); + softly.assertThat(fieldEnumType.getName()) + .isEqualTo(ProtoUtils.toUserIdentifier(fieldEnumDescriptor.getName())); + softly.assertThat(fieldEnumType.getStorageName()) + .isEqualTo(fieldEnumDescriptor.getName()); + } else { + softly.assertThat(fieldEnumType.getName()) + .isNull(); + softly.assertThat(fieldEnumType.getStorageName()) + .isNull(); + } + } else if (fieldType instanceof Type.Record) { + final Type.Record fieldRecordType = (Type.Record) fieldType; + final Descriptors.Descriptor fieldTypeDescriptor = fieldDescriptor.getMessageType(); + if (preserveNames) { + softly.assertThat(fieldRecordType.getName()) + .isEqualTo(ProtoUtils.toUserIdentifier(fieldTypeDescriptor.getName())); + softly.assertThat(fieldRecordType.getStorageName()) + .isEqualTo(fieldTypeDescriptor.getName()); + } else { + softly.assertThat(fieldRecordType.getName()) + .isNull(); + softly.assertThat(fieldRecordType.getStorageName()) + .isNull(); + } + + // Recurse down + assertTypeNames(softly, fieldRecordType, fieldTypeDescriptor, preserveNames); + } + } + } + + /** + * Send a type to Protobuf and back. This allows us to check that certain features are preserved even in the face + * of type serialization/deserialization. + * + * @param type the type to serialize and deserialize + * @return the deserialized type + */ + @SuppressWarnings("unchecked") + @Nonnull + private static T roundTrip(@Nonnull T type) { + return (T) Type.fromTypeProto(PlanSerializationContext.newForCurrentMode(), + type.toTypeProto(PlanSerializationContext.newForCurrentMode())); + } } diff --git a/fdb-record-layer-core/src/test/proto/type_test.proto b/fdb-record-layer-core/src/test/proto/type_test.proto new file mode 100644 index 0000000000..2ac1a8ee3a --- /dev/null +++ b/fdb-record-layer-core/src/test/proto/type_test.proto @@ -0,0 +1,174 @@ +/* + * type_test.proto + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.apple.foundationdb.record.type_test; + +option java_package = "com.apple.foundationdb.record"; +option java_outer_classname = "TypeTestProto"; + +import "tuple_fields.proto"; + +message PrimitiveFields { + // Optional variants: + optional bool boolean_field = 1; + optional bytes bytes_field = 2; + optional double double_field = 3; + optional float float_field = 4; + optional int32 int_field = 5; + optional int64 long_field = 6; + optional string string_field = 7; + + // Required variants: + required bool req_boolean_field = 101; + required bytes req_bytes_field = 102; + required double req_double_field = 103; + required float req_float_field = 104; + required int32 req_int_field = 105; + required int64 req_long_field = 106; + required string req_string_field = 107; + + // Nullable arrays + message BooleanArray { + repeated bool values = 1; + } + optional BooleanArray arr_boolean_field = 201; + message BytesArray { + repeated bytes values = 1; + } + optional BytesArray arr_bytes_field = 202; + message DoubleArray { + repeated double values = 1; + } + optional DoubleArray arr_double_field = 203; + message FloatArray { + repeated float values = 1; + } + optional FloatArray arr_float_field = 204; + message IntArray { + repeated int32 values = 1; + } + optional IntArray arr_int_field = 205; + message LongArray { + repeated int64 values = 1; + } + optional LongArray arr_long_field = 206; + message StringArray { + repeated string values = 1; + } + optional StringArray arr_string_field = 207; + + // Non-nullable arrays: + repeated bool req_arr_boolean_field = 301; + repeated bytes req_arr_bytes_field = 302; + repeated double req_arr_double_field = 303; + repeated float req_arr_float_field = 304; + repeated int32 req_arr_int_field = 305; + repeated int64 req_arr_long_field = 306; + repeated string req_arr_string_field = 307; +} + +message Type1 { + optional string alpha = 1; + required int64 beta = 2; + optional UUID uuid_field = 4; +} + +message Type2 { + optional Type1 alpha = 1; + required Type1 beta = 5; + + enum T2Enum { + ALPHA = 0; + BRAVO = 1; + CHARLIE = 3; + DELTA = 4; + } + optional T2Enum gamma = 3; + + message Type1Array { + repeated Type1 values = 1; + } + optional Type1Array delta = 6; + repeated Type1 epsilon = 4; + + message T2EnumArray { + repeated T2Enum values = 1; + } + optional T2EnumArray zeta = 7; + repeated T2Enum eta = 8; +} + +message Type1__0Escaped { + optional string alpha__1 = 1; + required int64 beta__2 = 2; + optional UUID uuid__0field = 5; +} + +message Type2__0Escaped { + optional Type1__0Escaped alpha__1 = 1; + required Type1__0Escaped beta__2 = 6; + + enum T2Enum__0Escaped { + ALPHA__0 = 0; + BRAVO__1 = 1; + CHARLIE__2 = 4; + DELTA__03 = 3; + } + optional T2Enum__0Escaped gamma__0 = 3; + + message Type1__2Array { + repeated Type1__0Escaped values = 1; + } + optional Type1__2Array delta__2array = 4; + repeated Type1__0Escaped epsilon__2array = 5; + + message T2Enum__0EscapedArray { + repeated T2Enum__0Escaped values = 1; + } + optional T2Enum__0EscapedArray zeta__2array = 7; + repeated T2Enum__0Escaped eta__2array = 8; +} + +message Type1__EscapingNotReversible { + optional string alpha__1__blah = 1; + required int64 beta__2__ = 2; + optional UUID uuid____1field = 6; +} + +message Type2__EscapedNotReversible { + optional Type1__EscapingNotReversible alpha__blah = 1; + required Type1__EscapingNotReversible beta__2__ = 7; + + enum T2__EnumEscapedNotReversible { + ALPHA__ = 0; + BRAVO___1 = 1; + CHARLIE____2 = 7; + DELTA__3 = 4; + } + optional T2__EnumEscapedNotReversible gamma__ = 3; + + message Type1__Array { + repeated Type1__EscapingNotReversible values = 1; + } + optional Type1__Array delta__array = 4; + repeated Type1__EscapingNotReversible epsilon__array = 5; +} diff --git a/fdb-relational-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 4b6e6b4e1f..4768c52196 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 @@ -40,7 +40,6 @@ 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; @@ -97,7 +96,7 @@ private RecordLayerSchemaTemplate.Builder deserializeRecordMetaData() { 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()), ProtoUtils.toUserIdentifier(registeredType.getName()), registeredType.getName()); + final var recordLayerType = Type.Enum.fromDescriptorPreservingNames(false, registeredType.getEnumType()); schemaTemplateBuilder.addAuxiliaryType((DataType.Named) DataTypeUtils.toRelationalType(recordLayerType)); break; default: @@ -131,37 +130,16 @@ private RecordLayerSchemaTemplate.Builder deserializeRecordMetaData() { @Nonnull private RecordLayerTable.Builder generateTableBuilder(@Nonnull final String userName, @Nonnull final String storageName) { - return generateTableBuilder(userName, recordMetaData.getRecordType(storageName)); - } + final RecordType recordType = recordMetaData.getRecordType(storageName); - @Nonnull - 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 = new Type.Record(userName, recordType.getName(), false, - Type.Record.fromDescriptor(recordType.getDescriptor()).getFields()); // todo (yhatem) this is hacky and must be cleaned up. We need to understand the actually field types so we can take decisions - // on higher level based on these types (wave3). - if (recordLayerType.getFields().stream().anyMatch(f -> f.getFieldType().isRecord())) { - ImmutableList.Builder newFields = ImmutableList.builder(); - for (int i = 0; i < recordLayerType.getFields().size(); i++) { - final var protoField = recordType.getDescriptor().getFields().get(i); - final var field = recordLayerType.getField(i); - if (field.getFieldType().isRecord()) { - final String fieldTypeName = protoField.getMessageType().getName(); - Type.Record r = new Type.Record(ProtoUtils.toUserIdentifier(fieldTypeName), fieldTypeName, 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(new Type.Record(userName, recordType.getName(), false, newFields.build())) - .setPrimaryKey(recordType.getPrimaryKey()) - .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(recordType.getName(), index)).collect(Collectors.toSet())); - } + // on higher level based on these types (wave3). + final var recordLayerType = Type.Record.fromDescriptorPreservingName(recordType.getDescriptor()); return RecordLayerTable.Builder .from(recordLayerType) + .setName(userName) .setPrimaryKey(recordType.getPrimaryKey()) .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(recordType.getName(), index)).collect(Collectors.toSet())); } diff --git a/yaml-tests/src/test/java/MetaDataExportUtilityTests.java b/yaml-tests/src/test/java/MetaDataExportUtilityTests.java index fb33ca765e..e349a18a7f 100644 --- a/yaml-tests/src/test/java/MetaDataExportUtilityTests.java +++ b/yaml-tests/src/test/java/MetaDataExportUtilityTests.java @@ -71,6 +71,7 @@ void createValidIdentifiersMetaData() throws IOException { metaDataBuilder.addIndex(metaDataBuilder.getRecordType("T2"), new Index("T2$T2.COL1", "T2__1COL1")); metaDataBuilder.addIndex(metaDataBuilder.getRecordType("___T6__2__UNESCAPED"), new Index("T6$COL2", "__T6__2COL2__VALUE")); + metaDataBuilder.addIndex(metaDataBuilder.getRecordType("___T6__2__UNESCAPED"), new Index("T6$ENUM2", "T6__1__ENUM_2")); metaDataBuilder.addUserDefinedFunction(new RawSqlFunction("__func__T3$col2", "CREATE FUNCTION \"__func__T3$col2\"(in \"x$\" bigint) AS select \"__T3$COL1\" as \"c.1\", \"__T3$COL3\" as \"c.2\" from \"__T3\" WHERE \"__T3$COL2\" = \"x$\"")); metaDataBuilder.addView(new View("T4$view", diff --git a/yaml-tests/src/test/proto/identifiers.proto b/yaml-tests/src/test/proto/identifiers.proto index 558f817e35..894852b877 100644 --- a/yaml-tests/src/test/proto/identifiers.proto +++ b/yaml-tests/src/test/proto/identifiers.proto @@ -75,10 +75,19 @@ message T5__UNESCAPED { int64 __T5__COL2 = 3; } +enum T6__2__ENUM__0 { + A = 0; + B__0 = 1; + C__ = 2; + __D__2__ = 3; +} + message ___T6__2__UNESCAPED { int64 ID = 1; int64 T6__1__COL1__0 = 2; int64 __T6__2COL2__VALUE = 3; + optional T6__2__ENUM__0 T6__1__ENUM_1 = 4; + optional T6__2__ENUM__0 T6__1__ENUM_2 = 5; } message RecordTypeUnion { diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb index 0a9e10f97a..693f8251a4 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -840,67 +840,122 @@ unnamed-12JEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} 7 -unnamed-12)EXPLAIN SELECT * FROM "___T6.__UNESCAPED" -H (08@ISCAN(T6$COL2 <,>)digraph G { +unnamed-12)EXPLAIN SELECT * FROM "___T6.__UNESCAPED" +lb -(+0&8*@ISCAN(T6$ENUM2 <,>)digraph G { fontname=courier; rankdir=BT; splines=polyline; - 1 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; - 2 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 1 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} P -unnamed-12BEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 -Q (0,8%@EISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) digraph G { +unnamed-12BEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 +粜n ˦8(,0Z86@FISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) digraph G { fontname=courier; rankdir=BT; splines=polyline; - 1 [ label=<
Predicate Filter
WHERE q2.T6$__COL1__ EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; - 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; - 3 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 1 [ label=<
Predicate Filter
WHERE q2.T6$__COL1__ EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} e -unnamed-12WEXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 -c ͦ(038'@ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)digraph G { +unnamed-12WEXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 + O()089@ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)digraph G { fontname=courier; rankdir=BT; splines=polyline; - 1 [ label=<
Value Computation
MAP (q43.ID AS ID, q43.__T6.COL2__VALUE AS __T6.COL2__VALUE)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T6.COL2__VALUE)" ]; - 2 [ label=<
Predicate Filter
WHERE q2.T6$__COL1__ EQUALS promote(@c10 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; - 3 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; - 4 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 1 [ label=<
Value Computation
MAP (q60.ID AS ID, q60.__T6.COL2__VALUE AS __T6.COL2__VALUE)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T6$__COL1__ EQUALS promote(@c10 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 4 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 2 -> 1 [ label=< q43> label="q43" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} + 2 -> 1 [ label=< q60> label="q60" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + U -unnamed-12GEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 -ҴU ے(0'8%@1ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])digraph G { +unnamed-12GEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 +r Թ=(-0J86@1ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])digraph G { fontname=courier; rankdir=BT; splines=polyline; - 1 [ label=<
Index Scan
comparisons: [[LESS_THAN promote(@c8 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; - 2 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 1 [ label=<
Index Scan
comparisons: [[LESS_THAN promote(@c8 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} j -unnamed-12\EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 -άn ("080@COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) digraph G { +unnamed-12\EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + I(/08B@COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q92.ID AS ID, q92.__T6.COL2__VALUE AS __T6.COL2__VALUE)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T6.COL2__VALUE)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [[LESS_THAN promote(@c10 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q92> label="q92" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +V + +unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' +n Ɓ?(,0F86@gISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM)digraph G { fontname=courier; rankdir=BT; splines=polyline; - 1 [ label=<
Value Computation
MAP (q65.ID AS ID, q65.__T6.COL2__VALUE AS __T6.COL2__VALUE)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T6.COL2__VALUE)" ]; - 2 [ label=<
Covering Index Scan
comparisons: [[LESS_THAN promote(@c10 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; - 3 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE)" ]; + 1 [ label=<
Predicate Filter
WHERE q2.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 2 -> 1 [ label=< q65> label="q65" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +f + +unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' +ˆ O()0뺕89@ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q60.ID AS ID, q60.T6$__ENUM_2 AS T6$__ENUM_2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 4 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q60> label="q60" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + +V + +unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + +ĺp 8(-0;86@NISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)]) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Index Scan
comparisons: [EQUALS promote(@c8 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +f + +unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_1" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + Ϭ>(+0n8;@ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2.ID AS ID, q2.T6$__ENUM_1 AS T6$__ENUM_1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1)" ]; + 2 [ label=<
Index Scan
comparisons: [EQUALS promote(@c10 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; } \ No newline at end of file diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml index 6350c3734f..9acfbdff8a 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -634,56 +634,101 @@ unnamed-12: insert_new_count: 21 insert_reused_count: 2 - query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" - explain: ISCAN(T6$COL2 <,>) - task_count: 296 - task_total_time_ms: 10 - transform_count: 72 - transform_time_ms: 3 - transform_yield_count: 29 + explain: ISCAN(T6$ENUM2 <,>) + task_count: 405 + task_total_time_ms: 227 + transform_count: 98 + transform_time_ms: 95 + transform_yield_count: 43 insert_time_ms: 0 - insert_new_count: 29 - insert_reused_count: 3 + insert_new_count: 42 + insert_reused_count: 5 - query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 - explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) - task_count: 351 - task_total_time_ms: 12 - transform_count: 81 - transform_time_ms: 5 - transform_yield_count: 30 - insert_time_ms: 0 - insert_new_count: 37 - insert_reused_count: 2 + explain: ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) + task_count: 484 + task_total_time_ms: 346 + transform_count: 110 + transform_time_ms: 118 + transform_yield_count: 44 + insert_time_ms: 1 + insert_new_count: 54 + insert_reused_count: 3 - query: EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) - task_count: 375 - task_total_time_ms: 14 - transform_count: 99 - transform_time_ms: 4 - transform_yield_count: 28 - insert_time_ms: 0 - insert_new_count: 39 - insert_reused_count: 4 + task_count: 520 + task_total_time_ms: 297 + transform_count: 136 + transform_time_ms: 165 + transform_yield_count: 41 + insert_time_ms: 3 + insert_new_count: 57 + insert_reused_count: 6 - query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 explain: ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]]) - task_count: 367 - task_total_time_ms: 15 - transform_count: 85 - transform_time_ms: 6 - transform_yield_count: 31 - insert_time_ms: 0 - insert_new_count: 37 - insert_reused_count: 3 + task_count: 500 + task_total_time_ms: 353 + transform_count: 114 + transform_time_ms: 128 + transform_yield_count: 45 + insert_time_ms: 1 + insert_new_count: 54 + insert_reused_count: 4 - query: EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 explain: 'COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)' - task_count: 463 - task_total_time_ms: 9 + task_count: 608 + task_total_time_ms: 410 + transform_count: 147 + transform_time_ms: 153 + transform_yield_count: 47 + insert_time_ms: 2 + insert_new_count: 66 + insert_reused_count: 6 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + explain: ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM) + task_count: 484 + task_total_time_ms: 334 transform_count: 110 - transform_time_ms: 3 - transform_yield_count: 34 + transform_time_ms: 132 + transform_yield_count: 44 + insert_time_ms: 1 + insert_new_count: 54 + insert_reused_count: 3 +- query: EXPLAIN SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" + = '__D.__' + explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2) + task_count: 520 + task_total_time_ms: 306 + transform_count: 136 + transform_time_ms: 166 + transform_yield_count: 41 + insert_time_ms: 2 + insert_new_count: 57 + insert_reused_count: 6 +- query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + explain: ISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)]) + task_count: 500 + task_total_time_ms: 383 + transform_count: 112 + transform_time_ms: 119 + transform_yield_count: 45 insert_time_ms: 0 - insert_new_count: 48 + insert_new_count: 54 insert_reused_count: 4 +- query: EXPLAIN SELECT ID, "T6$__ENUM_1" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" + = '__D.__' + explain: ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) + | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1) + task_count: 549 + task_total_time_ms: 315 + transform_count: 139 + transform_time_ms: 130 + transform_yield_count: 43 + insert_time_ms: 1 + insert_new_count: 59 + insert_reused_count: 5 diff --git a/yaml-tests/src/test/resources/valid-identifiers.yamsql b/yaml-tests/src/test/resources/valid-identifiers.yamsql index 4da84af6f6..9fb8782e0a 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.yamsql +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -544,14 +544,14 @@ setup: (30, 5, -1), (31, -1, -1) - query: INSERT INTO "___T6.__UNESCAPED" - VALUES (32, 10, 16), - (33, 10, 17), - (34, 10, 18), - (35, 10, 19), - (36, 10, 20), - (37, -1, 5), - (38, 5, -1), - (39, -1, -1) + VALUES (32, 10, 16, 'A', 'A'), + (33, 10, 17, 'B__', 'B__'), + (34, 10, 18, 'C__', 'C__'), + (35, 10, 19, '__D.__', '__D.__'), + (36, 10, 20, 'B__', 'A'), + (37, -1, 5, 'C__', 'B__'), + (38, 5, -1, '__D.__', 'C__'), + (39, -1, -1, 'A', '__D.__') --- test_block: connect: "jdbc:embed:/FRL/IDENTIFIERS_PROTO_YAML?schema=TEST" @@ -694,26 +694,26 @@ test_block: ] - - query: SELECT * FROM "___T6.__UNESCAPED" - - explain: "ISCAN(T6$COL2 <,>)" + - explain: "ISCAN(T6$ENUM2 <,>)" - unorderedResult: [ - {"ID": 32, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 16}, - {"ID": 33, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 17}, - {"ID": 34, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 18}, - {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19}, - {"ID": 36, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 20}, - {"ID": 37, "T6$__COL1__": -1, "__T6.COL2__VALUE" : 5}, - {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1}, - {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1}, + {"ID": 32, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 16, "T6$__ENUM_1": 'A', "T6$__ENUM_2": 'A' }, + {"ID": 33, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 17, "T6$__ENUM_1": 'B__', "T6$__ENUM_2": 'B__' }, + {"ID": 34, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 18, "T6$__ENUM_1": 'C__', "T6$__ENUM_2": 'C__' }, + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": '__D.__' }, + {"ID": 36, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 20, "T6$__ENUM_1": 'B__', "T6$__ENUM_2": 'A' }, + {"ID": 37, "T6$__COL1__": -1, "__T6.COL2__VALUE" : 5, "T6$__ENUM_1": 'C__', "T6$__ENUM_2": 'B__' }, + {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": 'C__' }, + {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": 'A', "T6$__ENUM_2": '__D.__' }, ] - - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 - - explain: "ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG)" + - explain: "ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG)" - unorderedResult: [ - {"ID": 32, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 16}, - {"ID": 33, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 17}, - {"ID": 34, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 18}, - {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19}, - {"ID": 36, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 20}, + {"ID": 32, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 16, "T6$__ENUM_1": 'A', "T6$__ENUM_2": 'A' }, + {"ID": 33, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 17, "T6$__ENUM_1": 'B__', "T6$__ENUM_2": 'B__' }, + {"ID": 34, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 18, "T6$__ENUM_1": 'C__', "T6$__ENUM_2": 'C__' }, + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": '__D.__' }, + {"ID": 36, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 20, "T6$__ENUM_1": 'B__', "T6$__ENUM_2": 'A' }, ] - - query: SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 @@ -729,9 +729,9 @@ test_block: - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 - explain: "ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])" - result: [ - {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1}, - {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1}, - {"ID": 37, "T6$__COL1__": -1, "__T6.COL2__VALUE" : 5}, + {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": 'C__' }, + {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": 'A', "T6$__ENUM_2": '__D.__' }, + {"ID": 37, "T6$__COL1__": -1, "__T6.COL2__VALUE" : 5, "T6$__ENUM_1": 'C__', "T6$__ENUM_2": 'B__' }, ] - - query: SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 @@ -741,6 +741,38 @@ test_block: {"ID": 39, "__T6.COL2__VALUE" : -1}, {"ID": 37, "__T6.COL2__VALUE" : 5}, ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + - explain: "ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM)" + - unorderedResult: [ + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": '__D.__' }, + {"ID": 38, "T6$__COL1__": 5, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": 'C__' }, + ] + - + - query: SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + - explain: "ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2)" + - unorderedResult: [ + {"ID": 35, "T6$__ENUM_2": '__D.__' }, + {"ID": 38, "T6$__ENUM_2": 'C__' }, + ] + - + - query: SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + - explain: "ISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)])" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"ID": 35, "T6$__COL1__": 10, "__T6.COL2__VALUE" : 19, "T6$__ENUM_1": '__D.__', "T6$__ENUM_2": '__D.__' }, + {"ID": 39, "T6$__COL1__": -1, "__T6.COL2__VALUE" : -1, "T6$__ENUM_1": 'A', "T6$__ENUM_2": '__D.__' }, + ] + - + - query: SELECT ID, "T6$__ENUM_1" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' + - explain: "ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1)" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"ID": 35, "T6$__ENUM_1": '__D.__' }, + {"ID": 39, "T6$__ENUM_1": 'A' }, + ] --- setup: connect: "jdbc:embed:/__SYS?schema=CATALOG" diff --git a/yaml-tests/src/test/resources/valid_identifiers_metadata.json b/yaml-tests/src/test/resources/valid_identifiers_metadata.json index 654e20e818..885f5f7589 100644 --- a/yaml-tests/src/test/resources/valid_identifiers_metadata.json +++ b/yaml-tests/src/test/resources/valid_identifiers_metadata.json @@ -140,6 +140,27 @@ "number": 3, "label": "LABEL_OPTIONAL", "type": "TYPE_INT64" + }, { + "name": "T6__1__ENUM_1", + "number": 4, + "label": "LABEL_OPTIONAL", + "type": "TYPE_ENUM", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T6__2__ENUM__0", + "oneofIndex": 0, + "proto3Optional": true + }, { + "name": "T6__1__ENUM_2", + "number": 5, + "label": "LABEL_OPTIONAL", + "type": "TYPE_ENUM", + "typeName": ".com.apple.foundationdb.relational.yamltests.generated.identifierstests.T6__2__ENUM__0", + "oneofIndex": 1, + "proto3Optional": true + }], + "oneofDecl": [{ + "name": "_T6__1__ENUM_1" + }, { + "name": "_T6__1__ENUM_2" }] }, { "name": "RecordTypeUnion", @@ -193,6 +214,21 @@ "name": "T3__2E__2C", "number": 2 }] + }, { + "name": "T6__2__ENUM__0", + "value": [{ + "name": "A", + "number": 0 + }, { + "name": "B__0", + "number": 1 + }, { + "name": "C__", + "number": 2 + }, { + "name": "__D__2__", + "number": 3 + }] }], "options": { "javaOuterClassname": "IdentifiersTestProto" @@ -227,6 +263,20 @@ "lastModifiedVersion": 2, "type": "value", "addedVersion": 2 + }, { + "recordType": ["___T6__2__UNESCAPED"], + "name": "T6$ENUM2", + "rootExpression": { + "field": { + "fieldName": "T6__1__ENUM_2", + "fanType": "SCALAR", + "nullInterpretation": "NOT_UNIQUE" + } + }, + "subspaceKey": "AlQ2JEVOVU0yAA==", + "lastModifiedVersion": 3, + "type": "value", + "addedVersion": 3 }], "recordTypes": [{ "name": "T4", @@ -326,7 +376,7 @@ } }], "splitLongRecords": false, - "version": 2, + "version": 3, "storeRecordVersions": false, "userDefinedFunctions": [{ "sqlFunction": { From 23da0ae7a7f3c357f3bf587c5ad3d7f43c695001 Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Fri, 21 Nov 2025 14:29:52 +0000 Subject: [PATCH 04/12] Address normen662 comments. - make sure to convert FieldKeyExpression#fieldName (which is internal) to user-facing name when constructing match candidates. - also, add tests for deeply nested (and repeated) structures with non-pb-compliant field names, and an index. --- .../expressions/FieldKeyExpression.java | 10 ++- .../KeyExpressionExpansionVisitor.java | 2 +- .../cascades/ScalarTranslationVisitor.java | 3 +- .../src/test/java/YamlIntegrationTests.java | 1 + .../resources/valid-identifiers.metrics.binpb | 54 +++++++++++++- .../resources/valid-identifiers.metrics.yaml | 29 ++++++++ .../test/resources/valid-identifiers.yamsql | 73 +++++++++++++------ 7 files changed, 144 insertions(+), 28 deletions(-) diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java index 9abd194543..ff4fbc1af1 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java @@ -33,6 +33,7 @@ import com.apple.foundationdb.record.query.plan.cascades.KeyExpressionVisitor; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.expressions.ExplodeExpression; +import com.apple.foundationdb.record.util.ProtoUtils; import com.google.common.collect.ImmutableList; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; @@ -57,6 +58,13 @@ public class FieldKeyExpression extends BaseKeyExpression implements AtomKeyExpression, KeyExpressionWithoutChildren { private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Field-Key-Expression"); + /** + * The internal field name used in the underlying protobuf message. This name may differ from the user-visible + * identifier to ensure compliance with protobuf field naming conventions. When working with query planning or + * user-facing operations, use {@link ProtoUtils#toUserIdentifier(String)} to convert this to the user-visible + * form. However, when constructing physical operators that directly interact with stored protobuf messages, + * this internal name should be used as-is. + */ @Nonnull private final String fieldName; @Nonnull @@ -216,7 +224,7 @@ public R expand(@Nonnull final KeyExpr public Quantifier.ForEach explodeField(@Nonnull Quantifier.ForEach baseQuantifier, @Nonnull List fieldNamePrefix) { final List fieldNames = ImmutableList.builder() .addAll(fieldNamePrefix) - .add(fieldName) + .add(ProtoUtils.toUserIdentifier(fieldName)) .build(); switch (fanType) { case FanOut: diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/KeyExpressionExpansionVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/KeyExpressionExpansionVisitor.java index a6de2f1fa0..19b2666024 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/KeyExpressionExpansionVisitor.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/KeyExpressionExpansionVisitor.java @@ -242,7 +242,7 @@ public GraphExpansion visitExpression(@Nonnull final NestingKeyExpression nestin case None: List newPrefix = ImmutableList.builder() .addAll(fieldNamePrefix) - .add(parent.getFieldName()) + .add(ProtoUtils.toUserIdentifier((parent.getFieldName()))) .build(); if (NullableArrayTypeUtils.isArrayWrapper(nestingKeyExpression)) { final RecordKeyExpressionProto.KeyExpression childProto = nestingKeyExpression.getChild().toKeyExpression(); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ScalarTranslationVisitor.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ScalarTranslationVisitor.java index fccff89881..da6b583626 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ScalarTranslationVisitor.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/ScalarTranslationVisitor.java @@ -170,9 +170,10 @@ public Value visitExpression(@Nonnull final NestingKeyExpression nestingKeyExpre final ScalarVisitorState state = getCurrentState(); final List fieldNamePrefix = state.getFieldNamePrefix(); final KeyExpression child = nestingKeyExpression.getChild(); + final String parentFieldName = ProtoUtils.toUserIdentifier(parent.getFieldName()); final List newPrefix = ImmutableList.builder() .addAll(fieldNamePrefix) - .add(parent.getFieldName()) + .add(parentFieldName) .build(); // TODO resolve type return pop(child.expand(push(state.withFieldNamePrefix(newPrefix)))); diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index 96df1e13e8..00a117c554 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -350,6 +350,7 @@ public void castTests(YamlTest.Runner runner) throws Exception { * @see MetaDataExportUtilityTests#createValidIdentifiersMetaData() for how the custom meta-data is generated */ @TestTemplate + @MaintainYamlTestConfig(YamlTestConfigFilters.CORRECT_EXPLAIN_AND_METRICS) public void validIdentifierTests(YamlTest.Runner runner) throws Exception { runner.runYamsql("valid-identifiers.yamsql"); } diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb index 693f8251a4..e346851e63 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -1,4 +1,56 @@ - +2 + + all-testsEXPLAIN select t.id from "foo.table$nested.repeated" as t, t."level0.field1" as b where exists (select "level2$array.field.1" from b."level1$field.1" where "level2$array.field.1" = 10)0 +O <( 08*@SCAN(<,>) | TFILTER foo__2table__1nested__2repeated | FLATMAP q0 -> { EXPLODE q0.level0.field1 | FLATMAP q1 -> { EXPLODE q1.level1$field.1 | FILTER _.level2$array.field.1 EQUALS promote(@c27 AS LONG) | MAP (_.level2$array.field.1 AS level2$array.field.1) | DEFAULT NULL | FILTER _ NOT_NULL AS q2 RETURN (1 AS _0) } AS q3 RETURN (q0.ID AS ID) }-digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Nested Loop Join
FLATMAP (q2.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Type Filter
WHERE record IS [foo__2table__1nested__2repeated]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2) AS level0.field1)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Nested Loop Join
FLATMAP (1 AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ]; + 6 [ label=<
Value Computation
EXPLODE q2.level0.field1
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2)" ]; + 7 [ label=<
Predicate Filter
WHERE q10 NOT_NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1)" ]; + 8 [ label=<
Value Computation
FIRST $q10 OR NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1)" ]; + 9 [ label=<
Value Computation
MAP (q43.level2$array.field.1 AS level2$array.field.1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1)" ]; + 10 [ label=<
Predicate Filter
WHERE q6.level2$array.field.1 EQUALS promote(@c27 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1, LONG AS level2$field.2)" ]; + 11 [ label=<
Value Computation
EXPLODE q4.level1$field.1
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1, LONG AS level2$field.2)" ]; + 3 -> 2 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 5 [ label=< q4> label="q4" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 5 [ label=< q10> label="q10" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 8 -> 7 [ label=< q10> label="q10" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 9 -> 8 [ label=< q10> label="q10" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 10 -> 9 [ label=< q43> label="q43" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 11 -> 10 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 1 [ label=< q51> label="q51" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + { + rank=same; + rankDir=LR; + 6 -> 7 [ color="red" style="invis" ]; + } + { + rank=same; + rankDir=LR; + 2 -> 5 [ color="red" style="invis" ]; + } +} +r + all-testseEXPLAIN select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" where id = 1 +]r D(!084@COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q49.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$field.1)" ]; + 2 [ label=<
Predicate Filter
WHERE q45.ID EQUALS promote(@c12 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2 AS level0.field1)" ]; + 3 [ label=<
Covering Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2 AS level0.field1)" ]; + 4 [ label=<
Index
foo.table$nested.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2 AS level0.field1)" ]; + 3 -> 2 [ label=< q45> label="q45" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q49> label="q49" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} = all-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 { diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml index 9acfbdff8a..7329c082a0 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -1,4 +1,33 @@ all-tests: +- query: EXPLAIN select t.id from "foo.table$nested.repeated" as t, t."level0.field1" + as b where exists (select "level2$array.field.1" from b."level1$field.1" where + "level2$array.field.1" = 10) + explain: SCAN(<,>) | TFILTER foo__2table__1nested__2repeated | FLATMAP q0 -> { + EXPLODE q0.level0.field1 | FLATMAP q1 -> { EXPLODE q1.level1$field.1 | FILTER + _.level2$array.field.1 EQUALS promote(@c27 AS LONG) | MAP (_.level2$array.field.1 + AS level2$array.field.1) | DEFAULT NULL | FILTER _ NOT_NULL AS q2 RETURN (1 + AS _0) } AS q3 RETURN (q0.ID AS ID) } + task_count: 471 + task_total_time_ms: 165 + transform_count: 173 + transform_time_ms: 127 + transform_yield_count: 32 + insert_time_ms: 7 + insert_new_count: 42 + insert_reused_count: 5 +- query: EXPLAIN select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" + where id = 1 + explain: 'COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: + [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) + | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)' + task_count: 474 + task_total_time_ms: 196 + transform_count: 114 + transform_time_ms: 143 + transform_yield_count: 33 + insert_time_ms: 10 + insert_new_count: 52 + insert_reused_count: 6 - 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, diff --git a/yaml-tests/src/test/resources/valid-identifiers.yamsql b/yaml-tests/src/test/resources/valid-identifiers.yamsql index 9fb8782e0a..48c2ac2409 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.yamsql +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -21,7 +21,17 @@ options: supported_version: !current_version --- schema_template: - CREATE TYPE AS STRUCT "foo.struct"(S1 bigint, S2 bigint) + create type as struct "nested.type$level2" ("level2$field.1" bigint, "level2$field.2" bigint) + create type as struct "nested.type$level1" ("level1$field.1" "nested.type$level2", "level1$field.2" "nested.type$level2") + create table "foo.table$nested" (id bigint, "level0.field1" "nested.type$level1", primary key(id)) + create index "foo.table$nested.idx" as select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" order by "level0.field1"."level1$field.1"."level2$field.1" + + create type as struct "nested.repeated.type$level2" ("level2$array.field.1" bigint, "level2$field.2" bigint) + create type as struct "nested.repeated.type$level1" ("level1$field.1" "nested.repeated.type$level2" array, "level1$field.2" "nested.type$level2") + create table "foo.table$nested.repeated" (id bigint, "level0.field1" "nested.repeated.type$level1" array, primary key(id)) + create index "foo.table$nested.repeated.idx" as select B."level2$array.field.1" from "foo.table$nested.repeated" as t, (select A."level2$array.field.1" from t."level0.field1" as Y, (select "level2$array.field.1" from Y."level1$field.1") A) B + + 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" @@ -71,30 +81,37 @@ schema_template: --- setup: steps: - - query: INSERT INTO "foo.tableA" - VALUES (1, 10, 1), + - query: insert into "foo.table$nested" + values (1, ((10, 20), (30, 40))), + (2, ((100, 200), (300, 400))), + (3, ((1000, 2000), (3000, 4000))) + - query: insert into "foo.table$nested.repeated" + values (1, [([(10, 20), (30, 40), (50, 60)], (400, 500))]), + (2, [([(100, 200), (300, 400), (500, 600)], (900, 1000))]) + - query: insert into "foo.tableA" + values (1, 10, 1), (2, 10, 2) - - query: INSERT INTO "foo.tableB" - VALUES (1, 20, (4, 40)), + - 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)), + - 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), + - 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), + - 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), + - query: insert into "my$adjacency$list" + values (1, -1), (2, 1), (3, 1), (4, 1), @@ -102,8 +119,8 @@ setup: (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'), + - 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'), @@ -120,13 +137,21 @@ test_block: preset: single_repetition_ordered tests: - - - query: INSERT INTO "foo.tableA" ("foo.tableA.A3", "foo.tableA.A1", "foo.tableA.A2") VALUES (3, 3, 10) + - 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: + - # select from nested repeated table + - query: select t.id from "foo.table$nested.repeated" as t, t."level0.field1" as b where exists (select "level2$array.field.1" from b."level1$field.1" where "level2$array.field.1" = 10) + - explain: "SCAN(<,>) | TFILTER foo__2table__1nested__2repeated | FLATMAP q0 -> { EXPLODE q0.level0.field1 | FLATMAP q1 -> { EXPLODE q1.level1$field.1 | FILTER _.level2$array.field.1 EQUALS promote(@c27 AS LONG) | MAP (_.level2$array.field.1 AS level2$array.field.1) | DEFAULT NULL | FILTER _ NOT_NULL AS q2 RETURN (1 AS _0) } AS q3 RETURN (q0.ID AS ID) }" + - result: [{1}] + - # select from nested table + - query: select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" where id = 1 + - explain: "COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)" + - result: [{10}] - # qualified star - query: select "foo.tableA".* from "foo.tableA"; @@ -507,26 +532,26 @@ setup: setup: connect: "jdbc:embed:/FRL/IDENTIFIERS_PROTO_YAML?schema=TEST" steps: - - query: INSERT INTO T1 - VALUES (1, 10, 1), + - 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), + - 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), + - 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), + - 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), From 535ad2f917666a07a9382a7fdb3f345e52775197 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Mon, 24 Nov 2025 11:36:06 +0000 Subject: [PATCH 05/12] Update valid identifier tests of query on exploded fields so that it matches an index --- .../src/test/java/YamlIntegrationTests.java | 1 - .../resources/valid-identifiers.metrics.binpb | 394 +++++++++--------- .../resources/valid-identifiers.metrics.yaml | 289 +++++++------ .../test/resources/valid-identifiers.yamsql | 22 +- 4 files changed, 371 insertions(+), 335 deletions(-) diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index 00a117c554..96df1e13e8 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -350,7 +350,6 @@ public void castTests(YamlTest.Runner runner) throws Exception { * @see MetaDataExportUtilityTests#createValidIdentifiersMetaData() for how the custom meta-data is generated */ @TestTemplate - @MaintainYamlTestConfig(YamlTestConfigFilters.CORRECT_EXPLAIN_AND_METRICS) public void validIdentifierTests(YamlTest.Runner runner) throws Exception { runner.runYamsql("valid-identifiers.yamsql"); } diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb index e346851e63..e477672be7 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -1,45 +1,43 @@ -2 - - all-testsEXPLAIN select t.id from "foo.table$nested.repeated" as t, t."level0.field1" as b where exists (select "level2$array.field.1" from b."level1$field.1" where "level2$array.field.1" = 10)0 -O <( 08*@SCAN(<,>) | TFILTER foo__2table__1nested__2repeated | FLATMAP q0 -> { EXPLODE q0.level0.field1 | FLATMAP q1 -> { EXPLODE q1.level1$field.1 | FILTER _.level2$array.field.1 EQUALS promote(@c27 AS LONG) | MAP (_.level2$array.field.1 AS level2$array.field.1) | DEFAULT NULL | FILTER _ NOT_NULL AS q2 RETURN (1 AS _0) } AS q3 RETURN (q0.ID AS ID) }-digraph G { + + + all-testsEXPLAIN select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.3" = 20) +b K(50ҩ8E@sCOVERING(foo.table$nested.repeated.idx.field.1.3 [EQUALS promote(@c25 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID) digraph G { fontname=courier; rankdir=BT; splines=polyline; - 1 [ label=<
Nested Loop Join
FLATMAP (q2.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; - 2 [ label=<
Type Filter
WHERE record IS [foo__2table__1nested__2repeated]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2) AS level0.field1)" ]; - 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; - 4 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; - 5 [ label=<
Nested Loop Join
FLATMAP (1 AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ]; - 6 [ label=<
Value Computation
EXPLODE q2.level0.field1
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2)" ]; - 7 [ label=<
Predicate Filter
WHERE q10 NOT_NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1)" ]; - 8 [ label=<
Value Computation
FIRST $q10 OR NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1)" ]; - 9 [ label=<
Value Computation
MAP (q43.level2$array.field.1 AS level2$array.field.1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1)" ]; - 10 [ label=<
Predicate Filter
WHERE q6.level2$array.field.1 EQUALS promote(@c27 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1, LONG AS level2$field.2)" ]; - 11 [ label=<
Value Computation
EXPLODE q4.level1$field.1
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS level2$array.field.1, LONG AS level2$field.2)" ]; - 3 -> 2 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 2 -> 1 [ label=< q2> label="q2" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 6 -> 5 [ label=< q4> label="q4" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 7 -> 5 [ label=< q10> label="q10" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 8 -> 7 [ label=< q10> label="q10" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 9 -> 8 [ label=< q10> label="q10" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 10 -> 9 [ label=< q43> label="q43" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 11 -> 10 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - 5 -> 1 [ label=< q51> label="q51" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; - { - rank=same; - rankDir=LR; - 6 -> 7 [ color="red" style="invis" ]; - } - { - rank=same; - rankDir=LR; - 2 -> 5 [ color="red" style="invis" ]; - } + 1 [ label=<
Value Computation
MAP (q110.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS promote(@c25 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 [ label=<
Index
foo.table$nested.repeated.idx.field.1.3
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q110> label="q110" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-testsEXPLAIN select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.2"."level2$field.1" = 91) + (50փ8E@uCOVERING(foo.table$nested.repeated.idx.field.1.2.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q110.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS promote(@c27 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 [ label=<
Index
foo.table$nested.repeated.idx.field.1.2.1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q110> label="q110" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-testsEXPLAIN select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as b, b."level1$field.1" where "level2$array.field.1" = 10) + (E0Ƭ8`@ uCOVERING(foo.table$nested.repeated.idx.field.1.1.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q123.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS promote(@c27 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 [ label=<
Index
foo.table$nested.repeated.idx.field.1.1.1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(ARRAY(LONG AS level2$array.field.1, LONG AS level2$field.2) AS level1$field.1, LONG AS level2$field.1, LONG AS level2$field.2 AS level1$field.2, LONG AS level1$field.3) AS level0.field1)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q123> label="q123" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; } r all-testseEXPLAIN select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" where id = 1 -]r D(!084@COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)digraph G { +·r (!084@COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -50,10 +48,11 @@ r 3 -> 2 [ label=< q45> label="q45" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q49> label="q49" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} = - all-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 { + all-tests0EXPLAIN select "foo.tableA".* from "foo.tableA"; + +j (10;8-@COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -65,7 +64,7 @@ r } 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 { + 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; @@ -77,8 +76,7 @@ r } 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 { +j (1028-@COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -90,7 +88,7 @@ A } 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 { + u (,0;8$@nAISCAN(foo.tableA.idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP (_._0 AS foo.tableA.A2, _._1 AS _1) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -99,17 +97,17 @@ m 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 { + all-testssEXPLAIN select * from "foo.tableA", "foo.tableB" where "foo.tableA"."foo.tableA.A1" = "foo.tableB"."foo.tableB.B1"; +ͬ (E0n8k@SCAN(<,>) | TFILTER foo__2tableB | FLATMAP q0 -> { ISCAN(foo.tableA.idx [EQUALS q0.foo.tableB.B1]) AS q1 RETURN (q1.foo.tableA.A1 AS foo.tableA.A1, q1.foo.tableA.A2 AS foo.tableA.A2, q1.foo.tableA.A3 AS foo.tableA.A3, q0.foo.tableB.B1 AS foo.tableB.B1, q0.foo.tableB.B2 AS foo.tableB.B2, q0.foo.tableB.B3 AS foo.tableB.B3) }digraph G { fontname=courier; rankdir=BT; splines=polyline; 1 [ label=<
Nested Loop Join
FLATMAP (q2.foo.tableA.A1 AS foo.tableA.A1, q2.foo.tableA.A2 AS foo.tableA.A2, q2.foo.tableA.A3 AS foo.tableA.A3, q6.foo.tableB.B1 AS foo.tableB.B1, q6.foo.tableB.B2 AS foo.tableB.B2, q6.foo.tableB.B3 AS foo.tableB.B3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3, LONG AS foo.tableB.B1, LONG AS foo.tableB.B2, LONG AS S1, LONG AS S2 AS foo.tableB.B3)" ]; 2 [ label=<
Type Filter
WHERE record IS [foo__2tableB]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableB.B1, LONG AS foo.tableB.B2, LONG AS S1, LONG AS S2 AS foo.tableB.B3)" ]; 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; - 4 [ label=<
Primary Storage
record types: [foo__2tableA, 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)" ]; + 4 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 5 [ label=<
Index Scan
comparisons: [EQUALS q6.foo.tableB.B1]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; 6 [ label=<
Index
foo.tableA.idx
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS foo.tableA.A1, LONG AS foo.tableA.A2, LONG AS foo.tableA.A3)" ]; 3 -> 2 [ label=< q53> label="q53" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; @@ -125,7 +123,7 @@ m } 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 { +쭍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; @@ -137,7 +135,7 @@ m } 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 { +ԧ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; @@ -149,7 +147,7 @@ C } 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 { +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; @@ -163,7 +161,7 @@ a } 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 { +@ (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; @@ -175,7 +173,7 @@ T } 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 { +Ǽ@ (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; @@ -187,7 +185,7 @@ D } } 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 { +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; @@ -198,10 +196,10 @@ D 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 { + 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; @@ -209,15 +207,15 @@ D 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)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +}  - all-testsEXPLAIN select "alpha__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" as "f__e" where "βήτα__f"("f__e"."foo.tableE.E3") >= 50 -= (08@SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S2 GREATER_THAN_OR_EQUALS promote(@c23 AS LONG) | MAP (_.foo.tableE.E3.S1 AS ___h.1)digraph G { + 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; @@ -225,16 +223,16 @@ D 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)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -}& +}( 8 - all-tests+EXPLAIN select * from "__$func3"(10, 1, 1);& + 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 { + (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; @@ -242,11 +240,11 @@ D 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)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 6 [ label=<
Predicate Filter
WHERE q61.me EQUALS promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS me, LONG AS my__parent)" ]; 7 [ label=<
Type Filter
WHERE record IS [my__1adjacency__1list]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS me, LONG AS my__parent)" ]; 8 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; - 9 [ label=<
Primary Storage
record types: [foo__2tableA, 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)" ]; + 9 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q50> label="q50" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q201> label="q201" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; @@ -260,10 +258,10 @@ D 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 { + all-tests EXPLAIN select * from "$yay"(5); +ӭZ (0'83@zSCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$x.id)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -271,15 +269,15 @@ D 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)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q12> label="q12" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q54> label="q54" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} / - all-tests"EXPLAIN select * from "__2yay"(6); -Z (083@zSCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$y.id)digraph G { + all-tests"EXPLAIN select * from "__2yay"(6); +ՇZ (0&83@zSCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$y.id)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -287,15 +285,15 @@ D 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)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q12> label="q12" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q54> label="q54" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} 8 - all-tests+EXPLAIN select * from "नमस्त"(4); -Z (0ˍ83@zSCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$z.id)digraph G { + 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; @@ -303,15 +301,15 @@ D 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)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q12> label="q12" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q54> label="q54" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} 0 - all-tests#EXPLAIN select * from "$yay__view"; -؎R 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 { + all-tests#EXPLAIN select * from "$yay__view"; +R ŝ(0Ç8@SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 4 | MAP (_.foo.tableE.E1 AS _$x.id) | MAP (_._$x.id AS _$x.id)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -320,16 +318,16 @@ D 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)" ]; + 6 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q32> label="q32" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ label=< q25> label="q25" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 6 -> 5 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} 2 - all-tests%EXPLAIN select * from "__2yay__view"; -R 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 { + all-tests%EXPLAIN select * from "__2yay__view"; +R }(08@SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 5 | MAP (_.foo.tableE.E1 AS _$y.id) | MAP (_._$y.id AS _$y.id)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -338,16 +336,16 @@ D 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)" ]; + 6 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q32> label="q32" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ label=< q25> label="q25" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 6 -> 5 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} ; - all-tests.EXPLAIN select * from "வணக்கம்"; -͎R {(08@SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 6 | MAP (_.foo.tableE.E1 AS _$z.id) | MAP (_._$z.id AS _$z.id)digraph G { + 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; @@ -356,7 +354,7 @@ D 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)" ]; + 6 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q32> label="q32" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ label=< q25> label="q25" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; @@ -365,7 +363,7 @@ D } ^ 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 { +tD ޚ (08 @EXPLODE array((@c6 AS _$$$, @c8 AS _$$, @c10 AS _$), (@c14 AS _$$$, @c16 AS _$$, @c18 AS _$)) | MAP (_._$$$ AS _$$$, _._$$ AS _$$, _._$ AS _$)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -375,7 +373,7 @@ D } 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 { + (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; @@ -387,7 +385,7 @@ n } K all-tests>EXPLAIN select struct "x$$" ("foo.tableA".*) from "foo.tableA" -K (%0ͻ 8#@*ISCAN(foo.tableA.idx3 <,>) | MAP (_ AS _0) digraph G { +K ٓ(%08#@*ISCAN(foo.tableA.idx3 <,>) | MAP (_ AS _0) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -399,7 +397,7 @@ K } 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 { +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; @@ -411,7 +409,7 @@ j } 3 all-tests&EXPLAIN select * from "foo.enum.type"; -H (0լ 8@ ISCAN(foo.enum.type$enum__1 <,>)digraph G { +H ž(08@ ISCAN(foo.enum.type$enum__1 <,>)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -422,7 +420,7 @@ j 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 { +S (08%@cISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -433,7 +431,7 @@ U 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 { +S (08%@cISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -445,7 +443,7 @@ U 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 { +S (08%@cISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -455,7 +453,7 @@ S } 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 { +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; @@ -467,7 +465,7 @@ U } 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 { +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; @@ -479,7 +477,7 @@ U } 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 { +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; @@ -491,7 +489,7 @@ S } 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 { + (?0J8`@COVERING(foo.tableA.idx [EQUALS promote(@c10 AS LONG)] -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | DISTINCT BY PK | FETCH | UPDATE foo__2tableAdigraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -514,7 +512,8 @@ k }  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 { + + (<0:8d@COVERING(foo.tableA.idx [[GREATER_THAN promote(@c10 AS LONG)]] -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | DISTINCT BY PK | FETCH | UPDATE foo__2tableA | MAP (_.new.foo.tableA.A1 AS foo.tableA.A1)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -539,7 +538,7 @@ k }  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 { +č (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; @@ -560,7 +559,7 @@ k } X update-delete-statements 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 { +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)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} : -unnamed-12,EXPLAIN SELECT * FROM T1 WHERE "T1.COL2" > 3 -4 Ȉ(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 { +unnamed-12,EXPLAIN SELECT * FROM T1 WHERE "T1.COL2" > 3 +4 (08@SCAN(<,>) | TFILTER T1 | FILTER _.T1.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.T1.COL1 AS T1.COL1, _.T1.COL2 AS T1.COL2)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -603,16 +602,16 @@ unnamed-12,EXPLAIN SELECT * FROM T1 WHERE "T1.COL2" > 3 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)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} B -unnamed-124EXPLAIN SELECT "T1.COL2" FROM T1 WHERE "T1.COL2" > 3 -= (08@hSCAN(<,>) | TFILTER T1 | FILTER _.T1.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T1.COL2 AS T1.COL2)digraph G { +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; @@ -620,7 +619,7 @@ unnamed-124EXPLAIN SELECT "T1.COL2" FROM T1 WHERE "T1.COL2" > 3 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)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; @@ -629,7 +628,7 @@ unnamed-124EXPLAIN SELECT "T1.COL2" FROM T1 WHERE "T1.COL2" > 3 & unnamed-12EXPLAIN SELECT * FROM T2 -H (08@ISCAN(T2$T2.COL1 <,>)digraph G { +H (08@ISCAN(T2$T2.COL1 <,>)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -640,7 +639,7 @@ unnamed-12EXPLAIN SELECT * FROM T2 : 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) +ʃQ (0 8%@JISCAN(T2$T2.COL1 <,>) | FILTER _.T2$COL2 GREATER_THAN promote(@c8 AS LONG) digraph G { fontname=courier; rankdir=BT; @@ -654,7 +653,8 @@ digraph G { 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 { + +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; @@ -665,60 +665,60 @@ unnamed-124EXPLAIN SELECT "T2$COL2" FROM T2 WHERE "T2$COL2" > 8 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 { +unnamed-12EXPLAIN SELECT * FROM "__T3" +. `(0͆8@ySCAN(<,>) | TFILTER __T3 | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2, _.__T3$COL3 AS __T3$COL3)digraph G { fontname=courier; rankdir=BT; splines=polyline; - 1 [ label=<
Value Computation
MAP (q2.ID AS ID, q2.__T3$COL1 AS __T3$COL1, q2.__T3$COL2 AS __T3$COL2)
> 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)" ]; + 1 [ label=<
Value Computation
MAP (q2.ID AS ID, q2.__T3$COL1 AS __T3$COL1, q2.__T3$COL2 AS __T3$COL2, q2.__T3$COL3 AS __T3$COL3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 2 [ label=<
Type Filter
WHERE record IS [__T3]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; - 4 [ label=<
Primary Storage
record types: [T4, __T3, T1, T2]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} A -unnamed-123EXPLAIN SELECT * FROM "__T3" WHERE "__T3$COL2" > 13 -4 (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 { +unnamed-123EXPLAIN SELECT * FROM "__T3" WHERE "__T3$COL2" > 13 +4 ɹk(08@SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2, _.__T3$COL3 AS __T3$COL3)digraph G { fontname=courier; rankdir=BT; splines=polyline; - 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.__T3$COL1 AS __T3$COL1, q26.__T3$COL2 AS __T3$COL2)
> 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)" ]; + 1 [ label=<
Value Computation
MAP (q26.ID AS ID, q26.__T3$COL1 AS __T3$COL1, q26.__T3$COL2 AS __T3$COL2, q26.__T3$COL3 AS __T3$COL3)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.__T3$COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [__T3]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; - 5 [ label=<
Primary Storage
record types: [T4, __T3, T1, T2]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} K -unnamed-12=EXPLAIN SELECT "__T3$COL2" FROM "__T3" WHERE "__T3$COL2" > 13 -= Ȭ(028@pSCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.__T3$COL2 AS __T3$COL2)digraph G { +unnamed-12=EXPLAIN SELECT "__T3$COL2" FROM "__T3" WHERE "__T3$COL2" > 13 += (0ї8@pSCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.__T3$COL2 AS __T3$COL2)digraph G { fontname=courier; rankdir=BT; splines=polyline; 1 [ label=<
Value Computation
MAP (q26.__T3$COL2 AS __T3$COL2)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS __T3$COL2)" ]; - 2 [ label=<
Predicate Filter
WHERE q2.__T3$COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2)" ]; - 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)" ]; + 2 [ label=<
Predicate Filter
WHERE q2.__T3$COL2 GREATER_THAN promote(@c8 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; + 3 [ label=<
Type Filter
WHERE record IS [__T3]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS __T3$COL1, LONG AS __T3$COL2, ENUM<T3.E.A(0), T3.E.B(1), T3.E.C(2)> AS __T3$COL3)" ]; 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; - 5 [ label=<
Primary Storage
record types: [T4, __T3, T1, T2]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} 9 -unnamed-12+EXPLAIN SELECT * FROM "__func__T3$col2"(13) - Z (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 { +unnamed-12+EXPLAIN SELECT * FROM "__func__T3$col2"(13) +Z  (0/83@xSCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 EQUALS promote(@c6 AS LONG) | MAP (_.__T3$COL1 AS c.1, _.__T3$COL3 AS c.2)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -726,31 +726,32 @@ unnamed-12+EXPLAIN SELECT * FROM "__func__T3$col2"(13) 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)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q12> label="q12" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q47> label="q47" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q54> label="q54" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} & -unnamed-12EXPLAIN SELECT * FROM T4 -. ҥ(08@oSCAN(<,>) | TFILTER T4 | MAP (_.ID AS ID, _.___hidden AS ___hidden, _.T4.COL1 AS T4.COL1, _.T4.COL2 AS T4.COL2)digraph G { +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)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} ; -unnamed-12-EXPLAIN SELECT * FROM T4 WHERE "T4.COL2" > 18 -4 (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 { +unnamed-12-EXPLAIN SELECT * FROM T4 WHERE "T4.COL2" > 18 +Բ4 w(0 +8@SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.ID AS ID, _.___hidden AS ___hidden, _.T4.COL1 AS T4.COL1, _.T4.COL2 AS T4.COL2)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -758,16 +759,16 @@ unnamed-12-EXPLAIN SELECT * FROM T4 WHERE "T4.COL2" > 18 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)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} C -unnamed-125EXPLAIN SELECT "T4.COL2" FROM T4 WHERE "T4.COL2" > 18 -= (0Ά8@hSCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T4.COL2 AS T4.COL2)digraph G { +unnamed-125EXPLAIN SELECT "T4.COL2" FROM T4 WHERE "T4.COL2" > 18 += (08@hSCAN(<,>) | TFILTER T4 | FILTER _.T4.COL2 GREATER_THAN promote(@c8 AS LONG) | MAP (_.T4.COL2 AS T4.COL2)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -775,16 +776,16 @@ unnamed-125EXPLAIN SELECT "T4.COL2" FROM T4 WHERE "T4.COL2" > 18 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)" ]; + 5 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q26> label="q26" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} - -unnamed-12EXPLAIN SELECT * FROM "T4$view" -܉R (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 { +unnamed-12EXPLAIN SELECT * FROM "T4$view" +ΓR (0&8@SCAN(<,>) | TFILTER T4 | FILTER _.T4.COL1 GREATER_THAN 0 AND _.T4.COL2 GREATER_THAN 0 | MAP (_.T4.COL1 AS c__1, _.T4.COL2 AS c__2) | MAP (_.c__1 AS c__1, _.c__2 AS c__2)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -793,32 +794,33 @@ unnamed-12EXPLAIN SELECT * FROM "T4$view" 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)" ]; + 6 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q32> label="q32" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 5 -> 4 [ label=< q25> label="q25" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 6 -> 5 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q6> label="q6" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} 4 -unnamed-12&EXPLAIN SELECT "___hidden"."a" FROM T4 -) ( 08@1SCAN(<,>) | TFILTER T4 | MAP (_.___hidden.a AS a)digraph G { +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)" ]; + 4 [ label=<
Primary Storage
record types: [T4, T5__UNESCAPED, __T3, T1, T2, ___T6__2__UNESCAPED]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; 3 -> 2 [ label=< q19> label="q19" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} 1 -unnamed-12#EXPLAIN SELECT * FROM T5__UNESCAPED -ƛ) ( 08 @!SCAN(<,>) | TFILTER T5__UNESCAPED +unnamed-12#EXPLAIN SELECT * FROM T5__UNESCAPED +) v( 0 +8 @!SCAN(<,>) | TFILTER T5__UNESCAPED digraph G { fontname=courier; rankdir=BT; @@ -832,7 +834,7 @@ digraph G { E unnamed-127EXPLAIN SELECT * FROM T5__UNESCAPED WHERE T5__COL1 = 10 -0 Ƌ(0 8@QSCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 AS LONG)digraph G { +0 (0/8@QSCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 AS LONG)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -847,7 +849,7 @@ unnamed-127EXPLAIN SELECT * FROM T5__UNESCAPED WHERE T5__COL1 = 10 T unnamed-12FEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE T5__COL1 = 10 -뒟= (08@SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)digraph G { +Յ= (08@SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -864,7 +866,7 @@ unnamed-12FEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE T5__COL1 = I unnamed-12;EXPLAIN SELECT * FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 -0 ͐(08@VSCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c8 AS LONG)digraph G { +0 (08@VSCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c8 AS LONG)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -879,7 +881,7 @@ unnamed-12;EXPLAIN SELECT * FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 X unnamed-12JEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2" < 10 -= (098@SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)digraph G { += ҋ(0ψ8@SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.__T5__COL2 LESS_THAN promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -896,18 +898,18 @@ unnamed-12JEXPLAIN SELECT ID, "__T5__COL2" FROM T5__UNESCAPED WHERE "__T5__COL2 7 unnamed-12)EXPLAIN SELECT * FROM "___T6.__UNESCAPED" -lb -(+0&8*@ISCAN(T6$ENUM2 <,>)digraph G { +b ˅(+08*@ISCAN(T6$ENUM2 <,>)digraph G { fontname=courier; rankdir=BT; splines=polyline; 1 [ label=<
Index Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 2 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} P -unnamed-12BEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 -粜n ˦8(,0Z86@FISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) digraph G { +unnamed-12BEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 +n (,0986@FISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -916,11 +918,12 @@ unnamed-12BEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 3 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} e -unnamed-12WEXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 - O()089@ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)digraph G { +unnamed-12WEXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 +˛ + ()0x89@ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -931,23 +934,23 @@ unnamed-12WEXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q60> label="q60" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} U -unnamed-12GEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 -r Թ=(-0J86@1ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])digraph G { +unnamed-12GEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 +èr (-0!86@1ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]])digraph G { fontname=courier; rankdir=BT; splines=polyline; 1 [ label=<
Index Scan
comparisons: [[LESS_THAN promote(@c8 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 2 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} j -unnamed-12\EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 - I(/08B@COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) digraph G { +unnamed-12\EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 + (/0M8B@COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -956,11 +959,12 @@ unnamed-12\EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE 3 [ label=<
Index
T6$COL2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q92> label="q92" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} V -unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' -n Ɓ?(,0F86@gISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM)digraph G { +unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + +n Ш(,0B86@gISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -969,11 +973,11 @@ unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D 3 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} f -unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' -ˆ O()0뺕89@ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2)digraph G { +unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' + Ӊ()0:89@ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2)digraph G { fontname=courier; rankdir=BT; splines=polyline; @@ -984,24 +988,24 @@ unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_2" FROM "___T6.__UNESCAPED" WHERE "T6$ 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q60> label="q60" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} V -unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' +unnamed-12HEXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' -ĺp 8(-0;86@NISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)]) digraph G { + p (-0-86@NISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)]) digraph G { fontname=courier; rankdir=BT; splines=polyline; 1 [ label=<
Index Scan
comparisons: [EQUALS promote(@c8 AS ENUM<A(0), B__(1), C__(2), __D.__(3)>)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 2 [ label=<
Index
T6$ENUM2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, LONG AS T6$__COL1__, LONG AS __T6.COL2__VALUE, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_1, ENUM<A(0), B__(1), C__(2), __D.__(3)> AS T6$__ENUM_2)" ]; 2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; -} +} f -unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_1" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' - Ϭ>(+0n8;@ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1) digraph G { +unnamed-12XEXPLAIN SELECT ID, "T6$__ENUM_1" FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' +  (+0X8;@ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1) digraph G { fontname=courier; rankdir=BT; splines=polyline; diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml index 7329c082a0..4d30c54bfd 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -1,31 +1,53 @@ all-tests: -- query: EXPLAIN select t.id from "foo.table$nested.repeated" as t, t."level0.field1" - as b where exists (select "level2$array.field.1" from b."level1$field.1" where - "level2$array.field.1" = 10) - explain: SCAN(<,>) | TFILTER foo__2table__1nested__2repeated | FLATMAP q0 -> { - EXPLODE q0.level0.field1 | FLATMAP q1 -> { EXPLODE q1.level1$field.1 | FILTER - _.level2$array.field.1 EQUALS promote(@c27 AS LONG) | MAP (_.level2$array.field.1 - AS level2$array.field.1) | DEFAULT NULL | FILTER _ NOT_NULL AS q2 RETURN (1 - AS _0) } AS q3 RETURN (q0.ID AS ID) } - task_count: 471 - task_total_time_ms: 165 - transform_count: 173 - transform_time_ms: 127 - transform_yield_count: 32 - insert_time_ms: 7 - insert_new_count: 42 +- query: EXPLAIN select t.id from "foo.table$nested.repeated" as t where exists + (select * from t."level0.field1" as x where x."level1$field.3" = 20) + explain: 'COVERING(foo.table$nested.repeated.idx.field.1.3 [EQUALS promote(@c25 + AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)' + task_count: 667 + task_total_time_ms: 206 + transform_count: 203 + transform_time_ms: 158 + transform_yield_count: 53 + insert_time_ms: 6 + insert_new_count: 69 + insert_reused_count: 5 +- query: EXPLAIN select t.id from "foo.table$nested.repeated" as t where exists + (select * from t."level0.field1" as x where x."level1$field.2"."level2$field.1" + = 91) + explain: 'COVERING(foo.table$nested.repeated.idx.field.1.2.1 [EQUALS promote(@c27 + AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)' + task_count: 667 + task_total_time_ms: 34 + transform_count: 203 + transform_time_ms: 19 + transform_yield_count: 53 + insert_time_ms: 2 + insert_new_count: 69 insert_reused_count: 5 +- query: EXPLAIN select t.id from "foo.table$nested.repeated" as t where exists + (select * from t."level0.field1" as b, b."level1$field.1" where "level2$array.field.1" + = 10) + explain: 'COVERING(foo.table$nested.repeated.idx.field.1.1.1 [EQUALS promote(@c27 + AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)' + task_count: 914 + task_total_time_ms: 64 + transform_count: 322 + transform_time_ms: 38 + transform_yield_count: 69 + insert_time_ms: 4 + insert_new_count: 96 + insert_reused_count: 9 - query: EXPLAIN select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" where id = 1 explain: 'COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)' task_count: 474 - task_total_time_ms: 196 + task_total_time_ms: 46 transform_count: 114 - transform_time_ms: 143 + transform_time_ms: 27 transform_yield_count: 33 - insert_time_ms: 10 + insert_time_ms: 3 insert_new_count: 52 insert_reused_count: 6 - query: EXPLAIN select "foo.tableA".* from "foo.tableA"; @@ -33,11 +55,11 @@ all-tests: 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 + task_total_time_ms: 21 transform_count: 106 - transform_time_ms: 81 + transform_time_ms: 11 transform_yield_count: 49 - insert_time_ms: 2 + insert_time_ms: 0 insert_new_count: 45 insert_reused_count: 5 - query: EXPLAIN select * from "foo.tableA"; @@ -45,9 +67,9 @@ all-tests: 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 + task_total_time_ms: 20 transform_count: 106 - transform_time_ms: 12 + transform_time_ms: 10 transform_yield_count: 49 insert_time_ms: 1 insert_new_count: 45 @@ -57,11 +79,11 @@ all-tests: 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 + task_total_time_ms: 16 transform_count: 106 - transform_time_ms: 10 + transform_time_ms: 8 transform_yield_count: 49 - insert_time_ms: 2 + insert_time_ms: 0 insert_new_count: 45 insert_reused_count: 5 - query: EXPLAIN select "foo.tableA.A2", sum("foo.tableA.A1") from "foo.tableA" @@ -69,11 +91,11 @@ all-tests: 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 + task_total_time_ms: 26 transform_count: 117 - transform_time_ms: 20 + transform_time_ms: 19 transform_yield_count: 44 - insert_time_ms: 1 + insert_time_ms: 0 insert_new_count: 36 insert_reused_count: 3 - query: EXPLAIN select * from "foo.tableA", "foo.tableB" where "foo.tableA"."foo.tableA.A1" @@ -84,11 +106,11 @@ all-tests: 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 + task_total_time_ms: 34 transform_count: 214 - transform_time_ms: 16 + transform_time_ms: 17 transform_yield_count: 69 - insert_time_ms: 2 + insert_time_ms: 1 insert_new_count: 107 insert_reused_count: 8 - query: EXPLAIN select "foo$tableC$C2", sum("foo$tableC$C1") from "foo$tableC" @@ -96,9 +118,9 @@ all-tests: 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 + task_total_time_ms: 10 transform_count: 103 - transform_time_ms: 8 + transform_time_ms: 6 transform_yield_count: 34 insert_time_ms: 0 insert_new_count: 24 @@ -108,9 +130,9 @@ all-tests: 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 + task_total_time_ms: 7 transform_count: 80 - transform_time_ms: 4 + transform_time_ms: 3 transform_yield_count: 35 insert_time_ms: 0 insert_new_count: 32 @@ -122,7 +144,7 @@ all-tests: 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 + task_total_time_ms: 9 transform_count: 99 transform_time_ms: 4 transform_yield_count: 39 @@ -135,7 +157,7 @@ all-tests: task_count: 262 task_total_time_ms: 5 transform_count: 64 - transform_time_ms: 2 + transform_time_ms: 3 transform_yield_count: 29 insert_time_ms: 0 insert_new_count: 28 @@ -144,9 +166,9 @@ all-tests: 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 + task_total_time_ms: 7 transform_count: 64 - transform_time_ms: 2 + transform_time_ms: 4 transform_yield_count: 29 insert_time_ms: 0 insert_new_count: 28 @@ -158,9 +180,9 @@ all-tests: 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 + task_total_time_ms: 7 transform_count: 82 - transform_time_ms: 5 + transform_time_ms: 3 transform_yield_count: 33 insert_time_ms: 0 insert_new_count: 35 @@ -196,9 +218,9 @@ all-tests: 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 + task_total_time_ms: 43 transform_count: 301 - transform_time_ms: 14 + transform_time_ms: 17 transform_yield_count: 110 insert_time_ms: 5 insert_new_count: 289 @@ -207,9 +229,9 @@ all-tests: 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 + task_total_time_ms: 9 transform_count: 90 - transform_time_ms: 2 + transform_time_ms: 3 transform_yield_count: 28 insert_time_ms: 0 insert_new_count: 51 @@ -218,9 +240,9 @@ all-tests: 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 + task_total_time_ms: 10 transform_count: 90 - transform_time_ms: 3 + transform_time_ms: 6 transform_yield_count: 28 insert_time_ms: 0 insert_new_count: 51 @@ -229,9 +251,9 @@ all-tests: 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 + task_total_time_ms: 8 transform_count: 90 - transform_time_ms: 2 + transform_time_ms: 3 transform_yield_count: 28 insert_time_ms: 0 insert_new_count: 51 @@ -240,9 +262,9 @@ all-tests: 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 + task_total_time_ms: 5 transform_count: 82 - transform_time_ms: 1 + transform_time_ms: 2 transform_yield_count: 19 insert_time_ms: 0 insert_new_count: 28 @@ -253,7 +275,7 @@ all-tests: task_count: 322 task_total_time_ms: 4 transform_count: 82 - transform_time_ms: 1 + transform_time_ms: 2 transform_yield_count: 19 insert_time_ms: 0 insert_new_count: 28 @@ -262,7 +284,7 @@ all-tests: 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 + task_total_time_ms: 5 transform_count: 82 transform_time_ms: 2 transform_yield_count: 19 @@ -287,9 +309,9 @@ all-tests: 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 + task_total_time_ms: 11 transform_count: 138 - transform_time_ms: 6 + transform_time_ms: 5 transform_yield_count: 50 insert_time_ms: 0 insert_new_count: 54 @@ -299,7 +321,7 @@ all-tests: task_count: 301 task_total_time_ms: 7 transform_count: 75 - transform_time_ms: 3 + transform_time_ms: 4 transform_yield_count: 37 insert_time_ms: 0 insert_new_count: 35 @@ -330,9 +352,9 @@ all-tests: explain: ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) task_count: 367 - task_total_time_ms: 8 + task_total_time_ms: 9 transform_count: 83 - transform_time_ms: 4 + transform_time_ms: 5 transform_yield_count: 31 insert_time_ms: 0 insert_new_count: 37 @@ -341,9 +363,9 @@ all-tests: explain: ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) task_count: 367 - task_total_time_ms: 8 + task_total_time_ms: 9 transform_count: 83 - transform_time_ms: 4 + transform_time_ms: 5 transform_yield_count: 31 insert_time_ms: 0 insert_new_count: 37 @@ -352,9 +374,9 @@ all-tests: explain: ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)]) task_count: 367 - task_total_time_ms: 8 + task_total_time_ms: 9 transform_count: 83 - transform_time_ms: 4 + transform_time_ms: 5 transform_yield_count: 31 insert_time_ms: 0 insert_new_count: 37 @@ -363,9 +385,9 @@ all-tests: 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 + task_total_time_ms: 6 transform_count: 81 - transform_time_ms: 3 + transform_time_ms: 2 transform_yield_count: 30 insert_time_ms: 0 insert_new_count: 37 @@ -374,9 +396,9 @@ all-tests: 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 + task_total_time_ms: 6 transform_count: 81 - transform_time_ms: 3 + transform_time_ms: 2 transform_yield_count: 30 insert_time_ms: 0 insert_new_count: 37 @@ -385,9 +407,9 @@ all-tests: 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 + task_total_time_ms: 6 transform_count: 81 - transform_time_ms: 3 + transform_time_ms: 2 transform_yield_count: 30 insert_time_ms: 0 insert_new_count: 37 @@ -399,9 +421,9 @@ update-delete-statements: 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 + task_total_time_ms: 15 transform_count: 159 - transform_time_ms: 8 + transform_time_ms: 6 transform_yield_count: 63 insert_time_ms: 1 insert_new_count: 96 @@ -412,9 +434,9 @@ update-delete-statements: 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 + task_total_time_ms: 21 transform_count: 170 - transform_time_ms: 5 + transform_time_ms: 7 transform_yield_count: 60 insert_time_ms: 0 insert_new_count: 100 @@ -424,19 +446,19 @@ update-delete-statements: 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 + task_total_time_ms: 13 transform_count: 164 - transform_time_ms: 6 + transform_time_ms: 5 transform_yield_count: 65 - insert_time_ms: 1 + insert_time_ms: 0 insert_new_count: 95 insert_reused_count: 3 - query: EXPLAIN DELETE FROM "foo.tableA" WHERE "foo.tableA.A2" = 100 explain: ISCAN(foo.tableA.idx3 [EQUALS promote(@c7 AS LONG)]) | DELETE task_count: 636 - task_total_time_ms: 12 + task_total_time_ms: 11 transform_count: 134 - transform_time_ms: 5 + transform_time_ms: 4 transform_yield_count: 54 insert_time_ms: 0 insert_new_count: 76 @@ -446,7 +468,7 @@ unnamed-12: 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 + task_total_time_ms: 6 transform_count: 46 transform_time_ms: 2 transform_yield_count: 15 @@ -457,9 +479,9 @@ unnamed-12: 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 + task_total_time_ms: 9 transform_count: 52 - transform_time_ms: 2 + transform_time_ms: 3 transform_yield_count: 16 insert_time_ms: 0 insert_new_count: 20 @@ -468,9 +490,9 @@ unnamed-12: 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 + task_total_time_ms: 5 transform_count: 61 - transform_time_ms: 3 + transform_time_ms: 2 transform_yield_count: 15 insert_time_ms: 0 insert_new_count: 21 @@ -478,9 +500,9 @@ unnamed-12: - query: EXPLAIN SELECT * FROM T2 explain: ISCAN(T2$T2.COL1 <,>) task_count: 296 - task_total_time_ms: 8 + task_total_time_ms: 12 transform_count: 72 - transform_time_ms: 3 + transform_time_ms: 4 transform_yield_count: 29 insert_time_ms: 0 insert_new_count: 29 @@ -489,9 +511,9 @@ unnamed-12: explain: ISCAN(T2$T2.COL1 <,>) | FILTER _.T2$COL2 GREATER_THAN promote(@c8 AS LONG) task_count: 351 - task_total_time_ms: 13 + task_total_time_ms: 17 transform_count: 81 - transform_time_ms: 4 + transform_time_ms: 5 transform_yield_count: 30 insert_time_ms: 0 insert_new_count: 37 @@ -500,31 +522,32 @@ unnamed-12: 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 + task_total_time_ms: 21 transform_count: 98 - transform_time_ms: 4 + transform_time_ms: 12 transform_yield_count: 28 insert_time_ms: 0 insert_new_count: 39 insert_reused_count: 4 - query: EXPLAIN SELECT * FROM "__T3" explain: SCAN(<,>) | TFILTER __T3 | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, - _.__T3$COL2 AS __T3$COL2) + _.__T3$COL2 AS __T3$COL2, _.__T3$COL3 AS __T3$COL3) task_count: 187 - task_total_time_ms: 7 + task_total_time_ms: 5 transform_count: 46 - transform_time_ms: 2 + transform_time_ms: 1 transform_yield_count: 15 insert_time_ms: 0 insert_new_count: 16 insert_reused_count: 1 - query: EXPLAIN SELECT * FROM "__T3" WHERE "__T3$COL2" > 13 explain: SCAN(<,>) | TFILTER __T3 | FILTER _.__T3$COL2 GREATER_THAN promote(@c8 - AS LONG) | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2) + AS LONG) | MAP (_.ID AS ID, _.__T3$COL1 AS __T3$COL1, _.__T3$COL2 AS __T3$COL2, + _.__T3$COL3 AS __T3$COL3) task_count: 218 - task_total_time_ms: 10 + task_total_time_ms: 3 transform_count: 52 - transform_time_ms: 3 + transform_time_ms: 1 transform_yield_count: 16 insert_time_ms: 0 insert_new_count: 20 @@ -533,7 +556,7 @@ unnamed-12: 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 + task_total_time_ms: 8 transform_count: 61 transform_time_ms: 3 transform_yield_count: 15 @@ -544,9 +567,9 @@ unnamed-12: 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 + task_total_time_ms: 13 transform_count: 90 - transform_time_ms: 6 + transform_time_ms: 5 transform_yield_count: 28 insert_time_ms: 0 insert_new_count: 51 @@ -555,7 +578,7 @@ unnamed-12: 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 + task_total_time_ms: 5 transform_count: 46 transform_time_ms: 2 transform_yield_count: 15 @@ -567,9 +590,9 @@ unnamed-12: 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 + task_total_time_ms: 4 transform_count: 52 - transform_time_ms: 3 + transform_time_ms: 1 transform_yield_count: 16 insert_time_ms: 0 insert_new_count: 20 @@ -592,7 +615,7 @@ unnamed-12: task_count: 322 task_total_time_ms: 11 transform_count: 82 - transform_time_ms: 2 + transform_time_ms: 3 transform_yield_count: 19 insert_time_ms: 0 insert_new_count: 28 @@ -600,9 +623,9 @@ unnamed-12: - query: EXPLAIN SELECT "___hidden"."a" FROM T4 explain: SCAN(<,>) | TFILTER T4 | MAP (_.___hidden.a AS a) task_count: 159 - task_total_time_ms: 8 + task_total_time_ms: 9 transform_count: 41 - transform_time_ms: 2 + transform_time_ms: 4 transform_yield_count: 13 insert_time_ms: 0 insert_new_count: 15 @@ -612,7 +635,7 @@ unnamed-12: task_count: 159 task_total_time_ms: 7 transform_count: 41 - transform_time_ms: 2 + transform_time_ms: 1 transform_yield_count: 13 insert_time_ms: 0 insert_new_count: 13 @@ -621,9 +644,9 @@ unnamed-12: explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c8 AS LONG) task_count: 187 - task_total_time_ms: 4 + task_total_time_ms: 10 transform_count: 48 - transform_time_ms: 2 + transform_time_ms: 4 transform_yield_count: 14 insert_time_ms: 0 insert_new_count: 17 @@ -632,9 +655,9 @@ unnamed-12: explain: SCAN(<,>) | TFILTER T5__UNESCAPED | FILTER _.T5__COL1 EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T5__COL2 AS __T5__COL2) task_count: 230 - task_total_time_ms: 10 + task_total_time_ms: 6 transform_count: 61 - transform_time_ms: 3 + transform_time_ms: 2 transform_yield_count: 15 insert_time_ms: 0 insert_new_count: 21 @@ -645,7 +668,7 @@ unnamed-12: task_count: 187 task_total_time_ms: 10 transform_count: 48 - transform_time_ms: 4 + transform_time_ms: 3 transform_yield_count: 14 insert_time_ms: 0 insert_new_count: 17 @@ -665,9 +688,9 @@ unnamed-12: - query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" explain: ISCAN(T6$ENUM2 <,>) task_count: 405 - task_total_time_ms: 227 + task_total_time_ms: 15 transform_count: 98 - transform_time_ms: 95 + transform_time_ms: 6 transform_yield_count: 43 insert_time_ms: 0 insert_new_count: 42 @@ -675,11 +698,11 @@ unnamed-12: - query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" = 10 explain: ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c8 AS LONG) task_count: 484 - task_total_time_ms: 346 + task_total_time_ms: 16 transform_count: 110 - transform_time_ms: 118 + transform_time_ms: 6 transform_yield_count: 44 - insert_time_ms: 1 + insert_time_ms: 0 insert_new_count: 54 insert_reused_count: 3 - query: EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "T6$__COL1__" @@ -687,21 +710,21 @@ unnamed-12: explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__COL1__ EQUALS promote(@c10 AS LONG) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE) task_count: 520 - task_total_time_ms: 297 + task_total_time_ms: 21 transform_count: 136 - transform_time_ms: 165 + transform_time_ms: 8 transform_yield_count: 41 - insert_time_ms: 3 + insert_time_ms: 1 insert_new_count: 57 insert_reused_count: 6 - query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" < 10 explain: ISCAN(T6$COL2 [[LESS_THAN promote(@c8 AS LONG)]]) task_count: 500 - task_total_time_ms: 353 + task_total_time_ms: 15 transform_count: 114 - transform_time_ms: 128 + transform_time_ms: 7 transform_yield_count: 45 - insert_time_ms: 1 + insert_time_ms: 0 insert_new_count: 54 insert_reused_count: 4 - query: EXPLAIN SELECT ID, "__T6.COL2__VALUE" FROM "___T6.__UNESCAPED" WHERE "__T6.COL2__VALUE" @@ -709,20 +732,20 @@ unnamed-12: explain: 'COVERING(T6$COL2 [[LESS_THAN promote(@c10 AS LONG)]] -> [ID: KEY[2], __T6__2COL2__VALUE: KEY[0]]) | MAP (_.ID AS ID, _.__T6.COL2__VALUE AS __T6.COL2__VALUE)' task_count: 608 - task_total_time_ms: 410 + task_total_time_ms: 17 transform_count: 147 - transform_time_ms: 153 + transform_time_ms: 7 transform_yield_count: 47 - insert_time_ms: 2 + insert_time_ms: 1 insert_new_count: 66 insert_reused_count: 6 - query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_1" = '__D.__' explain: ISCAN(T6$ENUM2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c8 AS ENUM) task_count: 484 - task_total_time_ms: 334 + task_total_time_ms: 22 transform_count: 110 - transform_time_ms: 132 + transform_time_ms: 9 transform_yield_count: 44 insert_time_ms: 1 insert_new_count: 54 @@ -732,19 +755,19 @@ unnamed-12: explain: ISCAN(T6$COL2 <,>) | FILTER _.T6$__ENUM_1 EQUALS promote(@c10 AS ENUM) | MAP (_.ID AS ID, _.T6$__ENUM_2 AS T6$__ENUM_2) task_count: 520 - task_total_time_ms: 306 + task_total_time_ms: 14 transform_count: 136 - transform_time_ms: 166 + transform_time_ms: 5 transform_yield_count: 41 - insert_time_ms: 2 + insert_time_ms: 0 insert_new_count: 57 insert_reused_count: 6 - query: EXPLAIN SELECT * FROM "___T6.__UNESCAPED" WHERE "T6$__ENUM_2" = '__D.__' explain: ISCAN(T6$ENUM2 [EQUALS promote(@c8 AS ENUM)]) task_count: 500 - task_total_time_ms: 383 + task_total_time_ms: 28 transform_count: 112 - transform_time_ms: 119 + transform_time_ms: 11 transform_yield_count: 45 insert_time_ms: 0 insert_new_count: 54 @@ -754,9 +777,9 @@ unnamed-12: explain: ISCAN(T6$ENUM2 [EQUALS promote(@c10 AS ENUM)]) | MAP (_.ID AS ID, _.T6$__ENUM_1 AS T6$__ENUM_1) task_count: 549 - task_total_time_ms: 315 + task_total_time_ms: 23 transform_count: 139 - transform_time_ms: 130 + transform_time_ms: 10 transform_yield_count: 43 insert_time_ms: 1 insert_new_count: 59 diff --git a/yaml-tests/src/test/resources/valid-identifiers.yamsql b/yaml-tests/src/test/resources/valid-identifiers.yamsql index 48c2ac2409..d4fa0d47e0 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.yamsql +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -27,9 +27,11 @@ schema_template: create index "foo.table$nested.idx" as select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" order by "level0.field1"."level1$field.1"."level2$field.1" create type as struct "nested.repeated.type$level2" ("level2$array.field.1" bigint, "level2$field.2" bigint) - create type as struct "nested.repeated.type$level1" ("level1$field.1" "nested.repeated.type$level2" array, "level1$field.2" "nested.type$level2") + create type as struct "nested.repeated.type$level1" ("level1$field.1" "nested.repeated.type$level2" array, "level1$field.2" "nested.type$level2", "level1$field.3" bigint) create table "foo.table$nested.repeated" (id bigint, "level0.field1" "nested.repeated.type$level1" array, primary key(id)) - create index "foo.table$nested.repeated.idx" as select B."level2$array.field.1" from "foo.table$nested.repeated" as t, (select A."level2$array.field.1" from t."level0.field1" as Y, (select "level2$array.field.1" from Y."level1$field.1") A) B + create index "foo.table$nested.repeated.idx.field.1.3" as select "nested$level1"."level1$field.3" from "foo.table$nested.repeated" as "level0$t", "level0$t"."level0.field1" AS "nested$level1" + create index "foo.table$nested.repeated.idx.field.1.2.1" as select "nested$level1"."level1$field.2"."level2$field.1" from "foo.table$nested.repeated" as "level0$t", "level0$t"."level0.field1" AS "nested$level1" + create index "foo.table$nested.repeated.idx.field.1.1.1" as select "nested$level2"."level2$array.field.1" from "foo.table$nested.repeated" as "level0$t", "level0$t"."level0.field1" AS "nested$level1", "nested$level1"."level1$field.1" AS "nested$level2" create 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")) @@ -86,8 +88,8 @@ setup: (2, ((100, 200), (300, 400))), (3, ((1000, 2000), (3000, 4000))) - query: insert into "foo.table$nested.repeated" - values (1, [([(10, 20), (30, 40), (50, 60)], (400, 500))]), - (2, [([(100, 200), (300, 400), (500, 600)], (900, 1000))]) + values (1, [([(10, 20), (30, 40), (50, 60)], (90, 100), 10), ([(11, 21), (31, 41), (51, 61)], (91, 101), 11)]), + (2, [([(100, 200), (300, 400), (500, 600)], (900, 1000), 20), ([(101, 201), (301, 401), (501, 601)], (901, 1001), 21)]) - query: insert into "foo.tableA" values (1, 10, 1), (2, 10, 2) @@ -145,8 +147,16 @@ test_block: preset: single_repetition_ordered tests: - # select from nested repeated table - - query: select t.id from "foo.table$nested.repeated" as t, t."level0.field1" as b where exists (select "level2$array.field.1" from b."level1$field.1" where "level2$array.field.1" = 10) - - explain: "SCAN(<,>) | TFILTER foo__2table__1nested__2repeated | FLATMAP q0 -> { EXPLODE q0.level0.field1 | FLATMAP q1 -> { EXPLODE q1.level1$field.1 | FILTER _.level2$array.field.1 EQUALS promote(@c27 AS LONG) | MAP (_.level2$array.field.1 AS level2$array.field.1) | DEFAULT NULL | FILTER _ NOT_NULL AS q2 RETURN (1 AS _0) } AS q3 RETURN (q0.ID AS ID) }" + - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.3" = 20) + - explain: "COVERING(foo.table$nested.repeated.idx.field.1.3 [EQUALS promote(@c25 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - result: [{2}] + - + - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.2"."level2$field.1" = 91) + - explain: "COVERING(foo.table$nested.repeated.idx.field.1.2.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - result: [{1}] + - # select from deeply nested repeated table + - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as b, b."level1$field.1" where "level2$array.field.1" = 10) + - explain: "COVERING(foo.table$nested.repeated.idx.field.1.1.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" - result: [{1}] - # select from nested table - query: select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" where id = 1 From 5cb917620bf5eb4ea482632e439474c701864dae Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Mon, 24 Nov 2025 12:14:15 +0000 Subject: [PATCH 06/12] Add tests for scalar repeateds with weird identifiers and validate it can match the indexes in the same circumstances as cases with non-escaped identifiers --- .../test/resources/in-predicate.metrics.binpb | 74 +++++++++++++++++++ .../test/resources/in-predicate.metrics.yaml | 46 ++++++++++++ .../src/test/resources/in-predicate.yamsql | 19 ++++- .../resources/valid-identifiers.metrics.binpb | 57 ++++++++++++++ .../resources/valid-identifiers.metrics.yaml | 48 ++++++++++++ .../test/resources/valid-identifiers.yamsql | 41 ++++++++-- 6 files changed, 279 insertions(+), 6 deletions(-) diff --git a/yaml-tests/src/test/resources/in-predicate.metrics.binpb b/yaml-tests/src/test/resources/in-predicate.metrics.binpb index e79481991c..f48e3cef86 100644 --- a/yaml-tests/src/test/resources/in-predicate.metrics.binpb +++ b/yaml-tests/src/test/resources/in-predicate.metrics.binpb @@ -137,4 +137,78 @@ b rankDir=LR; 2 -> 3 [ color="red" style="invis" ]; } +} +s + unnamed-2fEXPLAIN select id from array_table where exists (select 1 from array_table.fruits f where f = 'apple') +C /("0Ɖ8.@ACOVERING(FRUITS [EQUALS @c18] -> [ID: KEY[2]]) | MAP (_.ID AS ID) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q67.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS @c18]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 [ label=<
Index
FRUITS
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q67> label="q67" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +G + unnamed-2:EXPLAIN select id from array_table where 'apple' in fruits +w ˅(0d8&@KSCAN(<,>) | TFILTER ARRAY_TABLE | FILTER @c6 IN _.FRUITS | MAP (_.ID AS ID)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q36.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Predicate Filter
WHERE @c6 IN q2.FRUITS
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 [ label=<
Type Filter
WHERE record IS [ARRAY_TABLE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [ARRAY_TABLE, TA]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q29> label="q29" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q36> label="q36" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +}# +o + unnamed-2bEXPLAIN select id from array_table where exists (select 1 from array_table.numbers n where n = 10)" + +g (0@8"@SCAN(<,>) | TFILTER ARRAY_TABLE | FLATMAP q0 -> { EXPLODE q0.NUMBERS | FILTER _ EQUALS promote(@c18 AS LONG) | MAP (@c9 AS _0) | DEFAULT NULL | FILTER _ NOT_NULL AS q1 RETURN (q0.ID AS ID) }!digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Nested Loop Join
FLATMAP (q2.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Type Filter
WHERE record IS [ARRAY_TABLE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 4 [ label=<
Primary Storage
record types: [ARRAY_TABLE, TA]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Predicate Filter
WHERE q8 NOT_NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ]; + 6 [ label=<
Value Computation
FIRST $q8 OR NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ]; + 7 [ label=<
Value Computation
MAP (@c9 AS _0)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ]; + 8 [ label=<
Predicate Filter
WHERE q4 EQUALS promote(@c18 AS LONG)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG)" ]; + 9 [ label=<
Value Computation
EXPLODE q2.NUMBERS
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG)" ]; + 3 -> 2 [ label=< q40> label="q40" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 5 [ label=< q8> label="q8" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 6 [ label=< q8> label="q8" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 8 -> 7 [ label=< q36> label="q36" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 9 -> 8 [ label=< q4> label="q4" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 1 [ label=< q8> label="q8" color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + { + rank=same; + rankDir=LR; + 2 -> 5 [ color="red" style="invis" ]; + } +} +C + unnamed-26EXPLAIN select id from array_table where 10 in numbers +ԁw (0c8&@]SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER promote(@c6 AS LONG) IN _.NUMBERS | MAP (_.ID AS ID)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q36.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Predicate Filter
WHERE promote(@c6 AS LONG) IN q2.NUMBERS
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 3 [ label=<
Type Filter
WHERE record IS [ARRAY_TABLE]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(STRING) AS FRUITS, ARRAY(LONG) AS NUMBERS, ARRAY(STRING AS NAME, STRING AS COLOR) AS FRUIT_RECORDS)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [ARRAY_TABLE, TA]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q29> label="q29" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q36> label="q36" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; } \ No newline at end of file diff --git a/yaml-tests/src/test/resources/in-predicate.metrics.yaml b/yaml-tests/src/test/resources/in-predicate.metrics.yaml index 87fbc30f19..1b93134187 100644 --- a/yaml-tests/src/test/resources/in-predicate.metrics.yaml +++ b/yaml-tests/src/test/resources/in-predicate.metrics.yaml @@ -81,3 +81,49 @@ unnamed-2: insert_time_ms: 10 insert_new_count: 134 insert_reused_count: 8 +- query: EXPLAIN select id from array_table where exists (select 1 from array_table.fruits + f where f = 'apple') + explain: 'COVERING(FRUITS [EQUALS @c18] -> [ID: KEY[2]]) | MAP (_.ID AS ID)' + task_count: 472 + task_total_time_ms: 141 + transform_count: 137 + transform_time_ms: 100 + transform_yield_count: 34 + insert_time_ms: 6 + insert_new_count: 46 + insert_reused_count: 2 +- query: EXPLAIN select id from array_table where 'apple' in fruits + explain: SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER @c6 IN _.FRUITS | MAP (_.ID + AS ID) + task_count: 400 + task_total_time_ms: 34 + transform_count: 119 + transform_time_ms: 10 + transform_yield_count: 25 + insert_time_ms: 1 + insert_new_count: 38 + insert_reused_count: 4 +- query: EXPLAIN select id from array_table where exists (select 1 from array_table.numbers + n where n = 10) + explain: SCAN(<,>) | TFILTER ARRAY_TABLE | FLATMAP q0 -> { EXPLODE q0.NUMBERS + | FILTER _ EQUALS promote(@c18 AS LONG) | MAP (@c9 AS _0) | DEFAULT NULL | + FILTER _ NOT_NULL AS q1 RETURN (q0.ID AS ID) } + task_count: 367 + task_total_time_ms: 21 + transform_count: 103 + transform_time_ms: 5 + transform_yield_count: 23 + insert_time_ms: 1 + insert_new_count: 34 + insert_reused_count: 2 +- query: EXPLAIN select id from array_table where 10 in numbers + explain: SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER promote(@c6 AS LONG) IN _.NUMBERS + | MAP (_.ID AS ID) + task_count: 400 + task_total_time_ms: 35 + transform_count: 119 + transform_time_ms: 9 + transform_yield_count: 25 + insert_time_ms: 1 + insert_new_count: 38 + insert_reused_count: 4 diff --git a/yaml-tests/src/test/resources/in-predicate.yamsql b/yaml-tests/src/test/resources/in-predicate.yamsql index 64eb0a6c5b..f15cbade17 100644 --- a/yaml-tests/src/test/resources/in-predicate.yamsql +++ b/yaml-tests/src/test/resources/in-predicate.yamsql @@ -21,8 +21,9 @@ schema_template: create type as struct ts(sa bigint, sb bigint) create type as struct fruit_type(name string, color string) create table ta(a bigint, b bigint, c double, d boolean, e string, f ts, primary key(a)) - create table array_table(id bigint, fruits string array, numbers bigint array, fruit_records fruit_type array, primary key(id)) create index f1 as select f.sa, f.sb from ta order by f.sa, f.sb + create table array_table(id bigint, fruits string array, numbers bigint array, fruit_records fruit_type array, primary key(id)) + create index fruits as select fruits from array_table --- setup: steps: @@ -189,10 +190,18 @@ test_block: # constant LONG value matched against IN list of LONG values. - query: select a from ta where 1 in (1, 2, 3) - unorderedResult: [{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}] + - + # Test alternate IN-like predicate with primitive array column - string in string array + - query: select id from array_table where exists (select 1 from array_table.fruits f where f = 'apple') + - supported_version: 4.8.1.0 + - explain: "COVERING(FRUITS [EQUALS @c18] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - unorderedResult: [{1}, {3}] - # Test IN predicate with primitive array column - string in string array - query: select id from array_table where 'apple' in fruits - supported_version: 4.8.1.0 + # Note: this should be able to match the "fruits" index like the above query, but it does not + - explain: "SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER @c6 IN _.FRUITS | MAP (_.ID AS ID)" - unorderedResult: [{1}, {3}] - # Test IN predicate with primitive array column - multiple matches @@ -204,10 +213,18 @@ test_block: - query: select id from array_table where 'pineapple' in fruits - supported_version: 4.8.1.0 - result: [] + - + # Test alternate IN-like predicate with primitive array column - int in int array + - query: select id from array_table where exists (select 1 from array_table.numbers n where n = 10) + - supported_version: 4.8.1.0 + - explain: "SCAN(<,>) | TFILTER ARRAY_TABLE | FLATMAP q0 -> { EXPLODE q0.NUMBERS | FILTER _ EQUALS promote(@c18 AS LONG) | MAP (@c9 AS _0) | DEFAULT NULL | FILTER _ NOT_NULL AS q1 RETURN (q0.ID AS ID) }" + - unorderedResult: [{1}, {3}] - # Test IN predicate with primitive array column - bigint in bigint array - query: select id from array_table where 10 in numbers - supported_version: 4.8.1.0 + # Note: as there is no index on the numbers field, this is the expected plan. Ideally, the previous query would also have the same plan as this one + - explain: "SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER promote(@c6 AS LONG) IN _.NUMBERS | MAP (_.ID AS ID)" - unorderedResult: [{1}, {3}] - # Test IN predicate with primitive array column - bigint no match diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb index e477672be7..5716e92ad2 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -48,6 +48,63 @@ r 3 -> 2 [ label=< q45> label="q45" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 4 -> 3 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; 2 -> 1 [ label=< q49> label="q49" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-teststEXPLAIN select "foo.table$repeated".id from "foo.table$repeated" where 'foo' IN "foo.table$repeated"."field.1$array" +y (0^8&@]SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER @c8 IN _.field.1$array | MAP (_.ID AS ID)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q46.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Predicate Filter
WHERE @c8 IN q2.field.1$array
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2table__1repeated]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1repeated, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q39> label="q39" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q46> label="q46" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-testsEXPLAIN select "foo.table$repeated".id from "foo.table$repeated" where exists (select 1 from "foo.table$repeated"."field.1$array" r where r = 'foo') + ($0)8.@OCOVERING(foo.table$repeated.2 [EQUALS @c20] -> [ID: KEY[2]]) | MAP (_.ID AS ID) +digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q77.ID AS ID)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID)" ]; + 2 [ label=<
Covering Index Scan
comparisons: [EQUALS @c20]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 [ label=<
Index
foo.table$repeated.2
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q77> label="q77" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} +i + all-tests\EXPLAIN select t."field.1$array" from "foo.table$repeated" as t where 3 IN t."field.0$array" +ךy ۲(028&@SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER promote(@c10 AS LONG) IN _.field.0$array | MAP (_.field.1$array AS field.1$array)digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q46.field.1$array AS field.1$array)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(ARRAY(STRING) AS field.1$array)" ]; + 2 [ label=<
Predicate Filter
WHERE promote(@c10 AS LONG) IN q2.field.0$array
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 [ label=<
Type Filter
WHERE record IS [foo__2table__1repeated]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 4 [ label=<
Scan
range: <-∞, ∞>
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 5 [ label=<
Primary Storage
record types: [foo__2tableA, foo__1tableC, foo__2table__1nested, foo__2table__1repeated, foo__2table__1nested__2repeated, my__1adjacency__1list, foo__2enum__2type, foo__2tableB, __foo__0tableD, foo__2tableE]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(RECORD)" ]; + 3 -> 2 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 3 [ label=< q39> label="q39" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 4 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q46> label="q46" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} + + all-tests|EXPLAIN select t."field.1$array" from "foo.table$repeated" as t where exists (select 1 from t."field.0$array" r where r = 3) + ("058+@cISCAN(foo.table$repeated.1 [EQUALS promote(@c22 AS LONG)]) | MAP (_.field.1$array AS field.1$array) digraph G { + fontname=courier; + rankdir=BT; + splines=polyline; + 1 [ label=<
Value Computation
MAP (q2.field.1$array AS field.1$array)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(ARRAY(STRING) AS field.1$array)" ]; + 2 [ label=<
Index Scan
comparisons: [EQUALS promote(@c22 AS LONG)]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 [ label=<
Index
foo.table$repeated.1
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS ID, ARRAY(LONG) AS field.0$array, ARRAY(STRING) AS field.1$array)" ]; + 3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; } = all-tests0EXPLAIN select "foo.tableA".* from "foo.tableA"; diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml index 4d30c54bfd..0d327ab246 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -50,6 +50,54 @@ all-tests: insert_time_ms: 3 insert_new_count: 52 insert_reused_count: 6 +- query: EXPLAIN select "foo.table$repeated".id from "foo.table$repeated" where + 'foo' IN "foo.table$repeated"."field.1$array" + explain: SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER @c8 IN _.field.1$array + | MAP (_.ID AS ID) + task_count: 404 + task_total_time_ms: 15 + transform_count: 121 + transform_time_ms: 8 + transform_yield_count: 27 + insert_time_ms: 1 + insert_new_count: 38 + insert_reused_count: 4 +- query: EXPLAIN select "foo.table$repeated".id from "foo.table$repeated" where + exists (select 1 from "foo.table$repeated"."field.1$array" r where r = 'foo') + explain: 'COVERING(foo.table$repeated.2 [EQUALS @c20] -> [ID: KEY[2]]) | MAP (_.ID + AS ID)' + task_count: 476 + task_total_time_ms: 14 + transform_count: 139 + transform_time_ms: 6 + transform_yield_count: 36 + insert_time_ms: 0 + insert_new_count: 46 + insert_reused_count: 2 +- query: EXPLAIN select t."field.1$array" from "foo.table$repeated" as t where 3 + IN t."field.0$array" + explain: SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER promote(@c10 AS LONG) + IN _.field.0$array | MAP (_.field.1$array AS field.1$array) + task_count: 404 + task_total_time_ms: 10 + transform_count: 121 + transform_time_ms: 5 + transform_yield_count: 27 + insert_time_ms: 0 + insert_new_count: 38 + insert_reused_count: 4 +- query: EXPLAIN select t."field.1$array" from "foo.table$repeated" as t where exists + (select 1 from t."field.0$array" r where r = 3) + explain: ISCAN(foo.table$repeated.1 [EQUALS promote(@c22 AS LONG)]) | MAP (_.field.1$array + AS field.1$array) + task_count: 448 + task_total_time_ms: 17 + transform_count: 135 + transform_time_ms: 10 + transform_yield_count: 34 + insert_time_ms: 0 + insert_new_count: 43 + insert_reused_count: 2 - query: EXPLAIN select "foo.tableA".* from "foo.tableA"; explain: 'COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, diff --git a/yaml-tests/src/test/resources/valid-identifiers.yamsql b/yaml-tests/src/test/resources/valid-identifiers.yamsql index d4fa0d47e0..ddf20260b5 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.yamsql +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -33,6 +33,10 @@ schema_template: create index "foo.table$nested.repeated.idx.field.1.2.1" as select "nested$level1"."level1$field.2"."level2$field.1" from "foo.table$nested.repeated" as "level0$t", "level0$t"."level0.field1" AS "nested$level1" create index "foo.table$nested.repeated.idx.field.1.1.1" as select "nested$level2"."level2$array.field.1" from "foo.table$nested.repeated" as "level0$t", "level0$t"."level0.field1" AS "nested$level1", "nested$level1"."level1$field.1" AS "nested$level2" + create table "foo.table$repeated" (id bigint, "field.0$array" bigint array, "field.1$array" string array, primary key (id)) + create index "foo.table$repeated.1" as select t."field.0$array" from "foo.table$repeated" AS t + create index "foo.table$repeated.2" as select t."field.1$array" from "foo.table$repeated" AS t + create type as struct "foo.struct"(S1 bigint, S2 bigint) create table "foo.tableA"("foo.tableA.A1" bigint, "foo.tableA.A2" bigint, "foo.tableA.A3" bigint, primary key("foo.tableA.A1")) create index "foo.tableA.idx" as select "foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3" FROM "foo.tableA" order by "foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3" @@ -90,6 +94,9 @@ setup: - query: insert into "foo.table$nested.repeated" values (1, [([(10, 20), (30, 40), (50, 60)], (90, 100), 10), ([(11, 21), (31, 41), (51, 61)], (91, 101), 11)]), (2, [([(100, 200), (300, 400), (500, 600)], (900, 1000), 20), ([(101, 201), (301, 401), (501, 601)], (901, 1001), 21)]) + - query: insert into "foo.table$repeated" + values (1, [ 1, 2, 3], ['foo', 'bar', 'baz']), + (2, [10, 20, 30], ['foo', 'quox']) - query: insert into "foo.tableA" values (1, 10, 1), (2, 10, 2) @@ -97,7 +104,6 @@ setup: 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)), @@ -144,24 +150,49 @@ test_block: --- test_block: name: all-tests - preset: single_repetition_ordered tests: - - # select from nested repeated table + - + # select from nested repeated table - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.3" = 20) - explain: "COVERING(foo.table$nested.repeated.idx.field.1.3 [EQUALS promote(@c25 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" - result: [{2}] - + # select a nested field field on a nested repeated struct - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as x where x."level1$field.2"."level2$field.1" = 91) - explain: "COVERING(foo.table$nested.repeated.idx.field.1.2.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" - result: [{1}] - - # select from deeply nested repeated table + - + # select from deeply nested repeated table - query: select t.id from "foo.table$nested.repeated" as t where exists (select * from t."level0.field1" as b, b."level1$field.1" where "level2$array.field.1" = 10) - explain: "COVERING(foo.table$nested.repeated.idx.field.1.1.1 [EQUALS promote(@c27 AS LONG)] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" - result: [{1}] - - # select from nested table + - + # select from nested table - query: select "level0.field1"."level1$field.1"."level2$field.1" from "foo.table$nested" where id = 1 - explain: "COVERING(foo.table$nested.idx <,> -> [ID: KEY[2], level0__2field1: [level1__1field__21: [level2__1field__21: KEY[0]]]]) | FILTER _.ID EQUALS promote(@c12 AS LONG) | MAP (_.level0.field1.level1$field.1.level2$field.1 AS level2$field.1)" - result: [{10}] + - + # select with predicates on repeated scalar fields + - query: select "foo.table$repeated".id from "foo.table$repeated" where 'foo' IN "foo.table$repeated"."field.1$array" + # Ideally, this would use the index "foo.table$repeated.2". It appears to be a matching problem unrelated to the identifier choices + - explain: "SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER @c8 IN _.field.1$array | MAP (_.ID AS ID)" + - result: [{1}, {2}] + - + # select with predicates on repeated scalar fields + - query: select "foo.table$repeated".id from "foo.table$repeated" where exists (select 1 from "foo.table$repeated"."field.1$array" r where r = 'foo') + - explain: "COVERING(foo.table$repeated.2 [EQUALS @c20] -> [ID: KEY[2]]) | MAP (_.ID AS ID)" + - result: [{1}, {2}] + - + # select repeated scalar fields + - query: select t."field.1$array" from "foo.table$repeated" as t where 3 IN t."field.0$array" + # Ideally, this would use the index "foo.table$repeated.1". It appears to be a matching problem unrelated to the identifier choices + - explain: "SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER promote(@c10 AS LONG) IN _.field.0$array | MAP (_.field.1$array AS field.1$array)" + - result: [{["foo", "bar", "baz"]}] + - + # select repeated scalar fields + - query: select t."field.1$array" from "foo.table$repeated" as t where exists (select 1 from t."field.0$array" r where r = 3) + - explain: "ISCAN(foo.table$repeated.1 [EQUALS promote(@c22 AS LONG)]) | MAP (_.field.1$array AS field.1$array)" + - result: [{["foo", "bar", "baz"]}] - # qualified star - query: select "foo.tableA".* from "foo.tableA"; From c6809001e9251b652b6f5270d46ee1de586978a4 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Mon, 24 Nov 2025 12:52:27 +0000 Subject: [PATCH 07/12] addreess spotbugs failure ; add documentation --- .../query/plan/cascades/typing/Type.java | 35 +++++++++++++++---- .../plan/cascades/values/PromoteValue.java | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) 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 3bd07206b7..ec905da4b8 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java @@ -2145,7 +2145,7 @@ 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. */ - public Record(@Nullable final String name, @Nullable final String storageName, 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; @@ -2602,12 +2602,14 @@ private static List normalizeFields(@Nullable final List fields) { ? Optional.empty() : Optional.of(fieldName)) .orElse("_" + i); - final var storageFieldName = ProtoUtils.toProtoBufCompliantName(explicitFieldName); + final var fieldStorageName = + field.getFieldStorageNameOptional() + .orElseGet(() -> ProtoUtils.toProtoBufCompliantName(explicitFieldName)); fieldToBeAdded = new Field(field.getFieldType(), Optional.of(explicitFieldName), Optional.of(i + 1), - Optional.of(storageFieldName)); + Optional.of(fieldStorageName)); } if (!(fieldNamesSeen.add(fieldToBeAdded.getFieldName()))) { @@ -2679,8 +2681,11 @@ public Type getFieldType() { } /** - * Returns the field name. - * @return The field name. + * Returns the field name if set. This should be the name of the field as the user would refer to it. + * This may not be set if the user has used un-named fields, in which case names based on the field + * index will be generated. + * + * @return The field name if set. */ @Nonnull public Optional getFieldNameOptional() { @@ -2688,19 +2693,37 @@ public Optional getFieldNameOptional() { } /** - * Returns the field name. + * Returns the field name. If the underlying {@link Optional} is not set, this will throw an error. + * * @return The field name. + * @see #getFieldStorageNameOptional() + * @throws RecordCoreException if the field is not set */ @Nonnull public String getFieldName() { return getFieldNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); } + /** + * Returns the name of the underlying field in protobuf storage if set. This can differ from the user-visible + * field name if, for example, there are characters in there are fields that need to be adjusted in order + * to produce a valid protobuf identifier. + * + * @return The protobuf field name used to serialize this field if set. + * @see ProtoUtils#toProtoBufCompliantName(String) for the escaping used + */ @Nonnull public Optional getFieldStorageNameOptional() { return fieldStorageNameOptional; } + /** + * Returns the name of the underlying field in protobuf storage if set. If the underlying {@link Optional} + * is not set, this will throw an error. + * + * @return The protobuf field name used to serialize this field. + * @see #getFieldStorageNameOptional() + */ @Nonnull public String getFieldStorageName() { return getFieldStorageNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); 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 3960abe60c..31adbdf6af 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PromoteValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PromoteValue.java @@ -153,7 +153,7 @@ public static Descriptors.EnumValueDescriptor stringToEnumValue(Descriptors.Enum } } SemanticException.check(maybeValue != null, SemanticException.ErrorCode.INVALID_ENUM_VALUE, value); - return maybeValue; + return Objects.requireNonNull(maybeValue); } public static UUID stringToUuidValue(String value) { From 168fc7d175cd7b26aacf705893cf5bb566e6774b Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Mon, 24 Nov 2025 15:33:04 +0000 Subject: [PATCH 08/12] adjust teamscale findings --- .../record/query/plan/cascades/TypeTest.java | 10 +- .../metadata/RecordLayerTable.java | 4 +- .../metadata/RecordLayerColumnTests.java | 134 +++++++++++------- yaml-tests/src/test/proto/identifiers.proto | 2 +- 4 files changed, 88 insertions(+), 62 deletions(-) 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 3dfb725240..1d3ed6b1e2 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/TypeTest.java @@ -630,12 +630,12 @@ void createRecordProtobuf(@Nonnull Type.Record recordType, @Nullable String expe } @Nonnull - private Type.Record adjustFieldsForDescriptorParsing(@Nonnull Type.Record record) { + private Type.Record adjustFieldsForDescriptorParsing(@Nonnull Type.Record recordType) { // There are a number of changes that happen to a type when we create a protobuf descriptor for it that // fail to round trip. These may be bugs, but we can at least assert that everything except for these // components are preserved - final ImmutableList.Builder newFields = ImmutableList.builderWithExpectedSize(record.getFields().size()); - for (Type.Record.Field field : record.getFields()) { + final ImmutableList.Builder newFields = ImmutableList.builderWithExpectedSize(recordType.getFields().size()); + for (Type.Record.Field field : recordType.getFields()) { Type fieldType = field.getFieldType(); if (fieldType instanceof Type.Array) { // Array types retain their nullability as there are separate. However, they make their own element types not nullable @@ -655,7 +655,7 @@ private Type.Record adjustFieldsForDescriptorParsing(@Nonnull Type.Record record } newFields.add(Type.Record.Field.of(fieldType, field.getFieldNameOptional(), field.getFieldIndexOptional())); } - return Type.Record.fromFields(record.isNullable(), newFields.build()); + return Type.Record.fromFields(recordType.isNullable(), newFields.build()); } @ParameterizedTest(name = "recordEqualsIgnoresName[{0}]") @@ -900,7 +900,6 @@ private static void assertTypeNames(@Nonnull SoftAssertions softly, @Nonnull Typ } for (Type.Record.Field field : recordType.getFields()) { - Type fieldType = field.getFieldType(); Descriptors.FieldDescriptor fieldDescriptor = descriptor.findFieldByName(field.getFieldStorageName()); softly.assertThat(fieldDescriptor) .as("field descriptor not found for field %s", field) @@ -913,6 +912,7 @@ private static void assertTypeNames(@Nonnull SoftAssertions softly, @Nonnull Typ // Extract the element from arrays. If the type is not nullable, we just replace the field // type with its element type. If the field is nullable, we need to also replace the // field descriptor so it points to the underlying values. + Type fieldType = field.getFieldType(); if (fieldType instanceof Type.Array) { Type.Array fieldArrayType = (Type.Array) fieldType; fieldType = fieldArrayType.getElementType(); 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 5bcd92f7b9..632059339a 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerTable.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerTable.java @@ -340,13 +340,13 @@ private static String getFieldStorageName(@Nullable Type.Record.Field field, @No } @Nullable - private static Type.Record getFieldRecordType(@Nullable Type.Record record, @Nullable Type.Record.Field field) { + private static Type.Record getFieldRecordType(@Nullable Type.Record recordType, @Nullable Type.Record.Field field) { if (field == null) { return null; } Type fieldType = field.getFieldType(); if (!(fieldType instanceof Type.Record)) { - Assert.failUnchecked(ErrorCode.INVALID_COLUMN_REFERENCE, "Field '" + field.getFieldName() + "' on type '" + (record == null ? "UNKNONW" : record.getName()) + "' is not a struct"); + Assert.failUnchecked(ErrorCode.INVALID_COLUMN_REFERENCE, "Field '" + field.getFieldName() + "' on type '" + (recordType == null ? "UNKNONW" : recordType.getName()) + "' is not a struct"); } return (Type.Record) fieldType; } 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 index a080e3919c..e7ccaf6aed 100644 --- 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 @@ -21,7 +21,14 @@ package com.apple.foundationdb.relational.recordlayer.metadata; import com.apple.foundationdb.relational.api.metadata.DataType; +import org.assertj.core.api.AutoCloseableSoftAssertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.annotation.Nonnull; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -29,66 +36,85 @@ * Unit tests for {@link RecordLayerColumn}. */ class RecordLayerColumnTests { + private static final RecordLayerColumn BLAH_COLUMN = RecordLayerColumn.newBuilder() + .setName("blah") + .setDataType(DataType.LongType.nullable()) + .setIndex(1) + .build(); @Test - void basicBuilder() { - final RecordLayerColumn column = RecordLayerColumn.newBuilder() - .setName("blah") - .setDataType(DataType.LongType.nullable()) - .setIndex(1) - .build(); - assertThat(column) - .hasToString("blah: long ∪ ∅ = 1"); - + void basicCopy() { 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()) + .setName(BLAH_COLUMN.getName()) + .setDataType(BLAH_COLUMN.getDataType()) + .setIndex(BLAH_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); + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(copy) + .hasToString("blah: long ∪ ∅ = 1"); + softly.assertThat(BLAH_COLUMN) + .hasToString("blah: long ∪ ∅ = 1"); + softly.assertThat(copy.getName()) + .isEqualTo("blah"); + softly.assertThat(copy.getDataType()) + .isEqualTo(DataType.LongType.nullable()); + softly.assertThat(copy.getIndex()) + .isOne(); + } + } - 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); + @Nonnull + static Stream testDiffersFromBlah() { + return Stream.of( + Arguments.of( + // Different name + RecordLayerColumn.newBuilder() + .setName("blag") + .setDataType(BLAH_COLUMN.getDataType()) + .setIndex(BLAH_COLUMN.getIndex()) + .build(), + "blag: long ∪ ∅ = 1" + ), + Arguments.of( + // Different type 1 + RecordLayerColumn.newBuilder() + .setName(BLAH_COLUMN.getName()) + .setDataType(DataType.LongType.notNullable()) + .setIndex(BLAH_COLUMN.getIndex()) + .build(), + "blah: long = 1" + ), + Arguments.of( + // Different type 2 + RecordLayerColumn.newBuilder() + .setName(BLAH_COLUMN.getName()) + .setDataType(DataType.StringType.nullable()) + .setIndex(BLAH_COLUMN.getIndex()) + .build(), + "blah: string ∪ ∅ = 1" + ), + Arguments.of( + // Different index + RecordLayerColumn.newBuilder() + .setName(BLAH_COLUMN.getName()) + .setDataType(BLAH_COLUMN.getDataType()) + .setIndex(BLAH_COLUMN.getIndex() + 1) + .build(), + "blah: long ∪ ∅ = 2" + ) + ); + } - 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); + @ParameterizedTest(name = "testDiffersFromBlah[{1}]") + @MethodSource + void testDiffersFromBlah(@Nonnull RecordLayerColumn differentColumn, @Nonnull String toString) { + try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(differentColumn) + .hasToString(toString) + .isNotEqualTo(BLAH_COLUMN) + .doesNotHaveSameHashCodeAs(BLAH_COLUMN) + .matches(c -> !BLAH_COLUMN.getName().equals(c.getName()) || !BLAH_COLUMN.getDataType().equals(c.getDataType()) || BLAH_COLUMN.getIndex() != c.getIndex()); + } } @Test diff --git a/yaml-tests/src/test/proto/identifiers.proto b/yaml-tests/src/test/proto/identifiers.proto index 894852b877..9be753c948 100644 --- a/yaml-tests/src/test/proto/identifiers.proto +++ b/yaml-tests/src/test/proto/identifiers.proto @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2021-2024 Apple Inc. and the FoundationDB project authors + * Copyright 2021-2025 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From c2ed1432b90f71e868a40f7f4966c381e453fd6a Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Tue, 25 Nov 2025 10:44:44 +0000 Subject: [PATCH 09/12] be more explicit about the storage name in RecordMetadataDeserializer --- .../recordlayer/metadata/RecordLayerIndex.java | 10 +++++----- .../metadata/serde/RecordMetadataDeserializer.java | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) 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 7571fef485..c1abf7b926 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 @@ -42,7 +42,7 @@ public final class RecordLayerIndex implements Index { private final String tableName; @Nonnull - private final String storageTableName; + private final String tableStorageName; private final String indexType; @@ -59,14 +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 tableStorageName, @Nonnull final String indexType, @Nonnull final String name, @Nonnull final KeyExpression keyExpression, @Nullable final RecordMetaDataProto.Predicate predicate, @Nonnull final Map options) { this.tableName = tableName; - this.storageTableName = storageTableName; + this.tableStorageName = tableStorageName; this.indexType = indexType; this.name = name; this.keyExpression = keyExpression; @@ -82,7 +82,7 @@ public String getTableName() { @Nonnull public String getTableStorageName() { - return storageTableName; + return tableStorageName; } @Nonnull @@ -124,7 +124,7 @@ public Map getOptions() { } @Nonnull - public static RecordLayerIndex from(@Nonnull final String tableName, @Nonnull final com.apple.foundationdb.record.metadata.Index index) { + public static RecordLayerIndex from(@Nonnull final String tableName, @Nonnull String tableStorageName, @Nonnull final com.apple.foundationdb.record.metadata.Index index) { final var indexProto = index.toProto(); return newBuilder().setName(index.getName()) .setIndexType(index.getType()) 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 4768c52196..f24c446da7 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataDeserializer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/serde/RecordMetadataDeserializer.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @@ -141,7 +142,7 @@ private RecordLayerTable.Builder generateTableBuilder(@Nonnull final String user .from(recordLayerType) .setName(userName) .setPrimaryKey(recordType.getPrimaryKey()) - .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(recordType.getName(), index)).collect(Collectors.toSet())); + .addIndexes(recordType.getIndexes().stream().map(index -> RecordLayerIndex.from(Objects.requireNonNull(recordLayerType.getName()), Objects.requireNonNull(recordLayerType.getStorageName()), index)).collect(Collectors.toSet())); } @Nonnull From c17255656e1f78fb324977b8dcd30f230b42cc49 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Tue, 25 Nov 2025 11:58:27 +0000 Subject: [PATCH 10/12] tag yamsql files with unexpected plans with relevant issue --- yaml-tests/src/test/resources/in-predicate.yamsql | 1 + yaml-tests/src/test/resources/valid-identifiers.yamsql | 2 ++ 2 files changed, 3 insertions(+) diff --git a/yaml-tests/src/test/resources/in-predicate.yamsql b/yaml-tests/src/test/resources/in-predicate.yamsql index f15cbade17..7563b05061 100644 --- a/yaml-tests/src/test/resources/in-predicate.yamsql +++ b/yaml-tests/src/test/resources/in-predicate.yamsql @@ -201,6 +201,7 @@ test_block: - query: select id from array_table where 'apple' in fruits - supported_version: 4.8.1.0 # Note: this should be able to match the "fruits" index like the above query, but it does not + # See: https://github.com/FoundationDB/fdb-record-layer/issues/3777 - explain: "SCAN(<,>) | TFILTER ARRAY_TABLE | FILTER @c6 IN _.FRUITS | MAP (_.ID AS ID)" - unorderedResult: [{1}, {3}] - diff --git a/yaml-tests/src/test/resources/valid-identifiers.yamsql b/yaml-tests/src/test/resources/valid-identifiers.yamsql index ddf20260b5..7b6a82ba5f 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.yamsql +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -175,6 +175,7 @@ test_block: # select with predicates on repeated scalar fields - query: select "foo.table$repeated".id from "foo.table$repeated" where 'foo' IN "foo.table$repeated"."field.1$array" # Ideally, this would use the index "foo.table$repeated.2". It appears to be a matching problem unrelated to the identifier choices + # See: https://github.com/FoundationDB/fdb-record-layer/issues/3777 - explain: "SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER @c8 IN _.field.1$array | MAP (_.ID AS ID)" - result: [{1}, {2}] - @@ -186,6 +187,7 @@ test_block: # select repeated scalar fields - query: select t."field.1$array" from "foo.table$repeated" as t where 3 IN t."field.0$array" # Ideally, this would use the index "foo.table$repeated.1". It appears to be a matching problem unrelated to the identifier choices + # See: https://github.com/FoundationDB/fdb-record-layer/issues/3777 - explain: "SCAN(<,>) | TFILTER foo__2table__1repeated | FILTER promote(@c10 AS LONG) IN _.field.0$array | MAP (_.field.1$array AS field.1$array)" - result: [{["foo", "bar", "baz"]}] - From 4077de6f9ae33fb58c6a815c306b776e38bee497 Mon Sep 17 00:00:00 2001 From: Youssef Hatem Date: Thu, 27 Nov 2025 12:42:55 +0000 Subject: [PATCH 11/12] add test that verifies converting pb-names to user-defined when deserializing RecordLayerSchemaTemplate. (#3) --- .../metadata/SchemaTemplateSerDeTests.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java index d9faabf372..f532fa132c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java @@ -24,9 +24,11 @@ import com.apple.foundationdb.record.RecordStoreState; import com.apple.foundationdb.record.metadata.IndexTypes; import com.apple.foundationdb.record.metadata.Key; +import com.apple.foundationdb.record.metadata.RecordTypeBuilder; import com.apple.foundationdb.record.metadata.expressions.KeyExpression; import com.apple.foundationdb.record.provider.foundationdb.IndexMaintainerFactoryRegistryImpl; import com.apple.foundationdb.record.query.plan.cascades.RawSqlFunction; +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.ddl.NoOpQueryFactory; @@ -47,6 +49,7 @@ import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Descriptors; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; @@ -332,6 +335,48 @@ void deserializationNestedTypesPreservesNamesCorrectly() { Assertions.assertEquals("Subtype", typeName); } + + @Test + void deserializationTranslatesUserDefinedNameCorrectly() { + final var metaDataBuilder = RecordMetaData.newBuilder(); + metaDataBuilder.setRecords(createEscapedRecordTypesDescriptor()); + RecordTypeBuilder typeBuilder = metaDataBuilder.getRecordType("Foo__0Bar__1Baz__2End"); + final var primaryKey = Key.Expressions.concat(Key.Expressions.recordType(), Key.Expressions.field("id")); + typeBuilder.setPrimaryKey(primaryKey); + typeBuilder.setRecordTypeKey(1L); + + // the RecordLayerSchemaTemplate deserializer translates proto fields to user-defined names. + final var expectedTableName = ProtoUtils.toUserIdentifier("Foo__0Bar__1Baz__2End"); + + final var expectedTable = RecordLayerTable.newBuilder(false) + .setName(expectedTableName) + .addColumn(RecordLayerColumn.newBuilder() + .setName("id") + .setDataType(DataType.Primitives.NULLABLE_LONG.type()) + .build()) + .addColumn(RecordLayerColumn.newBuilder() + .setName(ProtoUtils.toUserIdentifier("a__0b__1c__2d")) + .setDataType(DataType.Primitives.NULLABLE_LONG.type()) + .build()) + .addColumn(RecordLayerColumn.newBuilder() + .setName("otherField") + .setDataType(DataType.Primitives.NULLABLE_STRING.type()) + .build()) + .setPrimaryKey(primaryKey) + .build(); + + final var actualSchemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaDataBuilder.build(), "TestSchemaTemplate", 42); + Assertions.assertEquals(1, actualSchemaTemplate.getTables().size()); + + final var tableMaybe = actualSchemaTemplate.findTableByName(expectedTableName); + Assertions.assertTrue(tableMaybe.isPresent()); + final var actualTable = tableMaybe.get(); + final var actualRecordTable = Assertions.assertInstanceOf(RecordLayerTable.class, actualTable); + Assertions.assertEquals(expectedTable.getName(), actualRecordTable.getName()); + Assertions.assertEquals(expectedTable.getColumns(), actualRecordTable.getColumns()); + Assertions.assertEquals(expectedTable.getPrimaryKey(), actualRecordTable.getPrimaryKey()); + } + @Test void findTableByNameWorksCorrectly() { final var sampleRecordSchemaTemplate = RecordLayerSchemaTemplate.newBuilder() @@ -817,6 +862,52 @@ void testFindViewByNameReturnsEmpty() { Assertions.assertFalse(viewOpt.isPresent()); } + @Nonnull + private static Descriptors.FileDescriptor createEscapedRecordTypesDescriptor() { + DescriptorProtos.FileDescriptorProto fileDescriptorProto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setName("test_schema_with_escaping.proto") + .setPackage("com.apple.foundationdb.record.test1") + .setSyntax("proto2") + .addMessageType(DescriptorProtos.DescriptorProto.newBuilder() + .setName("Foo__0Bar__1Baz__2End") + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64) + .setName("id") + .setNumber(1) + ) + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64) + .setName("a__0b__1c__2d") + .setNumber(2) + ) + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_STRING) + .setName("otherField") + .setNumber(3) + ) + ) + .addMessageType(DescriptorProtos.DescriptorProto.newBuilder() + .setName("RecordTypeUnion") + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE) + .setTypeName("Foo__0Bar__1Baz__2End") + .setName("_Foo__0Bar__1Baz__2End") + .setNumber(1) + ) + ) + .build(); + + try { + return Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[0]); + } catch (Descriptors.DescriptorValidationException e) { + return Assertions.fail("unable to build file descriptor", e); + } + } + private static final class RecordMetadataDeserializerWithPeekingFunctionSupplier extends RecordMetadataDeserializer { @Nonnull @@ -874,4 +965,6 @@ public T clock(@Nonnull RelationalMetric.RelationalEvent event, IndexMaintainerFactoryRegistryImpl.instance(), Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()); } } + + } From 5d212bc1f51f5786a687e3b26e81c2d109a9e079 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Thu, 27 Nov 2025 13:08:48 +0000 Subject: [PATCH 12/12] Add test for malformed escape sequences in RecordLayerScheamaTemplateSerDeTests --- .../metadata/RecordLayerIndex.java | 1 + .../metadata/SchemaTemplateSerDeTests.java | 122 +++++++++++++++++- 2 files changed, 118 insertions(+), 5 deletions(-) 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 c1abf7b926..68aebe2f3d 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java @@ -129,6 +129,7 @@ public static RecordLayerIndex from(@Nonnull final String tableName, @Nonnull St return newBuilder().setName(index.getName()) .setIndexType(index.getType()) .setTableName(tableName) + .setTableStorageName(tableStorageName) .setKeyExpression(index.getRootExpression()) .setPredicate(indexProto.hasPredicate() ? indexProto.getPredicate() : null) .setOptions(index.getOptions()) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java index f532fa132c..22ad220205 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java @@ -22,13 +22,13 @@ import com.apple.foundationdb.record.RecordMetaData; import com.apple.foundationdb.record.RecordStoreState; +import com.apple.foundationdb.record.metadata.Index; import com.apple.foundationdb.record.metadata.IndexTypes; import com.apple.foundationdb.record.metadata.Key; import com.apple.foundationdb.record.metadata.RecordTypeBuilder; import com.apple.foundationdb.record.metadata.expressions.KeyExpression; import com.apple.foundationdb.record.provider.foundationdb.IndexMaintainerFactoryRegistryImpl; import com.apple.foundationdb.record.query.plan.cascades.RawSqlFunction; -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.ddl.NoOpQueryFactory; @@ -48,6 +48,7 @@ import com.apple.foundationdb.relational.util.Assert; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.protobuf.DescriptorProtos; import com.google.protobuf.Descriptors; import org.hamcrest.MatcherAssert; @@ -344,9 +345,18 @@ void deserializationTranslatesUserDefinedNameCorrectly() { final var primaryKey = Key.Expressions.concat(Key.Expressions.recordType(), Key.Expressions.field("id")); typeBuilder.setPrimaryKey(primaryKey); typeBuilder.setRecordTypeKey(1L); + metaDataBuilder.addIndex(typeBuilder, new Index("Foo__Bar$Baz.End$$a__b$c.d", "a__0b__1c__2d")); + final RecordMetaData metaData = metaDataBuilder.build(); + final var actualSchemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaData, "TestSchemaTemplate", metaData.getVersion()); // the RecordLayerSchemaTemplate deserializer translates proto fields to user-defined names. - final var expectedTableName = ProtoUtils.toUserIdentifier("Foo__0Bar__1Baz__2End"); + final var expectedTableName = "Foo__Bar$Baz.End"; + Assertions.assertEquals(1, actualSchemaTemplate.getTables().size()); + final var tableMaybe = actualSchemaTemplate.findTableByName(expectedTableName); + Assertions.assertTrue(tableMaybe.isPresent()); + final var actualTable = tableMaybe.get(); + final var actualRecordTable = Assertions.assertInstanceOf(RecordLayerTable.class, actualTable); + Assertions.assertEquals("Foo__0Bar__1Baz__2End", actualRecordTable.getType().getStorageName()); final var expectedTable = RecordLayerTable.newBuilder(false) .setName(expectedTableName) @@ -355,7 +365,7 @@ void deserializationTranslatesUserDefinedNameCorrectly() { .setDataType(DataType.Primitives.NULLABLE_LONG.type()) .build()) .addColumn(RecordLayerColumn.newBuilder() - .setName(ProtoUtils.toUserIdentifier("a__0b__1c__2d")) + .setName("a__b$c.d") .setDataType(DataType.Primitives.NULLABLE_LONG.type()) .build()) .addColumn(RecordLayerColumn.newBuilder() @@ -364,17 +374,73 @@ void deserializationTranslatesUserDefinedNameCorrectly() { .build()) .setPrimaryKey(primaryKey) .build(); + Assertions.assertEquals(expectedTable.getName(), actualRecordTable.getName()); + Assertions.assertEquals(expectedTable.getColumns(), actualRecordTable.getColumns()); + Assertions.assertEquals(expectedTable.getPrimaryKey(), actualRecordTable.getPrimaryKey()); - final var actualSchemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaDataBuilder.build(), "TestSchemaTemplate", 42); - Assertions.assertEquals(1, actualSchemaTemplate.getTables().size()); + final var actualIndexes = actualTable.getIndexes(); + Assertions.assertEquals(1, actualIndexes.size(), () -> "actual indexes: " + actualIndexes + " should have size 1"); + final var actualIndex = Iterables.getOnlyElement(actualIndexes); + Assertions.assertEquals("Foo__Bar$Baz.End$$a__b$c.d", actualIndex.getName()); + Assertions.assertEquals(expectedTableName, actualIndex.getTableName()); + Assertions.assertInstanceOf(RecordLayerIndex.class, actualIndex); + final var actualRecordLayerIndex = (RecordLayerIndex) actualIndex; + Assertions.assertEquals(Key.Expressions.field("a__0b__1c__2d"), actualRecordLayerIndex.getKeyExpression()); + Assertions.assertEquals("Foo__0Bar__1Baz__2End", actualRecordLayerIndex.getTableStorageName()); + } + @Test + void deserializeTemplateWithMalformedNamesCorrectly() { + final var metaDataBuilder = RecordMetaData.newBuilder(); + metaDataBuilder.setRecords(createRecordTypesDescriptorWithMalformedEscaping()); + RecordTypeBuilder typeBuilder = metaDataBuilder.getRecordType("_Foo__Bar__1Baz"); + final var primaryKey = Key.Expressions.concat(Key.Expressions.recordType(), Key.Expressions.field("id")); + typeBuilder.setPrimaryKey(primaryKey); + typeBuilder.setRecordTypeKey(1L); + metaDataBuilder.addIndex(typeBuilder, new Index("_Foo__Bar__1Baz$a__b$c.d", "a__b__1c__2d")); + final RecordMetaData metaData = metaDataBuilder.build(); + final var actualSchemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaData, "TestSchemaTemplate", metaData.getVersion()); + + // the RecordLayerSchemaTemplate deserializer translates proto fields to user-defined names as best it can. + // Note that if we went back the other way, the table name we'd have gotten back would have a type name of + // "Foo__0Bar__1Baz" + final var expectedTableName = "_Foo__Bar$Baz"; + Assertions.assertEquals(1, actualSchemaTemplate.getTables().size()); final var tableMaybe = actualSchemaTemplate.findTableByName(expectedTableName); Assertions.assertTrue(tableMaybe.isPresent()); final var actualTable = tableMaybe.get(); final var actualRecordTable = Assertions.assertInstanceOf(RecordLayerTable.class, actualTable); + Assertions.assertEquals("_Foo__Bar__1Baz", actualRecordTable.getType().getStorageName()); + + final var expectedTable = RecordLayerTable.newBuilder(false) + .setName(expectedTableName) + .addColumn(RecordLayerColumn.newBuilder() + .setName("id") + .setDataType(DataType.Primitives.NULLABLE_LONG.type()) + .build()) + .addColumn(RecordLayerColumn.newBuilder() + .setName("a__b$c.d") + .setDataType(DataType.Primitives.NULLABLE_LONG.type()) + .build()) + .addColumn(RecordLayerColumn.newBuilder() + .setName("otherField") + .setDataType(DataType.Primitives.NULLABLE_STRING.type()) + .build()) + .setPrimaryKey(primaryKey) + .build(); Assertions.assertEquals(expectedTable.getName(), actualRecordTable.getName()); Assertions.assertEquals(expectedTable.getColumns(), actualRecordTable.getColumns()); Assertions.assertEquals(expectedTable.getPrimaryKey(), actualRecordTable.getPrimaryKey()); + + final var actualIndexes = actualTable.getIndexes(); + Assertions.assertEquals(1, actualIndexes.size(), () -> "actual indexes: " + actualIndexes + " should have size 1"); + final var actualIndex = Iterables.getOnlyElement(actualIndexes); + Assertions.assertEquals("_Foo__Bar__1Baz$a__b$c.d", actualIndex.getName()); + Assertions.assertEquals(expectedTableName, actualIndex.getTableName()); + Assertions.assertInstanceOf(RecordLayerIndex.class, actualIndex); + final var actualRecordLayerIndex = (RecordLayerIndex) actualIndex; + Assertions.assertEquals(Key.Expressions.field("a__b__1c__2d"), actualRecordLayerIndex.getKeyExpression()); + Assertions.assertEquals("_Foo__Bar__1Baz", actualRecordLayerIndex.getTableStorageName()); } @Test @@ -908,6 +974,52 @@ private static Descriptors.FileDescriptor createEscapedRecordTypesDescriptor() { } } + @Nonnull + private static Descriptors.FileDescriptor createRecordTypesDescriptorWithMalformedEscaping() { + DescriptorProtos.FileDescriptorProto fileDescriptorProto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setName("test_schema_with_malformed_escaping.proto") + .setPackage("com.apple.foundationdb.record.test1") + .setSyntax("proto2") + .addMessageType(DescriptorProtos.DescriptorProto.newBuilder() + .setName("_Foo__Bar__1Baz") + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64) + .setName("id") + .setNumber(1) + ) + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64) + .setName("a__b__1c__2d") + .setNumber(2) + ) + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_STRING) + .setName("otherField") + .setNumber(3) + ) + ) + .addMessageType(DescriptorProtos.DescriptorProto.newBuilder() + .setName("RecordTypeUnion") + .addField(DescriptorProtos.FieldDescriptorProto.newBuilder() + .setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE) + .setTypeName("_Foo__Bar__1Baz") + .setName("__Foo__Bar__1Baz") + .setNumber(1) + ) + ) + .build(); + + try { + return Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[0]); + } catch (Descriptors.DescriptorValidationException e) { + return Assertions.fail("unable to build file descriptor", e); + } + } + private static final class RecordMetadataDeserializerWithPeekingFunctionSupplier extends RecordMetadataDeserializer { @Nonnull