Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -772,27 +772,68 @@
return tempFunctions;
}
}

Check notice on line 775 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/RecordMetaData.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 95.0% (287/302 lines) | Changed lines: 100.0% (9/9 lines)

/**
* The type the planner sees for the records of the record type with the given name. The name is resolved with
* {@link #getRecordType(String)}, which only resolves stored record types.
*
* @param recordTypeName the name of a stored record type
* @return the planner's type for the records of that record type
* @throws MetaDataException if there is no stored record type with that name
*/
@Nonnull
public Type.Record getPlannerType(@Nonnull String recordTypeName) {
final RecordType recordType = getRecordType(recordTypeName);
return getPlannerTypeForRecordType(getRecordType(recordTypeName));
}

/**
* The type the planner sees for the records of a record type.
*
* @param recordType the record type to describe
* @return the planner's type for the records of {@code recordType}
*/
@Nonnull
public Type.Record getPlannerTypeForRecordType(@Nonnull RecordType recordType) {
Type.Record plannerType = Type.Record.fromDescriptor(recordType.getDescriptor());
if (storeRecordVersions) {
plannerType = plannerType.addPseudoFields();
}
return plannerType;
}

/**
* The type the planner sees for records of any of the record types with the given names. The names are resolved
* with {@link #getRecordType(String)}, which only resolves stored record types.
*
* @param recordTypeNames the names of stored record types
* @return the planner's type for records of any of those record types
* @throws MetaDataException if there is no stored record type with one of those names
*/
@Nonnull
public Type.Record getPlannerType(@Nonnull Collection<String> recordTypeNames) {
Comment thread
robert-brunel marked this conversation as resolved.
Resolved
if (recordTypeNames.size() == 1) {
final String recordTypeName = Iterables.getOnlyElement(recordTypeNames);
return getPlannerType(recordTypeName);
return getPlannerType(Iterables.getOnlyElement(recordTypeNames));
}
return getPlannerTypeForRecordTypes(recordTypeNames.stream()
.map(this::getRecordType)
.toList());
}

