use of com.apple.foundationdb.annotation.API in project fdb-record-layer by FoundationDB.
the class RecordQueryPlan method structuralEquals.
/**
* Determine if two plans are structurally equal. This differs from the semantic equality defined in
* {@link RelationalExpression}. For instance this method would return false
* for two given plans {@code UNION(p1, p2)} and {@code UNION(p2, p1)} of two different sub-plans {@code p1} and
* {@code p2}. In contrast to that these plans are considered semantically equal.
* @param other object to compare this object with
* @param equivalenceMap alias map to indicate aliases that should be considered as equal when {@code other} is
* compared to {@code this}. For instance {@code q1.x = 1} is only structurally equal with {@code q2.x = 1}
* if there is a mapping {@code q1 -> q2} in the alias map passed in
* @return {@code true} if {@code this} is structurally equal to {@code other}, {@code false} otherwise
*/
@API(API.Status.EXPERIMENTAL)
default boolean structuralEquals(@Nullable final Object other, @Nonnull final AliasMap equivalenceMap) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
final RelationalExpression otherExpression = (RelationalExpression) other;
// We know this and otherExpression are of the same class. canCorrelate() needs to match as well.
Verify.verify(canCorrelate() == otherExpression.canCorrelate());
final List<Quantifier.Physical> quantifiers = Quantifiers.narrow(Quantifier.Physical.class, getQuantifiers());
final List<Quantifier.Physical> otherQuantifiers = Quantifiers.narrow(Quantifier.Physical.class, otherExpression.getQuantifiers());
if (quantifiers.size() != otherQuantifiers.size()) {
return false;
}
final Iterable<AliasMap> boundCorrelatedReferencesIterable = enumerateUnboundCorrelatedTo(equivalenceMap, otherExpression);
for (final AliasMap boundCorrelatedReferencesMap : boundCorrelatedReferencesIterable) {
final AliasMap.Builder boundCorrelatedToBuilder = boundCorrelatedReferencesMap.derived();
AliasMap boundCorrelatedToMap = AliasMap.emptyMap();
int i;
for (i = 0; i < quantifiers.size(); i++) {
boundCorrelatedToMap = boundCorrelatedToBuilder.build();
final Quantifier.Physical quantifier = quantifiers.get(i);
final Quantifier.Physical otherQuantifier = otherQuantifiers.get(i);
if (quantifier.structuralHashCode() != otherQuantifier.structuralHashCode()) {
break;
}
if (!quantifier.structuralEquals(otherQuantifier)) {
break;
}
if (canCorrelate()) {
boundCorrelatedToBuilder.put(quantifier.getAlias(), otherQuantifier.getAlias());
}
}
if (i == quantifiers.size() && (equalsWithoutChildren(otherExpression, boundCorrelatedToMap))) {
return true;
}
}
return false;
}
use of com.apple.foundationdb.annotation.API in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method getPrimaryKeyBoundaries.
/**
* Return a cursor of boundaries separating the key ranges maintained by each FDB server. This information can be
* useful for splitting a large task (e.g., rebuilding an index for a large record store) into smaller tasks (e.g.,
* rebuilding the index for records in certain primary key ranges) more evenly so that they can be executed in a
* parallel fashion efficiently. The returned boundaries are an estimate from FDB's locality API and may not
* represent the exact boundary locations at any database version.
* <p>
* The boundaries are returned as a cursor which is sorted and does not contain any duplicates. The first element of
* the list is greater than or equal to <code>low</code>, and the last element is less than or equal to
* <code>high</code>.
* <p>
* This implementation may not work when there are too many shard boundaries to complete in a single transaction.
* <p>
* Note: the returned cursor is blocking and must not be used in an asynchronous context
*
* @param low low endpoint of primary key range (inclusive)
* @param high high endpoint of primary key range (exclusive)
* @return the list of boundary primary keys
*/
@API(API.Status.EXPERIMENTAL)
@Nonnull
public RecordCursor<Tuple> getPrimaryKeyBoundaries(@Nonnull Tuple low, @Nonnull Tuple high) {
final Transaction transaction = ensureContextActive();
byte[] rangeStart = recordsSubspace().pack(low);
byte[] rangeEnd = recordsSubspace().pack(high);
CloseableAsyncIterator<byte[]> cursor = context.getDatabase().getLocalityProvider().getBoundaryKeys(transaction, rangeStart, rangeEnd);
final boolean hasSplitRecordSuffix = hasSplitRecordSuffix();
DistinctFilterCursorClosure closure = new DistinctFilterCursorClosure();
return RecordCursor.flatMapPipelined(ignore -> RecordCursor.fromIterator(getExecutor(), cursor), (result, ignore) -> RecordCursor.fromIterator(getExecutor(), transaction.snapshot().getRange(result, rangeEnd, 1).iterator()), null, DEFAULT_PIPELINE_SIZE).map(keyValue -> {
Tuple recordKey = recordsSubspace().unpack(keyValue.getKey());
return hasSplitRecordSuffix ? recordKey.popBack() : recordKey;
}).filter(closure::pred);
}
use of com.apple.foundationdb.annotation.API in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method runSyntheticMaintainers.
@Nonnull
@API(API.Status.EXPERIMENTAL)
private CompletableFuture<Void> runSyntheticMaintainers(@Nonnull Map<RecordType, Collection<IndexMaintainer>> maintainers, @Nullable FDBSyntheticRecord oldRecord, @Nullable FDBSyntheticRecord newRecord) {
if (oldRecord == null && newRecord == null) {
return AsyncUtil.DONE;
}
final RecordType recordType = oldRecord != null ? oldRecord.getRecordType() : newRecord.getRecordType();
final List<CompletableFuture<Void>> futures = new ArrayList<>();
for (IndexMaintainer indexMaintainer : maintainers.get(recordType)) {
CompletableFuture<Void> future = indexMaintainer.update(oldRecord, newRecord);
if (!MoreAsyncUtil.isCompletedNormally(future)) {
futures.add(future);
}
}
if (futures.isEmpty()) {
return AsyncUtil.DONE;
} else if (futures.size() == 1) {
return futures.get(0);
} else {
return AsyncUtil.whenAll(futures);
}
}
use of com.apple.foundationdb.annotation.API in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method saveTypedRecord.
@Nonnull
@API(API.Status.INTERNAL)
protected <M extends Message> CompletableFuture<FDBStoredRecord<M>> saveTypedRecord(@Nonnull RecordSerializer<M> typedSerializer, @Nonnull M rec, @Nonnull RecordExistenceCheck existenceCheck, @Nullable FDBRecordVersion version, @Nonnull VersionstampSaveBehavior behavior) {
final RecordMetaData metaData = metaDataProvider.getRecordMetaData();
final Descriptors.Descriptor recordDescriptor = rec.getDescriptorForType();
final RecordType recordType = metaData.getRecordTypeForDescriptor(recordDescriptor);
final KeyExpression primaryKeyExpression = recordType.getPrimaryKey();
final FDBStoredRecordBuilder<M> recordBuilder = FDBStoredRecord.newBuilder(rec).setRecordType(recordType);
final FDBRecordVersion recordVersion = recordVersionForSave(metaData, version, behavior);
recordBuilder.setVersion(recordVersion);
final Tuple primaryKey = primaryKeyExpression.evaluateSingleton(recordBuilder).toTuple();
recordBuilder.setPrimaryKey(primaryKey);
final CompletableFuture<FDBStoredRecord<M>> result = loadExistingRecord(typedSerializer, primaryKey).thenCompose(oldRecord -> {
if (oldRecord == null) {
if (existenceCheck.errorIfNotExists()) {
throw new RecordDoesNotExistException("record does not exist", LogMessageKeys.PRIMARY_KEY, primaryKey);
}
} else {
if (existenceCheck.errorIfExists()) {
throw new RecordAlreadyExistsException("record already exists", LogMessageKeys.PRIMARY_KEY, primaryKey);
}
if (existenceCheck.errorIfTypeChanged() && oldRecord.getRecordType() != recordType) {
throw new RecordTypeChangedException("record type changed", LogMessageKeys.PRIMARY_KEY, primaryKey, LogMessageKeys.ACTUAL_TYPE, oldRecord.getRecordType().getName(), LogMessageKeys.EXPECTED_TYPE, recordType.getName());
}
}
final FDBStoredRecord<M> newRecord = serializeAndSaveRecord(typedSerializer, recordBuilder, metaData, oldRecord);
if (oldRecord == null) {
addRecordCount(metaData, newRecord, LITTLE_ENDIAN_INT64_ONE);
} else {
if (getTimer() != null) {
getTimer().increment(FDBStoreTimer.Counts.REPLACE_RECORD_VALUE_BYTES, oldRecord.getValueSize());
}
}
return updateSecondaryIndexes(oldRecord, newRecord).thenApply(v -> newRecord);
});
return context.instrument(FDBStoreTimer.Events.SAVE_RECORD, result);
}
use of com.apple.foundationdb.annotation.API in project fdb-record-layer by FoundationDB.
the class FDBRecordStore method updateSyntheticIndexes.
@API(API.Status.EXPERIMENTAL)
private <M extends Message> void updateSyntheticIndexes(@Nullable FDBStoredRecord<M> oldRecord, @Nullable FDBStoredRecord<M> newRecord, @Nonnull final List<CompletableFuture<Void>> futures) {
final SyntheticRecordPlanner planner = new SyntheticRecordPlanner(this);
// Index maintainers are not required to be thread-safe, so only do one synthetic record at a time.
final int pipelineSize = 1;
if (oldRecord != null && newRecord != null && oldRecord.getRecordType() == newRecord.getRecordType()) {
// TODO: An important optimization here is determining that no field used in the join condition or
// indexed in the synthetic record is changed, in which case all this can be skipped.
final SyntheticRecordFromStoredRecordPlan plan = planner.fromStoredType(newRecord.getRecordType(), true);
if (plan == null) {
return;
}
final Map<RecordType, Collection<IndexMaintainer>> maintainers = getSyntheticMaintainers(plan.getSyntheticRecordTypes());
final Map<Tuple, FDBSyntheticRecord> oldRecords = new ConcurrentHashMap<>();
CompletableFuture<Void> future = plan.execute(this, oldRecord).forEach(syntheticRecord -> oldRecords.put(syntheticRecord.getPrimaryKey(), syntheticRecord));
// @SpotBugsSuppressWarnings("NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE", justification = "https://github.com/spotbugs/spotbugs/issues/552")
@Nonnull final FDBStoredRecord<M> theNewRecord = newRecord;
future = future.thenCompose(v -> plan.execute(this, theNewRecord).forEachAsync(syntheticRecord -> runSyntheticMaintainers(maintainers, oldRecords.remove(syntheticRecord.getPrimaryKey()), syntheticRecord), pipelineSize));
future = future.thenCompose(v -> {
// Any synthetic record that was generated by the plan on the old record but not by the plan on the new record needs to be removed from its indexes.
final List<CompletableFuture<Void>> subFutures = new ArrayList<>();
for (FDBSyntheticRecord oldSyntheticRecord : oldRecords.values()) {
CompletableFuture<Void> subFuture = runSyntheticMaintainers(maintainers, oldSyntheticRecord, null);
if (!MoreAsyncUtil.isCompletedNormally(subFuture)) {
subFutures.add(subFuture);
}
}
if (subFutures.isEmpty()) {
return AsyncUtil.DONE;
} else if (subFutures.size() == 1) {
return subFutures.get(0);
} else {
return AsyncUtil.whenAll(subFutures);
}
});
futures.add(future);
} else {
if (oldRecord != null) {
final SyntheticRecordFromStoredRecordPlan plan = planner.fromStoredType(oldRecord.getRecordType(), true);
if (plan != null) {
final Map<RecordType, Collection<IndexMaintainer>> maintainers = getSyntheticMaintainers(plan.getSyntheticRecordTypes());
futures.add(plan.execute(this, oldRecord).forEachAsync(syntheticRecord -> runSyntheticMaintainers(maintainers, syntheticRecord, null), pipelineSize));
}
}
if (newRecord != null) {
final SyntheticRecordFromStoredRecordPlan plan = planner.fromStoredType(newRecord.getRecordType(), true);
if (plan != null) {
final Map<RecordType, Collection<IndexMaintainer>> maintainers = getSyntheticMaintainers(plan.getSyntheticRecordTypes());
futures.add(plan.execute(this, newRecord).forEachAsync(syntheticRecord -> runSyntheticMaintainers(maintainers, null, syntheticRecord), pipelineSize));
}
}
}
}
Aggregations