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 @@ -274,13 +274,12 @@ private void validateField(@Nonnull FieldDescriptor oldFieldDescriptor, @Nonnull
if (oldFieldDescriptor.isRequired() && !newFieldDescriptor.isRequired()) {
throw new MetaDataException("required field is no longer required",
LogMessageKeys.FIELD_NAME, oldFieldDescriptor.getName());
} else if (oldFieldDescriptor.isOptional() && !newFieldDescriptor.isOptional()) {
// TODO: In theory, optional -> repeated is okay, but only if the field is not indexed
throw new MetaDataException("optional field is no longer optional",
LogMessageKeys.FIELD_NAME, oldFieldDescriptor.getName());
} else if (oldFieldDescriptor.isRepeated() && !newFieldDescriptor.isRepeated()) {
throw new MetaDataException("repeated field is no longer repeated",
LogMessageKeys.FIELD_NAME, oldFieldDescriptor.getName());
} else if (oldFieldDescriptor.hasPresence() != newFieldDescriptor.hasPresence()) {
throw new MetaDataException("field changed whether default values are stored if set explicitly",
LogMessageKeys.FIELD_NAME, oldFieldDescriptor.getName());
}
if (oldFieldDescriptor.getType().equals(FieldDescriptor.Type.ENUM)) {
validateEnum(newFieldDescriptor.getName(), oldFieldDescriptor.getEnumType(), newFieldDescriptor.getEnumType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -857,12 +857,17 @@ public void fieldLabelChanged() {
DescriptorProtos.FieldDescriptorProto.Label.LABEL_REQUIRED
);
for (int i = 0; i < labels.size(); i++) {
final int itr = i;
final DescriptorProtos.FieldDescriptorProto.Label label = labels.get(itr);
final String labelText = label.name().substring(label.name().indexOf('_') + 1).toLowerCase(Locale.ROOT);
final String errMsg = labelText + " field is no longer " + labelText;
final DescriptorProtos.FieldDescriptorProto.Label label = labels.get(i);
final String errMsg;
if (label == DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL) {
errMsg = "field changed whether default values are stored if set explicitly";
} else {
final String labelText = label.name().substring(label.name().indexOf('_') + 1).toLowerCase(Locale.ROOT);
errMsg = labelText + " field is no longer " + labelText;
}
final DescriptorProtos.FieldDescriptorProto.Label newLabel = labels.get((i + 1) % labels.size());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point, we probably want a test like "compare a proto2/proto3 file with explicit optionals to a file with editions set correctly/incorrectly", but I think we throw an error right now if the syntax doesn't match between two files anyway, so that test is probably pre-mature. This change, on the whole, seems to put us closer to that future, though

FileDescriptor updatedFile = mutateField("MySimpleRecord", "str_value_indexed", oldFile,
field -> field.setLabel(labels.get((itr + 1) % labels.size())));
field -> field.setLabel(newLabel));
assertInvalid(errMsg, oldFile, updatedFile);

oldFile = updatedFile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.apple.foundationdb.record.RecordMetaDataBuilder;
import com.apple.foundationdb.record.TestRecordsNulls2Proto;
import com.apple.foundationdb.record.TestRecordsNulls3Proto;
import com.apple.foundationdb.record.TestRecordsNulls3ExplicitProto;
import com.apple.foundationdb.record.TestRecordsTupleFieldsProto;
import com.apple.foundationdb.record.TupleFieldsProto;
import com.apple.foundationdb.record.metadata.Key;
Expand Down Expand Up @@ -108,6 +109,10 @@
return metaData.getRecordMetaData();
}

protected static RecordMetaData proto3ExplicitMetaData() {
return RecordMetaData.newBuilder().setRecords(TestRecordsNulls3ExplicitProto.getDescriptor()).getRecordMetaData();
}

@FunctionalInterface
interface RecordBuilder<M extends Message> {
M build(@Nonnull String name, @Nullable Integer intValue, @Nullable String stringValue);
Expand Down Expand Up @@ -175,6 +180,18 @@
return builder.build();
}

protected static TestRecordsNulls3ExplicitProto.MyNullRecord buildRecord3Explicit(@Nonnull String name, @Nullable Integer intValue, @Nullable String stringValue) {
TestRecordsNulls3ExplicitProto.MyNullRecord.Builder builder = TestRecordsNulls3ExplicitProto.MyNullRecord.newBuilder();
builder.setName(name);
if (intValue != null) {
builder.setIntValue(intValue);
}
if (stringValue != null) {
builder.setStringValue(stringValue);
}
return builder.build();
}

protected <M extends Message> void saveTestRecords(@Nonnull RecordBuilder<M> builder) {
recordStore.saveRecord(builder.build("empty", null, null));
recordStore.saveRecord(builder.build("default", 0, ""));
Expand Down Expand Up @@ -365,6 +382,53 @@
}
}

// Explicit presence ("optional") correctly distguishes as well (results same as proto2).
@Test
public void testProto3Explicit() {

Check warning on line 387 in fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBRecordStoreNullQueryTest.java

View check run for this annotation

fdb.teamscale.io / Teamscale | Findings

fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/query/FDBRecordStoreNullQueryTest.java#L387

`testProto3Explicit` should have package visibility https://fdb.teamscale.io/findings/details/foundationdb-fdb-record-layer?t=FORK_MR%2F3920%2FMMcM%2Fproto3-explicit-presence%3AHEAD&id=7C52637BBE026A5C751CC3772D9F79C5
try (FDBRecordContext context = openContext()) {
createOrOpenRecordStore(context, proto3ExplicitMetaData());
saveTestRecords(FDBRecordStoreNullQueryTest::buildRecord3Explicit);

assertThat(executeQuery(RecordQuery.newBuilder()
.setRecordType("MyNullRecord")
.setFilter(Query.field("int_value").equalsValue(2))
.build()),
is(Collections.singletonList("two")));
assertThat(executeQuery(RecordQuery.newBuilder()
.setRecordType("MyNullRecord")
.setFilter(Query.field("string_value").equalsValue("B"))
.build()),
is(Collections.singletonList("two")));

assertThat(executeQuery(RecordQuery.newBuilder()
.setRecordType("MyNullRecord")
.setFilter(Query.field("int_value").isNull())
.build()),
is(Collections.singletonList("empty")));
assertThat(executeQuery(RecordQuery.newBuilder()
.setRecordType("MyNullRecord")
.setFilter(Query.field("int_value").equalsValue(0))
.build()),
is(Collections.singletonList("default")));
assertThat(executeQuery(RecordQuery.newBuilder()
.setRecordType("MyNullRecord")
.setFilter(Query.field("string_value").isNull())
.build()),
is(Collections.singletonList("empty")));
assertThat(executeQuery(RecordQuery.newBuilder()
.setRecordType("MyNullRecord")
.setFilter(Query.field("string_value").equalsValue(""))
.build()),
is(Collections.singletonList("default")));

assertThat(executeQuery(RecordQuery.newBuilder()
.setRecordType("MyNullRecord")
.setSort(Key.Expressions.field("int_value"))
.build()),
is(Arrays.asList("empty", "minus", "default", "one", "two")));
}
}

@Test
public void testCompareSerialization() throws Exception {
final TestRecordsNulls2Proto.MyNullRecord emptyProto2 = buildRecord2("record", null, null);
Expand Down
19 changes: 19 additions & 0 deletions fdb-record-layer-core/src/test/proto/test_records_nulls_3.proto
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
/*
* test_records_nulls_3.proto
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2018 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";

package com.apple.foundationdb.record.testnulls3;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* test_records_nulls_3_explicit.proto
*
* This source file is part of the FoundationDB open source project
*
* Copyright 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.
*/
syntax = "proto3";

package com.apple.foundationdb.record.testnulls3explicit;

option java_package = "com.apple.foundationdb.record";
option java_outer_classname = "TestRecordsNulls3ExplicitProto";

import "record_metadata_options.proto";

message MyNullRecord {
optional string name = 1 [(field).primary_key = true];
optional string string_value = 2 [(field).index = {}];
optional int32 int_value = 3 [(field).index = {}];
}

message RecordTypeUnion {
optional MyNullRecord _MyNullRecord = 1;
}
Loading