/**
* The type the planner sees for records of any of several record types: the type of the one record type, or the
* union of the fields of all of them.
*
* @param recordTypes the record types to describe
* @return the planner's type for records of any of {@code recordTypes}
*/
@Nonnull
public Type.Record getPlannerTypeForRecordTypes(@Nonnull Collection<RecordType> recordTypes) {
if (recordTypes.size() == 1) {
return getPlannerTypeForRecordType(Iterables.getOnlyElement(recordTypes));
}
// todo: should be removed https://github.com/FoundationDB/fdb-record-layer/issues/1884
LinkedHashMap<String, Type.Record.Field> fieldsByName = recordTypeNames.stream()
.map(this::getPlannerType)
LinkedHashMap<String, Type.Record.Field> fieldsByName = recordTypes.stream()
.map(this::getPlannerTypeForRecordType)
.flatMap(type -> type.getFields().stream())
.collect(Collectors.groupingBy(Type.Record.Field::getFieldName,
LinkedHashMap::new,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@
final Set<String> indexedRecordTypeNames = indexedRecordTypes.stream()
.map(RecordType::getName)
.collect(ImmutableSet.toImmutableSet());
@Nonnull

Check notice on line 139 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/IndexExpansionInfo.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 96.0% (24/25 lines) | Changed lines: 100.0% (1/1 lines)
final Type.Record baseType = metaData.getPlannerType(indexedRecordTypeNames);
final Type.Record baseType = metaData.getPlannerTypeForRecordTypes(indexedRecordTypes);
@Nullable
final KeyExpression commonPrimaryKeyForTypes = RecordMetaData.commonPrimaryKey(indexedRecordTypes);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* RecordMetaDataPlannerTypeTest.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2026 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;

import com.apple.foundationdb.record.metadata.JoinedRecordTypeBuilder;
import com.apple.foundationdb.record.metadata.MetaDataException;
import com.apple.foundationdb.record.metadata.SyntheticRecordType;
import com.apple.foundationdb.record.query.plan.cascades.typing.PseudoField;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import org.junit.jupiter.api.Test;

import javax.annotation.Nonnull;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.apple.foundationdb.record.metadata.Key.Expressions.field;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

/**
* Tests of the planner types {@link RecordMetaData} describes record types with, by name and by {@link
* com.apple.foundationdb.record.metadata.RecordType}.
*/
class RecordMetaDataPlannerTypeTest {
@Nonnull
private static final String OUTER = "OuterRecord";
@Nonnull
private static final String OTHER = "OtherRecord";
@Nonnull
private static final String JOINED = "Joined";

@Test
void getPlannerTypeForRecordTypeDescribesASyntheticType() {
final RecordMetaData metaData = metaData(metaDataBuilder -> {
final JoinedRecordTypeBuilder typeBuilder = metaDataBuilder.addJoinedRecordType(JOINED);
typeBuilder.addConstituent("outer", OUTER);
typeBuilder.addConstituent("other", OTHER);
typeBuilder.addJoin("outer", field("other_id"), "other", field("other_id"));
});
final SyntheticRecordType<?> type = metaData.getSyntheticRecordType(JOINED);

// A synthetic type's name cannot be resolved against the stored types, so only the type-taking overload can
// describe it.
assertThatThrownBy(() -> metaData.getPlannerType(JOINED)).isInstanceOf(MetaDataException.class);
final Type.Record plannerType = metaData.getPlannerTypeForRecordType(type);
assertThat(plannerType).isEqualTo(Type.Record.fromDescriptor(type.getDescriptor()));
assertThat(fieldNames(plannerType)).containsExactly("outer", "other");
assertThat(metaData.getPlannerTypeForRecordTypes(List.of(type))).isEqualTo(plannerType);
}

@Test
void getPlannerTypeForRecordTypeMatchesTheNameTakingOverloads() {
final RecordMetaData metaData = metaData(metaDataBuilder -> metaDataBuilder.setStoreRecordVersions(true));

assertThat(metaData.getPlannerTypeForRecordType(metaData.getRecordType(OUTER)))
.isEqualTo(metaData.getPlannerType(OUTER));
assertThat(metaData.getPlannerTypeForRecordTypes(List.of(metaData.getRecordType(OUTER))))
.isEqualTo(metaData.getPlannerType(List.of(OUTER)));
assertThat(metaData.getPlannerTypeForRecordTypes(
List.of(metaData.getRecordType(OUTER), metaData.getRecordType(OTHER))))
.isEqualTo(metaData.getPlannerType(List.of(OUTER, OTHER)));

// Storing record versions adds the pseudo field to every stored type, including the union of several of them.
final String versionField = PseudoField.ROW_VERSION.getFieldName();
assertThat(metaData.getPlannerTypeForRecordType(metaData.getRecordType(OUTER)).getFieldNameFieldMap())
.containsKey(versionField);
assertThat(metaData.getPlannerTypeForRecordTypes(
List.of(metaData.getRecordType(OUTER), metaData.getRecordType(OTHER))).getFieldNameFieldMap())
.containsKey(versionField);
}

@Test
void getPlannerTypeForRecordTypesUnionsTheirFields() {
final RecordMetaData metaData = metaData(metaDataBuilder -> { });
final Type.Record unionType = metaData.getPlannerTypeForRecordTypes(
List.of(metaData.getRecordType(OUTER), metaData.getRecordType(OTHER)));

// `rec_id` and `other_id` are shared, so the union has them once; the remaining fields come from one type each.
assertThat(fieldNames(unionType)).containsExactly("rec_id", "other_id", "map", "other_value");

// A single type is described exactly as the type-taking overload would describe it on its own.
assertThat(metaData.getPlannerTypeForRecordTypes(List.of(metaData.getRecordType(OUTER))))
.isEqualTo(metaData.getPlannerTypeForRecordType(metaData.getRecordType(OUTER)));
}

@Nonnull
private static RecordMetaData metaData(@Nonnull final Consumer<RecordMetaDataBuilder> hook) {
final RecordMetaDataBuilder metaDataBuilder = RecordMetaData.newBuilder()
.setRecords(TestRecordsNestedMapProto.getDescriptor());
hook.accept(metaDataBuilder);
return metaDataBuilder.build();
}

@Nonnull
private static List<String> fieldNames(@Nonnull final Type.Record type) {
return type.getFields().stream().map(Type.Record.Field::getFieldName).collect(Collectors.toList());
}
}
Loading