Add wal for ExistenceConstraints
Summary: Depends on D1797 Reviewers: msantl, ipaljak, mferencevic Reviewed By: msantl, mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1862
This commit is contained in:
parent
e07aad44a0
commit
6cc8d8f5b1
@ -41,6 +41,8 @@ GraphDb::GraphDb(Config config) : config_(config) {
|
|||||||
durability::RecoverWal(config_.durability_directory, this, &recovery_data,
|
durability::RecoverWal(config_.durability_directory, this, &recovery_data,
|
||||||
&recovery_transactions);
|
&recovery_transactions);
|
||||||
durability::RecoverIndexes(this, recovery_data.indexes);
|
durability::RecoverIndexes(this, recovery_data.indexes);
|
||||||
|
durability::RecoverExistenceConstraints(
|
||||||
|
this, recovery_data.existence_constraints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,12 +193,13 @@ void GraphDbAccessor::UpdateLabelIndices(storage::Label label,
|
|||||||
throw IndexConstraintViolationException(
|
throw IndexConstraintViolationException(
|
||||||
"Node couldn't be updated due to index constraint violation!");
|
"Node couldn't be updated due to index constraint violation!");
|
||||||
}
|
}
|
||||||
db_.storage().labels_index_.Update(label, vlist_ptr, vertex);
|
|
||||||
|
|
||||||
if (!db_.storage().existence_constraints_.CheckIfSatisfies(vertex)) {
|
if (!db_.storage().existence_constraints_.CheckIfSatisfies(vertex)) {
|
||||||
throw IndexConstraintViolationException(
|
throw IndexConstraintViolationException(
|
||||||
"Node couldn't be updated due to existence constraint violation!");
|
"Node couldn't be updated due to existence constraint violation!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db_.storage().labels_index_.Update(label, vlist_ptr, vertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphDbAccessor::UpdatePropertyIndex(
|
void GraphDbAccessor::UpdatePropertyIndex(
|
||||||
@ -208,7 +209,7 @@ void GraphDbAccessor::UpdatePropertyIndex(
|
|||||||
if (!db_.storage().label_property_index_.UpdateOnProperty(
|
if (!db_.storage().label_property_index_.UpdateOnProperty(
|
||||||
property, vertex_accessor.address(), vertex)) {
|
property, vertex_accessor.address(), vertex)) {
|
||||||
throw IndexConstraintViolationException(
|
throw IndexConstraintViolationException(
|
||||||
"Node couldn't be updated due to existence constraint violation!");
|
"Node couldn't be updated due to unique index violation!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,13 +231,19 @@ void GraphDbAccessor::BuildExistenceConstraint(
|
|||||||
for (auto v : dba->Vertices(false)) {
|
for (auto v : dba->Vertices(false)) {
|
||||||
if (!CheckIfSatisfiesExistenceRule(v.GetOld(), rule)) {
|
if (!CheckIfSatisfiesExistenceRule(v.GetOld(), rule)) {
|
||||||
throw IndexConstraintViolationException(
|
throw IndexConstraintViolationException(
|
||||||
"Node couldn't be updated due to existence constraint violation!");
|
"Existence constraint couldn't be built because existing data is "
|
||||||
|
"violating it!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db_.storage().existence_constraints_.AddConstraint(rule);
|
db_.storage().existence_constraints_.AddConstraint(rule);
|
||||||
// TODO
|
std::vector<std::string> property_names(properties.size());
|
||||||
//wal().Emplace(database::StateDelta::BuildExistenceConstraint();
|
std::transform(properties.begin(), properties.end(), property_names.begin(),
|
||||||
|
[&dba](auto p) { return dba->PropertyName(p); });
|
||||||
|
|
||||||
|
dba->wal().Emplace(database::StateDelta::BuildExistenceConstraint(
|
||||||
|
dba->transaction().id_, label, dba->LabelName(label), properties,
|
||||||
|
property_names));
|
||||||
dba->Commit();
|
dba->Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,8 +253,13 @@ void GraphDbAccessor::DeleteExistenceConstraint(
|
|||||||
db_.AccessBlocking(std::experimental::make_optional(transaction().id_));
|
db_.AccessBlocking(std::experimental::make_optional(transaction().id_));
|
||||||
ExistenceRule rule{label, properties};
|
ExistenceRule rule{label, properties};
|
||||||
db_.storage().existence_constraints_.RemoveConstraint(rule);
|
db_.storage().existence_constraints_.RemoveConstraint(rule);
|
||||||
// TODO
|
std::vector<std::string> property_names(properties.size());
|
||||||
//wal().Emplace(database::StateDelta::DeleteExistenceConstraint();
|
std::transform(properties.begin(), properties.end(), property_names.begin(),
|
||||||
|
[&dba](auto p) { return dba->PropertyName(p); });
|
||||||
|
|
||||||
|
dba->wal().Emplace(database::StateDelta::DropExistenceConstraint(
|
||||||
|
dba->transaction().id_, label, dba->LabelName(label), properties,
|
||||||
|
property_names));
|
||||||
dba->Commit();
|
dba->Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +270,11 @@ bool GraphDbAccessor::ExistenceConstraintExists(
|
|||||||
return db_.storage().existence_constraints_.Exists(rule);
|
return db_.storage().existence_constraints_.Exists(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::list<ExistenceRule> GraphDbAccessor::ExistenceConstraintsList()
|
||||||
|
const {
|
||||||
|
return db_.storage().existence_constraints_.ListConstraints();
|
||||||
|
}
|
||||||
|
|
||||||
int64_t GraphDbAccessor::VerticesCount() const {
|
int64_t GraphDbAccessor::VerticesCount() const {
|
||||||
DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted";
|
DCHECK(!commited_ && !aborted_) << "Accessor committed or aborted";
|
||||||
return db_.storage().vertices_.access().size();
|
return db_.storage().vertices_.access().size();
|
||||||
|
@ -488,6 +488,10 @@ class GraphDbAccessor {
|
|||||||
storage::Label label,
|
storage::Label label,
|
||||||
const std::vector<storage::Property> &properties) const;
|
const std::vector<storage::Property> &properties) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of existence constraints currently active.
|
||||||
|
*/
|
||||||
|
std::list<ExistenceRule> ExistenceConstraintsList() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return approximate number of all vertices in the database.
|
* Return approximate number of all vertices in the database.
|
||||||
|
@ -150,6 +150,31 @@ bool RecoverSnapshot(const fs::path &snapshot_file, database::GraphDb *db,
|
|||||||
/*create = */ true, unique.ValueBool()});
|
/*create = */ true, unique.ValueBool()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read a list of existence constraints
|
||||||
|
RETURN_IF_NOT(decoder.ReadValue(&dv, Value::Type::List));
|
||||||
|
auto existence_constraints = dv.ValueList();
|
||||||
|
for (auto it = existence_constraints.begin();
|
||||||
|
it != existence_constraints.end();) {
|
||||||
|
RETURN_IF_NOT(it->IsString());
|
||||||
|
auto label = it->ValueString();
|
||||||
|
++it;
|
||||||
|
RETURN_IF_NOT(it != existence_constraints.end());
|
||||||
|
RETURN_IF_NOT(it->IsInt());
|
||||||
|
auto prop_size = it->ValueInt();
|
||||||
|
++it;
|
||||||
|
std::vector<std::string> properties;
|
||||||
|
properties.reserve(prop_size);
|
||||||
|
for (size_t i = 0; i < prop_size; ++i) {
|
||||||
|
RETURN_IF_NOT(it != existence_constraints.end());
|
||||||
|
RETURN_IF_NOT(it->IsString());
|
||||||
|
properties.emplace_back(it->ValueString());
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
recovery_data->existence_constraints.emplace_back(
|
||||||
|
ExistenceConstraintRecoveryData{label, std::move(properties), true});
|
||||||
|
}
|
||||||
|
|
||||||
auto dba = db->Access();
|
auto dba = db->Access();
|
||||||
std::unordered_map<uint64_t, VertexAccessor> vertices;
|
std::unordered_map<uint64_t, VertexAccessor> vertices;
|
||||||
for (int64_t i = 0; i < vertex_count; ++i) {
|
for (int64_t i = 0; i < vertex_count; ++i) {
|
||||||
@ -408,7 +433,6 @@ void RecoverWal(const fs::path &durability_dir, database::GraphDb *db,
|
|||||||
transactions->Commit(delta.transaction_id);
|
transactions->Commit(delta.transaction_id);
|
||||||
break;
|
break;
|
||||||
case database::StateDelta::Type::BUILD_INDEX: {
|
case database::StateDelta::Type::BUILD_INDEX: {
|
||||||
// TODO index building might still be problematic in HA
|
|
||||||
auto drop_it = std::find_if(
|
auto drop_it = std::find_if(
|
||||||
recovery_data->indexes.begin(), recovery_data->indexes.end(),
|
recovery_data->indexes.begin(), recovery_data->indexes.end(),
|
||||||
[label = delta.label_name, property = delta.property_name](
|
[label = delta.label_name, property = delta.property_name](
|
||||||
@ -448,6 +472,48 @@ void RecoverWal(const fs::path &durability_dir, database::GraphDb *db,
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case database::StateDelta::Type::BUILD_EXISTENCE_CONSTRAINT: {
|
||||||
|
auto drop_it = std::find_if(
|
||||||
|
recovery_data->existence_constraints.begin(),
|
||||||
|
recovery_data->existence_constraints.end(),
|
||||||
|
[&delta](const ExistenceConstraintRecoveryData &data) {
|
||||||
|
return data.label == delta.label_name &&
|
||||||
|
std::is_permutation(data.properties.begin(),
|
||||||
|
data.properties.end(),
|
||||||
|
delta.property_names.begin()) &&
|
||||||
|
data.create == false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (drop_it != recovery_data->existence_constraints.end()) {
|
||||||
|
recovery_data->existence_constraints.erase(drop_it);
|
||||||
|
} else {
|
||||||
|
recovery_data->existence_constraints.emplace_back(
|
||||||
|
ExistenceConstraintRecoveryData{delta.label_name,
|
||||||
|
delta.property_names, true});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case database::StateDelta::Type::DROP_EXISTENCE_CONSTRAINT: {
|
||||||
|
auto build_it = std::find_if(
|
||||||
|
recovery_data->existence_constraints.begin(),
|
||||||
|
recovery_data->existence_constraints.end(),
|
||||||
|
[&delta](const ExistenceConstraintRecoveryData &data) {
|
||||||
|
return data.label == delta.label_name &&
|
||||||
|
std::is_permutation(data.properties.begin(),
|
||||||
|
data.properties.end(),
|
||||||
|
delta.property_names.begin()) &&
|
||||||
|
data.create == true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (build_it != recovery_data->existence_constraints.end()) {
|
||||||
|
recovery_data->existence_constraints.erase(build_it);
|
||||||
|
} else {
|
||||||
|
recovery_data->existence_constraints.emplace_back(
|
||||||
|
ExistenceConstraintRecoveryData{
|
||||||
|
delta.label_name, delta.property_names, false});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
transactions->Apply(delta);
|
transactions->Apply(delta);
|
||||||
}
|
}
|
||||||
@ -477,4 +543,23 @@ void RecoverIndexes(database::GraphDb *db,
|
|||||||
dba->Commit();
|
dba->Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RecoverExistenceConstraints(
|
||||||
|
database::GraphDb *db,
|
||||||
|
const std::vector<ExistenceConstraintRecoveryData> &constraints) {
|
||||||
|
auto dba = db->Access();
|
||||||
|
for (auto &constraint : constraints) {
|
||||||
|
auto label = dba->Label(constraint.label);
|
||||||
|
std::vector<storage::Property> properties;
|
||||||
|
properties.reserve(constraint.properties.size());
|
||||||
|
for (auto &prop : constraint.properties) {
|
||||||
|
properties.push_back(dba->Property(prop));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraint.create) {
|
||||||
|
dba->BuildExistenceConstraint(label, properties);
|
||||||
|
} else {
|
||||||
|
dba->DeleteExistenceConstraint(label, properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace durability
|
} // namespace durability
|
||||||
|
@ -43,6 +43,13 @@ struct IndexRecoveryData {
|
|||||||
bool unique; // used only when creating an index
|
bool unique; // used only when creating an index
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ExistenceConstraintRecoveryData {
|
||||||
|
std::string label;
|
||||||
|
std::vector<std::string> properties;
|
||||||
|
// distinguish between creating and dropping existence constraint
|
||||||
|
bool create;
|
||||||
|
};
|
||||||
|
|
||||||
// A data structure for exchanging info between main recovery function and
|
// A data structure for exchanging info between main recovery function and
|
||||||
// snapshot and WAL recovery functions.
|
// snapshot and WAL recovery functions.
|
||||||
struct RecoveryData {
|
struct RecoveryData {
|
||||||
@ -53,6 +60,8 @@ struct RecoveryData {
|
|||||||
// can be rebuilt at the end of the recovery transaction.
|
// can be rebuilt at the end of the recovery transaction.
|
||||||
std::vector<IndexRecoveryData> indexes;
|
std::vector<IndexRecoveryData> indexes;
|
||||||
|
|
||||||
|
std::vector<ExistenceConstraintRecoveryData> existence_constraints;
|
||||||
|
|
||||||
void Clear() {
|
void Clear() {
|
||||||
snapshooter_tx_id = 0;
|
snapshooter_tx_id = 0;
|
||||||
snapshooter_tx_snapshot.clear();
|
snapshooter_tx_snapshot.clear();
|
||||||
@ -148,4 +157,8 @@ void RecoverWal(const std::experimental::filesystem::path &durability_dir,
|
|||||||
void RecoverIndexes(database::GraphDb *db,
|
void RecoverIndexes(database::GraphDb *db,
|
||||||
const std::vector<IndexRecoveryData> &indexes);
|
const std::vector<IndexRecoveryData> &indexes);
|
||||||
|
|
||||||
|
void RecoverExistenceConstraints(
|
||||||
|
database::GraphDb *db,
|
||||||
|
const std::vector<ExistenceConstraintRecoveryData> &constraints);
|
||||||
|
|
||||||
} // namespace durability
|
} // namespace durability
|
||||||
|
@ -17,7 +17,7 @@ namespace fs = std::experimental::filesystem;
|
|||||||
namespace durability {
|
namespace durability {
|
||||||
|
|
||||||
// Snapshot layout is described in durability/version.hpp
|
// Snapshot layout is described in durability/version.hpp
|
||||||
static_assert(durability::kVersion == 7,
|
static_assert(durability::kVersion == 8,
|
||||||
"Wrong snapshot version, please update!");
|
"Wrong snapshot version, please update!");
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -55,6 +55,20 @@ bool Encode(const fs::path &snapshot_file, database::GraphDb &db,
|
|||||||
encoder.WriteList(index_vec);
|
encoder.WriteList(index_vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write existence constraints to snapshoot
|
||||||
|
{
|
||||||
|
std::vector<communication::bolt::Value> existence_constraints;
|
||||||
|
for (const auto &rule : dba.ExistenceConstraintsList()) {
|
||||||
|
existence_constraints.emplace_back(dba.LabelName(rule.label));
|
||||||
|
existence_constraints.emplace_back(
|
||||||
|
static_cast<int64_t>(rule.properties.size()));
|
||||||
|
for (auto &prop : rule.properties) {
|
||||||
|
existence_constraints.emplace_back(dba.PropertyName(prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder.WriteList(existence_constraints);
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &vertex : dba.Vertices(false)) {
|
for (const auto &vertex : dba.Vertices(false)) {
|
||||||
encoder.WriteVertex(glue::ToBoltVertex(vertex));
|
encoder.WriteVertex(glue::ToBoltVertex(vertex));
|
||||||
vertex_num++;
|
vertex_num++;
|
||||||
|
@ -126,6 +126,32 @@ StateDelta StateDelta::DropIndex(tx::TransactionId tx_id, storage::Label label,
|
|||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StateDelta StateDelta::BuildExistenceConstraint(
|
||||||
|
tx::TransactionId tx_id, storage::Label label,
|
||||||
|
const std::string &label_name,
|
||||||
|
const std::vector<storage::Property> &properties,
|
||||||
|
const std::vector<std::string> &property_names) {
|
||||||
|
StateDelta op(StateDelta::Type::BUILD_EXISTENCE_CONSTRAINT, tx_id);
|
||||||
|
op.label = label;
|
||||||
|
op.label_name = label_name;
|
||||||
|
op.properties = properties;
|
||||||
|
op.property_names = property_names;
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
StateDelta StateDelta::DropExistenceConstraint(
|
||||||
|
tx::TransactionId tx_id, storage::Label label,
|
||||||
|
const std::string &label_name,
|
||||||
|
const std::vector<storage::Property> &properties,
|
||||||
|
const std::vector<std::string> &property_names) {
|
||||||
|
StateDelta op(StateDelta::Type::DROP_EXISTENCE_CONSTRAINT, tx_id);
|
||||||
|
op.label = label;
|
||||||
|
op.label_name = label_name;
|
||||||
|
op.properties = properties;
|
||||||
|
op.property_names = property_names;
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
void StateDelta::Encode(
|
void StateDelta::Encode(
|
||||||
HashedFileWriter &writer,
|
HashedFileWriter &writer,
|
||||||
communication::bolt::BaseEncoder<HashedFileWriter> &encoder) const {
|
communication::bolt::BaseEncoder<HashedFileWriter> &encoder) const {
|
||||||
@ -184,6 +210,28 @@ void StateDelta::Encode(
|
|||||||
encoder.WriteInt(property.Id());
|
encoder.WriteInt(property.Id());
|
||||||
encoder.WriteString(property_name);
|
encoder.WriteString(property_name);
|
||||||
break;
|
break;
|
||||||
|
case Type::BUILD_EXISTENCE_CONSTRAINT:
|
||||||
|
encoder.WriteInt(label.Id());
|
||||||
|
encoder.WriteString(label_name);
|
||||||
|
encoder.WriteInt(properties.size());
|
||||||
|
for (auto prop : properties) {
|
||||||
|
encoder.WriteInt(prop.Id());
|
||||||
|
}
|
||||||
|
for (auto &name : property_names) {
|
||||||
|
encoder.WriteString(name);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Type::DROP_EXISTENCE_CONSTRAINT:
|
||||||
|
encoder.WriteInt(label.Id());
|
||||||
|
encoder.WriteString(label_name);
|
||||||
|
encoder.WriteInt(properties.size());
|
||||||
|
for (auto prop : properties) {
|
||||||
|
encoder.WriteInt(prop.Id());
|
||||||
|
}
|
||||||
|
for (auto &name : property_names) {
|
||||||
|
encoder.WriteString(name);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteValue(writer.hash());
|
writer.WriteValue(writer.hash());
|
||||||
@ -265,6 +313,38 @@ std::experimental::optional<StateDelta> StateDelta::Decode(
|
|||||||
DECODE_MEMBER_CAST(property, ValueInt, storage::Property)
|
DECODE_MEMBER_CAST(property, ValueInt, storage::Property)
|
||||||
DECODE_MEMBER(property_name, ValueString)
|
DECODE_MEMBER(property_name, ValueString)
|
||||||
break;
|
break;
|
||||||
|
case Type::BUILD_EXISTENCE_CONSTRAINT: {
|
||||||
|
DECODE_MEMBER_CAST(label, ValueInt, storage::Label)
|
||||||
|
DECODE_MEMBER(label_name, ValueString)
|
||||||
|
if (!decoder.ReadValue(&dv)) return nullopt;
|
||||||
|
int size = dv.ValueInt();
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
if (!decoder.ReadValue(&dv)) return nullopt;
|
||||||
|
r_val.properties.push_back(
|
||||||
|
static_cast<storage::Property>(dv.ValueInt()));
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
if (!decoder.ReadValue(&dv)) return nullopt;
|
||||||
|
r_val.property_names.push_back(dv.ValueString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Type::DROP_EXISTENCE_CONSTRAINT: {
|
||||||
|
DECODE_MEMBER_CAST(label, ValueInt, storage::Label)
|
||||||
|
DECODE_MEMBER(label_name, ValueString)
|
||||||
|
if (!decoder.ReadValue(&dv)) return nullopt;
|
||||||
|
int size = dv.ValueInt();
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
if (!decoder.ReadValue(&dv)) return nullopt;
|
||||||
|
r_val.properties.push_back(
|
||||||
|
static_cast<storage::Property>(dv.ValueInt()));
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
if (!decoder.ReadValue(&dv)) return nullopt;
|
||||||
|
r_val.property_names.push_back(dv.ValueString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto decoder_hash = reader.hash();
|
auto decoder_hash = reader.hash();
|
||||||
@ -334,6 +414,24 @@ void StateDelta::Apply(GraphDbAccessor &dba) const {
|
|||||||
LOG(FATAL) << "Index handling not handled in Apply";
|
LOG(FATAL) << "Index handling not handled in Apply";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Type::BUILD_EXISTENCE_CONSTRAINT: {
|
||||||
|
std::vector<storage::Property> properties;
|
||||||
|
properties.reserve(property_names.size());
|
||||||
|
for (auto &p : property_names) {
|
||||||
|
properties.push_back(dba.Property(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
dba.BuildExistenceConstraint(dba.Label(label_name), properties);
|
||||||
|
} break;
|
||||||
|
case Type::DROP_EXISTENCE_CONSTRAINT: {
|
||||||
|
std::vector<storage::Property> properties;
|
||||||
|
properties.reserve(property_names.size());
|
||||||
|
for (auto &p : property_names) {
|
||||||
|
properties.push_back(dba.Property(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
dba.DeleteExistenceConstraint(dba.Label(label_name), properties);
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ cpp<#
|
|||||||
(edge-type-name "std::string")
|
(edge-type-name "std::string")
|
||||||
(property "storage::Property")
|
(property "storage::Property")
|
||||||
(property-name "std::string")
|
(property-name "std::string")
|
||||||
|
(properties "std::vector<storage::Property>")
|
||||||
|
(property-names "std::vector<std::string>")
|
||||||
(value "PropertyValue" :initval "PropertyValue::Null")
|
(value "PropertyValue" :initval "PropertyValue::Null")
|
||||||
(label "storage::Label")
|
(label "storage::Label")
|
||||||
(label-name "std::string")
|
(label-name "std::string")
|
||||||
@ -71,6 +73,8 @@ in StateDeltas.")
|
|||||||
remove-edge ;; edge_id
|
remove-edge ;; edge_id
|
||||||
build-index ;; label, label_name, property, property_name, unique
|
build-index ;; label, label_name, property, property_name, unique
|
||||||
drop-index ;; label, label_name, property, property_name
|
drop-index ;; label, label_name, property, property_name
|
||||||
|
build-existence-constraint ;; label, label_name, properties, property_names
|
||||||
|
drop-existence-constraint ;; label, label_name, properties, property_names
|
||||||
)
|
)
|
||||||
(:documentation
|
(:documentation
|
||||||
"Defines StateDelta type. For each type the comment indicates which values
|
"Defines StateDelta type. For each type the comment indicates which values
|
||||||
@ -129,6 +133,16 @@ omitted in the comment."))
|
|||||||
const std::string &label_name,
|
const std::string &label_name,
|
||||||
storage::Property property,
|
storage::Property property,
|
||||||
const std::string &property_name);
|
const std::string &property_name);
|
||||||
|
static StateDelta BuildExistenceConstraint(
|
||||||
|
tx::TransactionId tx_id, storage::Label label,
|
||||||
|
const std::string &label_name,
|
||||||
|
const std::vector<storage::Property> &properties,
|
||||||
|
const std::vector<std::string> &property_names);
|
||||||
|
static StateDelta DropExistenceConstraint(
|
||||||
|
tx::TransactionId tx_id, storage::Label label,
|
||||||
|
const std::string &label_name,
|
||||||
|
const std::vector<storage::Property> &property,
|
||||||
|
const std::vector<std::string> &property_names);
|
||||||
|
|
||||||
/// Applies CRUD delta to database accessor. Fails on other types of deltas
|
/// Applies CRUD delta to database accessor. Fails on other types of deltas
|
||||||
void Apply(GraphDbAccessor &dba) const;
|
void Apply(GraphDbAccessor &dba) const;
|
||||||
|
@ -15,9 +15,9 @@ constexpr std::array<uint8_t, 4> kSnapshotMagic{{'M', 'G', 's', 'n'}};
|
|||||||
constexpr std::array<uint8_t, 4> kWalMagic{{'M', 'G', 'w', 'l'}};
|
constexpr std::array<uint8_t, 4> kWalMagic{{'M', 'G', 'w', 'l'}};
|
||||||
|
|
||||||
// The current default version of snapshot and WAL encoding / decoding.
|
// The current default version of snapshot and WAL encoding / decoding.
|
||||||
constexpr int64_t kVersion{7};
|
constexpr int64_t kVersion{8};
|
||||||
|
|
||||||
// Snapshot format (version 7):
|
// Snapshot format (version 8):
|
||||||
// 1) Magic number + snapshot version
|
// 1) Magic number + snapshot version
|
||||||
//
|
//
|
||||||
// The following two entries are required when recovering from snapshot combined
|
// The following two entries are required when recovering from snapshot combined
|
||||||
@ -27,14 +27,15 @@ constexpr int64_t kVersion{7};
|
|||||||
//
|
//
|
||||||
// 4) A list of label+property indices.
|
// 4) A list of label+property indices.
|
||||||
//
|
//
|
||||||
// 5) Bolt encoded nodes. Each node is written in the following format:
|
// 5) A list of existence constraints
|
||||||
|
//
|
||||||
|
// 6) Bolt encoded nodes. Each node is written in the following format:
|
||||||
// * gid, labels, properties
|
// * gid, labels, properties
|
||||||
// 6) Bolt encoded edges. Each edge is written in the following format:
|
// 7) Bolt encoded edges. Each edge is written in the following format:
|
||||||
// * gid
|
// * gid
|
||||||
// * from, to
|
// * from, to
|
||||||
// * edge_type
|
// * edge_type
|
||||||
// * properties
|
// * properties
|
||||||
//
|
//
|
||||||
// 7) Snapshot summary (number of nodes, number of edges, hash)
|
// 8) Snapshot summary (number of nodes, number of edges, hash)
|
||||||
|
|
||||||
} // namespace durability
|
} // namespace durability
|
||||||
|
@ -147,6 +147,8 @@ bool WriteAheadLog::IsStateDeltaTransactionEnd(
|
|||||||
case database::StateDelta::Type::REMOVE_EDGE:
|
case database::StateDelta::Type::REMOVE_EDGE:
|
||||||
case database::StateDelta::Type::BUILD_INDEX:
|
case database::StateDelta::Type::BUILD_INDEX:
|
||||||
case database::StateDelta::Type::DROP_INDEX:
|
case database::StateDelta::Type::DROP_INDEX:
|
||||||
|
case database::StateDelta::Type::BUILD_EXISTENCE_CONSTRAINT:
|
||||||
|
case database::StateDelta::Type::DROP_EXISTENCE_CONSTRAINT:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,9 @@ struct ExistenceRule {
|
|||||||
std::vector<storage::Property> properties;
|
std::vector<storage::Property> properties;
|
||||||
|
|
||||||
bool operator==(const ExistenceRule &rule) const {
|
bool operator==(const ExistenceRule &rule) const {
|
||||||
return label == rule.label && properties == rule.properties;
|
return label == rule.label &&
|
||||||
|
std::is_permutation(properties.begin(), properties.end(),
|
||||||
|
rule.properties.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const ExistenceRule &rule) const { return !(*this == rule); }
|
bool operator!=(const ExistenceRule &rule) const { return !(*this == rule); }
|
||||||
|
@ -18,9 +18,9 @@ int main(int argc, char *argv[]) {
|
|||||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
google::InitGoogleLogging(argv[0]);
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
// At the time this was written, the version was 7. This makes sure we update
|
// This makes sure we update the explorer when we bump the snapshot version.
|
||||||
// the explorer when we bump the snapshot version.
|
// Snapshot layout is described in durability/version.hpp
|
||||||
static_assert(durability::kVersion == 7,
|
static_assert(durability::kVersion == 8,
|
||||||
"Wrong snapshot version, please update!");
|
"Wrong snapshot version, please update!");
|
||||||
|
|
||||||
fs::path snapshot_path(FLAGS_snapshot_file);
|
fs::path snapshot_path(FLAGS_snapshot_file);
|
||||||
@ -82,6 +82,32 @@ int main(int argc, char *argv[]) {
|
|||||||
<< property.ValueString();
|
<< property.ValueString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decoder.ReadValue(&dv, Value::Type::List);
|
||||||
|
auto existence_constraint = dv.ValueList();
|
||||||
|
for (auto it = existence_constraint.begin(); it != existence_constraint.end();) {
|
||||||
|
std::string log("Adding existence constraint: ");
|
||||||
|
CHECK(it->IsString()) << "Label is not a string!";
|
||||||
|
log.append(it->ValueString());
|
||||||
|
log.append(" -> [");
|
||||||
|
++it;
|
||||||
|
CHECK(it->IsInt()) << "Number of properties is not an int!";
|
||||||
|
int64_t prop_size = it->ValueInt();
|
||||||
|
++it;
|
||||||
|
for (size_t i = 0; i < prop_size; ++i) {
|
||||||
|
CHECK(it->IsString()) << "Property is not a string!";
|
||||||
|
log.append(it->ValueString());
|
||||||
|
if (i != prop_size -1) {
|
||||||
|
log.append(", ");
|
||||||
|
} else {
|
||||||
|
log.append("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << log;
|
||||||
|
}
|
||||||
|
|
||||||
for (int64_t i = 0; i < vertex_count; ++i) {
|
for (int64_t i = 0; i < vertex_count; ++i) {
|
||||||
auto vertex = decoder.ReadValue(&dv, Value::Type::Vertex);
|
auto vertex = decoder.ReadValue(&dv, Value::Type::Vertex);
|
||||||
CHECK(vertex) << "Failed to read vertex " << i;
|
CHECK(vertex) << "Failed to read vertex " << i;
|
||||||
|
@ -45,6 +45,10 @@ std::string StateDeltaTypeToString(database::StateDelta::Type type) {
|
|||||||
return "BUILD_INDEX";
|
return "BUILD_INDEX";
|
||||||
case database::StateDelta::Type::DROP_INDEX:
|
case database::StateDelta::Type::DROP_INDEX:
|
||||||
return "DROP_INDEX";
|
return "DROP_INDEX";
|
||||||
|
case database::StateDelta::Type::BUILD_EXISTENCE_CONSTRAINT:
|
||||||
|
return "BUILD_EXISTENCE_CONSTRAINT";
|
||||||
|
case database::StateDelta::Type::DROP_EXISTENCE_CONSTRAINT:
|
||||||
|
return "DROP_EXISTENCE_CONSTRAINT";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +166,36 @@ class DbGenerator {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool CompareExistenceConstraints(const database::GraphDbAccessor &dba1,
|
||||||
|
const database::GraphDbAccessor &dba2) {
|
||||||
|
auto c1 = dba1.ExistenceConstraintsList();
|
||||||
|
auto c2 = dba2.ExistenceConstraintsList();
|
||||||
|
|
||||||
|
auto compare_prop = [](auto &dba1, auto &p1, auto &dba2, auto &p2) {
|
||||||
|
std::vector<std::string> p1_names;
|
||||||
|
p1_names.reserve(p1.size());
|
||||||
|
std::vector<std::string> p2_names;
|
||||||
|
p2_names.reserve(p2.size());
|
||||||
|
|
||||||
|
std::transform(p1.begin(), p1.end(), std::back_inserter(p1_names),
|
||||||
|
[&dba1](auto p) { return dba1.PropertyName(p); });
|
||||||
|
std::transform(p2.begin(), p2.end(), std::back_inserter(p2_names),
|
||||||
|
[&dba2](auto p) { return dba2.PropertyName(p); });
|
||||||
|
|
||||||
|
return p1_names.size() == p2_names.size() &&
|
||||||
|
std::is_permutation(p1_names.begin(), p1_names.end(),
|
||||||
|
p2_names.begin());
|
||||||
|
};
|
||||||
|
|
||||||
|
return c1.size() == c2.size() &&
|
||||||
|
std::is_permutation(
|
||||||
|
c1.begin(), c1.end(), c2.begin(),
|
||||||
|
[&dba1, &dba2, &compare_prop](auto &r1, auto &r2) {
|
||||||
|
return dba1.LabelName(r1.label) == dba2.LabelName(r2.label) &&
|
||||||
|
compare_prop(dba1, r1.properties, dba2, r2.properties);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Checks if the given databases have the same contents (indices,
|
/** Checks if the given databases have the same contents (indices,
|
||||||
* vertices and edges). */
|
* vertices and edges). */
|
||||||
void CompareDbs(database::GraphDb &a, database::GraphDb &b) {
|
void CompareDbs(database::GraphDb &a, database::GraphDb &b) {
|
||||||
@ -181,6 +211,7 @@ void CompareDbs(database::GraphDb &a, database::GraphDb &b) {
|
|||||||
<< "Indexes not equal [" << utils::Join(index_a, ", ") << "] != ["
|
<< "Indexes not equal [" << utils::Join(index_a, ", ") << "] != ["
|
||||||
<< utils::Join(index_b, ", ");
|
<< utils::Join(index_b, ", ");
|
||||||
}
|
}
|
||||||
|
EXPECT_TRUE(CompareExistenceConstraints(*dba_a, *dba_b));
|
||||||
|
|
||||||
auto is_permutation_props = [&dba_a, &dba_b](const auto &p1_id,
|
auto is_permutation_props = [&dba_a, &dba_b](const auto &p1_id,
|
||||||
const auto &p2_id) {
|
const auto &p2_id) {
|
||||||
@ -485,6 +516,11 @@ TEST_F(Durability, SnapshotEncoding) {
|
|||||||
EXPECT_EQ(dv.ValueList()[1].ValueString(), "p1");
|
EXPECT_EQ(dv.ValueList()[1].ValueString(), "p1");
|
||||||
EXPECT_EQ(dv.ValueList()[2].ValueBool(), false);
|
EXPECT_EQ(dv.ValueList()[2].ValueBool(), false);
|
||||||
|
|
||||||
|
// Existence constraints
|
||||||
|
decoder.ReadValue(&dv);
|
||||||
|
ASSERT_TRUE(dv.IsList());
|
||||||
|
ASSERT_EQ(dv.ValueList().size(), 0);
|
||||||
|
|
||||||
std::map<gid::Gid, communication::bolt::Vertex> decoded_vertices;
|
std::map<gid::Gid, communication::bolt::Vertex> decoded_vertices;
|
||||||
|
|
||||||
// Decode vertices.
|
// Decode vertices.
|
||||||
@ -984,6 +1020,81 @@ TEST_F(Durability, UniqueConstraintRecoveryWal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(Durability, ExistenceConstraintRecoveryWal) {
|
||||||
|
auto config = DbConfig(true, false);
|
||||||
|
database::GraphDb db{config};
|
||||||
|
{
|
||||||
|
// Fill database with some data
|
||||||
|
auto dba = db.Access();
|
||||||
|
DbGenerator gen(*dba);
|
||||||
|
dba->InsertVertex();
|
||||||
|
gen.InsertVertex();
|
||||||
|
gen.InsertVertex();
|
||||||
|
auto l1 = dba->Label("l1");
|
||||||
|
std::vector<storage::Property> p1{dba->Property("p1"), dba->Property("p2")};
|
||||||
|
dba->BuildExistenceConstraint(l1, p1);
|
||||||
|
gen.InsertEdge();
|
||||||
|
auto l2 = dba->Label("l2");
|
||||||
|
std::vector<storage::Property> p2{dba->Property("p3"), dba->Property("p4")};
|
||||||
|
dba->BuildExistenceConstraint(l2, p2);
|
||||||
|
gen.InsertVertex();
|
||||||
|
gen.InsertEdge();
|
||||||
|
dba->DeleteExistenceConstraint(l1, p1);
|
||||||
|
dba->Commit();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Recover and compare
|
||||||
|
db.wal().Flush();
|
||||||
|
auto recovered_config = DbConfig(false, true);
|
||||||
|
database::GraphDb recovered_db{recovered_config};
|
||||||
|
CompareDbs(db, recovered_db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(Durability, ExistenceConstraintRecoverySnapshotAndWal) {
|
||||||
|
auto config = DbConfig(true, false);
|
||||||
|
database::GraphDb db{config};
|
||||||
|
{
|
||||||
|
// Fill database with some data
|
||||||
|
auto dba = db.Access();
|
||||||
|
DbGenerator gen(*dba);
|
||||||
|
dba->InsertVertex();
|
||||||
|
gen.InsertVertex();
|
||||||
|
gen.InsertVertex();
|
||||||
|
auto l1 = dba->Label("l1");
|
||||||
|
std::vector<storage::Property> p1{dba->Property("p1"), dba->Property("p2")};
|
||||||
|
dba->BuildExistenceConstraint(l1, p1);
|
||||||
|
gen.InsertEdge();
|
||||||
|
auto l2 = dba->Label("l2");
|
||||||
|
std::vector<storage::Property> p2{dba->Property("p3"), dba->Property("p4")};
|
||||||
|
dba->BuildExistenceConstraint(l2, p2);
|
||||||
|
gen.InsertVertex();
|
||||||
|
gen.InsertEdge();
|
||||||
|
dba->DeleteExistenceConstraint(l1, p1);
|
||||||
|
dba->Commit();
|
||||||
|
}
|
||||||
|
// create snapshot with build index and vertex
|
||||||
|
MakeSnapshot(db);
|
||||||
|
{
|
||||||
|
auto dba = db.Access();
|
||||||
|
DbGenerator gen(*dba);
|
||||||
|
gen.InsertVertex();
|
||||||
|
gen.InsertVertex();
|
||||||
|
|
||||||
|
auto l3 = dba->Label("l3");
|
||||||
|
std::vector<storage::Property> p3{dba->Property("p5"), dba->Property("p6")};
|
||||||
|
dba->BuildExistenceConstraint(l3, p3);
|
||||||
|
dba->Commit();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Recover and compare
|
||||||
|
db.wal().Flush();
|
||||||
|
auto recovered_config = DbConfig(false, true);
|
||||||
|
database::GraphDb recovered_db{recovered_config};
|
||||||
|
CompareDbs(db, recovered_db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
google::InitGoogleLogging(argv[0]);
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
#include "utils/string.hpp"
|
#include "utils/string.hpp"
|
||||||
#include "utils/timer.hpp"
|
#include "utils/timer.hpp"
|
||||||
|
|
||||||
|
// Snapshot layout is described in durability/version.hpp
|
||||||
|
static_assert(durability::kVersion == 8,
|
||||||
|
"Wrong snapshot version, please update!");
|
||||||
|
|
||||||
bool ValidateNotEmpty(const char *flagname, const std::string &value) {
|
bool ValidateNotEmpty(const char *flagname, const std::string &value) {
|
||||||
if (utils::Trim(value).empty()) {
|
if (utils::Trim(value).empty()) {
|
||||||
printf("The argument '%s' is required\n", flagname);
|
printf("The argument '%s' is required\n", flagname);
|
||||||
@ -420,6 +424,7 @@ void Convert(const std::vector<std::string> &nodes,
|
|||||||
encoder.WriteInt(0); // Id of transaction that is snapshooting.
|
encoder.WriteInt(0); // Id of transaction that is snapshooting.
|
||||||
encoder.WriteList({}); // Transactional snapshot.
|
encoder.WriteList({}); // Transactional snapshot.
|
||||||
encoder.WriteList({}); // Label + property indexes.
|
encoder.WriteList({}); // Label + property indexes.
|
||||||
|
encoder.WriteList({}); // Existence constraints
|
||||||
// PassNodes streams vertices to the encoder.
|
// PassNodes streams vertices to the encoder.
|
||||||
for (const auto &nodes_file : nodes) {
|
for (const auto &nodes_file : nodes) {
|
||||||
node_count +=
|
node_count +=
|
||||||
|
Loading…
Reference in New Issue
Block a user