From a8e0bdd060fd56d806bac9b5a56ab043ae99feb1 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Mon, 10 Nov 2025 16:52:34 +0000 Subject: [PATCH 1/8] Reintroduce translation of identifiers to Protobuf compliant names This reintroduces the work that was reverted (namely #3696 and #3706; see also #3726). It mainly copies the original PR for the translation work itself, though it takes a different approach to actually performing the translation. Perhaps most notably, this purposefully omits translation of function, view, and index names--essentially, any item that is not serialized into a Protobuf identifier is skipped. That means that we only worry about type (including table, struct, and enum names), enum values, and struct/table fields. From an implementation this perspective, this moves the translation of the names into the areas that are designed to interface between the `Type` system of the planner and the Protobuf-based system of Record Layer core. This is then stored in the `Type` information that is associated with the plan. At the moment, we cheat in a few places and require that the type's fields are always the result of applying the translation function to the display name, or vice versa. However, it allows us to think about allowing custom field names in the future. In a world where we only use Protobuf for storage but have a different mechanism for keeping track of fields in the runtime, we'd need a similar mechanism, though it may take the form of something like a field name to ordinal position mapping, say. This borrows and expands the tests from #3969 and #3706. In particular, it adds additional tests around enums and view and function definitions with non-compliant names, which now work. --- .../KeyExpressionExpansionVisitor.java | 3 +- .../cascades/ScalarTranslationVisitor.java | 3 +- .../CompatibleTypeEvolutionPredicate.java | 2 +- .../query/plan/cascades/typing/Type.java | 142 +++- .../plan/cascades/values/FieldValue.java | 51 +- .../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 | 4 + .../query/plan/cascades/BooleanValueTest.java | 12 +- .../values/RecordConstructorValueTest.java | 16 +- .../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 | 27 + .../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 | 84 ++- .../recordlayer/query/LogicalOperator.java | 12 +- .../recordlayer/query/PlanGenerator.java | 4 +- .../recordlayer/query/SemanticAnalyzer.java | 10 +- .../query/visitors/DdlVisitor.java | 7 +- .../query/visitors/QueryVisitor.java | 22 +- .../recordlayer/util/TypeUtils.java | 2 +- .../api/ddl/DdlStatementParsingTest.java | 143 ++++ .../utils/RelationalStructAssert.java | 3 +- .../yamltests/command/QueryExecutor.java | 16 +- .../utils/ExportSchemaTemplateUtil.java | 55 ++ .../test/java/MetaDataExportUtilityTests.java | 80 +++ .../src/test/java/YamlIntegrationTests.java | 12 + yaml-tests/src/test/proto/identifiers.proto | 77 ++ .../resources/valid-identifiers.metrics.binpb | 677 ++++++++++++++++++ .../resources/valid-identifiers.metrics.yaml | 531 ++++++++++++++ .../test/resources/valid-identifiers.yamsql | 630 ++++++++++++++++ .../resources/valid_identifiers_metadata.json | 247 +++++++ 42 files changed, 2924 insertions(+), 201 deletions(-) create mode 100644 yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/ExportSchemaTemplateUtil.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..8715867ac0 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. @@ -1752,15 +1754,17 @@ class Enum implements Type { public Enum(final boolean isNullable, @Nullable final List enumValues) { - this(isNullable, enumValues, null); + 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,7 @@ public boolean isNullable() { @Nonnull @Override public Enum withNullability(final boolean newIsNullable) { - return new Enum(newIsNullable, enumValues, name); + return new Enum(newIsNullable, enumValues, name, storageName); } @Nullable @@ -1800,13 +1804,13 @@ public String getName() { @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 +1891,19 @@ public static > Enum forJavaEnum(@Nonnull final Clas T[] enumConstants = enumClass.getEnumConstants(); for (int i = 0; i < enumConstants.length; i++) { final var enumConstant = enumConstants[i]; - enumValuesBuilder.add(new EnumValue(enumConstant.name(), i)); + enumValuesBuilder.add(EnumValue.from(enumConstant.name(), i)); } - return new Enum(false, enumValuesBuilder.build(), null); + return new Enum(false, enumValuesBuilder.build(), null, null); } private static Enum fromProtoValues(boolean isNullable, @Nonnull List values) { - return new Enum(isNullable, enumValuesFromProto(values), null); + return new Enum(isNullable, enumValuesFromProto(values), null, null); } 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 +1918,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 +1940,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 +1980,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 +1995,11 @@ public String getName() { return name; } + @Nonnull + public String getStorageName() { + return storageName; + } + public int getNumber() { return number; } @@ -2004,14 +2030,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 +2061,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 +2109,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 +2118,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 +2151,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 +2164,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 +2275,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.getStorageFieldName(); fieldType.addProtoField(typeRepositoryBuilder, recordMsgBuilder, field.getFieldIndex(), fieldName, @@ -2335,6 +2382,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 +2418,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 +2460,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 +2498,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 +2574,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 +2614,9 @@ public static class Field implements Comparable, PlanSerializable { @Nonnull private final Optional fieldIndexOptional; + @Nonnull + private final Optional storageFieldNameOptional; + /** * Memoized hash function. */ @@ -2576,10 +2634,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 storageFieldNameOptional) { this.fieldType = fieldType; this.fieldNameOptional = fieldNameOptional; this.fieldIndexOptional = fieldIndexOptional; + this.storageFieldNameOptional = storageFieldNameOptional; } /** @@ -2609,6 +2668,16 @@ public String getFieldName() { return getFieldNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); } + @Nonnull + public Optional getStorageFieldNameOptional() { + return storageFieldNameOptional; + } + + @Nonnull + public String getStorageFieldName() { + return getStorageFieldNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); + } + /** * Returns the field index. * @return The field index. @@ -2647,7 +2716,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, storageFieldNameOptional); } @Nonnull @@ -2690,14 +2759,21 @@ public PRecordType.PField toProto(@Nonnull final PlanSerializationContext serial fieldProtoBuilder.setFieldType(fieldType.toTypeProto(serializationContext)); fieldNameOptional.ifPresent(fieldProtoBuilder::setFieldName); fieldIndexOptional.ifPresent(fieldProtoBuilder::setFieldIndex); + storageFieldNameOptional.ifPresent(storageFieldName -> { + if (!fieldProtoBuilder.getFieldName().equals(storageFieldName)) { + fieldProtoBuilder.setStorageFieldName(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.hasStorageFieldName() ? Optional.of(fieldProto.getStorageFieldName()) : fieldNameOptional; + return new Field(fieldType, fieldNameOptional, fieldIndexOptional, storageFieldNameOptional); } /** @@ -2709,7 +2785,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 +2796,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 +2806,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().getStorageFieldNameOptional()) .collect(ImmutableList.toImmutableList()); } @@ -550,8 +550,8 @@ private static List computeFieldTypes(@Nonnull final List= 0); + /* this.name = name; this.ordinal = ordinal; this.type = type; + */ + this.field = field; + this.ordinal = ordinal; } @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,17 +696,17 @@ 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); + builder.setName(field.getFieldName()); builder.setOrdinal(ordinal); - if (type != null) { - builder.setType(type.toTypeProto(serializationContext)); + if (field.getFieldType() != null) { + builder.setType(getType().toTypeProto(serializationContext)); } return builder.build(); } @@ -706,22 +720,25 @@ public static ResolvedAccessor fromProto(@Nonnull PlanSerializationContext seria } else { type = null; } - return new ResolvedAccessor(resolvedAccessorProto.getName(), resolvedAccessorProto.getOrdinal(), type); + final Field field = Field.of(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); + final Field field = Field.of(null, Optional.ofNullable(fieldName)); + 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..9bddbea214 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 storage_field_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 { 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/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-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..905d5c69cf 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,32 @@ 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 "RecordLayerColumn{" + + "name='" + name + '\'' + + ", dataType=" + dataType + + ", index=" + 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..8d13e11ee4 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(@Nonnull String tableStorageName) { + this.tableStorageName = tableStorageName; + return this; + } + + @Nonnull + public Builder setTableType(@Nonnull Type.Record record) { + return setTableName(record.getName()) + .setTableStorageName(record.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..99075bb087 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 record, @Nonnull final List fields) { Assert.thatUnchecked(!fields.isEmpty()); - return toKeyExpression(fields, 0); + return toKeyExpression(record, 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 record, @Nonnull final Iterator fields) { + Assert.thatUnchecked(fields.hasNext()); + String fieldName = fields.next(); + Type.Record.Field field = getFieldDefinition(record, fieldName); + final FieldKeyExpression expression = Key.Expressions.field(getFieldStorageName(field, fieldName)); + if (fields.hasNext()) { + Type.Record fieldType = getFieldRecordType(record, 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 record, @Nonnull String fieldName) { + return record == null ? null : record.getFieldNameFieldMap().get(fieldName); + } + + @Nonnull + private static String getFieldStorageName(@Nullable Type.Record.Field field, @Nonnull String fieldName) { + return field == null ? ProtoUtils.toProtoBufCompliantName(fieldName) : field.getStorageFieldName(); + } + + @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..2351272a71 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,8 @@ 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())); + Type childType = fieldValue.getChild().getResultType(); + return toKeyExpression(childType, fieldValue.getFieldPath().getFieldAccessors().iterator()); } else if (value instanceof ArithmeticValue) { var children = value.getChildren(); var builder = ImmutableList.builder(); @@ -561,15 +565,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 +652,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 @@ -726,7 +730,9 @@ private Value dereference(@Nonnull Value value) { final var valueWithChild = (ValueWithChild) value; return valueWithChild.withNewChild(dereference(valueWithChild.getChild())); } else if (value instanceof QuantifiedObjectValue) { - return dereference(correlatedKeyExpressions.get(value.getCorrelatedTo().stream().findFirst().orElseThrow())); + Value dereferenced = dereference(correlatedKeyExpressions.get(value.getCorrelatedTo().stream().findFirst().orElseThrow())); + // Don't dereference the QOV if it changes the result type + return dereferenced.getResultType().equals(value.getResultType()) ? dereferenced : value; } else if (value instanceof ArithmeticValue) { final List newChildren = new ArrayList<>(); for (Value v:value.getChildren()) { @@ -739,23 +745,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 Type type, @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(field.getFieldType(), 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) @@ -766,13 +769,24 @@ public String getRecordTypeName() { return recordTypes.stream().findFirst().orElseThrow(); } + /* + @Nonnull + private static Type.Record.Field resolveField(@Nonnull Type type, @Nonnull FieldValue.ResolvedAccessor resolvedAccessor) { + Assert.thatUnchecked(type.isRecord(), "field accessors must be resolved on record types"); + final Type.Record recordType = (Type.Record) type; + return recordType.getFieldNameFieldMap().get(resolvedAccessor.getName()); + } + */ + @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.getStorageFieldName()); + 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.getStorageFieldName(), 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..d31e9432bf 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; @@ -241,7 +242,8 @@ private Plan generatePhysicalPlanForCompilableStatement(@Nonnull AstNormalize return maybePlan.optimize(planner, planContext, currentPlanHashMode); } 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(); + ErrorCode code = mde instanceof ProtoUtils.InvalidNameException ? ErrorCode.INVALID_NAME : ErrorCode.SYNTAX_OR_ACCESS_VIOLATION; + throw new RelationalException(mde.getMessage(), code, mde).toUncheckedWrappedException(); } catch (VerifyException | SemanticException ve) { throw ExceptionUtil.toRelationalException(ve).toUncheckedWrappedException(); } catch (RelationalException e) { 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..de4a7a9986 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)); 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/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..fb6ef149a2 --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/ExportSchemaTemplateUtil.java @@ -0,0 +1,55 @@ +/* + * 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; + +public class ExportSchemaTemplateUtil { + private ExportSchemaTemplateUtil() { + } + + 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/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..d651f31e81 --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -0,0 +1,677 @@ + += + 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" ]; +} +& + +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..8ae5d83024 --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -0,0 +1,531 @@ +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 +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..8f78324947 --- /dev/null +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -0,0 +1,630 @@ +# +# valid-identifiers.yamsql +# +# This source file is part of the FoundationDB open source project +# +# Copyright 2021-2024 Apple Inc. and the FoundationDB project authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +options: + supported_version: !current_version +--- +schema_template: + CREATE TYPE AS STRUCT "foo.struct"(S1 bigint, S2 bigint) + create table "foo.tableA"("foo.tableA.A1" bigint, "foo.tableA.A2" bigint, "foo.tableA.A3" bigint, primary key("foo.tableA.A1")) + create index "foo.tableA.idx" as select "foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3" FROM "foo.tableA" order by "foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3" + create index "foo.tableA.idx2" as select sum("foo.tableA.A1") FROM "foo.tableA" group by "foo.tableA.A2" + create index "foo.tableA.idx3" as select "foo.tableA.A2" FROM "foo.tableA" order by "foo.tableA.A2" + + create table "foo.tableB"("foo.tableB.B1" bigint, "foo.tableB.B2" bigint, "foo.tableB.B3" "foo.struct", primary key("foo.tableB.B1")) + + create table "foo$tableC"("foo$tableC$C1" bigint, "foo$tableC$C2" bigint, "foo$tableC$C3" bigint, primary key("foo$tableC$C1")) + create index "foo$tableC$idx" as select "foo$tableC$C1", "foo$tableC$C2", "foo$tableC$C3" FROM "foo$tableC" order by "foo$tableC$C1", "foo$tableC$C2", "foo$tableC$C3" + create index "foo$tableC$idx2" as select sum("foo$tableC$C1") FROM "foo$tableC" group by "foo$tableC$C2" + + create table "__foo__tableD"("__foo__tableD$D1" bigint, "__foo__tableD$D2" bigint, "__foo__tableD$D3" bigint, primary key("__foo__tableD$D1")) + create index "__foo__tableD$idx" as select "__foo__tableD$D1", "__foo__tableD$D2", "__foo__tableD$D3" FROM "__foo__tableD" order by "__foo__tableD$D1", "__foo__tableD$D2", "__foo__tableD$D3" + create index "__foo__tableD$idx2" as select sum("__foo__tableD$D1") FROM "__foo__tableD" group by "__foo__tableD$D2" + + create table "foo.tableE"("foo.tableE.E1" bigint, "foo.tableE.E2" bigint array, "foo.tableE.E3" "foo.struct", primary key("foo.tableE.E1")) + + create function "$yay" (in "_1$blah" bigint) + as select "foo.tableE"."foo.tableE.E1" as "_$x.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = "_1$blah" + create function "__2yay" (in "_2$blah" bigint) + as select "foo.tableE"."foo.tableE.E1" as "_$y.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = "_2$blah" + create function "नमस्त" (in "x.y" bigint) + as select "foo.tableE"."foo.tableE.E1" as "_$z.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = "x.y" + + create function "alpha__f" (in "__in__foo.struct" TYPE "foo.struct") RETURNS bigint AS "__in__foo.struct".S1 + create function "βήτα__f" (in "__in__foo.struct" TYPE "foo.struct") RETURNS bigint AS "__in__foo.struct".S2 + + create view "$yay__view" + as select "foo.tableE"."foo.tableE.E1" as "_$x.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = 4 + create view "__2yay__view" + as select "foo.tableE"."foo.tableE.E1" as "_$y.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = 5 + create view "வணக்கம்" + as select "foo.tableE"."foo.tableE.E1" as "_$z.id" from "foo.tableE" where "foo.tableE"."foo.tableE.E3".S1 = 6 + + create table "my$adjacency$list"("me" bigint, "my__parent" bigint, primary key("me")) + create function "__$func1" ( in "__$func1$A" bigint, in "__$func1$B" bigint ) + as select "me" as "__A", "my__parent" as "__B" from "my$adjacency$list" where "me" < "__$func1$A" and "my$adjacency$list"."my__parent" = "__$func1$B" + create function "__$func2" ( "__$func2$A" bigint ) + as select "me" as "__A", "my__parent" as "__B" from "my$adjacency$list" where "me" = "__$func2$A" + create function "__$func3" ( in "__$func3$A" bigint, in "__$func3$B" bigint, in "__$func3$C" bigint) as select "__..f1"."__A", "__..f1"."__B", "__..f2"."__A", "__..f2"."__B" from "__$func1"("__$func3$A", "__$func3$B") "__..f1", "__$func2"("__$func3$C") "__..f2" + + create type as enum "foo.enum"('A', 'B$C', 'C.D', 'E__F', '__G$H') + create table "foo.enum.type"("enum_type.id" bigint, "enum_type.enum__1" "foo.enum", "enum_type.enum__2" "foo.enum", primary key ("enum_type.id")) + + create index "foo.enum.type$enum__1" as select "enum_type.enum__1" from "foo.enum.type" + +--- +setup: + steps: + - query: INSERT INTO "foo.tableA" + VALUES (1, 10, 1), + (2, 10, 2) + - query: INSERT INTO "foo.tableB" + VALUES (1, 20, (4, 40)), + (2, 20, (5, 50)), + (3, 20, (6, 60)) + + - query: INSERT INTO "foo.tableE" + VALUES (1, [1, 2, 3], (4, 40)), + (2, [2, 3, 4], (5, 50)), + (3, [3, 4, 5], (6, 60)) + - query: INSERT INTO "foo$tableC" + VALUES (1, 20, 1), + (2, 20, 2), + (3, 20, 3), + (4, 20, 4) + - query: INSERT INTO "__foo__tableD" + VALUES (1, 20, 1), + (2, 20, 2), + (3, 20, 3), + (4, 20, 4) + - query: INSERT INTO "my$adjacency$list" + VALUES (1, -1), + (2, 1), + (3, 1), + (4, 1), + (5, 2), + (6, 2) + + # Note: the insert below flips the order of the enum__1 and enum__2 columns + - query: INSERT INTO "foo.enum.type"("enum_type.id", "enum_type.enum__2", "enum_type.enum__1") + VALUES ( 1, 'B$C', 'A'), + ( 2, 'B$C', 'B$C'), + ( 3, 'B$C', 'C.D'), + ( 4, 'B$C', 'E__F'), + ( 5, 'B$C', '__G$H'), + ( 6, 'A', 'B$C'), + ( 7, 'C.D', 'B$C'), + ( 8, 'E__F', 'B$C'), + ( 9, '__G$H', 'B$C'), + (10, 'B$C', null), + (11, null, 'B$C') +--- +test_block: + name: insert-explicit-columns + preset: single_repetition_ordered + tests: + - + - query: INSERT INTO "foo.tableA" ("foo.tableA.A3", "foo.tableA.A1", "foo.tableA.A2") VALUES (3, 3, 10) + - count: 1 +--- +test_block: + name: all-tests + preset: single_repetition_ordered + tests: + - + # qualified star + - query: select "foo.tableA".* from "foo.tableA"; + - explain: "COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)" + - result: [{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1}, {"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}, {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}] + - + # non-qualified star + - query: select * from "foo.tableA"; + - explain: "COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)" + - result: [{"foo.tableA.A1": 1 , 10, 1}, {"foo.tableA.A1": 2, 10, 2}, {"foo.tableA.A1": 3, 10, 3}] + - + # aliased star + - query: select "_$$$".* from "foo.tableA" as "_$$$"; + - explain: "COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP (_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3)" + - result: [{"foo.tableA.A1": 1 , 10, 1}, {"foo.tableA.A1": 2, 10, 2}, {"foo.tableA.A1": 3, 10, 3}] + - + # with predicate + - query: select "foo.tableA".* from "foo.tableA" where "foo.tableA.A3" >= 2; + - result: [{"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}, {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}] + - + # qualified select element + - query: select "foo.tableA"."foo.tableA.A1" from "foo.tableA"; + - result: [{"foo.tableA.A1": 1}, {"foo.tableA.A1": 2}, {"foo.tableA.A1": 3}] + - + # with select element alias + - query: select "foo.tableA"."foo.tableA.A1" AS "__.__." from "foo.tableA"; + - result: [{"__.__.": 1}, {"__.__.": 2}, {"__.__.": 3}] + - + # with select element alias prefixed with . + - query: select "foo.tableA"."foo.tableA.A1" AS ".__." from "foo.tableA"; + - error: "42602" + - + # with select element alias prefixed with $ + - query: select "foo.tableA"."foo.tableA.A1" AS "$__." from "foo.tableA"; + - error: "42602" + - + # with select element alias with non-supported characters + - query: select "foo.tableA"."foo.tableA.A1" AS "उपनाम" from "foo.tableA"; + - error: "42602" + - + # multi-level CTEs + - query: with "A$_$__$$" as (with "A$__$" as (select "foo.tableA.A1" as "A$" from "foo.tableA") select * from "A$__$") select * from "A$_$__$$" + - result: [{"A$": 1}, {"A$": 2}, {"A$": 3}] + - + # CTEs with column aliases + - query: with "A$__$" ("__$$a", "__$$b", "__$$c") as (select "foo.tableA".* from "foo.tableA") select * from "A$__$" + - result: [{"__$$a": 1, "__$$b": 10, "__$$c": 1}, {"__$$a": 2, "__$$b": 10, "__$$c": 2}, {"__$$a": 3, "__$$b": 10, "__$$c": 3}] + - + # with recursive CTE + - query: with recursive "x$__$" as ( + select "me", "my__parent", 0 as "__level__" from "my$adjacency$list" where "me" = 5 + union all + select "x$"."me", "x$"."my__parent", "x$$".y as "__level__" from "my$adjacency$list" as "x$", (select "me", "my__parent", "__level__" + 1 as y from "x$__$") as "x$$" where "x$$"."my__parent" = "x$"."me") + traversal order pre_order + select "me", "my__parent", "__level__" from "x$__$" + - result: [{"me": 5, "my__parent": 2, "__level__": 0}, {"me": 2, "my__parent": 1, "__level__": 1}, {"me": 1, "my__parent": -1, "__level__": 2}] + - + # recursive CTE with column aliases + - query: with recursive "_$__$" ("__a", "__b", "__c") as ( + select "me", "my__parent", 0 as "__level__" from "my$adjacency$list" where "me" = 5 + union all + select "_$"."me", "_$"."my__parent", "_$$".y as "__level__" from "my$adjacency$list" as "_$", (select "me", "my__parent", "__level__" + 1 as y from "_$__$") as "_$$" where "_$$"."my__parent" = "_$"."me") + traversal order pre_order + select "__a", "__b", "__c" from "_$__$" + - result: [{"__a": 5, "__b": 2, "__c": 0}, {"__a": 2, "__b": 1, "__c": 1}, {"__a": 1, "__b": -1, "__c": 2}] + - + # with non-qualified select element + - query: select "foo.tableA.A1" from "foo.tableA"; + - result: [{"foo.tableA.A1": 1}, {"foo.tableA.A1": 2}, {"foo.tableA.A1": 3}] + - + # order by + - query: select "foo.tableA".* from "foo.tableA" where "foo.tableA.A3" >= 2 order by "foo.tableA.A1"; + - result: [{"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}, {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}] + - + # order by alias + - query: select "foo.tableA.A1" as "_______" from "foo.tableA" where "foo.tableA.A3" >= 2 order by "_______"; + - result: [{"_______": 2}, {"_______": 3}] + - + # group by + - query: select "foo.tableA.A2", sum("foo.tableA.A1") from "foo.tableA" group by "foo.tableA.A2"; + - explain: "AISCAN(foo.tableA.idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP (_._0 AS foo.tableA.A2, _._1 AS _1)" + - result: [{"foo.tableA.A2": 10, 6}] + - + # group by alias + - query: select "__$__" from "foo.tableA" group by "foo.tableA.A2" as "__$__"; + - result: [{10}] + - + # star over simple join + - query: select * from "foo.tableA", "foo.tableB" where "foo.tableA"."foo.tableA.A1" = "foo.tableB"."foo.tableB.B1"; + - explain: "SCAN(<,>) | TFILTER foo__2tableB | FLATMAP q0 -> { ISCAN(foo.tableA.idx [EQUALS q0.foo.tableB.B1]) AS q1 RETURN (q1.foo.tableA.A1 AS foo.tableA.A1, q1.foo.tableA.A2 AS foo.tableA.A2, q1.foo.tableA.A3 AS foo.tableA.A3, q0.foo.tableB.B1 AS foo.tableB.B1, q0.foo.tableB.B2 AS foo.tableB.B2, q0.foo.tableB.B3 AS foo.tableB.B3) }" + - result: [{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1, "foo.tableB.B1": 1, "foo.tableB.B2": 20, "foo.tableB.B3": {4, 40}}, + {"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2, "foo.tableB.B1": 2, "foo.tableB.B2": 20, "foo.tableB.B3": {5, 50}}, + {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3, "foo.tableB.B1": 3, "foo.tableB.B2": 20, "foo.tableB.B3": {6, 60}}] + - + # qualified star over simple join + - query: select "foo.tableA".*, "foo.tableB".* from "foo.tableA", "foo.tableB" where "foo.tableA"."foo.tableA.A1" = "foo.tableB"."foo.tableB.B1"; + - result: [{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1, "foo.tableB.B1": 1, "foo.tableB.B2": 20, "foo.tableB.B3": {4, 40}}, + {"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2, "foo.tableB.B1": 2, "foo.tableB.B2": 20, "foo.tableB.B3": {5, 50}}, + {"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3, "foo.tableB.B1": 3, "foo.tableB.B2": 20, "foo.tableB.B3": {6, 60}}] + - + - query: select "foo$tableC".* from "foo$tableC"; + - result: [{"foo$tableC$C1": 1 , "foo$tableC$C2": 20, "foo$tableC$C3": 1}, {"foo$tableC$C1": 2, "foo$tableC$C2": 20, "foo$tableC$C3": 2}, {"foo$tableC$C1": 3, "foo$tableC$C2": 20, "foo$tableC$C3": 3}, {"foo$tableC$C1": 4, "foo$tableC$C2": 20, "foo$tableC$C3": 4}] + - + - query: select "foo$tableC".* from "foo$tableC" where "foo$tableC$C3" >= 2; + - result: [{"foo$tableC$C1": 2, "foo$tableC$C2": 20, "foo$tableC$C3": 2}, {"foo$tableC$C1": 3, "foo$tableC$C2": 20, "foo$tableC$C3": 3}, {"foo$tableC$C1": 4, "foo$tableC$C2": 20, "foo$tableC$C3": 4}] + - + - query: select "foo$tableC"."foo$tableC$C1" from "foo$tableC"; + - result: [{"foo$tableC$C1": 1}, {"foo$tableC$C1": 2}, {"foo$tableC$C1": 3}, {"foo$tableC$C1": 4}] + - + - query: select "foo$tableC$C1" from "foo$tableC"; + - result: [{"foo$tableC$C1": 1}, {"foo$tableC$C1": 2}, {"foo$tableC$C1": 3}, {"foo$tableC$C1": 4}] + - + - query: select "foo$tableC".* from "foo$tableC" where "foo$tableC$C3" >= 2 order by "foo$tableC$C1"; + - result: [{"foo$tableC$C1": 2, "foo$tableC$C2": 20, "foo$tableC$C3": 2}, {"foo$tableC$C1": 3, "foo$tableC$C2": 20, "foo$tableC$C3": 3}, {"foo$tableC$C1": 4, "foo$tableC$C2": 20, "foo$tableC$C3": 4}] + - + - query: select "foo$tableC$C2", sum("foo$tableC$C1") from "foo$tableC" group by "foo$tableC$C2"; + - explain: "AISCAN(foo$tableC$idx2 <,> BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP (_._0 AS foo$tableC$C2, _._1 AS _1)" + - result: [{"foo$tableC$C2": 20, 10}] + - + - query: select "__foo__tableD".* from "__foo__tableD"; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)" + - result: [{"__foo__tableD$D1": 1 , "__foo__tableD$D2": 20, "__foo__tableD$D3": 1}, {"__foo__tableD$D1": 2, "__foo__tableD$D2": 20, "__foo__tableD$D3": 2}, {"__foo__tableD$D1": 3, "__foo__tableD$D2": 20, "__foo__tableD$D3": 3}, {"__foo__tableD$D1": 4, "__foo__tableD$D2": 20, "__foo__tableD$D3": 4}] + - + - query: select "__foo__tableD".* from "__foo__tableD" where "__foo__tableD$D3" >= 2; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | FILTER _.__foo__tableD$D3 GREATER_THAN_OR_EQUALS promote(@c11 AS LONG) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)" + - result: [{"__foo__tableD$D1": 2, "__foo__tableD$D2": 20, "__foo__tableD$D3": 2}, {"__foo__tableD$D1": 3, "__foo__tableD$D2": 20, "__foo__tableD$D3": 3}, {"__foo__tableD$D1": 4, "__foo__tableD$D2": 20, "__foo__tableD$D3": 4}] + - + - query: select "__foo__tableD"."__foo__tableD$D1" from "__foo__tableD"; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1)" + - result: [{"__foo__tableD$D1": 1}, {"__foo__tableD$D1": 2}, {"__foo__tableD$D1": 3}, {"__foo__tableD$D1": 4}] + - + - query: select "__foo__tableD$D1" from "__foo__tableD"; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1)" + - result: [{"__foo__tableD$D1": 1}, {"__foo__tableD$D1": 2}, {"__foo__tableD$D1": 3}, {"__foo__tableD$D1": 4}] + - + - query: select "__foo__tableD".* from "__foo__tableD" where "__foo__tableD$D3" >= 2 order by "__foo__tableD$D1"; + - explain: "COVERING(__foo__tableD$idx <,> -> [__foo__0tableD__1D1: KEY[0], __foo__0tableD__1D2: KEY[1], __foo__0tableD__1D3: KEY[2]]) | FILTER _.__foo__tableD$D3 GREATER_THAN_OR_EQUALS promote(@c11 AS LONG) | MAP (_.__foo__tableD$D1 AS __foo__tableD$D1, _.__foo__tableD$D2 AS __foo__tableD$D2, _.__foo__tableD$D3 AS __foo__tableD$D3)" + - result: [{"__foo__tableD$D1": 2, "__foo__tableD$D2": 20, "__foo__tableD$D3": 2}, {"__foo__tableD$D1": 3, "__foo__tableD$D2": 20, "__foo__tableD$D3": 3}, {"__foo__tableD$D1": 4, "__foo__tableD$D2": 20, "__foo__tableD$D3": 4}] + - + - query: select sum("__foo__tableD$D1") from "__foo__tableD" group by "__foo__tableD$D2"; + - result: [{10}] + - + # fanned-out array + - query: select "foo.tableE.__array_elements" from "foo.tableE", "foo.tableE"."foo.tableE.E2" as "foo.tableE.__array_elements" where "foo.tableE.E3".S1 = 5; + - result: [{2}, {3}, {4}] + + # macro functions + - + - query: select "βήτα__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" as "f__e" where "alpha__f"("f__e"."foo.tableE.E3") = 5 + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c22 AS LONG) | MAP (_.foo.tableE.E3.S2 AS ___h.1)" + - result: [ {"___h.1": 50 }] + - + - query: select "alpha__f"("f__e"."foo.tableE.E3") AS "___h.1" from "foo.tableE" as "f__e" where "βήτα__f"("f__e"."foo.tableE.E3") >= 50 + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S2 GREATER_THAN_OR_EQUALS promote(@c23 AS LONG) | MAP (_.foo.tableE.E3.S1 AS ___h.1)" + - result: [ {"___h.1": 5 }, {"___h.1": 6 }] + + # functions + - + - query: select * from "__$func3"(10, 1, 1); + - explain: "SCAN(<,>) | TFILTER my__1adjacency__1list | FILTER _.me LESS_THAN promote(@c6 AS LONG) AND _.my__parent EQUALS promote(@c8 AS LONG) | FLATMAP q0 -> { SCAN(<,>) | TFILTER my__1adjacency__1list | FILTER _.me EQUALS promote(@c8 AS LONG) AS q1 RETURN (q0.me AS _0, q0.my__parent AS _1, q1.me AS _2, q1.my__parent AS _3) }" + - result: [{"_0": 2, "_1": 1, "_2": 1, "_3": -1}, {"_0": 3, "_1": 1, "_2": 1, "_3": -1}, {"_0": 4, "_1": 1, "_2": 1, "_3": -1}] + - + - query: select * from "$yay"(5); + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$x.id)" + - result: [{"_$x.id": 2}] + - + - query: select * from "__2yay"(6); + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$y.id)" + - result: [{"_$y.id": 3}] + - + - query: select * from "नमस्त"(4); + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS promote(@c6 AS LONG) | MAP (_.foo.tableE.E1 AS _$z.id)" + - result: [{"_$z.id": 1}] + + # views + - + - query: select * from "$yay__view"; + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 4 | MAP (_.foo.tableE.E1 AS _$x.id) | MAP (_._$x.id AS _$x.id)" + - result: [{"_$x.id": 1}] + - + - query: select * from "__2yay__view"; + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 5 | MAP (_.foo.tableE.E1 AS _$y.id) | MAP (_._$y.id AS _$y.id)" + - result: [{"_$y.id": 2}] + - + - query: select * from "வணக்கம்"; + - explain: "SCAN(<,>) | TFILTER foo__2tableE | FILTER _.foo.tableE.E3.S1 EQUALS 6 | MAP (_.foo.tableE.E1 AS _$z.id) | MAP (_._$z.id AS _$z.id)" + - result: [{"_$z.id": 3}] + - + # inline table definition + - query: select * from values (1, 2, 3), (4, 5, 6) as "_$$$$"("_$$$", "_$$", "_$") + - explain: "EXPLODE array((@c6 AS _$$$, @c8 AS _$$, @c10 AS _$), (@c14 AS _$$$, @c16 AS _$$, @c18 AS _$)) | MAP (_._$$$ AS _$$$, _._$$ AS _$$, _._$ AS _$)" + - result: [{"_$$$": 1, "_$$": 2, "_$": 3}, { "_$$$": 4, "_$$": 5, "_$": 6}] + - + # named record construction + - query: select struct "x$$" ("foo.tableA.A1", "foo.tableA.A2", "foo.tableA.A3") from "foo.tableA" + - explain: "COVERING(foo.tableA.idx <,> -> [foo__2tableA__2A1: KEY[0], foo__2tableA__2A2: KEY[1], foo__2tableA__2A3: KEY[2]]) | MAP ((_.foo.tableA.A1 AS foo.tableA.A1, _.foo.tableA.A2 AS foo.tableA.A2, _.foo.tableA.A3 AS foo.tableA.A3) AS _0)" + - result: [{{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1}}, {{"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}}, {{"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}}] + - + # named record construction with uid star + - query: select struct "x$$" ("foo.tableA".*) from "foo.tableA" + - explain: "ISCAN(foo.tableA.idx3 <,>) | MAP (_ AS _0)" + - result: [{{"foo.tableA.A1": 1 , "foo.tableA.A2": 10, "foo.tableA.A3": 1}}, {{"foo.tableA.A1": 2, "foo.tableA.A2": 10, "foo.tableA.A3": 2}}, {{"foo.tableA.A1": 3, "foo.tableA.A2": 10, "foo.tableA.A3": 3}}] + - + # named record construction with aliased expressions + - query: select struct "x$$" ("foo.tableA.A2" + "foo.tableA.A1" as "__$$__") from "foo.tableA" + - explain: "ISCAN(foo.tableA.idx3 <,>) | MAP ((_.foo.tableA.A2 + _.foo.tableA.A1 AS __$$__) AS _0)" + - result: [{{"__$$__": 11}}, {{"__$$__": 12}}, {{"__$$__": 13}}] + + # enums + - + - query: select * from "foo.enum.type"; + - explain: "ISCAN(foo.enum.type$enum__1 <,>)" + - unorderedResult: [ + {"enum_type.id": 1, "enum_type.enum__1": "A", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 2, "enum_type.enum__1": "B$C", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 3, "enum_type.enum__1": "C.D", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 4, "enum_type.enum__1": "E__F", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 5, "enum_type.enum__1": "__G$H", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 6, "enum_type.enum__1": "B$C", "enum_type.enum__2": "A"}, + {"enum_type.id": 7, "enum_type.enum__1": "B$C", "enum_type.enum__2": "C.D"}, + {"enum_type.id": 8, "enum_type.enum__1": "B$C", "enum_type.enum__2": "E__F"}, + {"enum_type.id": 9, "enum_type.enum__1": "B$C", "enum_type.enum__2": "__G$H"}, + {"enum_type.id": 10, "enum_type.enum__1": !null _, "enum_type.enum__2": "B$C"}, + {"enum_type.id": 11, "enum_type.enum__1": "B$C", "enum_type.enum__2": !null _}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__1" = 'B$C'; + - explain: "ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)])" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"enum_type.id": 2, "enum_type.enum__1": "B$C", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 6, "enum_type.enum__1": "B$C", "enum_type.enum__2": "A"}, + {"enum_type.id": 7, "enum_type.enum__1": "B$C", "enum_type.enum__2": "C.D"}, + {"enum_type.id": 8, "enum_type.enum__1": "B$C", "enum_type.enum__2": "E__F"}, + {"enum_type.id": 9, "enum_type.enum__1": "B$C", "enum_type.enum__2": "__G$H"}, + {"enum_type.id": 11, "enum_type.enum__1": "B$C", "enum_type.enum__2": !null _}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__1" = 'C.D'; + - explain: "ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)])" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"enum_type.id": 3, "enum_type.enum__1": "C.D", "enum_type.enum__2": "B$C"}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__1" = 'A'; + - explain: "ISCAN(foo.enum.type$enum__1 [EQUALS promote(@c8 AS ENUM)])" + # Disable force_continuations on this plan until we resolve: https://github.com/FoundationDB/fdb-record-layer/issues/3734 + - maxRows: 0 + - result: [ + {"enum_type.id": 1, "enum_type.enum__1": "A", "enum_type.enum__2": "B$C"}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__2" = 'B$C'; + - explain: "ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)" + - unorderedResult: [ + {"enum_type.id": 1, "enum_type.enum__1": "A", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 2, "enum_type.enum__1": "B$C", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 3, "enum_type.enum__1": "C.D", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 4, "enum_type.enum__1": "E__F", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 5, "enum_type.enum__1": "__G$H", "enum_type.enum__2": "B$C"}, + {"enum_type.id": 10, "enum_type.enum__1": !null _, "enum_type.enum__2": "B$C"}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__2" = 'C.D'; + - explain: "ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)" + - result: [ + {"enum_type.id": 7, "enum_type.enum__1": "B$C", "enum_type.enum__2": "C.D"}, + ] + - + - query: select * from "foo.enum.type" where "enum_type.enum__2" = 'A'; + - explain: "ISCAN(foo.enum.type$enum__1 <,>) | FILTER _.enum_type.enum__2 EQUALS promote(@c8 AS ENUM)" + - result: [ + {"enum_type.id": 6, "enum_type.enum__1": "B$C", "enum_type.enum__2": "A"}, + ] +--- +test_block: + name: update-delete-statements + preset: single_repetition_ordered + tests: + - + - query: UPDATE "foo.tableA" SET "foo.tableA.A2" = 100 WHERE "foo.tableA.A1" = 1 + - count: 1 + - + - query: UPDATE "foo.tableA" SET "foo.tableA.A2" = 100 WHERE "foo.tableA.A1" > 1 RETURNING "new"."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" + - result: [{102}] + - + - query: DELETE FROM "foo.tableA" WHERE "foo.tableA.A2" = 100 + - 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 e4a6db4e186d7ae795f92659d690165c94f2fcbb Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Tue, 11 Nov 2025 10:19:16 +0000 Subject: [PATCH 2/8] address teamscale warnings ; remove change to dereference that was causing trouble --- .../query/plan/cascades/typing/Type.java | 24 +++++++------- .../plan/cascades/values/FieldValue.java | 31 +++++++++---------- .../src/main/proto/record_query_plan.proto | 3 +- .../metadata/RecordLayerIndex.java | 8 ++--- .../metadata/RecordLayerTable.java | 16 +++++----- .../recordlayer/query/IndexGenerator.java | 24 ++++---------- 6 files changed, 47 insertions(+), 59 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 8715867ac0..e2a3cb4503 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 @@ -2281,7 +2281,7 @@ public void defineProtoType(final TypeRepository.Builder typeRepositoryBuilder) for (final var field : fields) { final var fieldType = field.getFieldType(); - final var fieldName = field.getStorageFieldName(); + final var fieldName = field.getFieldStorageName(); fieldType.addProtoField(typeRepositoryBuilder, recordMsgBuilder, field.getFieldIndex(), fieldName, @@ -2615,7 +2615,7 @@ public static class Field implements Comparable, PlanSerializable { private final Optional fieldIndexOptional; @Nonnull - private final Optional storageFieldNameOptional; + private final Optional fieldStorageNameOptional; /** * Memoized hash function. @@ -2634,11 +2634,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, @Nonnull Optional storageFieldNameOptional) { + 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.storageFieldNameOptional = storageFieldNameOptional; + this.fieldStorageNameOptional = fieldStorageNameOptional; } /** @@ -2669,13 +2669,13 @@ public String getFieldName() { } @Nonnull - public Optional getStorageFieldNameOptional() { - return storageFieldNameOptional; + public Optional getFieldStorageNameOptional() { + return fieldStorageNameOptional; } @Nonnull - public String getStorageFieldName() { - return getStorageFieldNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); + public String getFieldStorageName() { + return getFieldStorageNameOptional().orElseThrow(() -> new RecordCoreException("field name should have been set")); } /** @@ -2716,7 +2716,7 @@ public Field withNullability(boolean newNullability) { return this; } var newFieldType = getFieldType().withNullability(newNullability); - return new Field(newFieldType, fieldNameOptional, fieldIndexOptional, storageFieldNameOptional); + return new Field(newFieldType, fieldNameOptional, fieldIndexOptional, fieldStorageNameOptional); } @Nonnull @@ -2759,9 +2759,9 @@ public PRecordType.PField toProto(@Nonnull final PlanSerializationContext serial fieldProtoBuilder.setFieldType(fieldType.toTypeProto(serializationContext)); fieldNameOptional.ifPresent(fieldProtoBuilder::setFieldName); fieldIndexOptional.ifPresent(fieldProtoBuilder::setFieldIndex); - storageFieldNameOptional.ifPresent(storageFieldName -> { + fieldStorageNameOptional.ifPresent(storageFieldName -> { if (!fieldProtoBuilder.getFieldName().equals(storageFieldName)) { - fieldProtoBuilder.setStorageFieldName(storageFieldName); + fieldProtoBuilder.setFieldStorageName(storageFieldName); } }); return fieldProtoBuilder.build(); @@ -2772,7 +2772,7 @@ public static Field fromProto(@Nonnull final PlanSerializationContext serializat 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.hasStorageFieldName() ? Optional.of(fieldProto.getStorageFieldName()) : fieldNameOptional; + final Optional storageFieldNameOptional = fieldProto.hasFieldStorageName() ? Optional.of(fieldProto.getFieldStorageName()) : fieldNameOptional; return new Field(fieldType, fieldNameOptional, fieldIndexOptional, storageFieldNameOptional); } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java index 55c62b4a57..a87ef13358 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java @@ -531,7 +531,7 @@ public static FieldPath empty() { @Nonnull private static List> computeFieldNames(@Nonnull final List fieldAccessors) { return fieldAccessors.stream() - .map(accessor -> accessor.getField().getStorageFieldNameOptional()) + .map(accessor -> accessor.getField().getFieldStorageNameOptional()) .collect(ImmutableList.toImmutableList()); } @@ -635,24 +635,10 @@ public int hashCode() { public static class ResolvedAccessor implements PlanSerializable { @Nonnull final Field field; - - /* - @Nullable - final String name; - - @Nullable - private final Type type; - */ - final int ordinal; protected ResolvedAccessor(@Nonnull Field field, int ordinal) { Preconditions.checkArgument(ordinal >= 0); - /* - this.name = name; - this.ordinal = ordinal; - this.type = type; - */ this.field = field; this.ordinal = ordinal; } @@ -703,11 +689,14 @@ public String toString() { @Override public PResolvedAccessor toProto(@Nonnull final PlanSerializationContext serializationContext) { PResolvedAccessor.Builder builder = PResolvedAccessor.newBuilder(); + // Older serialization: write out the name, ordinal, and type manually builder.setName(field.getFieldName()); builder.setOrdinal(ordinal); 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(); } @@ -720,7 +709,17 @@ public static ResolvedAccessor fromProto(@Nonnull PlanSerializationContext seria } else { type = null; } - final Field field = Field.of(type, Optional.of(resolvedAccessorProto.getName())); + + 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()); } 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 9bddbea214..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 @@ -104,7 +104,7 @@ message PType { optional PType field_type = 1; optional string field_name = 2; optional int32 field_index = 3; - optional string storage_field_name = 4; + optional string field_storage_name = 4; } optional int32 reference_id = 1; @@ -486,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-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 8d13e11ee4..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 @@ -176,15 +176,15 @@ public Builder setTableName(String tableName) { } @Nonnull - public Builder setTableStorageName(@Nonnull String tableStorageName) { + public Builder setTableStorageName(String tableStorageName) { this.tableStorageName = tableStorageName; return this; } @Nonnull - public Builder setTableType(@Nonnull Type.Record record) { - return setTableName(record.getName()) - .setTableStorageName(record.getStorageName()); + public Builder setTableType(@Nonnull Type.Record tableType) { + return setTableName(tableType.getName()) + .setTableStorageName(tableType.getStorageName()); } @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 99075bb087..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 @@ -310,19 +310,19 @@ public Builder addColumns(@Nonnull final Collection columns) } @Nonnull - private static KeyExpression toKeyExpression(@Nullable Type.Record record, @Nonnull final List fields) { + private static KeyExpression toKeyExpression(@Nullable Type.Record type, @Nonnull final List fields) { Assert.thatUnchecked(!fields.isEmpty()); - return toKeyExpression(record, fields.iterator()); + return toKeyExpression(type, fields.iterator()); } @Nonnull - private static KeyExpression toKeyExpression(@Nullable Type.Record record, @Nonnull final Iterator fields) { + 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(record, fieldName); + Type.Record.Field field = getFieldDefinition(type, fieldName); final FieldKeyExpression expression = Key.Expressions.field(getFieldStorageName(field, fieldName)); if (fields.hasNext()) { - Type.Record fieldType = getFieldRecordType(record, field); + Type.Record fieldType = getFieldRecordType(type, field); return expression.nest(toKeyExpression(fieldType, fields)); } else { return expression; @@ -330,13 +330,13 @@ private static KeyExpression toKeyExpression(@Nullable Type.Record record, @Nonn } @Nullable - private static Type.Record.Field getFieldDefinition(@Nullable Type.Record record, @Nonnull String fieldName) { - return record == null ? null : record.getFieldNameFieldMap().get(fieldName); + 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.getStorageFieldName(); + return field == null ? ProtoUtils.toProtoBufCompliantName(fieldName) : field.getFieldStorageName(); } @Nullable 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 2351272a71..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 @@ -532,8 +532,7 @@ private KeyExpression toKeyExpression(@Nonnull Value value) { return VersionKeyExpression.VERSION; } else if (value instanceof FieldValue) { FieldValue fieldValue = (FieldValue) value; - Type childType = fieldValue.getChild().getResultType(); - return toKeyExpression(childType, fieldValue.getFieldPath().getFieldAccessors().iterator()); + return toKeyExpression(fieldValue.getFieldPath().getFieldAccessors().iterator()); } else if (value instanceof ArithmeticValue) { var children = value.getChildren(); var builder = ImmutableList.builder(); @@ -730,9 +729,7 @@ private Value dereference(@Nonnull Value value) { final var valueWithChild = (ValueWithChild) value; return valueWithChild.withNewChild(dereference(valueWithChild.getChild())); } else if (value instanceof QuantifiedObjectValue) { - Value dereferenced = dereference(correlatedKeyExpressions.get(value.getCorrelatedTo().stream().findFirst().orElseThrow())); - // Don't dereference the QOV if it changes the result type - return dereferenced.getResultType().equals(value.getResultType()) ? dereferenced : value; + return dereference(correlatedKeyExpressions.get(value.getCorrelatedTo().stream().findFirst().orElseThrow())); } else if (value instanceof ArithmeticValue) { final List newChildren = new ArrayList<>(); for (Value v:value.getChildren()) { @@ -745,12 +742,12 @@ private Value dereference(@Nonnull Value value) { } @Nonnull - private KeyExpression toKeyExpression(@Nonnull Type type, @Nonnull Iterator resolvedAccessors) { + 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(field.getFieldType(), resolvedAccessors); + KeyExpression childExpression = toKeyExpression(resolvedAccessors); return fieldExpression.nest(childExpression); } else { return fieldExpression; @@ -769,24 +766,15 @@ private String getRecordTypeName() { return recordTypes.stream().findFirst().orElseThrow(); } - /* - @Nonnull - private static Type.Record.Field resolveField(@Nonnull Type type, @Nonnull FieldValue.ResolvedAccessor resolvedAccessor) { - Assert.thatUnchecked(type.isRecord(), "field accessors must be resolved on record types"); - final Type.Record recordType = (Type.Record) type; - return recordType.getFieldNameFieldMap().get(resolvedAccessor.getName()); - } - */ - @Nonnull private static FieldKeyExpression toFieldKeyExpression(@Nonnull Type.Record.Field fieldType) { - Assert.notNullUnchecked(fieldType.getStorageFieldName()); + Assert.notNullUnchecked(fieldType.getFieldStorageName()); final var fanType = fieldType.getFieldType().getTypeCode() == Type.TypeCode.ARRAY ? KeyExpression.FanType.FanOut : KeyExpression.FanType.None; // At this point, we need to use the storage field name as that will be the name referenced // in Protobuf storage - return field(fieldType.getStorageFieldName(), fanType); + return field(fieldType.getFieldStorageName(), fanType); } @Nonnull From 2c2557af04a99305c4b3c4ddfafb39a7b9eee2e6 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Tue, 11 Nov 2025 12:50:30 +0000 Subject: [PATCH 3/8] fix spotbugs and PMD failures --- .../plan/cascades/values/FieldValue.java | 7 ++++-- .../query/FDBSimpleQueryGraphTest.java | 12 +++++----- .../recordlayer/query/PlanGenerator.java | 5 ++-- .../utils/ExportSchemaTemplateUtil.java | 14 +++++++++++ .../yamltests/utils/package-info.java | 24 +++++++++++++++++++ 5 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/utils/package-info.java diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java index a87ef13358..7c3ab14d93 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FieldValue.java @@ -62,6 +62,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -735,8 +736,10 @@ public static ResolvedAccessor of(@Nullable final String fieldName, final int or } @Nonnull - public static ResolvedAccessor of(@Nullable final String fieldName, final int ordinalFieldNumber) { - final Field field = Field.of(null, Optional.ofNullable(fieldName)); + 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/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-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 d31e9432bf..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 @@ -240,10 +240,11 @@ 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 - ErrorCode code = mde instanceof ProtoUtils.InvalidNameException ? ErrorCode.INVALID_NAME : ErrorCode.SYNTAX_OR_ACCESS_VIOLATION; - throw new RelationalException(mde.getMessage(), code, mde).toUncheckedWrappedException(); + throw new RelationalException(mde.getMessage(), ErrorCode.SYNTAX_OR_ACCESS_VIOLATION, mde).toUncheckedWrappedException(); } catch (VerifyException | SemanticException ve) { throw ExceptionUtil.toRelationalException(ve).toUncheckedWrappedException(); } catch (RelationalException e) { 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 index fb6ef149a2..adf24085e1 100644 --- 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 @@ -30,10 +30,24 @@ 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(); 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; From ce372a4d233d3773f9402764d0fe001522b5b764 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Tue, 11 Nov 2025 15:25:36 +0000 Subject: [PATCH 4/8] remove unused method and switch functions around so they are more chained --- .../record/query/plan/cascades/typing/Type.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 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 e2a3cb4503..7dff537f1a 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 @@ -1752,8 +1752,8 @@ class Enum implements Type { @Nonnull private final Supplier hashFunctionSupplier = Suppliers.memoize(this::computeHashCode); - public Enum(final boolean isNullable, - @Nullable final List enumValues) { + private Enum(final boolean isNullable, + @Nullable final List enumValues) { this(isNullable, enumValues, null, null); } @@ -1896,10 +1896,12 @@ public static > Enum forJavaEnum(@Nonnull final Clas 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, null); + return Enum.fromValues(isNullable, enumValuesFromProto(values)); } + @Nonnull public static List enumValuesFromProto(@Nonnull final List enumValueDescriptors) { return enumValueDescriptors .stream() From 1c5d76e0686a02611d79ee0f5c4630b17a27b54d Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Wed, 12 Nov 2025 17:52:32 +0000 Subject: [PATCH 5/8] add additional tests to cover test gaps from teamscale --- .../query/plan/cascades/typing/Type.java | 8 + .../record/query/plan/cascades/TypeTest.java | 390 +++++++++++++++++- .../metadata/RecordLayerColumn.java | 6 +- .../metadata/RecordLayerColumnTests.java | 110 +++++ 4 files changed, 493 insertions(+), 21 deletions(-) create mode 100644 fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerColumnTests.java 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 7dff537f1a..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 @@ -1793,6 +1793,9 @@ public boolean isNullable() { @Nonnull @Override public Enum withNullability(final boolean newIsNullable) { + if (newIsNullable == isNullable()) { + return this; + } return new Enum(newIsNullable, enumValues, name, storageName); } @@ -1801,6 +1804,11 @@ public String getName() { return name; } + @Nullable + public String getStorageName() { + return storageName; + } + @Override public void defineProtoType(@Nonnull final TypeRepository.Builder typeRepositoryBuilder) { Verify.verify(!isErased()); 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..ed245d3263 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,365 @@ 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); } } 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 905d5c69cf..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 @@ -80,11 +80,7 @@ public int hashCode() { @Override public String toString() { - return "RecordLayerColumn{" + - "name='" + name + '\'' + - ", dataType=" + dataType + - ", index=" + index + - '}'; + return name + ": " + dataType + " = " + index; } public static final class Builder { 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); + } +} From 4327f99ab11b69db6ce8552980365a9341d41607 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Wed, 12 Nov 2025 17:58:53 +0000 Subject: [PATCH 6/8] add unit tests for proto utils methods --- .../record/util/ProtoUtilsTest.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/util/ProtoUtilsTest.java 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(); + } +} From dee3b95312ea5411b3eb40ca85d8021d11186bcb Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Thu, 13 Nov 2025 10:05:39 +0000 Subject: [PATCH 7/8] address Field::withNullability test gap --- .../record/query/plan/cascades/TypeTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) 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 ed245d3263..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 @@ -665,4 +665,37 @@ void recordEqualsIgnoresName(@Nonnull Type.Record recordType) { .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()); + } + } } From 6df9fcb591f651350224dae711030b91b0c2b5be Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Thu, 13 Nov 2025 10:38:04 +0000 Subject: [PATCH 8/8] make DeleteExpression and UpdateExpression consistent --- .../query/visitors/QueryVisitor.java | 10 +-- .../resources/valid-identifiers.metrics.binpb | 88 +++++++++++++++++++ .../resources/valid-identifiers.metrics.yaml | 49 +++++++++++ .../test/resources/valid-identifiers.yamsql | 4 + 4 files changed, 146 insertions(+), 5 deletions(-) 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 de4a7a9986..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 @@ -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/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb index d651f31e81..feb4110926 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.binpb @@ -436,6 +436,94 @@ S 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" ]; + } } & diff --git a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml index 8ae5d83024..c140b60e50 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml +++ b/yaml-tests/src/test/resources/valid-identifiers.metrics.yaml @@ -363,6 +363,55 @@ all-tests: 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 diff --git a/yaml-tests/src/test/resources/valid-identifiers.yamsql b/yaml-tests/src/test/resources/valid-identifiers.yamsql index 8f78324947..0986c3e13a 100644 --- a/yaml-tests/src/test/resources/valid-identifiers.yamsql +++ b/yaml-tests/src/test/resources/valid-identifiers.yamsql @@ -411,15 +411,19 @@ test_block: 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: