Skip to content

Commit aa9a4db

Browse files
committed
fix bug
1 parent 035bc08 commit aa9a4db

File tree

14 files changed

+540
-105
lines changed

14 files changed

+540
-105
lines changed

docs/sphinx/source/ReleaseNotes.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,35 @@ Mixed mode testing run against the following previous versions:
7474

7575
[See full test run](https://github.com/FoundationDB/fdb-record-layer/actions/runs/14134843186)
7676

77+
### 4.2.2.1
78+
79+
<h4> Dependency Updates </h4>
80+
81+
* Downgrade protobuf version back to 3.25.5 and grpc common proto to 2.37.0 - [PR #3288](https://github.com/FoundationDB/fdb-record-layer/pull/3288)
82+
83+
<details>
84+
<summary>
85+
86+
<h4> Build/Test/Documentation/Style Improvements (click to expand) </h4>
87+
88+
</summary>
89+
90+
* Decrease the number of builds compared during mixed mode to the most recent 10 - [PR #3290](https://github.com/FoundationDB/fdb-record-layer/pull/3290)
91+
92+
</details>
93+
94+
95+
**[Full Changelog (4.2.2.0...4.2.2.1)](https://github.com/FoundationDB/fdb-record-layer/compare/4.2.2.0...4.2.2.1)**
96+
97+
#### Mixed Mode Test Results
98+
99+
Mixed mode testing run against the following previous versions:
100+
101+
`4.0.574.0`, ❌`4.0.575.0`, ❌`4.1.4.0`, ✅`4.1.5.0`, ✅`4.1.6.0`, ✅`4.1.8.0`, ✅`4.1.9.0`, ✅`4.1.10.0`, ✅`4.2.2.0`, ✅`4.2.3.0`
102+
103+
[See full test run](https://github.com/FoundationDB/fdb-record-layer/actions/runs/14239522793)
104+
105+
77106

78107

79108
### 4.2.2.0

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/JoinedRecordType.java

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore;
3030
import com.apple.foundationdb.record.provider.foundationdb.FDBStoredRecord;
3131
import com.apple.foundationdb.record.provider.foundationdb.FDBSyntheticRecord;
32+
import com.apple.foundationdb.record.provider.foundationdb.IndexOrphanBehavior;
3233
import com.apple.foundationdb.record.provider.foundationdb.RecordDoesNotExistException;
3334
import com.apple.foundationdb.tuple.Tuple;
3435
import com.google.protobuf.Descriptors;
@@ -39,9 +40,33 @@
3940
import java.util.Map;
4041
import java.util.concurrent.CompletableFuture;
4142
import java.util.concurrent.ConcurrentHashMap;
43+
import java.util.concurrent.atomic.AtomicBoolean;
4244

4345
/**
4446
* A <i>synthetic</i> record type representing the indexable result of <em>joining</em> stored records.
47+
* <p>
48+
* Joined record represents a collection of <i>joined constituents</i>, each represented by another record in the database.
49+
* The constituents are joined together by a <i>join condition</i>: in this case, a set of fields that are tested for
50+
* equality across all constituents.
51+
* The joined record primary key is represented by a Tuple as follows:
52+
* <pre>
53+
* [{record type}, {elements of constituent 1 PK}, ... {elements of constituent n PK}]
54+
* </pre>
55+
* When loading a joined record, the joined record primary key is used to iterate and load each constituent, eventually
56+
* combining the collection of constituents into the completed record.
57+
* <p>
58+
* There is nothing special needed to be done to save a joined record. Once the constituent records are saved
59+
* a joined record instance is implicitly assumed to exist. A subsequent {@link #loadByPrimaryKeyAsync(FDBRecordStore, Tuple, IndexOrphanBehavior)}
60+
* or a query for the record or a scan of a joined index will create the joined record. similarly, a deletion
61+
* of any or all of the constituents will implicitly render the joined record type deleted.
62+
* <p>
63+
* When loading a joined record (and similarly when scanning an index) an {@link IndexOrphanBehavior} can be used
64+
* to determine the behavior in case one or more of the constituents are missing:
65+
* <ul>
66+
* <li>{@link IndexOrphanBehavior#ERROR} (the default) will throw an exception</li>
67+
* <li>{@link IndexOrphanBehavior#RETURN} will return an instance of the records with no constituents</li>
68+
* <li>{@link IndexOrphanBehavior#SKIP} will return null</li>
69+
* </ul>
4570
*/
4671
@API(API.Status.EXPERIMENTAL)
4772
public class JoinedRecordType extends SyntheticRecordType<JoinedRecordType.JoinConstituent> {
@@ -124,10 +149,11 @@ public List<Join> getJoins() {
124149
@Nonnull
125150
@Override
126151
@API(API.Status.INTERNAL)
127-
public CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(FDBRecordStore store, Tuple primaryKey) {
152+
public CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(FDBRecordStore store, Tuple primaryKey, IndexOrphanBehavior orphanBehavior) {
128153
int nconstituents = getConstituents().size();
129154
final Map<String, FDBStoredRecord<? extends Message>> constituentValues = new ConcurrentHashMap<>(nconstituents);
130155
final CompletableFuture<?>[] futures = new CompletableFuture<?>[nconstituents];
156+
AtomicBoolean isMissingConstituent = new AtomicBoolean(false);
131157
for (int i = 0; i < nconstituents; i++) {
132158
final SyntheticRecordType.Constituent constituent = getConstituents().get(i);
133159
final Tuple constituentKey = primaryKey.getNestedTuple(i + 1);
@@ -136,17 +162,37 @@ public CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(FDBRecordStor
136162
} else {
137163
futures[i] = store.loadRecordAsync(constituentKey).thenApply(rec -> {
138164
if (rec == null) {
139-
throw new RecordDoesNotExistException("constituent record not found: " + constituent.getName());
165+
if (orphanBehavior.equals(IndexOrphanBehavior.ERROR)) {
166+
throw new RecordDoesNotExistException("constituent record not found: " + constituent.getName());
167+
} else {
168+
// For SKIP and RETURN
169+
isMissingConstituent.set(true);
170+
// ideally, we should be able to stop the iteration to fetch all other constituents
171+
// but because of the async nature of the loop this seems to be not worth it
172+
}
173+
} else {
174+
constituentValues.put(constituent.getName(), rec);
140175
}
141-
constituentValues.put(constituent.getName(), rec);
142176
return null;
143177
});
144178
}
145179
}
146-
return CompletableFuture.allOf(futures).thenApply(vignore -> FDBSyntheticRecord.of(this, constituentValues));
180+
return CompletableFuture.allOf(futures).thenApply(vignore -> {
181+
if ( ! isMissingConstituent.get()) {
182+
// all constituents have been found
183+
return FDBSyntheticRecord.of(this, constituentValues);
184+
} else {
185+
// some constituents are missing
186+
if (orphanBehavior.equals(IndexOrphanBehavior.SKIP)) {
187+
return null;
188+
} else {
189+
// This is for RETURN - return the shell of the record with no constituents
190+
return FDBSyntheticRecord.of(this, Map.of());
191+
}
192+
}
193+
});
147194
}
148195

149-
150196
@Nonnull
151197
public RecordMetaDataProto.JoinedRecordType toProto() {
152198
RecordMetaDataProto.JoinedRecordType.Builder typeBuilder = RecordMetaDataProto.JoinedRecordType.newBuilder()

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/SyntheticRecordType.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
2626
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore;
2727
import com.apple.foundationdb.record.provider.foundationdb.FDBSyntheticRecord;
28+
import com.apple.foundationdb.record.provider.foundationdb.IndexOrphanBehavior;
2829
import com.apple.foundationdb.tuple.Tuple;
2930
import com.google.protobuf.Descriptors;
3031

@@ -89,7 +90,13 @@ public boolean isSynthetic() {
8990

9091
@API(API.Status.INTERNAL)
9192
@Nonnull
92-
public abstract CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(FDBRecordStore store, Tuple primaryKey);
93+
public CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(FDBRecordStore store, Tuple primaryKey) {
94+
return loadByPrimaryKeyAsync(store, primaryKey, IndexOrphanBehavior.ERROR);
95+
}
96+
97+
@API(API.Status.INTERNAL)
98+
@Nonnull
99+
public abstract CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(FDBRecordStore store, Tuple primaryKey, IndexOrphanBehavior orphanBehavior);
93100

94101
@Override
95102
public String toString() {

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/UnnestedRecordType.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore;
3131
import com.apple.foundationdb.record.provider.foundationdb.FDBStoredRecord;
3232
import com.apple.foundationdb.record.provider.foundationdb.FDBSyntheticRecord;
33+
import com.apple.foundationdb.record.provider.foundationdb.IndexOrphanBehavior;
3334
import com.apple.foundationdb.record.provider.foundationdb.RecordDoesNotExistException;
3435
import com.apple.foundationdb.tuple.Tuple;
3536
import com.google.protobuf.Descriptors;
@@ -262,12 +263,21 @@ public NestedConstituent getParentConstituent() {
262263
@Override
263264
@Nonnull
264265
@API(API.Status.INTERNAL)
265-
public CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(@Nonnull final FDBRecordStore store, @Nonnull final Tuple primaryKey) {
266+
public CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(@Nonnull final FDBRecordStore store, @Nonnull final Tuple primaryKey, IndexOrphanBehavior orphanBehavior) {
266267
Tuple parentPrimaryKey = primaryKey.getNestedTuple(1);
267268
return store.loadRecordAsync(parentPrimaryKey).thenApply(storedRecord -> {
268269
if (storedRecord == null) {
269-
throw new RecordDoesNotExistException("constituent record not found: " + parentConstituent.getName())
270-
.addLogInfo(LogMessageKeys.PRIMARY_KEY, parentPrimaryKey);
270+
switch (orphanBehavior) {
271+
case ERROR:
272+
throw new RecordDoesNotExistException("constituent record not found: " + parentConstituent.getName())
273+
.addLogInfo(LogMessageKeys.PRIMARY_KEY, parentPrimaryKey);
274+
case SKIP:
275+
return null;
276+
case RETURN:
277+
return FDBSyntheticRecord.of(this, Map.of());
278+
default:
279+
throw new IllegalArgumentException("Unknown orphanBehavior value: " + orphanBehavior);
280+
}
271281
}
272282
Map<String, FDBStoredRecord<?>> constituentValues = new HashMap<>();
273283
constituentValues.put(getParentConstituent().getName(), storedRecord);
@@ -285,8 +295,18 @@ public CompletableFuture<FDBSyntheticRecord> loadByPrimaryKeyAsync(@Nonnull fina
285295
int childConstituentIndex = getConstituents().indexOf(constituent);
286296
int childElemIndex = (int) primaryKey.getNestedTuple(childConstituentIndex + 1).getLong(0);
287297
if (childElemIndex >= childElems.size()) {
288-
throw new RecordCoreException("child element position is too large")
289-
.addLogInfo(LogMessageKeys.CHILD_COUNT, childElems.size());
298+
// Constituent not found
299+
switch (orphanBehavior) {
300+
case ERROR:
301+
throw new RecordCoreException("child element position is too large")
302+
.addLogInfo(LogMessageKeys.CHILD_COUNT, childElems.size());
303+
case SKIP:
304+
return null;
305+
case RETURN:
306+
return FDBSyntheticRecord.of(this, Map.of());
307+
default:
308+
throw new IllegalArgumentException("Unknown orphanBehavior value: " + orphanBehavior);
309+
}
290310
}
291311
Key.Evaluated childElem = childElems.get(childElemIndex);
292312
Message childMessage = childElem.getObject(0, Message.class);

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,12 +817,12 @@ private CompletableFuture<Void> runSyntheticMaintainers(@Nonnull Map<RecordType,
817817
@API(API.Status.EXPERIMENTAL)
818818
@Nonnull
819819
@Override
820-
public CompletableFuture<FDBSyntheticRecord> loadSyntheticRecord(@Nonnull Tuple primaryKey) {
820+
public CompletableFuture<FDBSyntheticRecord> loadSyntheticRecord(@Nonnull Tuple primaryKey, final IndexOrphanBehavior orphanBehavior) {
821821
SyntheticRecordType<?> syntheticRecordType = getRecordMetaData().getSyntheticRecordTypeFromRecordTypeKey(primaryKey.get(0));
822822
if (syntheticRecordType.getConstituents().size() != primaryKey.size() - 1) {
823823
throw recordCoreException("Primary key does not have correct number of nested keys: " + primaryKey);
824824
}
825-
return syntheticRecordType.loadByPrimaryKeyAsync(this, primaryKey);
825+
return syntheticRecordType.loadByPrimaryKeyAsync(this, primaryKey, orphanBehavior);
826826
}
827827

828828
@Nonnull

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreBase.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,25 @@ default CompletableFuture<FDBStoredRecord<M>> loadRecordAsync(@Nonnull final Tup
720720
*/
721721
@Nonnull
722722
@API(API.Status.EXPERIMENTAL)
723-
CompletableFuture<FDBSyntheticRecord> loadSyntheticRecord(@Nonnull Tuple primaryKey);
723+
default CompletableFuture<FDBSyntheticRecord> loadSyntheticRecord(@Nonnull Tuple primaryKey) {
724+
return loadSyntheticRecord(primaryKey, IndexOrphanBehavior.ERROR);
725+
}
726+
727+
/**
728+
* Load a {@link FDBSyntheticRecord synthetic record} by loading its stored constituent records and synthesizing it from them.
729+
* In case any of the constituents are missing, the following will be returned:
730+
* <ul>
731+
* <li>{@link IndexOrphanBehavior#ERROR}: Exception is thrown</li>
732+
* <li>{@link IndexOrphanBehavior#SKIP}: Future(null) is returned</li>
733+
* <li>{@link IndexOrphanBehavior#RETURN}: Future(Record with no constituents) is returned</li>
734+
* </ul>
735+
* @param primaryKey the primary key of the synthetic record, which includes the primary keys of the constituents
736+
* @param orphanBehavior what to do if any of the record's constituents is missing
737+
* @return a future which completes to the synthesized record
738+
*/
739+
@Nonnull
740+
@API(API.Status.EXPERIMENTAL)
741+
CompletableFuture<FDBSyntheticRecord> loadSyntheticRecord(@Nonnull Tuple primaryKey, IndexOrphanBehavior orphanBehavior);
724742

725743
/**
726744
* Check if a record exists in the record store with the given primary key.

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBTypedRecordStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public CompletableFuture<Void> preloadRecordAsync(@Nonnull Tuple primaryKey) {
157157

158158
@Nonnull
159159
@Override
160-
public CompletableFuture<FDBSyntheticRecord> loadSyntheticRecord(@Nonnull final Tuple primaryKey) {
160+
public CompletableFuture<FDBSyntheticRecord> loadSyntheticRecord(@Nonnull final Tuple primaryKey, final IndexOrphanBehavior orphanBehavior) {
161161
throw new RecordCoreException("api unsupported on typed store");
162162
}
163163

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/indexes/ValueIndexScrubbingToolsDangling.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ public class ValueIndexScrubbingToolsDangling implements IndexScrubbingTools<Ind
5757

5858
@Override
5959
public void presetCommonParams(final Index index, final boolean allowRepair, final boolean isSynthetic, final Collection<RecordType> typesIgnored) {
60+
if (isSynthetic && allowRepair) {
61+
throw new UnsupportedOperationException("Scrubbing synthetic records with repair is not supported");
62+
}
6063
this.index = index;
6164
this.allowRepair = allowRepair;
6265
this.isSynthetic = isSynthetic;
@@ -93,7 +96,7 @@ public CompletableFuture<Issue> handleOneItem(final FDBRecordStore store, final
9396
}
9497

9598
if (isSynthetic) {
96-
return store.loadSyntheticRecord(indexEntry.getPrimaryKey()).thenApply(syntheticRecord -> {
99+
return store.loadSyntheticRecord(indexEntry.getPrimaryKey(), IndexOrphanBehavior.RETURN).thenApply(syntheticRecord -> {
97100
if (syntheticRecord.getConstituents().isEmpty()) {
98101
// None of the constituents of this synthetic type are present, so it must be dangling
99102
List<Tuple> primaryKeysForConflict = new ArrayList<>(indexEntry.getPrimaryKey().size() - 1);

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/indexes/ValueIndexScrubbingToolsMissing.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ public class ValueIndexScrubbingToolsMissing implements IndexScrubbingTools<FDBS
6666

6767
@Override
6868
public void presetCommonParams(Index index, boolean allowRepair, boolean isSynthetic, Collection<RecordType> types) {
69+
if (isSynthetic && allowRepair) {
70+
throw new UnsupportedOperationException("Scrubbing synthetic records with repair is not supported");
71+
}
6972
this.recordTypes = types;
7073
this.index = index;
7174
this.allowRepair = allowRepair;

0 commit comments

Comments
 (0)