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 @@ -173,8 +173,8 @@
if (argumentValues.size() != 1 || !(argumentValues.get(0) instanceof FieldValue)) {
return null;
}
final var fieldValue = (FieldValue)argumentValues.get(0);

Check notice on line 176 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexPredicate.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 82.7% (263/318 lines) | Changed lines: 100.0% (4/4 lines)
final var fieldNames = fieldValue.getFieldPathNames();
final var fieldNames = fieldValue.getFieldPathStorageNames();
if (fieldNames.isEmpty()) {
return null;
}
Expand All @@ -195,7 +195,7 @@
if (!(pv instanceof FieldValue)) {
return null;
}
final var partFieldNames = ((FieldValue)pv).getFieldPathNames();
final var partFieldNames = ((FieldValue)pv).getFieldPathStorageNames();
if (partFieldNames.isEmpty()) {
return null;
}
Expand Down Expand Up @@ -228,7 +228,7 @@
}
return IndexComparison.isSupported(valuePredicate.getComparison()) &&
valuePredicate.getValue() instanceof FieldValue &&
((FieldValue)valuePredicate.getValue()).getFieldPathNamesMaybe().stream().allMatch(Optional::isPresent);
((FieldValue)valuePredicate.getValue()).getFieldPathStorageNamesMaybe().stream().allMatch(Optional::isPresent);
} else {
return false;
}
Expand Down Expand Up @@ -564,7 +564,7 @@
@SuppressWarnings("java:S5803")
ValuePredicate(@Nonnull final com.apple.foundationdb.record.query.plan.cascades.predicates.ValuePredicate predicate) {
Verify.verify(predicate.getValue() instanceof FieldValue);
this.fieldPath = ImmutableList.copyOf(((FieldValue)predicate.getValue()).getFieldPathNames());
this.fieldPath = ImmutableList.copyOf(((FieldValue)predicate.getValue()).getFieldPathStorageNames());
this.comparison = IndexComparison.fromComparison(predicate.getComparison());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@

final var parentBuilderForField = parentBuilderForFieldOptional.get();

// TODO not sure what to do with the null standing requirement

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

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 93.2% (82/88 lines) | Changed lines: 100.0% (3/3 lines)
final var maybeFieldName = fieldValue.getLastFieldName();
final var maybeFieldName = fieldValue.getFieldPath().getLastFieldStorageName();
if (maybeFieldName.isEmpty()) {
return false;
}
Expand All @@ -123,7 +123,7 @@
private static Optional<IndexKeyValueToPartialRecord.Builder> getParentBuilderForFieldMaybe(@Nonnull IndexKeyValueToPartialRecord.Builder builder,
@Nonnull final FieldValue fieldValue) {
// TODO field names are for debugging purposes only, we should probably use field ordinals here instead.
for (final var maybeFieldName : fieldValue.getFieldPrefix().getOptionalFieldNames()) {
for (final var maybeFieldName : fieldValue.getFieldPrefix().getOptionalFieldStorageNames()) {
if (maybeFieldName.isEmpty()) {
return Optional.empty();
}
Expand Down Expand Up @@ -204,7 +204,7 @@
for (final var prefixFieldName : fieldValue.getFieldPrefix().getOptionalFieldNames()) {
node = node.withChild(prefixFieldName.orElseThrow());
}
node.withChild(fieldValue.getLastFieldName().orElseThrow()).cover(extraction.getValue());
node.withChild(fieldValue.getFieldPath().getLastFieldName().orElseThrow()).cover(extraction.getValue());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@
Extractor.of(FieldValue::getChild, name -> "child(" + name + ")"),
downstreamValue);
final TypedMatcherWithExtractAndDownstream<FieldValue> downstreamFieldPathMatcher =
typedWithDownstream(FieldValue.class,

Check notice on line 144 in fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/matching/structure/ValueMatchers.java

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 92.6% (75/81 lines) | Changed lines: 100.0% (2/2 lines)
Extractor.of(FieldValue::getFieldPathNames, name -> "fieldPathNames(" + name + ")"),
Extractor.of(FieldValue::getFieldPathStorageNames, name -> "fieldPathNames(" + name + ")"),
downstreamFieldPath);

return typedWithDownstream(FieldValue.class,
Expand All @@ -160,7 +160,7 @@
final TypedMatcherWithExtractAndDownstream<FieldValue> downstreamFieldPathMatcher =
typedWithDownstream(FieldValue.class,
Extractor.of(fieldValue -> {
final var fieldPathNames = fieldValue.getFieldPathNames();
final var fieldPathNames = fieldValue.getFieldPathStorageNames();
return fieldPathNames.get(fieldPathNames.size() - 1);
}, name -> "fieldPathNames(" + name + ")"),
downstreamFieldNameMatcher);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@
return Stream.empty();
}

final var fieldValue = (FieldValue)valuePredicate.getValue();

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

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 88.2% (231/262 lines) | Changed lines: 100.0% (1/1 lines)
if (fieldValue.getFieldPathNamesMaybe()
if (fieldValue.getFieldPathStorageNamesMaybe()
.stream()
.anyMatch(Optional::isEmpty)) {
return Stream.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,16 @@
@Nonnull
private final FieldPath fieldPath;

@Nonnull

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

View workflow job for this annotation

GitHub Actions / coverage

File coverage: 92.7% (242/261 lines) | Changed lines: 100.0% (11/11 lines)
private final Supplier<List<String>> fieldNamesSupplier;
private final Supplier<List<String>> fieldStorageNamesSupplier;
@Nonnull
private final Supplier<Type> resultTypeSupplier;

private FieldValue(@Nonnull final Value childValue, @Nonnull final FieldPath fieldPath) {
this.childValue = childValue;
this.fieldPath = fieldPath;
fieldNamesSupplier = Suppliers.memoize(() ->
fieldPath.getOptionalFieldNames()
fieldStorageNamesSupplier = Suppliers.memoize(() ->
fieldPath.getOptionalFieldStorageNames()
.stream()
.map(maybe -> maybe.orElseThrow(() -> new RecordCoreException("field name should have been set")))
.collect(Collectors.toList()));
Expand All @@ -105,9 +105,12 @@
return fieldPath;
}

/**
* The field names as spelled in the record descriptor.
*/
@Nonnull
public List<String> getFieldPathNames() {
return fieldNamesSupplier.get();
public List<String> getFieldPathStorageNames() {
return fieldStorageNamesSupplier.get();
}

@Nonnull
Expand All @@ -120,21 +123,19 @@
return fieldPath.getFieldOrdinals();
}

/**
* The field names as spelled in the record descriptor.
*/
@Nonnull
public List<Optional<String>> getFieldPathNamesMaybe() {
return fieldPath.getOptionalFieldNames();
public List<Optional<String>> getFieldPathStorageNamesMaybe() {
return fieldPath.getOptionalFieldStorageNames();
}

@Nonnull
public FieldPath getFieldPrefix() {
return fieldPath.getFieldPrefix();
}

@Nonnull
public Optional<String> getLastFieldName() {
return fieldPath.getLastFieldName();
}

@Nonnull
@Override
public Type getResultType() {
Expand Down Expand Up @@ -379,6 +380,9 @@
@Nonnull
private final List<ResolvedAccessor> fieldAccessors;

@Nonnull
private final Supplier<List<Optional<String>>> fieldStorageNamesSupplier;

@Nonnull
private final Supplier<List<Optional<String>>> fieldNamesSupplier;

Expand All @@ -397,6 +401,7 @@

public FieldPath(@Nonnull final List<ResolvedAccessor> fieldAccessors) {
this.fieldAccessors = ImmutableList.copyOf(fieldAccessors);
this.fieldStorageNamesSupplier = Suppliers.memoize(() -> computeFieldStorageNames(fieldAccessors));
this.fieldNamesSupplier = Suppliers.memoize(() -> computeFieldNames(fieldAccessors));
this.fieldOrdinalsSupplier = Suppliers.memoize(() -> computeOrdinals(fieldAccessors));
this.fieldTypesSupplier = Suppliers.memoize(() -> computeFieldTypes(fieldAccessors));
Expand Down Expand Up @@ -432,6 +437,17 @@
.collect(Collectors.joining());
}

/**
* The field names as spelled in the record descriptor.
*/
@Nonnull
public List<Optional<String>> getOptionalFieldStorageNames() {
return fieldStorageNamesSupplier.get();
}

/**
* The field names as spelled in SQL.
*/
@Nonnull
Comment thread
robert-brunel marked this conversation as resolved.
Resolved
public List<Optional<String>> getOptionalFieldNames() {
return fieldNamesSupplier.get();
Expand Down Expand Up @@ -459,6 +475,18 @@
return getFieldAccessors().get(size() - 1);
}

/**
* The last field's name as spelled in the record descriptor.
*/
@Nonnull
public Optional<String> getLastFieldStorageName() {
Preconditions.checkArgument(!isEmpty());
return getOptionalFieldStorageNames().get(size() - 1);
}

/**
* The last field's name as spelled in SQL.
*/
@Nonnull
public Optional<String> getLastFieldName() {
Preconditions.checkArgument(!isEmpty());
Expand Down Expand Up @@ -539,12 +567,19 @@
}

@Nonnull
private static List<Optional<String>> computeFieldNames(@Nonnull final List<ResolvedAccessor> fieldAccessors) {
private static List<Optional<String>> computeFieldStorageNames(@Nonnull final List<ResolvedAccessor> fieldAccessors) {
return fieldAccessors.stream()
.map(accessor -> accessor.getField().getFieldStorageNameOptional())
.collect(ImmutableList.toImmutableList());
}

@Nonnull
private static List<Optional<String>> computeFieldNames(@Nonnull final List<ResolvedAccessor> fieldAccessors) {
return fieldAccessors.stream()
.map(accessor -> accessor.getField().getFieldNameOptional())
.collect(ImmutableList.toImmutableList());
}

@Nonnull
private static ImmutableIntArray computeOrdinals(@Nonnull final List<ResolvedAccessor> fieldAccessors) {
final var resultBuilder = ImmutableIntArray.builder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.apple.foundationdb.tuple.Tuple;
import com.apple.foundationdb.tuple.TupleHelpers;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -151,6 +152,72 @@
.findFirst().orElseThrow().getValue().toString());
}

/**
* A field with an escaped identifier is spelled {@code foo.tableA.A1} by the planner and {@code foo__2tableA__2A1}
* by the record descriptor. The value based translator looks its columns up by the former, so it must be filled
* with that spelling too, or every column reads {@code NULL} (issue #4758).
*/
@Test
void bothTranslatorsDecodeEscapedIdentifiersAlike() throws Descriptors.DescriptorValidationException {
final var metaDataBuilder = RecordMetaData.newBuilder().setRecords(escapedRecordsDescriptor());
metaDataBuilder.getRecordType("Escaped").setPrimaryKey(field("rec_no"));
metaDataBuilder.addIndex("Escaped", new Index("escaped",
concat(field("foo__2tableA__2A1"), field("foo__2tableA__2A2"))));
final var metaData = metaDataBuilder.build();
final var recordType = metaData.getRecordType("Escaped");
final var descriptor = recordType.getDescriptor();
final var baseType = Type.Record.fromDescriptor(descriptor);
final var baseAlias = Quantifier.current();
final var baseObjectValue = QuantifiedObjectValue.of(baseAlias, baseType);

final var indexKeyValues = ImmutableList.<Value>of(
FieldValue.ofFieldNames(baseObjectValue, List.of("foo.tableA.A1")),
FieldValue.ofFieldNames(baseObjectValue, List.of("foo.tableA.A2")),
FieldValue.ofFieldNames(baseObjectValue, List.of("rec_no")));

final var translators = ScanWithFetchMatchCandidate.computeIndexEntryToLogicalRecord(List.of(recordType),
baseAlias, baseType, indexKeyValues, ImmutableList.of()).orElseThrow();

Check warning on line 179 in fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/IndexEntryTranslatorEquivalenceTest.java

View check run for this annotation

fdb.teamscale.io / Teamscale | Findings

fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/IndexEntryTranslatorEquivalenceTest.java#L179

Use `java.util.List.of` instead https://fdb.teamscale.io/findings/details/foundationdb-fdb-record-layer?id=1946CBF13140F32A595B89664E440CA2&t=apple%2Fhatyo%2Findex-entry-mangled-names%3AHEAD
final var recordValue = translators.indexEntryToRecordValue();
assertNotNull(recordValue, "escaped identifiers should still yield a value based translator");

final var entry = new IndexEntry(metaData.getIndex("escaped"), Tuple.from(1L, 2L, 7L), TupleHelpers.EMPTY);
final var byCopiers = translators.indexKeyValueToPartialRecord().toRecord(descriptor, entry);
final var byValue = evaluate(recordValue, entry);

assertEquals(1L, byCopiers.getField(descriptor.findFieldByName("foo__2tableA__2A1")));
assertEquals(2L, byCopiers.getField(descriptor.findFieldByName("foo__2tableA__2A2")));
assertSameRecord(descriptor, byCopiers, byValue);
}

/**
* {@code Escaped { rec_no, foo__2tableA__2A1, foo__2tableA__2A2 }}, all {@code int64}, and its union.
*/
@Nonnull
private static Descriptors.FileDescriptor escapedRecordsDescriptor() throws Descriptors.DescriptorValidationException {
final var escaped = DescriptorProtos.DescriptorProto.newBuilder().setName("Escaped");
int number = 1;
for (final var name : List.of("rec_no", "foo__2tableA__2A1", "foo__2tableA__2A2")) {
escaped.addField(DescriptorProtos.FieldDescriptorProto.newBuilder()
.setName(name)
.setNumber(number++)
.setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL)
.setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_INT64));
}
final var union = DescriptorProtos.DescriptorProto.newBuilder().setName("RecordTypeUnion")
.addField(DescriptorProtos.FieldDescriptorProto.newBuilder()
.setName("_Escaped")
.setNumber(1)
.setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL)
.setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE)
.setTypeName(".Escaped"));
final var fileProto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setName("escaped_records.proto")
.addMessageType(escaped)
.addMessageType(union)
.build();
return Descriptors.FileDescriptor.buildFrom(fileProto, new Descriptors.FileDescriptor[] {});
}

@Nonnull
private static Message evaluate(@Nonnull final Value recordValue, @Nonnull final IndexEntry entry) {
final var typeRepository = TypeRepository.newBuilder().addTypeIfNeeded(recordValue.getResultType()).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ private static RecordConstructorValue select(@Nonnull final String... projection
final var rcv = ValueTestHelpers.qov();
final List<Column<? extends Value>> columns = Arrays.stream(projection)
.map(field -> FieldValue.ofFieldName(rcv, field))
.map(field -> Column.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of(field.getLastFieldName().orElseThrow() + "p")), field))
.map(field -> Column.of(Type.Record.Field.of(Type.primitiveType(Type.TypeCode.STRING), Optional.of(field.getFieldPath().getLastFieldName().orElseThrow() + "p")), field))
.collect(Collectors.toList());
// create a bunch of aliases
return RecordConstructorValue.ofColumns(columns);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public FieldValueMatcher(@Nonnull String fieldName) {
@Override
protected boolean matchesSafely(final Value element) {
return element instanceof FieldValue &&
((FieldValue)element).getFieldPathNames().equals(fieldPath);
((FieldValue)element).getFieldPathStorageNames().equals(fieldPath);
}

@Override
Expand Down
Loading