277 lines
10 KiB
C++
277 lines
10 KiB
C++
/*
|
|
* WriteMap.h
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-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.
|
|
*/
|
|
|
|
#ifndef FDBCLIENT_WRITEMAP_H
|
|
#define FDBCLIENT_WRITEMAP_H
|
|
#pragma once
|
|
|
|
#include "fdbclient/FDBTypes.h"
|
|
#include "fdbclient/VersionedMap.h"
|
|
#include "SnapshotCache.h"
|
|
#include "fdbclient/Atomic.h"
|
|
|
|
struct RYWMutation {
|
|
Optional<ValueRef> value;
|
|
enum MutationRef::Type type;
|
|
|
|
RYWMutation(Optional<ValueRef> const& entry, MutationRef::Type type) : value(entry), type(type) {}
|
|
RYWMutation() : value(), type(MutationRef::NoOp) {}
|
|
|
|
bool operator==(const RYWMutation& r) const { return value == r.value && type == r.type; }
|
|
bool operator!=(const RYWMutation& r) const { return !(*this == r); }
|
|
};
|
|
|
|
class OperationStack {
|
|
private:
|
|
RYWMutation singletonOperation;
|
|
Optional<std::vector<RYWMutation>> optionalOperations;
|
|
bool hasVector() const { return optionalOperations.present(); }
|
|
bool defaultConstructed;
|
|
|
|
public:
|
|
OperationStack() { defaultConstructed = true; } // Don't use this!
|
|
explicit OperationStack(RYWMutation initialEntry) {
|
|
defaultConstructed = false;
|
|
singletonOperation = initialEntry;
|
|
}
|
|
void reset(RYWMutation initialEntry);
|
|
void poppush(RYWMutation entry);
|
|
void push(RYWMutation entry);
|
|
bool isDependent() const;
|
|
|
|
const RYWMutation& top() const { return hasVector() ? optionalOperations.get().back() : singletonOperation; }
|
|
RYWMutation& operator[](int n) { return (n == 0) ? singletonOperation : optionalOperations.get()[n - 1]; }
|
|
|
|
const RYWMutation& at(int n) const { return (n == 0) ? singletonOperation : optionalOperations.get()[n - 1]; }
|
|
|
|
int size() const { return defaultConstructed ? 0 : hasVector() ? optionalOperations.get().size() + 1 : 1; }
|
|
|
|
bool operator==(const OperationStack& r) const;
|
|
};
|
|
|
|
FDB_BOOLEAN_PARAM(FollowingKeysCleared);
|
|
FDB_BOOLEAN_PARAM(FollowingKeysConflict);
|
|
FDB_BOOLEAN_PARAM(IsConflict);
|
|
FDB_BOOLEAN_PARAM(FollowingKeysUnreadable);
|
|
FDB_BOOLEAN_PARAM(IsUnreadable);
|
|
|
|
struct WriteMapEntry {
|
|
KeyRef key;
|
|
OperationStack stack;
|
|
bool following_keys_cleared;
|
|
bool following_keys_conflict;
|
|
bool is_conflict;
|
|
bool following_keys_unreadable;
|
|
bool is_unreadable;
|
|
|
|
WriteMapEntry(KeyRef const& key,
|
|
OperationStack&& stack,
|
|
FollowingKeysCleared following_keys_cleared,
|
|
FollowingKeysConflict following_keys_conflict,
|
|
IsConflict is_conflict,
|
|
FollowingKeysUnreadable following_keys_unreadable,
|
|
IsUnreadable is_unreadable)
|
|
: key(key), stack(std::move(stack)), following_keys_cleared(following_keys_cleared),
|
|
following_keys_conflict(following_keys_conflict), is_conflict(is_conflict),
|
|
following_keys_unreadable(following_keys_unreadable), is_unreadable(is_unreadable) {}
|
|
|
|
int compare(StringRef const& r) const { return key.compare(r); }
|
|
|
|
int compare(ExtStringRef const& r) const { return -r.compare(key); }
|
|
|
|
int compare(WriteMapEntry const& r) const { return key.compare(r.key); }
|
|
|
|
std::string toString() const { return printable(key); }
|
|
};
|
|
|
|
inline int compare(StringRef const& l, WriteMapEntry const& r) {
|
|
return l.compare(r.key);
|
|
}
|
|
|
|
inline int compare(ExtStringRef const& l, WriteMapEntry const& r) {
|
|
return l.compare(r.key);
|
|
}
|
|
|
|
inline bool operator<(const WriteMapEntry& lhs, const WriteMapEntry& rhs) {
|
|
return lhs.key < rhs.key;
|
|
}
|
|
inline bool operator<(const WriteMapEntry& lhs, const StringRef& rhs) {
|
|
return lhs.key < rhs;
|
|
}
|
|
inline bool operator<(const StringRef& lhs, const WriteMapEntry& rhs) {
|
|
return lhs < rhs.key;
|
|
}
|
|
inline bool operator<(const WriteMapEntry& lhs, const ExtStringRef& rhs) {
|
|
return rhs.compare(lhs.key) > 0;
|
|
}
|
|
inline bool operator<(const ExtStringRef& lhs, const WriteMapEntry& rhs) {
|
|
return lhs.compare(rhs.key) < 0;
|
|
}
|
|
|
|
class WriteMap {
|
|
private:
|
|
using PTreeT = PTreeImpl::PTree<WriteMapEntry>;
|
|
using PTreeFingerT = PTreeImpl::PTreeFinger<WriteMapEntry>;
|
|
using Tree = Reference<PTreeT>;
|
|
|
|
public:
|
|
explicit WriteMap(Arena* arena) : arena(arena), writeMapEmpty(true), ver(-1), scratch_iterator(this) {
|
|
PTreeImpl::insert(writes,
|
|
ver,
|
|
WriteMapEntry(allKeys.begin,
|
|
OperationStack(),
|
|
FollowingKeysCleared::False,
|
|
FollowingKeysConflict::False,
|
|
IsConflict::False,
|
|
FollowingKeysUnreadable::False,
|
|
IsUnreadable::False));
|
|
PTreeImpl::insert(writes,
|
|
ver,
|
|
WriteMapEntry(allKeys.end,
|
|
OperationStack(),
|
|
FollowingKeysCleared::False,
|
|
FollowingKeysConflict::False,
|
|
IsConflict::False,
|
|
FollowingKeysUnreadable::False,
|
|
IsUnreadable::False));
|
|
PTreeImpl::insert(writes,
|
|
ver,
|
|
WriteMapEntry(afterAllKeys,
|
|
OperationStack(),
|
|
FollowingKeysCleared::False,
|
|
FollowingKeysConflict::False,
|
|
IsConflict::False,
|
|
FollowingKeysUnreadable::False,
|
|
IsUnreadable::False));
|
|
}
|
|
|
|
explicit(false) WriteMap(WriteMap&& r) noexcept
|
|
: arena(r.arena), writeMapEmpty(r.writeMapEmpty), writes(std::move(r.writes)), ver(r.ver),
|
|
scratch_iterator(std::move(r.scratch_iterator)) {}
|
|
|
|
WriteMap& operator=(WriteMap&& r) noexcept;
|
|
|
|
// a write with addConflict false on top of an existing write with a conflict range will not remove the conflict
|
|
void mutate(KeyRef key, MutationRef::Type operation, ValueRef param, bool addConflict);
|
|
|
|
void clear(KeyRangeRef keys, bool addConflict);
|
|
|
|
void addUnmodifiedAndUnreadableRange(KeyRangeRef keys);
|
|
|
|
void addConflictRange(KeyRangeRef keys);
|
|
|
|
struct iterator {
|
|
// Iterates over three types of segments: unmodified ranges, cleared ranges, and modified keys.
|
|
// Modified keys may be dependent (need to be collapsed with a snapshot value) or independent (value is known
|
|
// regardless of the snapshot value) Every key will belong to exactly one segment. The first segment begins at
|
|
// "" and the last segment ends at \xff\xff.
|
|
|
|
explicit iterator(WriteMap* map) : tree(map->writes), at(map->ver), offset(false) { ++map->ver; }
|
|
// Creates an iterator which is conceptually before the beginning of map (you may essentially only call skip()
|
|
// or ++ on it) This iterator also represents a snapshot (will be unaffected by future writes)
|
|
|
|
enum SEGMENT_TYPE { UNMODIFIED_RANGE, CLEARED_RANGE, INDEPENDENT_WRITE, DEPENDENT_WRITE };
|
|
|
|
SEGMENT_TYPE type() const;
|
|
bool is_cleared_range() const { return offset && entry().following_keys_cleared; }
|
|
bool is_unmodified_range() const { return offset && !entry().following_keys_cleared; }
|
|
bool is_operation() const { return !offset; }
|
|
bool is_conflict_range() const { return offset ? entry().following_keys_conflict : entry().is_conflict; }
|
|
bool is_unreadable() const { return offset ? entry().following_keys_unreadable : entry().is_unreadable; }
|
|
|
|
bool is_independent() const {
|
|
ASSERT(is_operation());
|
|
return entry().following_keys_cleared || !entry().stack.isDependent();
|
|
}
|
|
|
|
ExtStringRef beginKey() const { return ExtStringRef(entry().key, offset && entry().stack.size()); }
|
|
ExtStringRef endKey() const { return offset ? nextEntry().key : ExtStringRef(entry().key, 1); }
|
|
|
|
OperationStack const& op() const {
|
|
ASSERT(is_operation());
|
|
return entry().stack;
|
|
}
|
|
|
|
iterator& operator++();
|
|
iterator& operator--();
|
|
bool operator==(const iterator& r) const {
|
|
return offset == r.offset && beginLen == r.beginLen && finger[beginLen - 1] == r.finger[beginLen - 1];
|
|
}
|
|
void skip(KeyRef key);
|
|
|
|
private:
|
|
friend class WriteMap;
|
|
void reset(Tree const& tree, Version ver);
|
|
|
|
WriteMapEntry const& entry() const { return finger[beginLen - 1]->data; }
|
|
WriteMapEntry const& nextEntry() const { return finger[endLen - 1]->data; }
|
|
|
|
bool keyAtBegin() { return !offset || !entry().stack.size(); }
|
|
|
|
Tree tree;
|
|
Version at;
|
|
int beginLen, endLen;
|
|
PTreeFingerT finger;
|
|
bool offset; // false-> the operation stack at entry(); true-> the following cleared or unmodified range
|
|
};
|
|
|
|
bool empty() const { return writeMapEmpty; }
|
|
|
|
static RYWMutation coalesce(RYWMutation existingEntry, RYWMutation newEntry, Arena& arena);
|
|
static void coalesceOver(OperationStack& stack, RYWMutation newEntry, Arena& arena);
|
|
static RYWMutation coalesceUnder(OperationStack const& stack, Optional<ValueRef> const& value, Arena& arena);
|
|
|
|
private:
|
|
friend class ReadYourWritesTransaction;
|
|
Arena* arena;
|
|
bool writeMapEmpty;
|
|
Tree writes;
|
|
// an internal version number for the tree - no connection to database versions! Currently this is
|
|
// incremented after reads, so that consecutive writes have the same version and those separated by
|
|
// reads have different versions.
|
|
Version ver;
|
|
iterator scratch_iterator; // Avoid unnecessary memory allocation in write operations
|
|
|
|
void dump();
|
|
|
|
// SOMEDAY: clearNoConflict replaces cleared sets with two map entries for everyone one item cleared
|
|
void clearNoConflict(KeyRangeRef keys);
|
|
};
|
|
|
|
/*
|
|
|
|
for write in writes: # write.type in [ 'none', 'clear', 'independent', 'dependent' ]
|
|
for read in reads[ write.begin : write.end ]: # read.type in [ 'unknown', 'empty', 'value' ]
|
|
if write.type == "none":
|
|
yield read
|
|
elif write.type == "clear":
|
|
yield empty()
|
|
elif write.type == "independent":
|
|
yield value( write )
|
|
else: # Dependent write
|
|
if read.type == "unknown":
|
|
yield read
|
|
else:
|
|
yield value( collapse( read, write ) )
|
|
|
|
*/
|
|
|
|
#endif
|