From 9ec72bd96985bc3ecea5031f18dd90292daa5bc3 Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Wed, 16 Nov 2022 18:41:22 +0200 Subject: [PATCH 01/93] Add GetProperties shard handler and tests --- src/query/v2/requests.hpp | 40 +++-- src/storage/v3/request_helper.cpp | 62 +++++++- src/storage/v3/request_helper.hpp | 11 ++ src/storage/v3/shard_rsm.cpp | 138 ++++++++++++++++- tests/simulation/shard_rsm.cpp | 248 +++++++++++++++++++++++++++++- 5 files changed, 474 insertions(+), 25 deletions(-) diff --git a/src/query/v2/requests.hpp b/src/query/v2/requests.hpp index 2ea4efb57..4c9280848 100644 --- a/src/query/v2/requests.hpp +++ b/src/query/v2/requests.hpp @@ -321,10 +321,6 @@ struct Expression { std::string expression; }; -struct Filter { - std::string filter_expression; -}; - enum class OrderingDirection { ASCENDING = 1, DESCENDING = 2 }; struct OrderBy { @@ -366,22 +362,42 @@ struct ScanVerticesResponse { std::vector results; }; -using VertexOrEdgeIds = std::variant; +struct VertexAndEdgeId { + VertexId vertex; + std::optional edge; +}; struct GetPropertiesRequest { Hlc transaction_id; - // Shouldn't contain mixed vertex and edge ids - VertexOrEdgeIds vertex_or_edge_ids; + std::vector vertices_and_edges; + std::vector property_ids; - std::vector expressions; - bool only_unique = false; - std::optional> order_by; + std::vector expressions; + + std::vector order_by; std::optional limit; - std::optional filter; + + // Return only the properties of the vertices or edges that the filter predicate + // evaluates to true + std::optional filter; +}; + +struct PropIdValue { + std::vector ids; + std::vector properties; +}; + +struct GetPropertiesResultRow { + VertexAndEdgeId vertex_and_edge; + + PropIdValue properies_and_ids; + std::vector evaluated_expressions; }; struct GetPropertiesResponse { - bool success; + std::vector result_row; + enum RequestResult : uint16_t { OUT_OF_SHARD_RANGE, SUCCESS, FAILURE }; + RequestResult result; }; enum class EdgeDirection : uint8_t { OUT = 1, IN = 2, BOTH = 3 }; diff --git a/src/storage/v3/request_helper.cpp b/src/storage/v3/request_helper.cpp index bb1c8bca4..96170dccf 100644 --- a/src/storage/v3/request_helper.cpp +++ b/src/storage/v3/request_helper.cpp @@ -11,6 +11,7 @@ #include "storage/v3/request_helper.hpp" +#include #include #include "pretty_print_ast_to_original_expression.hpp" @@ -43,19 +44,74 @@ std::vector OrderByElements(Shard::Accessor &acc, DbAccessor &dba, Vert properties_order_by.reserve(order_bys.size()); for (const auto &order_by : order_bys) { - const auto val = + auto val = ComputeExpression(dba, *it, std::nullopt, order_by.expression.expression, expr::identifier_node_symbol, ""); - properties_order_by.push_back(val); + properties_order_by.push_back(std::move(val)); } ordered.push_back({std::move(properties_order_by), *it}); } - std::sort(ordered.begin(), ordered.end(), [compare_typed_values](const auto &pair1, const auto &pair2) { + std::sort(ordered.begin(), ordered.end(), [&compare_typed_values](const auto &pair1, const auto &pair2) { return compare_typed_values(pair1.properties_order_by, pair2.properties_order_by); }); return ordered; } +std::vector OrderByElements(DbAccessor &dba, std::vector &order_by, + std::vector &&vertices) { + std::vector ordering; + ordering.reserve(order_by.size()); + for (const auto &order : order_by) { + switch (order.direction) { + case memgraph::msgs::OrderingDirection::ASCENDING: { + ordering.push_back(Ordering::ASC); + break; + } + case memgraph::msgs::OrderingDirection::DESCENDING: { + ordering.push_back(Ordering::DESC); + break; + } + } + } + struct PropElement { + std::vector properties_order_by; + VertexAccessor vertex_acc; + GetPropElement *original_element; + }; + + std::vector ordered; + auto compare_typed_values = TypedValueVectorCompare(ordering); + for (auto &vertex : vertices) { + std::vector properties; + properties.reserve(order_by.size()); + const auto *symbol = (vertex.edge_acc) ? expr::identifier_edge_symbol : expr::identifier_node_symbol; + for (const auto &order : order_by) { + TypedValue val; + if (vertex.edge_acc) { + val = ComputeExpression(dba, vertex.vertex_acc, vertex.edge_acc, order.expression.expression, "", symbol); + } else { + val = ComputeExpression(dba, vertex.vertex_acc, vertex.edge_acc, order.expression.expression, symbol, ""); + } + properties.push_back(std::move(val)); + } + + ordered.push_back({std::move(properties), vertex.vertex_acc, &vertex}); + } + + std::sort(ordered.begin(), ordered.end(), [&compare_typed_values](const auto &lhs, const auto &rhs) { + return compare_typed_values(lhs.properties_order_by, rhs.properties_order_by); + }); + + std::vector results_ordered; + results_ordered.reserve(ordered.size()); + + for (auto &elem : ordered) { + results_ordered.push_back(std::move(*elem.original_element)); + } + + return results_ordered; +} + VerticesIterable::Iterator GetStartVertexIterator(VerticesIterable &vertex_iterable, const std::vector &start_ids, const View view) { auto it = vertex_iterable.begin(); diff --git a/src/storage/v3/request_helper.hpp b/src/storage/v3/request_helper.hpp index 24ed40f8c..f98f18cd3 100644 --- a/src/storage/v3/request_helper.hpp +++ b/src/storage/v3/request_helper.hpp @@ -12,6 +12,7 @@ #include #include "ast/ast.hpp" +#include "query/v2/requests.hpp" #include "storage/v3/bindings/typed_value.hpp" #include "storage/v3/shard.hpp" #include "storage/v3/vertex_accessor.hpp" @@ -104,9 +105,19 @@ struct Element { VertexAccessor vertex_acc; }; +struct GetPropElement { + std::vector properties_order_by; + std::vector ids; + VertexAccessor vertex_acc; + std::optional edge_acc; +}; + std::vector OrderByElements(Shard::Accessor &acc, DbAccessor &dba, VerticesIterable &vertices_iterable, std::vector &order_bys); +std::vector OrderByElements(DbAccessor &dba, std::vector &order_bys, + std::vector &&vertices); + VerticesIterable::Iterator GetStartVertexIterator(VerticesIterable &vertex_iterable, const std::vector &start_ids, View view); diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index f288e5e27..4036bbd80 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -15,8 +15,10 @@ #include #include #include +#include #include "parser/opencypher/parser.hpp" +#include "pretty_print_ast_to_original_expression.hpp" #include "query/v2/requests.hpp" #include "storage/v2/vertex.hpp" #include "storage/v2/view.hpp" @@ -29,6 +31,7 @@ #include "storage/v3/bindings/symbol_generator.hpp" #include "storage/v3/bindings/symbol_table.hpp" #include "storage/v3/bindings/typed_value.hpp" +#include "storage/v3/conversions.hpp" #include "storage/v3/expr.hpp" #include "storage/v3/id_types.hpp" #include "storage/v3/key_store.hpp" @@ -159,10 +162,15 @@ std::optional> CollectAllPropertiesFromAccessor(cons } bool FilterOnVertex(DbAccessor &dba, const storage::v3::VertexAccessor &v_acc, const std::vector &filters, - const std::string_view node_name) { - return std::ranges::all_of(filters, [&node_name, &dba, &v_acc](const auto &filter_expr) { - auto res = ComputeExpression(dba, v_acc, std::nullopt, filter_expr, node_name, ""); - return res.IsBool() && res.ValueBool(); + const std::string_view node_name, const std::optional &e_acc = std::nullopt) { + return std::ranges::all_of(filters, [&node_name, &dba, &v_acc, &e_acc](const auto &filter_expr) { + TypedValue result; + if (e_acc) { + result = ComputeExpression(dba, v_acc, e_acc, filter_expr, "", node_name); + } else { + result = ComputeExpression(dba, v_acc, e_acc, filter_expr, node_name, ""); + } + return result.IsBool() && result.ValueBool(); }); } @@ -936,9 +944,125 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CommitRequest &&req) { return msgs::CommitResponse{true}; }; -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) -msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest && /*req*/) { - return msgs::GetPropertiesResponse{}; +msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { + if (req.vertices_and_edges.empty()) { + return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::FAILURE}; + } + + auto shard_acc = shard_->Access(req.transaction_id); + auto dba = DbAccessor{&shard_acc}; + const auto view = storage::v3::View::NEW; + + auto collect_props = [](const VertexAccessor &acc, const std::vector &props, View view, + const std::optional &e_acc) mutable -> std::optional { + std::vector properties; + std::vector ids; + for (const auto &prop : props) { + Result result{PropertyValue()}; + if (e_acc) { + result = e_acc->GetProperty(prop, view); + } else { + result = acc.GetProperty(prop, view); + } + if (result.HasError() && result.GetError() == Error::NONEXISTENT_OBJECT) { + continue; + } + if (result.HasError()) { + spdlog::debug("Encountered an Error while trying to get a vertex property."); + return std::nullopt; + } + properties.push_back(PropertyToTypedValue(result.GetValue())); + ids.push_back(prop); + } + GetPropElement element{std::move(properties), std::move(ids), acc, e_acc}; + return {std::move(element)}; + }; + + auto find_edge = [](const VertexAccessor &v, const EdgeTypeId &e) -> std::optional { + auto in = v.InEdges(view, {e}); + MG_ASSERT(in.HasValue()); + for (auto &edge : in.GetValue()) { + if (edge.EdgeType() == e) { + return edge; + } + } + + auto out = v.OutEdges(view, {e}); + MG_ASSERT(out.HasValue()); + for (auto &edge : out.GetValue()) { + if (edge.EdgeType() == e) { + return edge; + } + } + return std::nullopt; + }; + + std::vector elements; + + for (const auto &[vertex, maybe_edge] : req.vertices_and_edges) { + const auto &[label, pk_v] = vertex; + auto pk = ConvertPropertyVector(pk_v); + auto v_acc = dba.FindVertex(pk, view); + if (!v_acc) { + return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::OUT_OF_SHARD_RANGE}; + } + std::optional e_acc; + if (maybe_edge) { + e_acc = find_edge(*v_acc, *maybe_edge); + if (!e_acc) { + return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::OUT_OF_SHARD_RANGE}; + } + } + + const auto *symbol = (maybe_edge) ? expr::identifier_edge_symbol : expr::identifier_node_symbol; + if (req.filter && !FilterOnVertex(dba, *v_acc, {*req.filter}, symbol, e_acc)) { + continue; + } + + std::optional collected_properties; + collected_properties = collect_props(*v_acc, req.property_ids, view, e_acc); + if (!collected_properties) { + return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::FAILURE}; + } + if (collected_properties->ids.empty()) { + continue; + } + elements.push_back(std::move(*collected_properties)); + } + + if (!req.order_by.empty()) { + elements = OrderByElements(dba, req.order_by, std::move(elements)); + } + + std::vector results; + results.reserve(elements.size()); + + const auto has_expr_to_evaluate = !req.expressions.empty(); + size_t limit = elements.size(); + if (req.limit && *req.limit < elements.size()) { + limit = *req.limit; + } + for (size_t index = 0; index != limit; ++index) { + auto &element = elements.at(index); + const auto id = element.vertex_acc.Id(view).GetValue(); + std::optional e_type = + (element.edge_acc) ? std::make_optional(element.edge_acc->EdgeType()) : std::nullopt; + msgs::VertexId v_id{msgs::Label{id.primary_label}, ConvertValueVector(id.primary_key)}; + results.push_back(msgs::GetPropertiesResultRow{ + .vertex_and_edge = {.vertex = std::move(v_id), .edge = e_type}, + .properies_and_ids = { + .ids = std::move(element.ids), + .properties = ConvertToValueVectorFromTypedValueVector(std::move(element.properties_order_by))}}); + if (has_expr_to_evaluate) { + auto expression_results = ConvertToValueVectorFromTypedValueVector( + EvaluateVertexExpressions(dba, element.vertex_acc, req.expressions, expr::identifier_node_symbol)); + results.back().evaluated_expressions = std::move(expression_results); + } + } + + return msgs::GetPropertiesResponse{std::move(results), msgs::GetPropertiesResponse::SUCCESS}; } +// TODO(kostasrim) Handle edges + } // namespace memgraph::storage::v3 diff --git a/tests/simulation/shard_rsm.cpp b/tests/simulation/shard_rsm.cpp index 64d0a0861..f2a90262f 100644 --- a/tests/simulation/shard_rsm.cpp +++ b/tests/simulation/shard_rsm.cpp @@ -446,6 +446,56 @@ std::tuple> AttemptToScanAllWithExpression } } +msgs::GetPropertiesResponse AttemptToGetProperties(ShardClient &client, std::vector properties, + std::vector vertices, + std::vector edges, + std::optional limit = std::nullopt, + std::optional filter_prop = std::nullopt, + bool edge = false, + std::optional order_by = std::nullopt) { + msgs::GetPropertiesRequest req{}; + req.transaction_id.logical_id = GetTransactionId(); + req.property_ids = std::move(properties); + + if (filter_prop) { + std::string filter_expr = (!edge) ? "MG_SYMBOL_NODE.prop1 >= " : "MG_SYMBOL_EDGE.e_prop = "; + filter_expr += std::to_string(*filter_prop); + req.filter = std::make_optional(std::move(filter_expr)); + } + if (order_by) { + std::string filter_expr = (!edge) ? "MG_SYMBOL_NODE." : "MG_SYMBOL_EDGE."; + filter_expr += *order_by; + msgs::OrderBy order_by{.expression = {std::move(filter_expr)}, .direction = msgs::OrderingDirection::DESCENDING}; + std::vector request_order_by; + request_order_by.push_back(std::move(order_by)); + req.order_by = std::move(request_order_by); + } + if (limit) { + req.limit = limit; + } + req.expressions = {std::string("5 = 5")}; + std::vector req_v; + for (auto &v : vertices) { + req_v.push_back(msgs::VertexAndEdgeId{.vertex = std::move(v)}); + } + for (auto index = 0; index != edges.size(); ++index) { + req_v[index].edge = edges[index]; + } + req.vertices_and_edges = std::move(req_v); + + while (true) { + auto read_res = client.SendReadRequest(req); + if (read_res.HasError()) { + continue; + } + + auto write_response_result = read_res.GetValue(); + auto write_response = std::get(write_response_result); + + return write_response; + } +} + void AttemptToScanAllWithOrderByOnPrimaryProperty(ShardClient &client, msgs::VertexId start_id, uint64_t batch_limit) { msgs::ScanVerticesRequest scan_req; scan_req.batch_limit = batch_limit; @@ -1064,6 +1114,193 @@ void TestExpandOneGraphTwo(ShardClient &client) { } } +void TestGetProperties(ShardClient &client) { + const auto unique_prop_val_1 = GetUniqueInteger(); + const auto unique_prop_val_2 = GetUniqueInteger(); + const auto unique_prop_val_3 = GetUniqueInteger(); + const auto unique_prop_val_4 = GetUniqueInteger(); + const auto unique_prop_val_5 = GetUniqueInteger(); + + MG_ASSERT(AttemptToCreateVertex(client, unique_prop_val_1)); + MG_ASSERT(AttemptToCreateVertex(client, unique_prop_val_2)); + MG_ASSERT(AttemptToCreateVertex(client, unique_prop_val_3)); + MG_ASSERT(AttemptToCreateVertex(client, unique_prop_val_4)); + MG_ASSERT(AttemptToCreateVertex(client, unique_prop_val_5)); + + const msgs::Label prim_label = {.id = get_primary_label()}; + const msgs::PrimaryKey prim_key = {msgs::Value(static_cast(unique_prop_val_1))}; + const msgs::VertexId v_id = {prim_label, prim_key}; + const msgs::PrimaryKey prim_key_2 = {msgs::Value(static_cast(unique_prop_val_2))}; + const msgs::VertexId v_id_2 = {prim_label, prim_key_2}; + const msgs::PrimaryKey prim_key_3 = {msgs::Value(static_cast(unique_prop_val_3))}; + const msgs::VertexId v_id_3 = {prim_label, prim_key_3}; + const msgs::PrimaryKey prim_key_4 = {msgs::Value(static_cast(unique_prop_val_4))}; + const msgs::VertexId v_id_4 = {prim_label, prim_key_4}; + const msgs::PrimaryKey prim_key_5 = {msgs::Value(static_cast(unique_prop_val_5))}; + const msgs::VertexId v_id_5 = {prim_label, prim_key_5}; + const auto prop_id_2 = PropertyId::FromUint(2); + const auto prop_id_4 = PropertyId::FromUint(4); + const auto prop_id_5 = PropertyId::FromUint(5); + // Vertices + { + // No properties + const auto result = AttemptToGetProperties(client, {}, {v_id, v_id_2}, {}, std::nullopt, unique_prop_val_2); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(result.result_row.empty()); + } + { + // All properties + const auto result = AttemptToGetProperties(client, {prop_id_2, prop_id_4, prop_id_5}, {v_id, v_id_2, v_id_3}, {}); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 3); + for (const auto &elem : result.result_row) { + MG_ASSERT(elem.properies_and_ids.ids.size() == 3); + MG_ASSERT(elem.properies_and_ids.properties.size() == 3); + } + } + { + // Two properties from two vertices with a filter on unique_prop_5 + const auto result = AttemptToGetProperties(client, {prop_id_2, prop_id_4}, {v_id, v_id_2, v_id_5}, {}, std::nullopt, + unique_prop_val_5); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 1); + } + { + // One property from three vertices. + const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 3); + MG_ASSERT(result.result_row[0].properies_and_ids.ids.size() == 1); + MG_ASSERT(result.result_row[1].properies_and_ids.ids.size() == 1); + MG_ASSERT(result.result_row[2].properies_and_ids.ids.size() == 1); + } + { + // Same as before but with limit of 1 row + const auto result = + AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::make_optional(1)); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 1); + } + { + // Same as before but with a limit greater than the elements returned + const auto result = + AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::make_optional(5)); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 3); + } + { + // Order by on `prop1` (descending) + const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::nullopt, + std::nullopt, false, "prop1"); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 3); + MG_ASSERT(result.result_row[0].vertex_and_edge.vertex == v_id_3); + MG_ASSERT(result.result_row[1].vertex_and_edge.vertex == v_id_2); + MG_ASSERT(result.result_row[2].vertex_and_edge.vertex == v_id); + } + { + // Order by and filter on >= unique_prop_val_3 && assert result row data members + const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3, v_id_4, v_id_5}, {}, + std::nullopt, unique_prop_val_3, false, "prop1"); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 3); + MG_ASSERT(result.result_row[0].vertex_and_edge.vertex == v_id_5); + MG_ASSERT(result.result_row[0].properies_and_ids.properties.size() == 1); + MG_ASSERT(result.result_row[0].properies_and_ids.properties.front() == prim_key_5.front()); + MG_ASSERT(result.result_row[0].properies_and_ids.ids.size() == 1); + MG_ASSERT(result.result_row[0].properies_and_ids.ids.front() == prop_id_2); + MG_ASSERT(result.result_row[0].evaluated_expressions.size() == 1); + MG_ASSERT(result.result_row[0].evaluated_expressions.front() == msgs::Value(true)); + + MG_ASSERT(result.result_row[1].vertex_and_edge.vertex == v_id_4); + MG_ASSERT(result.result_row[1].properies_and_ids.properties.size() == 1); + MG_ASSERT(result.result_row[1].properies_and_ids.properties.front() == prim_key_4.front()); + MG_ASSERT(result.result_row[1].properies_and_ids.ids.size() == 1); + MG_ASSERT(result.result_row[1].properies_and_ids.ids.front() == prop_id_2); + MG_ASSERT(result.result_row[1].evaluated_expressions.size() == 1); + MG_ASSERT(result.result_row[1].evaluated_expressions.front() == msgs::Value(true)); + + MG_ASSERT(result.result_row[2].vertex_and_edge.vertex == v_id_3); + MG_ASSERT(result.result_row[2].properies_and_ids.properties.size() == 1); + MG_ASSERT(result.result_row[2].properies_and_ids.properties.front() == prim_key_3.front()); + MG_ASSERT(result.result_row[2].properies_and_ids.ids.size() == 1); + MG_ASSERT(result.result_row[2].properies_and_ids.ids.front() == prop_id_2); + MG_ASSERT(result.result_row[2].evaluated_expressions.size() == 1); + MG_ASSERT(result.result_row[2].evaluated_expressions.front() == msgs::Value(true)); + } + + // Edges + const auto edge_gid = GetUniqueInteger(); + const auto edge_type_id = EdgeTypeId::FromUint(GetUniqueInteger()); + const auto unique_edge_prop_id = 7; + const auto edge_prop_val = GetUniqueInteger(); + MG_ASSERT(AttemptToAddEdgeWithProperties(client, unique_prop_val_1, unique_prop_val_2, edge_gid, unique_edge_prop_id, + edge_prop_val, {edge_type_id})); + const auto edge_gid_2 = GetUniqueInteger(); + const auto edge_prop_val_2 = GetUniqueInteger(); + MG_ASSERT(AttemptToAddEdgeWithProperties(client, unique_prop_val_3, unique_prop_val_4, edge_gid_2, + unique_edge_prop_id, edge_prop_val_2, {edge_type_id})); + const auto edge_prop_id = PropertyId::FromUint(unique_edge_prop_id); + // no properties + { + const auto result = AttemptToGetProperties(client, {}, {v_id_2, v_id_3}, {edge_type_id, edge_type_id}); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(result.result_row.empty()); + } + // properties for two vertices + { + const auto result = AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, {edge_type_id, edge_type_id}); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 2); + } + // filter + { + const auto result = AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, {edge_type_id, edge_type_id}, + {}, {edge_prop_val}, true); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 1); + MG_ASSERT(result.result_row.front().vertex_and_edge.edge); + MG_ASSERT(result.result_row.front().vertex_and_edge.edge.value() == edge_type_id); + MG_ASSERT(result.result_row.front().properies_and_ids.properties.size() == 1); + MG_ASSERT(result.result_row.front().properies_and_ids.properties.front() == + msgs::Value(static_cast(edge_prop_val))); + } + // Order by + { + const auto result = AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, {edge_type_id, edge_type_id}, + {}, {}, true, "e_prop"); + MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 2); + MG_ASSERT(result.result_row[0].vertex_and_edge.vertex == v_id_3); + MG_ASSERT(result.result_row[0].vertex_and_edge.edge); + MG_ASSERT(result.result_row[0].vertex_and_edge.edge.value() == edge_type_id); + MG_ASSERT(result.result_row[0].properies_and_ids.properties.size() == 1); + MG_ASSERT(result.result_row[0].properies_and_ids.properties.front() == + msgs::Value(static_cast(edge_prop_val_2))); + MG_ASSERT(result.result_row[0].evaluated_expressions.size() == 1); + MG_ASSERT(result.result_row[0].evaluated_expressions.front() == msgs::Value(true)); + + MG_ASSERT(result.result_row[1].vertex_and_edge.vertex == v_id_2); + MG_ASSERT(result.result_row[1].vertex_and_edge.edge); + MG_ASSERT(result.result_row[1].vertex_and_edge.edge.value() == edge_type_id); + MG_ASSERT(result.result_row[1].properies_and_ids.properties.size() == 1); + MG_ASSERT(result.result_row[1].properies_and_ids.properties.front() == + msgs::Value(static_cast(edge_prop_val))); + MG_ASSERT(result.result_row[1].evaluated_expressions.size() == 1); + MG_ASSERT(result.result_row[1].evaluated_expressions.front() == msgs::Value(true)); + } +} + } // namespace int TestMessages() { @@ -1102,9 +1339,12 @@ int TestMessages() { auto shard_ptr2 = std::make_unique(get_primary_label(), min_prim_key, max_prim_key, schema_prop); auto shard_ptr3 = std::make_unique(get_primary_label(), min_prim_key, max_prim_key, schema_prop); - shard_ptr1->StoreMapping({{1, "label"}, {2, "prop1"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}}); - shard_ptr2->StoreMapping({{1, "label"}, {2, "prop1"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}}); - shard_ptr3->StoreMapping({{1, "label"}, {2, "prop1"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}}); + shard_ptr1->StoreMapping( + {{1, "label"}, {2, "prop1"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}, {7, "e_prop"}}); + shard_ptr2->StoreMapping( + {{1, "label"}, {2, "prop1"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}, {7, "e_prop"}}); + shard_ptr3->StoreMapping( + {{1, "label"}, {2, "prop1"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}, {7, "e_prop"}}); std::vector
address_for_1{shard_server_2_address, shard_server_3_address}; std::vector
address_for_2{shard_server_1_address, shard_server_3_address}; @@ -1145,6 +1385,8 @@ int TestMessages() { TestExpandOneGraphOne(client); TestExpandOneGraphTwo(client); + // GetProperties tests + TestGetProperties(client); simulator.ShutDown(); SimulatorStats stats = simulator.Stats(); From 7a3caa320cb1044d155b192634cd53b56ca76aa6 Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Thu, 24 Nov 2022 14:25:20 +0200 Subject: [PATCH 02/93] WiP --- src/query/v2/shard_request_manager.hpp | 92 ++++++++++++++++++++-- src/storage/v3/shard_rsm.cpp | 3 + tests/simulation/CMakeLists.txt | 1 + tests/simulation/shard_request_manager.cpp | 19 ++++- 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/src/query/v2/shard_request_manager.hpp b/src/query/v2/shard_request_manager.hpp index 20bae7b97..4db77e645 100644 --- a/src/query/v2/shard_request_manager.hpp +++ b/src/query/v2/shard_request_manager.hpp @@ -105,6 +105,7 @@ struct ExecutionState { class ShardRequestManagerInterface { public: using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor; + using EdgeAccessor = memgraph::query::v2::accessors::EdgeAccessor; ShardRequestManagerInterface() = default; ShardRequestManagerInterface(const ShardRequestManagerInterface &) = delete; ShardRequestManagerInterface(ShardRequestManagerInterface &&) = delete; @@ -122,7 +123,8 @@ class ShardRequestManagerInterface { ExpandOneRequest request) = 0; virtual std::vector Request(ExecutionState &state, std::vector new_edges) = 0; - + virtual std::vector Request(ExecutionState &state, + GetPropertiesRequest request) = 0; virtual storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) const = 0; virtual storage::v3::PropertyId NameToProperty(const std::string &name) const = 0; virtual storage::v3::LabelId NameToLabel(const std::string &name) const = 0; @@ -146,6 +148,7 @@ class ShardRequestManager : public ShardRequestManagerInterface { using ShardMap = memgraph::coordinator::ShardMap; using CompoundKey = memgraph::coordinator::PrimaryKey; using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor; + using EdgeAccessor = memgraph::query::v2::accessors::EdgeAccessor; ShardRequestManager(CoordinatorClient coord, memgraph::io::Io &&io) : coord_cli_(std::move(coord)), io_(std::move(io)) {} @@ -353,6 +356,21 @@ class ShardRequestManager : public ShardRequestManagerInterface { return result_rows; } + std::vector Request(ExecutionState &state, + GetPropertiesRequest requests) override { + MaybeInitializeExecutionState(state, std::move(requests)); + SendAllRequests(state); + + std::vector responses; + // 2. Block untill all the futures are exhausted + do { + AwaitOnResponses(state, responses); + } while (!state.shard_cache.empty()); + + MaybeCompleteState(state); + return responses; + } + private: enum class PaginatedResponseState { Pending, PartiallyFinished }; @@ -373,6 +391,13 @@ class ShardRequestManager : public ShardRequestManagerInterface { } } + template + void ThrowIfStateExecuting(ExecutionState &state) const { + if (state.state == ExecutionState::EXECUTING) [[unlikely]] { + throw std::runtime_error("State is completed and must be reset"); + } + } + template void MaybeCompleteState(ExecutionState &state) const { if (state.requests.empty()) { @@ -508,6 +533,33 @@ class ShardRequestManager : public ShardRequestManagerInterface { state.state = ExecutionState::EXECUTING; } + void MaybeInitializeExecutionState(ExecutionState &state, GetPropertiesRequest request) { + ThrowIfStateCompleted(state); + ThrowIfStateExecuting(state); + + std::map per_shard_request_table; + auto top_level_rqst_template = request; + top_level_rqst_template.transaction_id = transaction_id_; + top_level_rqst_template.vertices_and_edges.clear(); + + state.transaction_id = transaction_id_; + + for (auto &[vertex, maybe_edge] : request.vertices_and_edges) { + auto shard = + shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second)); + if (!per_shard_request_table.contains(shard)) { + per_shard_request_table.insert(std::pair(shard, top_level_rqst_template)); + state.shard_cache.push_back(shard); + } + per_shard_request_table[shard].vertices_and_edges.push_back({std::move(vertex), maybe_edge}); + } + + for (auto &[shard, rqst] : per_shard_request_table) { + state.requests.push_back(std::move(rqst)); + } + state.state = ExecutionState::EXECUTING; + } + StorageClient &GetStorageClientForShard(Shard shard) { if (!storage_cli_manager_.Exists(shard)) { AddStorageClientToManager(shard); @@ -532,7 +584,8 @@ class ShardRequestManager : public ShardRequestManagerInterface { storage_cli_manager_.AddClient(target_shard, std::move(cli)); } - void SendAllRequests(ExecutionState &state) { + template + void SendAllRequests(ExecutionState &state) { int64_t shard_idx = 0; for (const auto &request : state.requests) { const auto ¤t_shard = state.shard_cache[shard_idx]; @@ -581,9 +634,6 @@ class ShardRequestManager : public ShardRequestManagerInterface { int64_t request_idx = 0; for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) { - // This is fine because all new_vertices of each request end up on the same shard - const auto labels = state.requests[request_idx].new_vertices[0].label_ids; - auto &storage_client = GetStorageClientForShard(*shard_it); auto poll_result = storage_client.AwaitAsyncWriteRequest(); @@ -649,6 +699,38 @@ class ShardRequestManager : public ShardRequestManagerInterface { } } + void AwaitOnResponses(ExecutionState &state, std::vector &responses) { + auto &shard_cache_ref = state.shard_cache; + int64_t request_idx = 0; + + for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) { + auto &storage_client = GetStorageClientForShard(*shard_it); + + auto poll_result = storage_client.PollAsyncReadRequest(); + if (!poll_result) { + ++shard_it; + ++request_idx; + continue; + } + + if (poll_result->HasError()) { + throw std::runtime_error("GetProperties request timed out"); + } + + ReadResponses response_variant = poll_result->GetValue(); + auto response = std::get(response_variant); + if (response.result != GetPropertiesResponse::SUCCESS) { + throw std::runtime_error("GetProperties request did not succeed"); + } + + responses.push_back(std::move(response)); + shard_it = shard_cache_ref.erase(shard_it); + // Needed to maintain the 1-1 mapping between the ShardCache and the requests. + auto it = state.requests.begin() + request_idx; + state.requests.erase(it); + } + } + void AwaitOnPaginatedRequests(ExecutionState &state, std::vector &responses, std::map &paginated_response_tracker) { diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index 3188458ce..9a2d6dcc8 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -518,6 +518,9 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { if (req.vertices_and_edges.empty()) { return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::FAILURE}; } + if (req.property_ids.empty()) { + return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::SUCCESS}; + } auto shard_acc = shard_->Access(req.transaction_id); auto dba = DbAccessor{&shard_acc}; diff --git a/tests/simulation/CMakeLists.txt b/tests/simulation/CMakeLists.txt index 9e1a4c71e..2d7e6c49c 100644 --- a/tests/simulation/CMakeLists.txt +++ b/tests/simulation/CMakeLists.txt @@ -32,3 +32,4 @@ add_simulation_test(trial_query_storage/query_storage_test.cpp) add_simulation_test(sharded_map.cpp) add_simulation_test(shard_rsm.cpp) add_simulation_test(cluster_property_test.cpp) +add_simulation_test(shard_request_manager.cpp) diff --git a/tests/simulation/shard_request_manager.cpp b/tests/simulation/shard_request_manager.cpp index 746ab385f..e62e15318 100644 --- a/tests/simulation/shard_request_manager.cpp +++ b/tests/simulation/shard_request_manager.cpp @@ -221,8 +221,22 @@ void TestExpandOne(msgs::ShardRequestManagerInterface &shard_request_manager) { MG_ASSERT(result_rows.size() == 2); } -template -void TestAggregate(ShardRequestManager &io) {} +void TestGetProperties(msgs::ShardRequestManagerInterface &shard_request_manager) { + using PropVal = msgs::Value; + + auto label_id = shard_request_manager.NameToLabel("test_label"); + msgs::VertexId v1{{label_id}, {PropVal(int64_t(1)), PropVal(int64_t(0))}}; + msgs::VertexId v2{{label_id}, {PropVal(int64_t(13)), PropVal(int64_t(13))}}; + + msgs::ExecutionState state; + msgs::GetPropertiesRequest request; + + request.vertices_and_edges.push_back({v1}); + request.vertices_and_edges.push_back({v2}); + + auto result = shard_request_manager.Request(state, std::move(request)); + MG_ASSERT(result.size() == 2); +} void DoTest() { SimulatorConfig config{ @@ -343,6 +357,7 @@ void DoTest() { TestScanVertices(io); TestCreateVertices(io); TestCreateExpand(io); + TestGetProperties(io); simulator.ShutDown(); From 8fe1b8d7fc9fdf9b80aa2a1f12c4ff9bec103fa4 Mon Sep 17 00:00:00 2001 From: jbajic Date: Thu, 24 Nov 2022 17:44:41 +0100 Subject: [PATCH 03/93] Add std::map and skiplist benchamrk --- tests/benchmark/CMakeLists.txt | 11 +- tests/benchmark/data_structures.cpp | 169 +++++++++++++++++++++++++++ tests/benchmark/skip_list_common.hpp | 26 ++++- 3 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 tests/benchmark/data_structures.cpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 31f0eebc0..46905a4d8 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -9,11 +9,13 @@ function(add_benchmark test_cpp) get_filename_component(exec_name ${test_cpp} NAME_WE) set(target_name ${test_prefix}${exec_name}) add_executable(${target_name} ${test_cpp} ${ARGN}) + # OUTPUT_NAME sets the real name of a target when it is built and can be # used to help create two targets of the same name even though CMake # requires unique logical target names set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${exec_name}) target_link_libraries(${target_name} benchmark gflags) + # register test add_test(${target_name} ${exec_name}) add_dependencies(memgraph__benchmark ${target_name}) @@ -37,9 +39,9 @@ target_link_libraries(${test_prefix}profile mg-query) add_benchmark(query/stripped.cpp) target_link_libraries(${test_prefix}stripped mg-query) -if (MG_ENTERPRISE) -add_benchmark(rpc.cpp) -target_link_libraries(${test_prefix}rpc mg-rpc) +if(MG_ENTERPRISE) + add_benchmark(rpc.cpp) + target_link_libraries(${test_prefix}rpc mg-rpc) endif() add_benchmark(skip_list_random.cpp) @@ -65,3 +67,6 @@ target_link_libraries(${test_prefix}storage_v2_property_store mg-storage-v2) add_benchmark(future.cpp) target_link_libraries(${test_prefix}future mg-io) + +add_benchmark(data_structures.cpp) +target_link_libraries(${test_prefix}data_structures mg-utils) diff --git a/tests/benchmark/data_structures.cpp b/tests/benchmark/data_structures.cpp new file mode 100644 index 000000000..f130d6faf --- /dev/null +++ b/tests/benchmark/data_structures.cpp @@ -0,0 +1,169 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "btree_map.hpp" +#include "skip_list_common.hpp" +#include "utils/skip_list.hpp" + +DEFINE_int32(max_element, 20000, "Maximum element in the intial list"); +DEFINE_int32(max_range, 2000000, "Maximum range used for the test"); +DEFINE_string(ds, "skip_list", "Which DS to test"); + +template +concept BasicIndexStructure = requires(ContainerType a, const ContainerType b) { + { a.contains() } -> std::same_as; + { a.find() } -> std::same_as; + { a.insert() } -> std::same_as>; + { a.remove() } -> std::same_as; +}; + +template +class BppStructure final { + using value_type = typename tlx::btree_map::value_type; + using iterator = typename tlx::btree_map::iterator; + + public: + bool contains(const T key) const { return data.exists(key); } + + auto find(const T key) const { return data.find(key); } + + std::pair insert(T val) { return data.insert({val, val}); } + + bool remove(const T key) { return data.erase(key); } + + size_t size() const { return data.size(); }; + + iterator end() { return data.end(); } + + private: + tlx::btree_map data; +}; + +template +class MapStructure final { + using value_type = typename std::map::value_type; + using iterator = typename std::map::iterator; + + public: + bool contains(const T key) const { return data.contains(key); } + + auto find(const T key) const { return data.find(key); } + + std::pair insert(T val) { return data.insert({val, val}); } + + bool remove(const T key) { return data.erase(key); } + + size_t size() const { return data.size(); }; + + iterator end() { return data.end(); } + + private: + std::map data; +}; + +template +void RunDSTest(DataStructure &ds) { + RunTest([&ds](const std::atomic &run, auto &stats) { + std::mt19937 generator(std::random_device{}()); + std::uniform_int_distribution distribution(0, 3); + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, FLAGS_max_range); + + while (run.load(std::memory_order_relaxed)) { + auto value = distribution(generator); + + auto item = i_distribution(i_generator); + switch (value) { + case 0: + stats.succ[OP_INSERT] += static_cast(ds.insert(item).second); + break; + case 1: + stats.succ[OP_CONTAINS] += static_cast(ds.contains(item)); + break; + case 2: + stats.succ[OP_REMOVE] += static_cast(ds.remove(item)); + break; + case 3: + stats.succ[OP_FIND] += static_cast(ds.find(item) != ds.end()); + break; + default: + std::terminate(); + } + ++stats.total; + } + }); +} + +template +void GenerateData(Accessor &acc) { + for (uint64_t i = 0; i <= FLAGS_max_element; ++i) { + MG_ASSERT(acc.insert(i).second); + } +} + +template +void AsserData(Accessor &acc) { + if constexpr (std::is_same_v>) { + uint64_t val = 0; + for (auto item : acc) { + MG_ASSERT(item == val); + ++val; + } + MG_ASSERT(val == FLAGS_max_element + 1); + } else { + MG_ASSERT(acc.size() == FLAGS_max_element + 1); + } +} + +int main(int argc, char **argv) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_ds == "skip_list") { + std::cout << "#### Testing skip list" << std::endl; + memgraph::utils::SkipList list; + auto acc = list.access(); + + GenerateData(acc); + AsserData(acc); + + RunDSTest(acc); + } else if (FLAGS_ds == "map") { + std::cout << "#### Testing map" << std::endl; + MapStructure map; + + GenerateData(map); + AsserData(map); + + RunDSTest(map); + } else if (FLAGS_ds == "bpp") { + std::cout << "#### Testing B+ tree" << std::endl; + BppStructure bpp; + + GenerateData(bpp); + AsserData(bpp); + + RunDSTest(bpp); + } else { + throw std::runtime_error("Select an existing data structure!"); + } + + return 0; +} diff --git a/tests/benchmark/skip_list_common.hpp b/tests/benchmark/skip_list_common.hpp index 988a2dfb7..8dd4c705b 100644 --- a/tests/benchmark/skip_list_common.hpp +++ b/tests/benchmark/skip_list_common.hpp @@ -1,4 +1,4 @@ -// Copyright 2021 Memgraph Ltd. +// Copyright 2022 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -81,3 +81,27 @@ inline void RunConcurrentTest(std::function *, Stats *)> std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; std::cout << "Total ops: " << tops << " (" << tops / FLAGS_duration << " calls/s)" << std::endl; } + +inline void RunTest(std::function &, Stats &)> test_func) { + Stats stats; + std::atomic run{true}; + + { + std::jthread bg_thread(test_func, std::cref(run), std::ref(stats)); + std::this_thread::sleep_for(std::chrono::seconds(FLAGS_duration)); + run.store(false, std::memory_order_relaxed); + } + + std::cout << " Operations: " << stats.total << std::endl; + std::cout << " Successful insert: " << stats.succ[0] << std::endl; + std::cout << " Successful contains: " << stats.succ[1] << std::endl; + std::cout << " Successful remove: " << stats.succ[2] << std::endl; + std::cout << " Successful find: " << stats.succ[3] << std::endl; + std::cout << std::endl; + + const auto tot = stats.succ[0] + stats.succ[1] + stats.succ[2] + stats.succ[3]; + const auto tops = stats.total; + + std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; + std::cout << "Total ops: " << tops << " (" << tops / FLAGS_duration << " calls/s)" << std::endl; +} From c5138c8d58d68580484646ba91acae41e2d7f6d4 Mon Sep 17 00:00:00 2001 From: jbajic Date: Thu, 24 Nov 2022 17:45:43 +0100 Subject: [PATCH 04/93] Add b+ tree --- src/utils/skip_list.hpp | 2 + tests/benchmark/btree.hpp | 3134 +++++++++++++++++++++++++++++++++ tests/benchmark/btree_map.hpp | 475 +++++ tests/benchmark/core.hpp | 267 +++ 4 files changed, 3878 insertions(+) create mode 100644 tests/benchmark/btree.hpp create mode 100644 tests/benchmark/btree_map.hpp create mode 100644 tests/benchmark/core.hpp diff --git a/src/utils/skip_list.hpp b/src/utils/skip_list.hpp index 486aade57..7d4897884 100644 --- a/src/utils/skip_list.hpp +++ b/src/utils/skip_list.hpp @@ -621,6 +621,8 @@ class SkipList final { TNode *node_; }; + using iterator = Iterator; + class Accessor final { private: friend class SkipList; diff --git a/tests/benchmark/btree.hpp b/tests/benchmark/btree.hpp new file mode 100644 index 000000000..97b912fb0 --- /dev/null +++ b/tests/benchmark/btree.hpp @@ -0,0 +1,3134 @@ +/******************************************************************************* + * tlx/container/btree.hpp + * + * Part of tlx - http://panthema.net/tlx + * + * Copyright (C) 2008-2017 Timo Bingmann + * + * All rights reserved. Published under the Boost Software License, Version 1.0 + ******************************************************************************/ + +#ifndef TLX_CONTAINER_BTREE_HEADER +#define TLX_CONTAINER_BTREE_HEADER + +#include "core.hpp" + +// *** Required Headers from the STL + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tlx { + +//! \addtogroup tlx_container +//! \{ +//! \defgroup tlx_container_btree B+ Trees +//! B+ tree variants +//! \{ + +// *** Debugging Macros + +#ifdef TLX_BTREE_DEBUG + +#include + +//! Print out debug information to std::cout if TLX_BTREE_DEBUG is defined. +#define TLX_BTREE_PRINT(x) \ + do { \ + if (debug) (std::cout << x << std::endl); \ + } while (0) + +//! Assertion only if TLX_BTREE_DEBUG is defined. This is not used in verify(). +#define TLX_BTREE_ASSERT(x) \ + do { \ + assert(x); \ + } while (0) + +#else + +//! Print out debug information to std::cout if TLX_BTREE_DEBUG is defined. +#define TLX_BTREE_PRINT(x) \ + do { \ + } while (0) + +//! Assertion only if TLX_BTREE_DEBUG is defined. This is not used in verify(). +#define TLX_BTREE_ASSERT(x) \ + do { \ + } while (0) + +#endif + +//! The maximum of a and b. Used in some compile-time formulas. +#define TLX_BTREE_MAX(a, b) ((a) < (b) ? (b) : (a)) + +#ifndef TLX_BTREE_FRIENDS +//! The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ +//! tree internals. This was added for wxBTreeDemo to be able to draw the +//! tree. +#define TLX_BTREE_FRIENDS friend class btree_friend +#endif + +/*! + * Generates default traits for a B+ tree used as a set or map. It estimates + * leaf and inner node sizes by assuming a cache line multiple of 256 bytes. + */ +template +struct btree_default_traits { + //! If true, the tree will self verify its invariants after each insert() or + //! erase(). The header must have been compiled with TLX_BTREE_DEBUG + //! defined. + static const bool self_verify = false; + + //! If true, the tree will print out debug information and a tree dump + //! during insert() or erase() operation. The header must have been + //! compiled with TLX_BTREE_DEBUG defined and key_type must be std::ostream + //! printable. + static const bool debug = false; + + //! Number of slots in each leaf of the tree. Estimated so that each node + //! has a size of about 256 bytes. + static const int leaf_slots = TLX_BTREE_MAX(8, 256 / (sizeof(Value))); + + //! Number of slots in each inner node of the tree. Estimated so that each + //! node has a size of about 256 bytes. + static const int inner_slots = TLX_BTREE_MAX(8, 256 / (sizeof(Key) + sizeof(void *))); + + //! As of stx-btree-0.9, the code does linear search in find_lower() and + //! find_upper() instead of binary_search, unless the node size is larger + //! than this threshold. See notes at + //! http://panthema.net/2013/0504-STX-B+Tree-Binary-vs-Linear-Search + static const size_t binsearch_threshold = 256; +}; + +/*! + * Basic class implementing a B+ tree data structure in memory. + * + * The base implementation of an in-memory B+ tree. It is based on the + * implementation in Cormen's Introduction into Algorithms, Jan Jannink's paper + * and other algorithm resources. Almost all STL-required function calls are + * implemented. The asymptotic time requirements of the STL are not always + * fulfilled in theory, however, in practice this B+ tree performs better than a + * red-black tree and almost always uses less memory. The insertion function + * splits the nodes on the recursion unroll. Erase is largely based on Jannink's + * ideas. + * + * This class is specialized into btree_set, btree_multiset, btree_map and + * btree_multimap using default template parameters and facade functions. + */ +template , + typename Traits = btree_default_traits, bool Duplicates = false, + typename Allocator = std::allocator> +class BTree { + public: + //! \name Template Parameter Types + //! \{ + + //! First template parameter: The key type of the B+ tree. This is stored in + //! inner nodes. + typedef Key key_type; + + //! Second template parameter: Composition pair of key and data types, or + //! just the key for set containers. This data type is stored in the leaves. + typedef Value value_type; + + //! Third template: key extractor class to pull key_type from value_type. + typedef KeyOfValue key_of_value; + + //! Fourth template parameter: key_type comparison function object + typedef Compare key_compare; + + //! Fifth template parameter: Traits object used to define more parameters + //! of the B+ tree + typedef Traits traits; + + //! Sixth template parameter: Allow duplicate keys in the B+ tree. Used to + //! implement multiset and multimap. + static const bool allow_duplicates = Duplicates; + + //! Seventh template parameter: STL allocator for tree nodes + typedef Allocator allocator_type; + + //! \} + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ + // tree internals. This was added for wxBTreeDemo to be able to draw the + // tree. + TLX_BTREE_FRIENDS; + + public: + //! \name Constructed Types + //! \{ + + //! Typedef of our own type + typedef BTree Self; + + //! Size type used to count keys + typedef size_t size_type; + + //! \} + + public: + //! \name Static Constant Options and Values of the B+ Tree + //! \{ + + //! Base B+ tree parameter: The number of key/data slots in each leaf + static const unsigned short leaf_slotmax = traits::leaf_slots; + + //! Base B+ tree parameter: The number of key slots in each inner node, + //! this can differ from slots in each leaf. + static const unsigned short inner_slotmax = traits::inner_slots; + + //! Computed B+ tree parameter: The minimum number of key/data slots used + //! in a leaf. If fewer slots are used, the leaf will be merged or slots + //! shifted from it's siblings. + static const unsigned short leaf_slotmin = (leaf_slotmax / 2); + + //! Computed B+ tree parameter: The minimum number of key slots used + //! in an inner node. If fewer slots are used, the inner node will be + //! merged or slots shifted from it's siblings. + static const unsigned short inner_slotmin = (inner_slotmax / 2); + + //! Debug parameter: Enables expensive and thorough checking of the B+ tree + //! invariants after each insert/erase operation. + static const bool self_verify = traits::self_verify; + + //! Debug parameter: Prints out lots of debug information about how the + //! algorithms change the tree. Requires the header file to be compiled + //! with TLX_BTREE_DEBUG and the key type must be std::ostream printable. + static const bool debug = traits::debug; + + //! \} + + private: + //! \name Node Classes for In-Memory Nodes + //! \{ + + //! The header structure of each node in-memory. This structure is extended + //! by InnerNode or LeafNode. + struct node { + //! Level in the b-tree, if level == 0 -> leaf node + unsigned short level; + + //! Number of key slotuse use, so the number of valid children or data + //! pointers + unsigned short slotuse; + + //! Delayed initialisation of constructed node. + void initialize(const unsigned short l) { + level = l; + slotuse = 0; + } + + //! True if this is a leaf node. + bool is_leafnode() const { return (level == 0); } + }; + + //! Extended structure of a inner node in-memory. Contains only keys and no + //! data items. + struct InnerNode : public node { + //! Define an related allocator for the InnerNode structs. + typedef typename std::allocator_traits::template rebind_alloc alloc_type; + + //! Keys of children or data pointers + key_type slotkey[inner_slotmax]; // NOLINT + + //! Pointers to children + node *childid[inner_slotmax + 1]; // NOLINT + + //! Set variables to initial values. + void initialize(const unsigned short l) { node::initialize(l); } + + //! Return key in slot s + const key_type &key(size_t s) const { return slotkey[s]; } + + //! True if the node's slots are full. + bool is_full() const { return (node::slotuse == inner_slotmax); } + + //! True if few used entries, less than half full. + bool is_few() const { return (node::slotuse <= inner_slotmin); } + + //! True if node has too few entries. + bool is_underflow() const { return (node::slotuse < inner_slotmin); } + }; + + //! Extended structure of a leaf node in memory. Contains pairs of keys and + //! data items. Key and data slots are kept together in value_type. + struct LeafNode : public node { + //! Define an related allocator for the LeafNode structs. + typedef typename std::allocator_traits::template rebind_alloc alloc_type; + + //! Double linked list pointers to traverse the leaves + LeafNode *prev_leaf; + + //! Double linked list pointers to traverse the leaves + LeafNode *next_leaf; + + //! Array of (key, data) pairs + value_type slotdata[leaf_slotmax]; // NOLINT + + //! Set variables to initial values + void initialize() { + node::initialize(0); + prev_leaf = next_leaf = nullptr; + } + + //! Return key in slot s. + const key_type &key(size_t s) const { return key_of_value::get(slotdata[s]); } + + //! True if the node's slots are full. + bool is_full() const { return (node::slotuse == leaf_slotmax); } + + //! True if few used entries, less than half full. + bool is_few() const { return (node::slotuse <= leaf_slotmin); } + + //! True if node has too few entries. + bool is_underflow() const { return (node::slotuse < leaf_slotmin); } + + //! Set the (key,data) pair in slot. Overloaded function used by + //! bulk_load(). + void set_slot(unsigned short slot, const value_type &value) { + TLX_BTREE_ASSERT(slot < node::slotuse); + slotdata[slot] = value; + } + }; + + //! \} + + public: + //! \name Iterators and Reverse Iterators + //! \{ + + class iterator; + class const_iterator; + class reverse_iterator; + class const_reverse_iterator; + + //! STL-like iterator object for B+ tree items. The iterator points to a + //! specific slot number in a leaf. + class iterator { + public: + // *** Types + + //! The key type of the btree. Returned by key(). + typedef typename BTree::key_type key_type; + + //! The value type of the btree. Returned by operator*(). + typedef typename BTree::value_type value_type; + + //! Reference to the value_type. STL required. + typedef value_type &reference; + + //! Pointer to the value_type. STL required. + typedef value_type *pointer; + + //! STL-magic iterator category + typedef std::bidirectional_iterator_tag iterator_category; + + //! STL-magic + typedef ptrdiff_t difference_type; + + //! Our own type + typedef iterator self; + + private: + // *** Members + + //! The currently referenced leaf node of the tree + typename BTree::LeafNode *curr_leaf; + + //! Current key/data slot referenced + unsigned short curr_slot; + + //! Friendly to the const_iterator, so it may access the two data items + //! directly. + friend class const_iterator; + + //! Also friendly to the reverse_iterator, so it may access the two + //! data items directly. + friend class reverse_iterator; + + //! Also friendly to the const_reverse_iterator, so it may access the + //! two data items directly. + friend class const_reverse_iterator; + + //! Also friendly to the base btree class, because erase_iter() needs + //! to read the curr_leaf and curr_slot values directly. + friend class BTree; + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access + // the B+ tree internals. This was added for wxBTreeDemo to be able to + // draw the tree. + TLX_BTREE_FRIENDS; + + public: + // *** Methods + + //! Default-Constructor of a mutable iterator + iterator() : curr_leaf(nullptr), curr_slot(0) {} + + //! Initializing-Constructor of a mutable iterator + iterator(typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} + + //! Copy-constructor from a reverse iterator + iterator(const reverse_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Dereference the iterator. + reference operator*() const { return curr_leaf->slotdata[curr_slot]; } + + //! Dereference the iterator. + pointer operator->() const { return &curr_leaf->slotdata[curr_slot]; } + + //! Key of the current slot. + const key_type &key() const { return curr_leaf->key(curr_slot); } + + //! Prefix++ advance the iterator to the next slot. + iterator &operator++() { + if (curr_slot + 1u < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 0; + } else { + // this is end() + curr_slot = curr_leaf->slotuse; + } + + return *this; + } + + //! Postfix++ advance the iterator to the next slot. + iterator operator++(int) { + iterator tmp = *this; // copy ourselves + + if (curr_slot + 1u < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 0; + } else { + // this is end() + curr_slot = curr_leaf->slotuse; + } + + return tmp; + } + + //! Prefix-- backstep the iterator to the last slot. + iterator &operator--() { + if (curr_slot > 0) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse - 1; + } else { + // this is begin() + curr_slot = 0; + } + + return *this; + } + + //! Postfix-- backstep the iterator to the last slot. + iterator operator--(int) { + iterator tmp = *this; // copy ourselves + + if (curr_slot > 0) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse - 1; + } else { + // this is begin() + curr_slot = 0; + } + + return tmp; + } + + //! Equality of iterators. + bool operator==(const iterator &x) const { return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); } + + //! Inequality of iterators. + bool operator!=(const iterator &x) const { return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); } + }; + + //! STL-like read-only iterator object for B+ tree items. The iterator + //! points to a specific slot number in a leaf. + class const_iterator { + public: + // *** Types + + //! The key type of the btree. Returned by key(). + typedef typename BTree::key_type key_type; + + //! The value type of the btree. Returned by operator*(). + typedef typename BTree::value_type value_type; + + //! Reference to the value_type. STL required. + typedef const value_type &reference; + + //! Pointer to the value_type. STL required. + typedef const value_type *pointer; + + //! STL-magic iterator category + typedef std::bidirectional_iterator_tag iterator_category; + + //! STL-magic + typedef ptrdiff_t difference_type; + + //! Our own type + typedef const_iterator self; + + private: + // *** Members + + //! The currently referenced leaf node of the tree + const typename BTree::LeafNode *curr_leaf; + + //! Current key/data slot referenced + unsigned short curr_slot; + + //! Friendly to the reverse_const_iterator, so it may access the two + //! data items directly + friend class const_reverse_iterator; + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access + // the B+ tree internals. This was added for wxBTreeDemo to be able to + // draw the tree. + TLX_BTREE_FRIENDS; + + public: + // *** Methods + + //! Default-Constructor of a const iterator + const_iterator() : curr_leaf(nullptr), curr_slot(0) {} + + //! Initializing-Constructor of a const iterator + const_iterator(const typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} + + //! Copy-constructor from a mutable iterator + const_iterator(const iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Copy-constructor from a mutable reverse iterator + const_iterator(const reverse_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Copy-constructor from a const reverse iterator + const_iterator(const const_reverse_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Dereference the iterator. + reference operator*() const { return curr_leaf->slotdata[curr_slot]; } + + //! Dereference the iterator. + pointer operator->() const { return &curr_leaf->slotdata[curr_slot]; } + + //! Key of the current slot. + const key_type &key() const { return curr_leaf->key(curr_slot); } + + //! Prefix++ advance the iterator to the next slot. + const_iterator &operator++() { + if (curr_slot + 1u < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 0; + } else { + // this is end() + curr_slot = curr_leaf->slotuse; + } + + return *this; + } + + //! Postfix++ advance the iterator to the next slot. + const_iterator operator++(int) { + const_iterator tmp = *this; // copy ourselves + + if (curr_slot + 1u < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 0; + } else { + // this is end() + curr_slot = curr_leaf->slotuse; + } + + return tmp; + } + + //! Prefix-- backstep the iterator to the last slot. + const_iterator &operator--() { + if (curr_slot > 0) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse - 1; + } else { + // this is begin() + curr_slot = 0; + } + + return *this; + } + + //! Postfix-- backstep the iterator to the last slot. + const_iterator operator--(int) { + const_iterator tmp = *this; // copy ourselves + + if (curr_slot > 0) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse - 1; + } else { + // this is begin() + curr_slot = 0; + } + + return tmp; + } + + //! Equality of iterators. + bool operator==(const const_iterator &x) const { return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); } + + //! Inequality of iterators. + bool operator!=(const const_iterator &x) const { return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); } + }; + + //! STL-like mutable reverse iterator object for B+ tree items. The + //! iterator points to a specific slot number in a leaf. + class reverse_iterator { + public: + // *** Types + + //! The key type of the btree. Returned by key(). + typedef typename BTree::key_type key_type; + + //! The value type of the btree. Returned by operator*(). + typedef typename BTree::value_type value_type; + + //! Reference to the value_type. STL required. + typedef value_type &reference; + + //! Pointer to the value_type. STL required. + typedef value_type *pointer; + + //! STL-magic iterator category + typedef std::bidirectional_iterator_tag iterator_category; + + //! STL-magic + typedef ptrdiff_t difference_type; + + //! Our own type + typedef reverse_iterator self; + + private: + // *** Members + + //! The currently referenced leaf node of the tree + typename BTree::LeafNode *curr_leaf; + + //! One slot past the current key/data slot referenced. + unsigned short curr_slot; + + //! Friendly to the const_iterator, so it may access the two data items + //! directly + friend class iterator; + + //! Also friendly to the const_iterator, so it may access the two data + //! items directly + friend class const_iterator; + + //! Also friendly to the const_iterator, so it may access the two data + //! items directly + friend class const_reverse_iterator; + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access + // the B+ tree internals. This was added for wxBTreeDemo to be able to + // draw the tree. + TLX_BTREE_FRIENDS; + + public: + // *** Methods + + //! Default-Constructor of a reverse iterator + reverse_iterator() : curr_leaf(nullptr), curr_slot(0) {} + + //! Initializing-Constructor of a mutable reverse iterator + reverse_iterator(typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} + + //! Copy-constructor from a mutable iterator + reverse_iterator(const iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Dereference the iterator. + reference operator*() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return curr_leaf->slotdata[curr_slot - 1]; + } + + //! Dereference the iterator. + pointer operator->() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return &curr_leaf->slotdata[curr_slot - 1]; + } + + //! Key of the current slot. + const key_type &key() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return curr_leaf->key(curr_slot - 1); + } + + //! Prefix++ advance the iterator to the next slot. + reverse_iterator &operator++() { + if (curr_slot > 1) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse; + } else { + // this is begin() == rend() + curr_slot = 0; + } + + return *this; + } + + //! Postfix++ advance the iterator to the next slot. + reverse_iterator operator++(int) { + reverse_iterator tmp = *this; // copy ourselves + + if (curr_slot > 1) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse; + } else { + // this is begin() == rend() + curr_slot = 0; + } + + return tmp; + } + + //! Prefix-- backstep the iterator to the last slot. + reverse_iterator &operator--() { + if (curr_slot < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 1; + } else { + // this is end() == rbegin() + curr_slot = curr_leaf->slotuse; + } + + return *this; + } + + //! Postfix-- backstep the iterator to the last slot. + reverse_iterator operator--(int) { + reverse_iterator tmp = *this; // copy ourselves + + if (curr_slot < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 1; + } else { + // this is end() == rbegin() + curr_slot = curr_leaf->slotuse; + } + + return tmp; + } + + //! Equality of iterators. + bool operator==(const reverse_iterator &x) const { + return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); + } + + //! Inequality of iterators. + bool operator!=(const reverse_iterator &x) const { + return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); + } + }; + + //! STL-like read-only reverse iterator object for B+ tree items. The + //! iterator points to a specific slot number in a leaf. + class const_reverse_iterator { + public: + // *** Types + + //! The key type of the btree. Returned by key(). + typedef typename BTree::key_type key_type; + + //! The value type of the btree. Returned by operator*(). + typedef typename BTree::value_type value_type; + + //! Reference to the value_type. STL required. + typedef const value_type &reference; + + //! Pointer to the value_type. STL required. + typedef const value_type *pointer; + + //! STL-magic iterator category + typedef std::bidirectional_iterator_tag iterator_category; + + //! STL-magic + typedef ptrdiff_t difference_type; + + //! Our own type + typedef const_reverse_iterator self; + + private: + // *** Members + + //! The currently referenced leaf node of the tree + const typename BTree::LeafNode *curr_leaf; + + //! One slot past the current key/data slot referenced. + unsigned short curr_slot; + + //! Friendly to the const_iterator, so it may access the two data items + //! directly. + friend class reverse_iterator; + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access + // the B+ tree internals. This was added for wxBTreeDemo to be able to + // draw the tree. + TLX_BTREE_FRIENDS; + + public: + // *** Methods + + //! Default-Constructor of a const reverse iterator. + const_reverse_iterator() : curr_leaf(nullptr), curr_slot(0) {} + + //! Initializing-Constructor of a const reverse iterator. + const_reverse_iterator(const typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} + + //! Copy-constructor from a mutable iterator. + const_reverse_iterator(const iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Copy-constructor from a const iterator. + const_reverse_iterator(const const_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Copy-constructor from a mutable reverse iterator. + const_reverse_iterator(const reverse_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Dereference the iterator. + reference operator*() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return curr_leaf->slotdata[curr_slot - 1]; + } + + //! Dereference the iterator. + pointer operator->() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return &curr_leaf->slotdata[curr_slot - 1]; + } + + //! Key of the current slot. + const key_type &key() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return curr_leaf->key(curr_slot - 1); + } + + //! Prefix++ advance the iterator to the previous slot. + const_reverse_iterator &operator++() { + if (curr_slot > 1) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse; + } else { + // this is begin() == rend() + curr_slot = 0; + } + + return *this; + } + + //! Postfix++ advance the iterator to the previous slot. + const_reverse_iterator operator++(int) { + const_reverse_iterator tmp = *this; // copy ourselves + + if (curr_slot > 1) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse; + } else { + // this is begin() == rend() + curr_slot = 0; + } + + return tmp; + } + + //! Prefix-- backstep the iterator to the next slot. + const_reverse_iterator &operator--() { + if (curr_slot < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 1; + } else { + // this is end() == rbegin() + curr_slot = curr_leaf->slotuse; + } + + return *this; + } + + //! Postfix-- backstep the iterator to the next slot. + const_reverse_iterator operator--(int) { + const_reverse_iterator tmp = *this; // copy ourselves + + if (curr_slot < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 1; + } else { + // this is end() == rbegin() + curr_slot = curr_leaf->slotuse; + } + + return tmp; + } + + //! Equality of iterators. + bool operator==(const const_reverse_iterator &x) const { + return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); + } + + //! Inequality of iterators. + bool operator!=(const const_reverse_iterator &x) const { + return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); + } + }; + + //! \} + + public: + //! \name Small Statistics Structure + //! \{ + + /*! + * A small struct containing basic statistics about the B+ tree. It can be + * fetched using get_stats(). + */ + struct tree_stats { + //! Number of items in the B+ tree + size_type size; + + //! Number of leaves in the B+ tree + size_type leaves; + + //! Number of inner nodes in the B+ tree + size_type inner_nodes; + + //! Base B+ tree parameter: The number of key/data slots in each leaf + static const unsigned short leaf_slots = Self::leaf_slotmax; + + //! Base B+ tree parameter: The number of key slots in each inner node. + static const unsigned short inner_slots = Self::inner_slotmax; + + //! Zero initialized + tree_stats() : size(0), leaves(0), inner_nodes(0) {} + + //! Return the total number of nodes + size_type nodes() const { return inner_nodes + leaves; } + + //! Return the average fill of leaves + double avgfill_leaves() const { return static_cast(size) / (leaves * leaf_slots); } + }; + + //! \} + + private: + //! \name Tree Object Data Members + //! \{ + + //! Pointer to the B+ tree's root node, either leaf or inner node. + node *root_; + + //! Pointer to first leaf in the double linked leaf chain. + LeafNode *head_leaf_; + + //! Pointer to last leaf in the double linked leaf chain. + LeafNode *tail_leaf_; + + //! Other small statistics about the B+ tree. + tree_stats stats_; + + //! Key comparison object. More comparison functions are generated from + //! this < relation. + key_compare key_less_; + + //! Memory allocator. + allocator_type allocator_; + + //! \} + + public: + //! \name Constructors and Destructor + //! \{ + + //! Default constructor initializing an empty B+ tree with the standard key + //! comparison function. + explicit BTree(const allocator_type &alloc = allocator_type()) + : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), allocator_(alloc) {} + + //! Constructor initializing an empty B+ tree with a special key + //! comparison object. + explicit BTree(const key_compare &kcf, const allocator_type &alloc = allocator_type()) + : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), key_less_(kcf), allocator_(alloc) {} + + //! Constructor initializing a B+ tree with the range [first,last). The + //! range need not be sorted. To create a B+ tree from a sorted range, use + //! bulk_load(). + template + BTree(InputIterator first, InputIterator last, const allocator_type &alloc = allocator_type()) + : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), allocator_(alloc) { + insert(first, last); + } + + //! Constructor initializing a B+ tree with the range [first,last) and a + //! special key comparison object. The range need not be sorted. To create + //! a B+ tree from a sorted range, use bulk_load(). + template + BTree(InputIterator first, InputIterator last, const key_compare &kcf, const allocator_type &alloc = allocator_type()) + : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), key_less_(kcf), allocator_(alloc) { + insert(first, last); + } + + //! Frees up all used B+ tree memory pages + ~BTree() { clear(); } + + //! Fast swapping of two identical B+ tree objects. + void swap(BTree &from) { + std::swap(root_, from.root_); + std::swap(head_leaf_, from.head_leaf_); + std::swap(tail_leaf_, from.tail_leaf_); + std::swap(stats_, from.stats_); + std::swap(key_less_, from.key_less_); + std::swap(allocator_, from.allocator_); + } + + //! \} + + public: + //! \name Key and Value Comparison Function Objects + //! \{ + + //! Function class to compare value_type objects. Required by the STL + class value_compare { + protected: + //! Key comparison function from the template parameter + key_compare key_comp; + + //! Constructor called from BTree::value_comp() + explicit value_compare(key_compare kc) : key_comp(kc) {} + + //! Friendly to the btree class so it may call the constructor + friend class BTree; + + public: + //! Function call "less"-operator resulting in true if x < y. + bool operator()(const value_type &x, const value_type &y) const { return key_comp(x.first, y.first); } + }; + + //! Constant access to the key comparison object sorting the B+ tree. + key_compare key_comp() const { return key_less_; } + + //! Constant access to a constructed value_type comparison object. Required + //! by the STL. + value_compare value_comp() const { return value_compare(key_less_); } + + //! \} + + private: + //! \name Convenient Key Comparison Functions Generated From key_less + //! \{ + + //! True if a < b ? "constructed" from key_less_() + bool key_less(const key_type &a, const key_type &b) const { return key_less_(a, b); } + + //! True if a <= b ? constructed from key_less() + bool key_lessequal(const key_type &a, const key_type &b) const { return !key_less_(b, a); } + + //! True if a > b ? constructed from key_less() + bool key_greater(const key_type &a, const key_type &b) const { return key_less_(b, a); } + + //! True if a >= b ? constructed from key_less() + bool key_greaterequal(const key_type &a, const key_type &b) const { return !key_less_(a, b); } + + //! True if a == b ? constructed from key_less(). This requires the < + //! relation to be a total order, otherwise the B+ tree cannot be sorted. + bool key_equal(const key_type &a, const key_type &b) const { return !key_less_(a, b) && !key_less_(b, a); } + + //! \} + + public: + //! \name Allocators + //! \{ + + //! Return the base node allocator provided during construction. + allocator_type get_allocator() const { return allocator_; } + + //! \} + + private: + //! \name Node Object Allocation and Deallocation Functions + //! \{ + + //! Return an allocator for LeafNode objects. + typename LeafNode::alloc_type leaf_node_allocator() { return typename LeafNode::alloc_type(allocator_); } + + //! Return an allocator for InnerNode objects. + typename InnerNode::alloc_type inner_node_allocator() { return typename InnerNode::alloc_type(allocator_); } + + //! Allocate and initialize a leaf node + LeafNode *allocate_leaf() { + LeafNode *n = new (leaf_node_allocator().allocate(1)) LeafNode(); + n->initialize(); + stats_.leaves++; + return n; + } + + //! Allocate and initialize an inner node + InnerNode *allocate_inner(unsigned short level) { + InnerNode *n = new (inner_node_allocator().allocate(1)) InnerNode(); + n->initialize(level); + stats_.inner_nodes++; + return n; + } + + //! Correctly free either inner or leaf node, destructs all contained key + //! and value objects. + void free_node(node *n) { + if (n->is_leafnode()) { + LeafNode *ln = static_cast(n); + typename LeafNode::alloc_type a(leaf_node_allocator()); + std::allocator_traits::destroy(a, ln); + std::allocator_traits::deallocate(a, ln, 1); + stats_.leaves--; + } else { + InnerNode *in = static_cast(n); + typename InnerNode::alloc_type a(inner_node_allocator()); + std::allocator_traits::destroy(a, in); + std::allocator_traits::deallocate(a, in, 1); + stats_.inner_nodes--; + } + } + + //! \} + + public: + //! \name Fast Destruction of the B+ Tree + //! \{ + + //! Frees all key/data pairs and all nodes of the tree. + void clear() { + if (root_) { + clear_recursive(root_); + free_node(root_); + + root_ = nullptr; + head_leaf_ = tail_leaf_ = nullptr; + + stats_ = tree_stats(); + } + + TLX_BTREE_ASSERT(stats_.size == 0); + } + + private: + //! Recursively free up nodes. + void clear_recursive(node *n) { + if (n->is_leafnode()) { + LeafNode *leafnode = static_cast(n); + + for (unsigned short slot = 0; slot < leafnode->slotuse; ++slot) { + // data objects are deleted by LeafNode's destructor + } + } else { + InnerNode *innernode = static_cast(n); + + for (unsigned short slot = 0; slot < innernode->slotuse + 1; ++slot) { + clear_recursive(innernode->childid[slot]); + free_node(innernode->childid[slot]); + } + } + } + + //! \} + + public: + //! \name STL Iterator Construction Functions + //! \{ + + //! Constructs a read/data-write iterator that points to the first slot in + //! the first leaf of the B+ tree. + iterator begin() { return iterator(head_leaf_, 0); } + + //! Constructs a read/data-write iterator that points to the first invalid + //! slot in the last leaf of the B+ tree. + iterator end() { return iterator(tail_leaf_, tail_leaf_ ? tail_leaf_->slotuse : 0); } + + //! Constructs a read-only constant iterator that points to the first slot + //! in the first leaf of the B+ tree. + const_iterator begin() const { return const_iterator(head_leaf_, 0); } + + //! Constructs a read-only constant iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. + const_iterator end() const { return const_iterator(tail_leaf_, tail_leaf_ ? tail_leaf_->slotuse : 0); } + + //! Constructs a read/data-write reverse iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. Uses STL magic. + reverse_iterator rbegin() { return reverse_iterator(end()); } + + //! Constructs a read/data-write reverse iterator that points to the first + //! slot in the first leaf of the B+ tree. Uses STL magic. + reverse_iterator rend() { return reverse_iterator(begin()); } + + //! Constructs a read-only reverse iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. Uses STL magic. + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + + //! Constructs a read-only reverse iterator that points to the first slot + //! in the first leaf of the B+ tree. Uses STL magic. + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + //! \} + + private: + //! \name B+ Tree Node Binary Search Functions + //! \{ + + //! Searches for the first key in the node n greater or equal to key. Uses + //! binary search with an optional linear self-verification. This is a + //! template function, because the slotkey array is located at different + //! places in LeafNode and InnerNode. + template + unsigned short find_lower(const node_type *n, const key_type &key) const { + if (sizeof(*n) > traits::binsearch_threshold) { + if (n->slotuse == 0) return 0; + + unsigned short lo = 0, hi = n->slotuse; + + while (lo < hi) { + unsigned short mid = (lo + hi) >> 1; + + if (key_lessequal(key, n->key(mid))) { + hi = mid; // key <= mid + } else { + lo = mid + 1; // key > mid + } + } + + TLX_BTREE_PRINT("BTree::find_lower: on " << n << " key " << key << " -> " << lo << " / " << hi); + + // verify result using simple linear search + if (self_verify) { + unsigned short i = 0; + while (i < n->slotuse && key_less(n->key(i), key)) ++i; + + TLX_BTREE_PRINT("BTree::find_lower: testfind: " << i); + TLX_BTREE_ASSERT(i == lo); + } + + return lo; + } else // for nodes <= binsearch_threshold do linear search. + { + unsigned short lo = 0; + while (lo < n->slotuse && key_less(n->key(lo), key)) ++lo; + return lo; + } + } + + //! Searches for the first key in the node n greater than key. Uses binary + //! search with an optional linear self-verification. This is a template + //! function, because the slotkey array is located at different places in + //! LeafNode and InnerNode. + template + unsigned short find_upper(const node_type *n, const key_type &key) const { + if (sizeof(*n) > traits::binsearch_threshold) { + if (n->slotuse == 0) return 0; + + unsigned short lo = 0, hi = n->slotuse; + + while (lo < hi) { + unsigned short mid = (lo + hi) >> 1; + + if (key_less(key, n->key(mid))) { + hi = mid; // key < mid + } else { + lo = mid + 1; // key >= mid + } + } + + TLX_BTREE_PRINT("BTree::find_upper: on " << n << " key " << key << " -> " << lo << " / " << hi); + + // verify result using simple linear search + if (self_verify) { + unsigned short i = 0; + while (i < n->slotuse && key_lessequal(n->key(i), key)) ++i; + + TLX_BTREE_PRINT("BTree::find_upper testfind: " << i); + TLX_BTREE_ASSERT(i == hi); + } + + return lo; + } else // for nodes <= binsearch_threshold do linear search. + { + unsigned short lo = 0; + while (lo < n->slotuse && key_lessequal(n->key(lo), key)) ++lo; + return lo; + } + } + + //! \} + + public: + //! \name Access Functions to the Item Count + //! \{ + + //! Return the number of key/data pairs in the B+ tree + size_type size() const { return stats_.size; } + + //! Returns true if there is at least one key/data pair in the B+ tree + bool empty() const { return (size() == size_type(0)); } + + //! Returns the largest possible size of the B+ Tree. This is just a + //! function required by the STL standard, the B+ Tree can hold more items. + size_type max_size() const { return size_type(-1); } + + //! Return a const reference to the current statistics. + const struct tree_stats &get_stats() const { return stats_; } + + //! \} + + public: + //! \name STL Access Functions Querying the Tree by Descending to a Leaf + //! \{ + + //! Non-STL function checking whether a key is in the B+ tree. The same as + //! (find(k) != end()) or (count() != 0). + bool exists(const key_type &key) const { + const node *n = root_; + if (!n) return false; + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))); + } + + //! Tries to locate a key in the B+ tree and returns an iterator to the + //! key/data slot if found. If unsuccessful it returns end(). + iterator find(const key_type &key) { + node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))) ? iterator(leaf, slot) : end(); + } + + //! Tries to locate a key in the B+ tree and returns an constant iterator to + //! the key/data slot if found. If unsuccessful it returns end(). + const_iterator find(const key_type &key) const { + const node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))) ? const_iterator(leaf, slot) : end(); + } + + //! Tries to locate a key in the B+ tree and returns the number of identical + //! key entries found. + size_type count(const key_type &key) const { + const node *n = root_; + if (!n) return 0; + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + size_type num = 0; + + while (leaf && slot < leaf->slotuse && key_equal(key, leaf->key(slot))) { + ++num; + if (++slot >= leaf->slotuse) { + leaf = leaf->next_leaf; + slot = 0; + } + } + + return num; + } + + //! Searches the B+ tree and returns an iterator to the first pair equal to + //! or greater than key, or end() if all keys are smaller. + iterator lower_bound(const key_type &key) { + node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return iterator(leaf, slot); + } + + //! Searches the B+ tree and returns a constant iterator to the first pair + //! equal to or greater than key, or end() if all keys are smaller. + const_iterator lower_bound(const key_type &key) const { + const node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return const_iterator(leaf, slot); + } + + //! Searches the B+ tree and returns an iterator to the first pair greater + //! than key, or end() if all keys are smaller or equal. + iterator upper_bound(const key_type &key) { + node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_upper(inner, key); + + n = inner->childid[slot]; + } + + LeafNode *leaf = static_cast(n); + + unsigned short slot = find_upper(leaf, key); + return iterator(leaf, slot); + } + + //! Searches the B+ tree and returns a constant iterator to the first pair + //! greater than key, or end() if all keys are smaller or equal. + const_iterator upper_bound(const key_type &key) const { + const node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_upper(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_upper(leaf, key); + return const_iterator(leaf, slot); + } + + //! Searches the B+ tree and returns both lower_bound() and upper_bound(). + std::pair equal_range(const key_type &key) { + return std::pair(lower_bound(key), upper_bound(key)); + } + + //! Searches the B+ tree and returns both lower_bound() and upper_bound(). + std::pair equal_range(const key_type &key) const { + return std::pair(lower_bound(key), upper_bound(key)); + } + + //! \} + + public: + //! \name B+ Tree Object Comparison Functions + //! \{ + + //! Equality relation of B+ trees of the same type. B+ trees of the same + //! size and equal elements (both key and data) are considered equal. Beware + //! of the random ordering of duplicate keys. + bool operator==(const BTree &other) const { + return (size() == other.size()) && std::equal(begin(), end(), other.begin()); + } + + //! Inequality relation. Based on operator==. + bool operator!=(const BTree &other) const { return !(*this == other); } + + //! Total ordering relation of B+ trees of the same type. It uses + //! std::lexicographical_compare() for the actual comparison of elements. + bool operator<(const BTree &other) const { + return std::lexicographical_compare(begin(), end(), other.begin(), other.end()); + } + + //! Greater relation. Based on operator<. + bool operator>(const BTree &other) const { return other < *this; } + + //! Less-equal relation. Based on operator<. + bool operator<=(const BTree &other) const { return !(other < *this); } + + //! Greater-equal relation. Based on operator<. + bool operator>=(const BTree &other) const { return !(*this < other); } + + //! \} + + public: + //! \name Fast Copy: Assign Operator and Copy Constructors + //! \{ + + //! Assignment operator. All the key/data pairs are copied. + BTree &operator=(const BTree &other) { + if (this != &other) { + clear(); + + key_less_ = other.key_comp(); + allocator_ = other.get_allocator(); + + if (other.size() != 0) { + stats_.leaves = stats_.inner_nodes = 0; + if (other.root_) { + root_ = copy_recursive(other.root_); + } + stats_ = other.stats_; + } + + if (self_verify) verify(); + } + return *this; + } + + //! Copy constructor. The newly initialized B+ tree object will contain a + //! copy of all key/data pairs. + BTree(const BTree &other) + : root_(nullptr), + head_leaf_(nullptr), + tail_leaf_(nullptr), + stats_(other.stats_), + key_less_(other.key_comp()), + allocator_(other.get_allocator()) { + if (size() > 0) { + stats_.leaves = stats_.inner_nodes = 0; + if (other.root_) { + root_ = copy_recursive(other.root_); + } + if (self_verify) verify(); + } + } + + private: + //! Recursively copy nodes from another B+ tree object + struct node *copy_recursive(const node *n) { + if (n->is_leafnode()) { + const LeafNode *leaf = static_cast(n); + LeafNode *newleaf = allocate_leaf(); + + newleaf->slotuse = leaf->slotuse; + std::copy(leaf->slotdata, leaf->slotdata + leaf->slotuse, newleaf->slotdata); + + if (head_leaf_ == nullptr) { + head_leaf_ = tail_leaf_ = newleaf; + newleaf->prev_leaf = newleaf->next_leaf = nullptr; + } else { + newleaf->prev_leaf = tail_leaf_; + tail_leaf_->next_leaf = newleaf; + tail_leaf_ = newleaf; + } + + return newleaf; + } else { + const InnerNode *inner = static_cast(n); + InnerNode *newinner = allocate_inner(inner->level); + + newinner->slotuse = inner->slotuse; + std::copy(inner->slotkey, inner->slotkey + inner->slotuse, newinner->slotkey); + + for (unsigned short slot = 0; slot <= inner->slotuse; ++slot) { + newinner->childid[slot] = copy_recursive(inner->childid[slot]); + } + + return newinner; + } + } + + //! \} + + public: + //! \name Public Insertion Functions + //! \{ + + //! Attempt to insert a key/data pair into the B+ tree. If the tree does not + //! allow duplicate keys, then the insert may fail if it is already present. + std::pair insert(const value_type &x) { return insert_start(key_of_value::get(x), x); } + + //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is + //! currently ignored by the B+ tree insertion routine. + iterator insert(iterator /* hint */, const value_type &x) { return insert_start(key_of_value::get(x), x).first; } + + //! Attempt to insert the range [first,last) of value_type pairs into the B+ + //! tree. Each key/data pair is inserted individually; to bulk load the + //! tree, use a constructor with range. + template + void insert(InputIterator first, InputIterator last) { + InputIterator iter = first; + while (iter != last) { + insert(*iter); + ++iter; + } + } + + //! \} + + private: + //! \name Private Insertion Functions + //! \{ + + //! Start the insertion descent at the current root and handle root splits. + //! Returns true if the item was inserted + std::pair insert_start(const key_type &key, const value_type &value) { + node *newchild = nullptr; + key_type newkey = key_type(); + + if (root_ == nullptr) { + root_ = head_leaf_ = tail_leaf_ = allocate_leaf(); + } + + std::pair r = insert_descend(root_, key, value, &newkey, &newchild); + + if (newchild) { + // this only occurs if insert_descend() could not insert the key + // into the root node, this mean the root is full and a new root + // needs to be created. + InnerNode *newroot = allocate_inner(root_->level + 1); + newroot->slotkey[0] = newkey; + + newroot->childid[0] = root_; + newroot->childid[1] = newchild; + + newroot->slotuse = 1; + + root_ = newroot; + } + + // increment size if the item was inserted + if (r.second) ++stats_.size; + +#ifdef TLX_BTREE_DEBUG + if (debug) print(std::cout); +#endif + + if (self_verify) { + verify(); + TLX_BTREE_ASSERT(exists(key)); + } + + return r; + } + + /*! + * Insert an item into the B+ tree. + * + * Descend down the nodes to a leaf, insert the key/data pair in a free + * slot. If the node overflows, then it must be split and the new split node + * inserted into the parent. Unroll / this splitting up to the root. + */ + std::pair insert_descend(node *n, const key_type &key, const value_type &value, key_type *splitkey, + node **splitnode) { + if (!n->is_leafnode()) { + InnerNode *inner = static_cast(n); + + key_type newkey = key_type(); + node *newchild = nullptr; + + unsigned short slot = find_lower(inner, key); + + TLX_BTREE_PRINT("BTree::insert_descend into " << inner->childid[slot]); + + std::pair r = insert_descend(inner->childid[slot], key, value, &newkey, &newchild); + + if (newchild) { + TLX_BTREE_PRINT("BTree::insert_descend newchild" + << " with key " << newkey << " node " << newchild << " at slot " << slot); + + if (inner->is_full()) { + split_inner_node(inner, splitkey, splitnode, slot); + + TLX_BTREE_PRINT("BTree::insert_descend done split_inner:" + << " putslot: " << slot << " putkey: " << newkey << " upkey: " << *splitkey); + +#ifdef TLX_BTREE_DEBUG + if (debug) { + print_node(std::cout, inner); + print_node(std::cout, *splitnode); + } +#endif + + // check if insert slot is in the split sibling node + TLX_BTREE_PRINT("BTree::insert_descend switch: " << slot << " > " << inner->slotuse + 1); + + if (slot == inner->slotuse + 1 && inner->slotuse < (*splitnode)->slotuse) { + // special case when the insert slot matches the split + // place between the two nodes, then the insert key + // becomes the split key. + + TLX_BTREE_ASSERT(inner->slotuse + 1 < inner_slotmax); + + InnerNode *split = static_cast(*splitnode); + + // move the split key and it's datum into the left node + inner->slotkey[inner->slotuse] = *splitkey; + inner->childid[inner->slotuse + 1] = split->childid[0]; + inner->slotuse++; + + // set new split key and move corresponding datum into + // right node + split->childid[0] = newchild; + *splitkey = newkey; + + return r; + } else if (slot >= inner->slotuse + 1) { + // in case the insert slot is in the newly create split + // node, we reuse the code below. + + slot -= inner->slotuse + 1; + inner = static_cast(*splitnode); + TLX_BTREE_PRINT( + "BTree::insert_descend switching to " + "splitted node " + << inner << " slot " << slot); + } + } + + // move items and put pointer to child node into correct slot + TLX_BTREE_ASSERT(slot >= 0 && slot <= inner->slotuse); + + std::copy_backward(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + inner->slotuse + 1); + std::copy_backward(inner->childid + slot, inner->childid + inner->slotuse + 1, + inner->childid + inner->slotuse + 2); + + inner->slotkey[slot] = newkey; + inner->childid[slot + 1] = newchild; + inner->slotuse++; + } + + return r; + } else // n->is_leafnode() == true + { + LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + + if (!allow_duplicates && slot < leaf->slotuse && key_equal(key, leaf->key(slot))) { + return std::pair(iterator(leaf, slot), false); + } + + if (leaf->is_full()) { + split_leaf_node(leaf, splitkey, splitnode); + + // check if insert slot is in the split sibling node + if (slot >= leaf->slotuse) { + slot -= leaf->slotuse; + leaf = static_cast(*splitnode); + } + } + + // move items and put data item into correct data slot + TLX_BTREE_ASSERT(slot >= 0 && slot <= leaf->slotuse); + + std::copy_backward(leaf->slotdata + slot, leaf->slotdata + leaf->slotuse, leaf->slotdata + leaf->slotuse + 1); + + leaf->slotdata[slot] = value; + leaf->slotuse++; + + if (splitnode && leaf != *splitnode && slot == leaf->slotuse - 1) { + // special case: the node was split, and the insert is at the + // last slot of the old node. then the splitkey must be updated. + *splitkey = key; + } + + return std::pair(iterator(leaf, slot), true); + } + } + + //! Split up a leaf node into two equally-filled sibling leaves. Returns the + //! new nodes and it's insertion key in the two parameters. + void split_leaf_node(LeafNode *leaf, key_type *out_newkey, node **out_newleaf) { + TLX_BTREE_ASSERT(leaf->is_full()); + + unsigned short mid = (leaf->slotuse >> 1); + + TLX_BTREE_PRINT("BTree::split_leaf_node on " << leaf); + + LeafNode *newleaf = allocate_leaf(); + + newleaf->slotuse = leaf->slotuse - mid; + + newleaf->next_leaf = leaf->next_leaf; + if (newleaf->next_leaf == nullptr) { + TLX_BTREE_ASSERT(leaf == tail_leaf_); + tail_leaf_ = newleaf; + } else { + newleaf->next_leaf->prev_leaf = newleaf; + } + + std::copy(leaf->slotdata + mid, leaf->slotdata + leaf->slotuse, newleaf->slotdata); + + leaf->slotuse = mid; + leaf->next_leaf = newleaf; + newleaf->prev_leaf = leaf; + + *out_newkey = leaf->key(leaf->slotuse - 1); + *out_newleaf = newleaf; + } + + //! Split up an inner node into two equally-filled sibling nodes. Returns + //! the new nodes and it's insertion key in the two parameters. Requires the + //! slot of the item will be inserted, so the nodes will be the same size + //! after the insert. + void split_inner_node(InnerNode *inner, key_type *out_newkey, node **out_newinner, unsigned int addslot) { + TLX_BTREE_ASSERT(inner->is_full()); + + unsigned short mid = (inner->slotuse >> 1); + + TLX_BTREE_PRINT("BTree::split_inner: mid " << mid << " addslot " << addslot); + + // if the split is uneven and the overflowing item will be put into the + // larger node, then the smaller split node may underflow + if (addslot <= mid && mid > inner->slotuse - (mid + 1)) mid--; + + TLX_BTREE_PRINT("BTree::split_inner: mid " << mid << " addslot " << addslot); + + TLX_BTREE_PRINT("BTree::split_inner_node on " << inner << " into two nodes " << mid << " and " + << inner->slotuse - (mid + 1) << " sized"); + + InnerNode *newinner = allocate_inner(inner->level); + + newinner->slotuse = inner->slotuse - (mid + 1); + + std::copy(inner->slotkey + mid + 1, inner->slotkey + inner->slotuse, newinner->slotkey); + std::copy(inner->childid + mid + 1, inner->childid + inner->slotuse + 1, newinner->childid); + + inner->slotuse = mid; + + *out_newkey = inner->key(mid); + *out_newinner = newinner; + } + + //! \} + + public: + //! \name Bulk Loader - Construct Tree from Sorted Sequence + //! \{ + + //! Bulk load a sorted range. Loads items into leaves and constructs a + //! B-tree above them. The tree must be empty when calling this function. + template + void bulk_load(Iterator ibegin, Iterator iend) { + TLX_BTREE_ASSERT(empty()); + + stats_.size = iend - ibegin; + + // calculate number of leaves needed, round up. + size_t num_items = iend - ibegin; + size_t num_leaves = (num_items + leaf_slotmax - 1) / leaf_slotmax; + + TLX_BTREE_PRINT("BTree::bulk_load, level 0: " + << stats_.size << " items into " << num_leaves << " leaves with up to " + << ((iend - ibegin + num_leaves - 1) / num_leaves) << " items per leaf."); + + Iterator it = ibegin; + for (size_t i = 0; i < num_leaves; ++i) { + // allocate new leaf node + LeafNode *leaf = allocate_leaf(); + + // copy keys or (key,value) pairs into leaf nodes, uses template + // switch leaf->set_slot(). + leaf->slotuse = static_cast(num_items / (num_leaves - i)); + for (size_t s = 0; s < leaf->slotuse; ++s, ++it) leaf->set_slot(s, *it); + + if (tail_leaf_ != nullptr) { + tail_leaf_->next_leaf = leaf; + leaf->prev_leaf = tail_leaf_; + } else { + head_leaf_ = leaf; + } + tail_leaf_ = leaf; + + num_items -= leaf->slotuse; + } + + TLX_BTREE_ASSERT(it == iend && num_items == 0); + + // if the btree is so small to fit into one leaf, then we're done. + if (head_leaf_ == tail_leaf_) { + root_ = head_leaf_; + return; + } + + TLX_BTREE_ASSERT(stats_.leaves == num_leaves); + + // create first level of inner nodes, pointing to the leaves. + size_t num_parents = (num_leaves + (inner_slotmax + 1) - 1) / (inner_slotmax + 1); + + TLX_BTREE_PRINT("BTree::bulk_load, level 1: " + << num_leaves << " leaves in " << num_parents << " inner nodes with up to " + << ((num_leaves + num_parents - 1) / num_parents) << " leaves per inner node."); + + // save inner nodes and maxkey for next level. + typedef std::pair nextlevel_type; + nextlevel_type *nextlevel = new nextlevel_type[num_parents]; + + LeafNode *leaf = head_leaf_; + for (size_t i = 0; i < num_parents; ++i) { + // allocate new inner node at level 1 + InnerNode *n = allocate_inner(1); + + n->slotuse = static_cast(num_leaves / (num_parents - i)); + TLX_BTREE_ASSERT(n->slotuse > 0); + // this counts keys, but an inner node has keys+1 children. + --n->slotuse; + + // copy last key from each leaf and set child + for (unsigned short s = 0; s < n->slotuse; ++s) { + n->slotkey[s] = leaf->key(leaf->slotuse - 1); + n->childid[s] = leaf; + leaf = leaf->next_leaf; + } + n->childid[n->slotuse] = leaf; + + // track max key of any descendant. + nextlevel[i].first = n; + nextlevel[i].second = &leaf->key(leaf->slotuse - 1); + + leaf = leaf->next_leaf; + num_leaves -= n->slotuse + 1; + } + + TLX_BTREE_ASSERT(leaf == nullptr && num_leaves == 0); + + // recursively build inner nodes pointing to inner nodes. + for (int level = 2; num_parents != 1; ++level) { + size_t num_children = num_parents; + num_parents = (num_children + (inner_slotmax + 1) - 1) / (inner_slotmax + 1); + + TLX_BTREE_PRINT("BTree::bulk_load, level " + << level << ": " << num_children << " children in " << num_parents << " inner nodes with up to " + << ((num_children + num_parents - 1) / num_parents) << " children per inner node."); + + size_t inner_index = 0; + for (size_t i = 0; i < num_parents; ++i) { + // allocate new inner node at level + InnerNode *n = allocate_inner(level); + + n->slotuse = static_cast(num_children / (num_parents - i)); + TLX_BTREE_ASSERT(n->slotuse > 0); + // this counts keys, but an inner node has keys+1 children. + --n->slotuse; + + // copy children and maxkeys from nextlevel + for (unsigned short s = 0; s < n->slotuse; ++s) { + n->slotkey[s] = *nextlevel[inner_index].second; + n->childid[s] = nextlevel[inner_index].first; + ++inner_index; + } + n->childid[n->slotuse] = nextlevel[inner_index].first; + + // reuse nextlevel array for parents, because we can overwrite + // slots we've already consumed. + nextlevel[i].first = n; + nextlevel[i].second = nextlevel[inner_index].second; + + ++inner_index; + num_children -= n->slotuse + 1; + } + + TLX_BTREE_ASSERT(num_children == 0); + } + + root_ = nextlevel[0].first; + delete[] nextlevel; + + if (self_verify) verify(); + } + + //! \} + + private: + //! \name Support Class Encapsulating Deletion Results + //! \{ + + //! Result flags of recursive deletion. + enum result_flags_t { + //! Deletion successful and no fix-ups necessary. + btree_ok = 0, + + //! Deletion not successful because key was not found. + btree_not_found = 1, + + //! Deletion successful, the last key was updated so parent slotkeys + //! need updates. + btree_update_lastkey = 2, + + //! Deletion successful, children nodes were merged and the parent needs + //! to remove the empty node. + btree_fixmerge = 4 + }; + + //! B+ tree recursive deletion has much information which is needs to be + //! passed upward. + struct result_t { + //! Merged result flags + result_flags_t flags; + + //! The key to be updated at the parent's slot + key_type lastkey; + + //! Constructor of a result with a specific flag, this can also be used + //! as for implicit conversion. + result_t(result_flags_t f = btree_ok) // NOLINT + : flags(f), lastkey() {} + + //! Constructor with a lastkey value. + result_t(result_flags_t f, const key_type &k) : flags(f), lastkey(k) {} + + //! Test if this result object has a given flag set. + bool has(result_flags_t f) const { return (flags & f) != 0; } + + //! Merge two results OR-ing the result flags and overwriting lastkeys. + result_t &operator|=(const result_t &other) { + flags = result_flags_t(flags | other.flags); + + // we overwrite existing lastkeys on purpose + if (other.has(btree_update_lastkey)) lastkey = other.lastkey; + + return *this; + } + }; + + //! \} + + public: + //! \name Public Erase Functions + //! \{ + + //! Erases one (the first) of the key/data pairs associated with the given + //! key. + bool erase_one(const key_type &key) { + TLX_BTREE_PRINT("BTree::erase_one(" << key << ") on btree size " << size()); + + if (self_verify) verify(); + + if (!root_) return false; + + result_t result = erase_one_descend(key, root_, nullptr, nullptr, nullptr, nullptr, nullptr, 0); + + if (!result.has(btree_not_found)) --stats_.size; + +#ifdef TLX_BTREE_DEBUG + if (debug) print(std::cout); +#endif + if (self_verify) verify(); + + return !result.has(btree_not_found); + } + + //! Erases all the key/data pairs associated with the given key. This is + //! implemented using erase_one(). + size_type erase(const key_type &key) { + size_type c = 0; + + while (erase_one(key)) { + ++c; + if (!allow_duplicates) break; + } + + return c; + } + + //! Erase the key/data pair referenced by the iterator. + void erase(iterator iter) { + TLX_BTREE_PRINT("BTree::erase_iter(" << iter.curr_leaf << "," << iter.curr_slot << ") on btree size " << size()); + + if (self_verify) verify(); + + if (!root_) return; + + result_t result = erase_iter_descend(iter, root_, nullptr, nullptr, nullptr, nullptr, nullptr, 0); + + if (!result.has(btree_not_found)) --stats_.size; + +#ifdef TLX_BTREE_DEBUG + if (debug) print(std::cout); +#endif + if (self_verify) verify(); + } + +#ifdef BTREE_TODO + //! Erase all key/data pairs in the range [first,last). This function is + //! currently not implemented by the B+ Tree. + void erase(iterator /* first */, iterator /* last */) { abort(); } +#endif + + //! \} + + private: + //! \name Private Erase Functions + //! \{ + + /*! + * Erase one (the first) key/data pair in the B+ tree matching key. + * + * Descends down the tree in search of key. During the descent the parent, + * left and right siblings and their parents are computed and passed + * down. Once the key/data pair is found, it is removed from the leaf. If + * the leaf underflows 6 different cases are handled. These cases resolve + * the underflow by shifting key/data pairs from adjacent sibling nodes, + * merging two sibling nodes or trimming the tree. + */ + result_t erase_one_descend(const key_type &key, node *curr, node *left, node *right, InnerNode *left_parent, + InnerNode *right_parent, InnerNode *parent, unsigned int parentslot) { + if (curr->is_leafnode()) { + LeafNode *leaf = static_cast(curr); + LeafNode *left_leaf = static_cast(left); + LeafNode *right_leaf = static_cast(right); + + unsigned short slot = find_lower(leaf, key); + + if (slot >= leaf->slotuse || !key_equal(key, leaf->key(slot))) { + TLX_BTREE_PRINT("Could not find key " << key << " to erase."); + + return btree_not_found; + } + + TLX_BTREE_PRINT("Found key in leaf " << curr << " at slot " << slot); + + std::copy(leaf->slotdata + slot + 1, leaf->slotdata + leaf->slotuse, leaf->slotdata + slot); + + leaf->slotuse--; + + result_t myres = btree_ok; + + // if the last key of the leaf was changed, the parent is notified + // and updates the key of this leaf + if (slot == leaf->slotuse) { + if (parent && parentslot < parent->slotuse) { + TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); + parent->slotkey[parentslot] = leaf->key(leaf->slotuse - 1); + } else { + if (leaf->slotuse >= 1) { + TLX_BTREE_PRINT("Scheduling lastkeyupdate: key " << leaf->key(leaf->slotuse - 1)); + myres |= result_t(btree_update_lastkey, leaf->key(leaf->slotuse - 1)); + } else { + TLX_BTREE_ASSERT(leaf == root_); + } + } + } + + if (leaf->is_underflow() && !(leaf == root_ && leaf->slotuse >= 1)) { + // determine what to do about the underflow + + // case : if this empty leaf is the root, then delete all nodes + // and set root to nullptr. + if (left_leaf == nullptr && right_leaf == nullptr) { + TLX_BTREE_ASSERT(leaf == root_); + TLX_BTREE_ASSERT(leaf->slotuse == 0); + + free_node(root_); + + root_ = leaf = nullptr; + head_leaf_ = tail_leaf_ = nullptr; + + // will be decremented soon by insert_start() + TLX_BTREE_ASSERT(stats_.size == 1); + TLX_BTREE_ASSERT(stats_.leaves == 0); + TLX_BTREE_ASSERT(stats_.inner_nodes == 0); + + return btree_ok; + } + // case : if both left and right leaves would underflow in case + // of a shift, then merging is necessary. choose the more local + // merger with our parent + else if ((left_leaf == nullptr || left_leaf->is_few()) && (right_leaf == nullptr || right_leaf->is_few())) { + if (left_parent == parent) + myres |= merge_leaves(left_leaf, leaf, left_parent); + else + myres |= merge_leaves(leaf, right_leaf, right_parent); + } + // case : the right leaf has extra data, so balance right with + // current + else if ((left_leaf != nullptr && left_leaf->is_few()) && (right_leaf != nullptr && !right_leaf->is_few())) { + if (right_parent == parent) + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + else + myres |= merge_leaves(left_leaf, leaf, left_parent); + } + // case : the left leaf has extra data, so balance left with + // current + else if ((left_leaf != nullptr && !left_leaf->is_few()) && (right_leaf != nullptr && right_leaf->is_few())) { + if (left_parent == parent) + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + else + myres |= merge_leaves(leaf, right_leaf, right_parent); + } + // case : both the leaf and right leaves have extra data and our + // parent, choose the leaf with more data + else if (left_parent == right_parent) { + if (left_leaf->slotuse <= right_leaf->slotuse) + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + else + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + } else { + if (left_parent == parent) + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + else + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + } + } + + return myres; + } else // !curr->is_leafnode() + { + InnerNode *inner = static_cast(curr); + InnerNode *left_inner = static_cast(left); + InnerNode *right_inner = static_cast(right); + + node *myleft, *myright; + InnerNode *myleft_parent, *myright_parent; + + unsigned short slot = find_lower(inner, key); + + if (slot == 0) { + myleft = (left == nullptr) ? nullptr : static_cast(left)->childid[left->slotuse - 1]; + myleft_parent = left_parent; + } else { + myleft = inner->childid[slot - 1]; + myleft_parent = inner; + } + + if (slot == inner->slotuse) { + myright = (right == nullptr) ? nullptr : static_cast(right)->childid[0]; + myright_parent = right_parent; + } else { + myright = inner->childid[slot + 1]; + myright_parent = inner; + } + + TLX_BTREE_PRINT("erase_one_descend into " << inner->childid[slot]); + + result_t result = + erase_one_descend(key, inner->childid[slot], myleft, myright, myleft_parent, myright_parent, inner, slot); + + result_t myres = btree_ok; + + if (result.has(btree_not_found)) { + return result; + } + + if (result.has(btree_update_lastkey)) { + if (parent && parentslot < parent->slotuse) { + TLX_BTREE_PRINT("Fixing lastkeyupdate: key " << result.lastkey << " into parent " << parent + << " at parentslot " << parentslot); + + TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); + parent->slotkey[parentslot] = result.lastkey; + } else { + TLX_BTREE_PRINT("Forwarding lastkeyupdate: key " << result.lastkey); + myres |= result_t(btree_update_lastkey, result.lastkey); + } + } + + if (result.has(btree_fixmerge)) { + // either the current node or the next is empty and should be + // removed + if (inner->childid[slot]->slotuse != 0) slot++; + + // this is the child slot invalidated by the merge + TLX_BTREE_ASSERT(inner->childid[slot]->slotuse == 0); + + free_node(inner->childid[slot]); + + std::copy(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + slot - 1); + std::copy(inner->childid + slot + 1, inner->childid + inner->slotuse + 1, inner->childid + slot); + + inner->slotuse--; + + if (inner->level == 1) { + // fix split key for children leaves + slot--; + LeafNode *child = static_cast(inner->childid[slot]); + inner->slotkey[slot] = child->key(child->slotuse - 1); + } + } + + if (inner->is_underflow() && !(inner == root_ && inner->slotuse >= 1)) { + // case: the inner node is the root and has just one child. that + // child becomes the new root + if (left_inner == nullptr && right_inner == nullptr) { + TLX_BTREE_ASSERT(inner == root_); + TLX_BTREE_ASSERT(inner->slotuse == 0); + + root_ = inner->childid[0]; + + inner->slotuse = 0; + free_node(inner); + + return btree_ok; + } + // case : if both left and right leaves would underflow in case + // of a shift, then merging is necessary. choose the more local + // merger with our parent + else if ((left_inner == nullptr || left_inner->is_few()) && (right_inner == nullptr || right_inner->is_few())) { + if (left_parent == parent) + myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); + else + myres |= merge_inner(inner, right_inner, right_parent, parentslot); + } + // case : the right leaf has extra data, so balance right with + // current + else if ((left_inner != nullptr && left_inner->is_few()) && + (right_inner != nullptr && !right_inner->is_few())) { + if (right_parent == parent) + shift_left_inner(inner, right_inner, right_parent, parentslot); + else + myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); + } + // case : the left leaf has extra data, so balance left with + // current + else if ((left_inner != nullptr && !left_inner->is_few()) && + (right_inner != nullptr && right_inner->is_few())) { + if (left_parent == parent) + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + else + myres |= merge_inner(inner, right_inner, right_parent, parentslot); + } + // case : both the leaf and right leaves have extra data and our + // parent, choose the leaf with more data + else if (left_parent == right_parent) { + if (left_inner->slotuse <= right_inner->slotuse) + shift_left_inner(inner, right_inner, right_parent, parentslot); + else + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + } else { + if (left_parent == parent) + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + else + shift_left_inner(inner, right_inner, right_parent, parentslot); + } + } + + return myres; + } + } + + /*! + * Erase one key/data pair referenced by an iterator in the B+ tree. + * + * Descends down the tree in search of an iterator. During the descent the + * parent, left and right siblings and their parents are computed and passed + * down. The difficulty is that the iterator contains only a pointer to a + * LeafNode, which means that this function must do a recursive depth first + * search for that leaf node in the subtree containing all pairs of the same + * key. This subtree can be very large, even the whole tree, though in + * practice it would not make sense to have so many duplicate keys. + * + * Once the referenced key/data pair is found, it is removed from the leaf + * and the same underflow cases are handled as in erase_one_descend. + */ + result_t erase_iter_descend(const iterator &iter, node *curr, node *left, node *right, InnerNode *left_parent, + InnerNode *right_parent, InnerNode *parent, unsigned int parentslot) { + if (curr->is_leafnode()) { + LeafNode *leaf = static_cast(curr); + LeafNode *left_leaf = static_cast(left); + LeafNode *right_leaf = static_cast(right); + + // if this is not the correct leaf, get next step in recursive + // search + if (leaf != iter.curr_leaf) { + return btree_not_found; + } + + if (iter.curr_slot >= leaf->slotuse) { + TLX_BTREE_PRINT("Could not find iterator (" << iter.curr_leaf << "," << iter.curr_slot + << ") to erase. Invalid leaf node?"); + + return btree_not_found; + } + + unsigned short slot = iter.curr_slot; + + TLX_BTREE_PRINT("Found iterator in leaf " << curr << " at slot " << slot); + + std::copy(leaf->slotdata + slot + 1, leaf->slotdata + leaf->slotuse, leaf->slotdata + slot); + + leaf->slotuse--; + + result_t myres = btree_ok; + + // if the last key of the leaf was changed, the parent is notified + // and updates the key of this leaf + if (slot == leaf->slotuse) { + if (parent && parentslot < parent->slotuse) { + TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); + parent->slotkey[parentslot] = leaf->key(leaf->slotuse - 1); + } else { + if (leaf->slotuse >= 1) { + TLX_BTREE_PRINT("Scheduling lastkeyupdate: key " << leaf->key(leaf->slotuse - 1)); + myres |= result_t(btree_update_lastkey, leaf->key(leaf->slotuse - 1)); + } else { + TLX_BTREE_ASSERT(leaf == root_); + } + } + } + + if (leaf->is_underflow() && !(leaf == root_ && leaf->slotuse >= 1)) { + // determine what to do about the underflow + + // case : if this empty leaf is the root, then delete all nodes + // and set root to nullptr. + if (left_leaf == nullptr && right_leaf == nullptr) { + TLX_BTREE_ASSERT(leaf == root_); + TLX_BTREE_ASSERT(leaf->slotuse == 0); + + free_node(root_); + + root_ = leaf = nullptr; + head_leaf_ = tail_leaf_ = nullptr; + + // will be decremented soon by insert_start() + TLX_BTREE_ASSERT(stats_.size == 1); + TLX_BTREE_ASSERT(stats_.leaves == 0); + TLX_BTREE_ASSERT(stats_.inner_nodes == 0); + + return btree_ok; + } + // case : if both left and right leaves would underflow in case + // of a shift, then merging is necessary. choose the more local + // merger with our parent + else if ((left_leaf == nullptr || left_leaf->is_few()) && (right_leaf == nullptr || right_leaf->is_few())) { + if (left_parent == parent) + myres |= merge_leaves(left_leaf, leaf, left_parent); + else + myres |= merge_leaves(leaf, right_leaf, right_parent); + } + // case : the right leaf has extra data, so balance right with + // current + else if ((left_leaf != nullptr && left_leaf->is_few()) && (right_leaf != nullptr && !right_leaf->is_few())) { + if (right_parent == parent) { + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + } else { + myres |= merge_leaves(left_leaf, leaf, left_parent); + } + } + // case : the left leaf has extra data, so balance left with + // current + else if ((left_leaf != nullptr && !left_leaf->is_few()) && (right_leaf != nullptr && right_leaf->is_few())) { + if (left_parent == parent) { + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + } else { + myres |= merge_leaves(leaf, right_leaf, right_parent); + } + } + // case : both the leaf and right leaves have extra data and our + // parent, choose the leaf with more data + else if (left_parent == right_parent) { + if (left_leaf->slotuse <= right_leaf->slotuse) { + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + } else { + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + } + } else { + if (left_parent == parent) { + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + } else { + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + } + } + } + + return myres; + } else // !curr->is_leafnode() + { + InnerNode *inner = static_cast(curr); + InnerNode *left_inner = static_cast(left); + InnerNode *right_inner = static_cast(right); + + // find first slot below which the searched iterator might be + // located. + + result_t result; + unsigned short slot = find_lower(inner, iter.key()); + + while (slot <= inner->slotuse) { + node *myleft, *myright; + InnerNode *myleft_parent, *myright_parent; + + if (slot == 0) { + myleft = (left == nullptr) ? nullptr : static_cast(left)->childid[left->slotuse - 1]; + myleft_parent = left_parent; + } else { + myleft = inner->childid[slot - 1]; + myleft_parent = inner; + } + + if (slot == inner->slotuse) { + myright = (right == nullptr) ? nullptr : static_cast(right)->childid[0]; + myright_parent = right_parent; + } else { + myright = inner->childid[slot + 1]; + myright_parent = inner; + } + + TLX_BTREE_PRINT("erase_iter_descend into " << inner->childid[slot]); + + result = + erase_iter_descend(iter, inner->childid[slot], myleft, myright, myleft_parent, myright_parent, inner, slot); + + if (!result.has(btree_not_found)) break; + + // continue recursive search for leaf on next slot + + if (slot < inner->slotuse && key_less(inner->slotkey[slot], iter.key())) return btree_not_found; + + ++slot; + } + + if (slot > inner->slotuse) return btree_not_found; + + result_t myres = btree_ok; + + if (result.has(btree_update_lastkey)) { + if (parent && parentslot < parent->slotuse) { + TLX_BTREE_PRINT("Fixing lastkeyupdate: key " << result.lastkey << " into parent " << parent + << " at parentslot " << parentslot); + + TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); + parent->slotkey[parentslot] = result.lastkey; + } else { + TLX_BTREE_PRINT("Forwarding lastkeyupdate: key " << result.lastkey); + myres |= result_t(btree_update_lastkey, result.lastkey); + } + } + + if (result.has(btree_fixmerge)) { + // either the current node or the next is empty and should be + // removed + if (inner->childid[slot]->slotuse != 0) slot++; + + // this is the child slot invalidated by the merge + TLX_BTREE_ASSERT(inner->childid[slot]->slotuse == 0); + + free_node(inner->childid[slot]); + + std::copy(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + slot - 1); + std::copy(inner->childid + slot + 1, inner->childid + inner->slotuse + 1, inner->childid + slot); + + inner->slotuse--; + + if (inner->level == 1) { + // fix split key for children leaves + slot--; + LeafNode *child = static_cast(inner->childid[slot]); + inner->slotkey[slot] = child->key(child->slotuse - 1); + } + } + + if (inner->is_underflow() && !(inner == root_ && inner->slotuse >= 1)) { + // case: the inner node is the root and has just one + // child. that child becomes the new root + if (left_inner == nullptr && right_inner == nullptr) { + TLX_BTREE_ASSERT(inner == root_); + TLX_BTREE_ASSERT(inner->slotuse == 0); + + root_ = inner->childid[0]; + + inner->slotuse = 0; + free_node(inner); + + return btree_ok; + } + // case : if both left and right leaves would underflow in case + // of a shift, then merging is necessary. choose the more local + // merger with our parent + else if ((left_inner == nullptr || left_inner->is_few()) && (right_inner == nullptr || right_inner->is_few())) { + if (left_parent == parent) { + myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); + } else { + myres |= merge_inner(inner, right_inner, right_parent, parentslot); + } + } + // case : the right leaf has extra data, so balance right with + // current + else if ((left_inner != nullptr && left_inner->is_few()) && + (right_inner != nullptr && !right_inner->is_few())) { + if (right_parent == parent) { + shift_left_inner(inner, right_inner, right_parent, parentslot); + } else { + myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); + } + } + // case : the left leaf has extra data, so balance left with + // current + else if ((left_inner != nullptr && !left_inner->is_few()) && + (right_inner != nullptr && right_inner->is_few())) { + if (left_parent == parent) { + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + } else { + myres |= merge_inner(inner, right_inner, right_parent, parentslot); + } + } + // case : both the leaf and right leaves have extra data and our + // parent, choose the leaf with more data + else if (left_parent == right_parent) { + if (left_inner->slotuse <= right_inner->slotuse) { + shift_left_inner(inner, right_inner, right_parent, parentslot); + } else { + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + } + } else { + if (left_parent == parent) { + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + } else { + shift_left_inner(inner, right_inner, right_parent, parentslot); + } + } + } + + return myres; + } + } + + //! Merge two leaf nodes. The function moves all key/data pairs from right + //! to left and sets right's slotuse to zero. The right slot is then removed + //! by the calling parent node. + result_t merge_leaves(LeafNode *left, LeafNode *right, InnerNode *parent) { + TLX_BTREE_PRINT("Merge leaf nodes " << left << " and " << right << " with common parent " << parent << "."); + (void)parent; + + TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); + TLX_BTREE_ASSERT(parent->level == 1); + + TLX_BTREE_ASSERT(left->slotuse + right->slotuse < leaf_slotmax); + + std::copy(right->slotdata, right->slotdata + right->slotuse, left->slotdata + left->slotuse); + + left->slotuse += right->slotuse; + + left->next_leaf = right->next_leaf; + if (left->next_leaf) + left->next_leaf->prev_leaf = left; + else + tail_leaf_ = left; + + right->slotuse = 0; + + return btree_fixmerge; + } + + //! Merge two inner nodes. The function moves all key/childid pairs from + //! right to left and sets right's slotuse to zero. The right slot is then + //! removed by the calling parent node. + static result_t merge_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_PRINT("Merge inner nodes " << left << " and " << right << " with common parent " << parent << "."); + + TLX_BTREE_ASSERT(left->level == right->level); + TLX_BTREE_ASSERT(parent->level == left->level + 1); + + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + TLX_BTREE_ASSERT(left->slotuse + right->slotuse < inner_slotmax); + + if (self_verify) { + // find the left node's slot in the parent's children + unsigned int leftslot = 0; + while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; + + TLX_BTREE_ASSERT(leftslot < parent->slotuse); + TLX_BTREE_ASSERT(parent->childid[leftslot] == left); + TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); + + TLX_BTREE_ASSERT(parentslot == leftslot); + } + + // retrieve the decision key from parent + left->slotkey[left->slotuse] = parent->slotkey[parentslot]; + left->slotuse++; + + // copy over keys and children from right + std::copy(right->slotkey, right->slotkey + right->slotuse, left->slotkey + left->slotuse); + std::copy(right->childid, right->childid + right->slotuse + 1, left->childid + left->slotuse); + + left->slotuse += right->slotuse; + right->slotuse = 0; + + return btree_fixmerge; + } + + //! Balance two leaf nodes. The function moves key/data pairs from right to + //! left so that both nodes are equally filled. The parent node is updated + //! if possible. + static result_t shift_left_leaf(LeafNode *left, LeafNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); + TLX_BTREE_ASSERT(parent->level == 1); + + TLX_BTREE_ASSERT(left->next_leaf == right); + TLX_BTREE_ASSERT(left == right->prev_leaf); + + TLX_BTREE_ASSERT(left->slotuse < right->slotuse); + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + unsigned int shiftnum = (right->slotuse - left->slotuse) >> 1; + + TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to left " << left << " from right " << right + << " with common parent " << parent << "."); + + TLX_BTREE_ASSERT(left->slotuse + shiftnum < leaf_slotmax); + + // copy the first items from the right node to the last slot in the left + // node. + + std::copy(right->slotdata, right->slotdata + shiftnum, left->slotdata + left->slotuse); + + left->slotuse += shiftnum; + + // shift all slots in the right node to the left + + std::copy(right->slotdata + shiftnum, right->slotdata + right->slotuse, right->slotdata); + + right->slotuse -= shiftnum; + + // fixup parent + if (parentslot < parent->slotuse) { + parent->slotkey[parentslot] = left->key(left->slotuse - 1); + return btree_ok; + } else { // the update is further up the tree + return result_t(btree_update_lastkey, left->key(left->slotuse - 1)); + } + } + + //! Balance two inner nodes. The function moves key/data pairs from right to + //! left so that both nodes are equally filled. The parent node is updated + //! if possible. + static void shift_left_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_ASSERT(left->level == right->level); + TLX_BTREE_ASSERT(parent->level == left->level + 1); + + TLX_BTREE_ASSERT(left->slotuse < right->slotuse); + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + unsigned int shiftnum = (right->slotuse - left->slotuse) >> 1; + + TLX_BTREE_PRINT("Shifting (inner) " << shiftnum << " entries to left " << left << " from right " << right + << " with common parent " << parent << "."); + + TLX_BTREE_ASSERT(left->slotuse + shiftnum < inner_slotmax); + + if (self_verify) { + // find the left node's slot in the parent's children and compare to + // parentslot + + unsigned int leftslot = 0; + while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; + + TLX_BTREE_ASSERT(leftslot < parent->slotuse); + TLX_BTREE_ASSERT(parent->childid[leftslot] == left); + TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); + + TLX_BTREE_ASSERT(leftslot == parentslot); + } + + // copy the parent's decision slotkey and childid to the first new key + // on the left + left->slotkey[left->slotuse] = parent->slotkey[parentslot]; + left->slotuse++; + + // copy the other items from the right node to the last slots in the + // left node. + std::copy(right->slotkey, right->slotkey + shiftnum - 1, left->slotkey + left->slotuse); + std::copy(right->childid, right->childid + shiftnum, left->childid + left->slotuse); + + left->slotuse += shiftnum - 1; + + // fixup parent + parent->slotkey[parentslot] = right->slotkey[shiftnum - 1]; + + // shift all slots in the right node + std::copy(right->slotkey + shiftnum, right->slotkey + right->slotuse, right->slotkey); + std::copy(right->childid + shiftnum, right->childid + right->slotuse + 1, right->childid); + + right->slotuse -= shiftnum; + } + + //! Balance two leaf nodes. The function moves key/data pairs from left to + //! right so that both nodes are equally filled. The parent node is updated + //! if possible. + static void shift_right_leaf(LeafNode *left, LeafNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); + TLX_BTREE_ASSERT(parent->level == 1); + + TLX_BTREE_ASSERT(left->next_leaf == right); + TLX_BTREE_ASSERT(left == right->prev_leaf); + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + TLX_BTREE_ASSERT(left->slotuse > right->slotuse); + + unsigned int shiftnum = (left->slotuse - right->slotuse) >> 1; + + TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to right " << right << " from left " << left + << " with common parent " << parent << "."); + + if (self_verify) { + // find the left node's slot in the parent's children + unsigned int leftslot = 0; + while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; + + TLX_BTREE_ASSERT(leftslot < parent->slotuse); + TLX_BTREE_ASSERT(parent->childid[leftslot] == left); + TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); + + TLX_BTREE_ASSERT(leftslot == parentslot); + } + + // shift all slots in the right node + + TLX_BTREE_ASSERT(right->slotuse + shiftnum < leaf_slotmax); + + std::copy_backward(right->slotdata, right->slotdata + right->slotuse, right->slotdata + right->slotuse + shiftnum); + + right->slotuse += shiftnum; + + // copy the last items from the left node to the first slot in the right + // node. + std::copy(left->slotdata + left->slotuse - shiftnum, left->slotdata + left->slotuse, right->slotdata); + + left->slotuse -= shiftnum; + + parent->slotkey[parentslot] = left->key(left->slotuse - 1); + } + + //! Balance two inner nodes. The function moves key/data pairs from left to + //! right so that both nodes are equally filled. The parent node is updated + //! if possible. + static void shift_right_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_ASSERT(left->level == right->level); + TLX_BTREE_ASSERT(parent->level == left->level + 1); + + TLX_BTREE_ASSERT(left->slotuse > right->slotuse); + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + unsigned int shiftnum = (left->slotuse - right->slotuse) >> 1; + + TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to right " << right << " from left " << left + << " with common parent " << parent << "."); + + if (self_verify) { + // find the left node's slot in the parent's children + unsigned int leftslot = 0; + while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; + + TLX_BTREE_ASSERT(leftslot < parent->slotuse); + TLX_BTREE_ASSERT(parent->childid[leftslot] == left); + TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); + + TLX_BTREE_ASSERT(leftslot == parentslot); + } + + // shift all slots in the right node + + TLX_BTREE_ASSERT(right->slotuse + shiftnum < inner_slotmax); + + std::copy_backward(right->slotkey, right->slotkey + right->slotuse, right->slotkey + right->slotuse + shiftnum); + std::copy_backward(right->childid, right->childid + right->slotuse + 1, + right->childid + right->slotuse + 1 + shiftnum); + + right->slotuse += shiftnum; + + // copy the parent's decision slotkey and childid to the last new key on + // the right + right->slotkey[shiftnum - 1] = parent->slotkey[parentslot]; + + // copy the remaining last items from the left node to the first slot in + // the right node. + std::copy(left->slotkey + left->slotuse - shiftnum + 1, left->slotkey + left->slotuse, right->slotkey); + std::copy(left->childid + left->slotuse - shiftnum + 1, left->childid + left->slotuse + 1, right->childid); + + // copy the first to-be-removed key from the left node to the parent's + // decision slot + parent->slotkey[parentslot] = left->slotkey[left->slotuse - shiftnum]; + + left->slotuse -= shiftnum; + } + + //! \} + +#ifdef TLX_BTREE_DEBUG + + public: + //! \name Debug Printing + //! \{ + + //! Print out the B+ tree structure with keys onto the given ostream. This + //! function requires that the header is compiled with TLX_BTREE_DEBUG and + //! that key_type is printable via std::ostream. + void print(std::ostream &os) const { + if (root_) { + print_node(os, root_, 0, true); + } + } + + //! Print out only the leaves via the double linked list. + void print_leaves(std::ostream &os) const { + os << "leaves:" << std::endl; + + const LeafNode *n = head_leaf_; + + while (n) { + os << " " << n << std::endl; + + n = n->next_leaf; + } + } + + private: + //! Recursively descend down the tree and print out nodes. + static void print_node(std::ostream &os, const node *node, unsigned int depth = 0, bool recursive = false) { + for (unsigned int i = 0; i < depth; i++) os << " "; + + os << "node " << node << " level " << node->level << " slotuse " << node->slotuse << std::endl; + + if (node->is_leafnode()) { + const LeafNode *leafnode = static_cast(node); + + for (unsigned int i = 0; i < depth; i++) os << " "; + os << " leaf prev " << leafnode->prev_leaf << " next " << leafnode->next_leaf << std::endl; + + for (unsigned int i = 0; i < depth; i++) os << " "; + + for (unsigned short slot = 0; slot < leafnode->slotuse; ++slot) { + // os << leafnode->key(slot) << " " + // << "(data: " << leafnode->slotdata[slot] << ") "; + os << leafnode->key(slot) << " "; + } + os << std::endl; + } else { + const InnerNode *innernode = static_cast(node); + + for (unsigned int i = 0; i < depth; i++) os << " "; + + for (unsigned short slot = 0; slot < innernode->slotuse; ++slot) { + os << "(" << innernode->childid[slot] << ") " << innernode->slotkey[slot] << " "; + } + os << "(" << innernode->childid[innernode->slotuse] << ")" << std::endl; + + if (recursive) { + for (unsigned short slot = 0; slot < innernode->slotuse + 1; ++slot) { + print_node(os, innernode->childid[slot], depth + 1, recursive); + } + } + } + } + + //! \} +#endif + + public: + //! \name Verification of B+ Tree Invariants + //! \{ + + //! Run a thorough verification of all B+ tree invariants. The program + //! aborts via tlx_die_unless() if something is wrong. + void verify() const { + key_type minkey, maxkey; + tree_stats vstats; + + if (root_) { + verify_node(root_, &minkey, &maxkey, vstats); + + tlx_die_unless(vstats.size == stats_.size); + tlx_die_unless(vstats.leaves == stats_.leaves); + tlx_die_unless(vstats.inner_nodes == stats_.inner_nodes); + + verify_leaflinks(); + } + } + + private: + //! Recursively descend down the tree and verify each node + void verify_node(const node *n, key_type *minkey, key_type *maxkey, tree_stats &vstats) const { + TLX_BTREE_PRINT("verifynode " << n); + + if (n->is_leafnode()) { + const LeafNode *leaf = static_cast(n); + + tlx_die_unless(leaf == root_ || !leaf->is_underflow()); + tlx_die_unless(leaf->slotuse > 0); + + for (unsigned short slot = 0; slot < leaf->slotuse - 1; ++slot) { + tlx_die_unless(key_lessequal(leaf->key(slot), leaf->key(slot + 1))); + } + + *minkey = leaf->key(0); + *maxkey = leaf->key(leaf->slotuse - 1); + + vstats.leaves++; + vstats.size += leaf->slotuse; + } else // !n->is_leafnode() + { + const InnerNode *inner = static_cast(n); + vstats.inner_nodes++; + + tlx_die_unless(inner == root_ || !inner->is_underflow()); + tlx_die_unless(inner->slotuse > 0); + + for (unsigned short slot = 0; slot < inner->slotuse - 1; ++slot) { + tlx_die_unless(key_lessequal(inner->key(slot), inner->key(slot + 1))); + } + + for (unsigned short slot = 0; slot <= inner->slotuse; ++slot) { + const node *subnode = inner->childid[slot]; + key_type subminkey = key_type(); + key_type submaxkey = key_type(); + + tlx_die_unless(subnode->level + 1 == inner->level); + verify_node(subnode, &subminkey, &submaxkey, vstats); + + TLX_BTREE_PRINT("verify subnode " << subnode << ": " << subminkey << " - " << submaxkey); + + if (slot == 0) + *minkey = subminkey; + else + tlx_die_unless(key_greaterequal(subminkey, inner->key(slot - 1))); + + if (slot == inner->slotuse) + *maxkey = submaxkey; + else + tlx_die_unless(key_equal(inner->key(slot), submaxkey)); + + if (inner->level == 1 && slot < inner->slotuse) { + // children are leaves and must be linked together in the + // correct order + const LeafNode *leafa = static_cast(inner->childid[slot]); + const LeafNode *leafb = static_cast(inner->childid[slot + 1]); + + tlx_die_unless(leafa->next_leaf == leafb); + tlx_die_unless(leafa == leafb->prev_leaf); + } + if (inner->level == 2 && slot < inner->slotuse) { + // verify leaf links between the adjacent inner nodes + const InnerNode *parenta = static_cast(inner->childid[slot]); + const InnerNode *parentb = static_cast(inner->childid[slot + 1]); + + const LeafNode *leafa = static_cast(parenta->childid[parenta->slotuse]); + const LeafNode *leafb = static_cast(parentb->childid[0]); + + tlx_die_unless(leafa->next_leaf == leafb); + tlx_die_unless(leafa == leafb->prev_leaf); + } + } + } + } + + //! Verify the double linked list of leaves. + void verify_leaflinks() const { + const LeafNode *n = head_leaf_; + + tlx_die_unless(n->level == 0); + tlx_die_unless(!n || n->prev_leaf == nullptr); + + unsigned int testcount = 0; + + while (n) { + tlx_die_unless(n->level == 0); + tlx_die_unless(n->slotuse > 0); + + for (unsigned short slot = 0; slot < n->slotuse - 1; ++slot) { + tlx_die_unless(key_lessequal(n->key(slot), n->key(slot + 1))); + } + + testcount += n->slotuse; + + if (n->next_leaf) { + tlx_die_unless(key_lessequal(n->key(n->slotuse - 1), n->next_leaf->key(0))); + + tlx_die_unless(n == n->next_leaf->prev_leaf); + } else { + tlx_die_unless(tail_leaf_ == n); + } + + n = n->next_leaf; + } + + tlx_die_unless(testcount == size()); + } + + //! \} +}; + +//! \} +//! \} + +} // namespace tlx + +#endif // !TLX_CONTAINER_BTREE_HEADER + +/******************************************************************************/ diff --git a/tests/benchmark/btree_map.hpp b/tests/benchmark/btree_map.hpp new file mode 100644 index 000000000..3f718677b --- /dev/null +++ b/tests/benchmark/btree_map.hpp @@ -0,0 +1,475 @@ +/******************************************************************************* + * tlx/container/btree_map.hpp + * + * Part of tlx - http://panthema.net/tlx + * + * Copyright (C) 2008-2017 Timo Bingmann + * + * All rights reserved. Published under the Boost Software License, Version 1.0 + ******************************************************************************/ + +#ifndef TLX_CONTAINER_BTREE_MAP_HEADER +#define TLX_CONTAINER_BTREE_MAP_HEADER + +#include +#include +#include + +#include "btree.hpp" + +namespace tlx { + +//! \addtogroup tlx_container_btree +//! \{ + +/*! + * Specialized B+ tree template class implementing STL's map container. + * + * Implements the STL map using a B+ tree. It can be used as a drop-in + * replacement for std::map. Not all asymptotic time requirements are met in + * theory. The class has a traits class defining B+ tree properties like slots + * and self-verification. Furthermore an allocator can be specified for tree + * nodes. + */ +template , + typename Traits_ = btree_default_traits>, + typename Alloc_ = std::allocator>> +class btree_map { + public: + //! \name Template Parameter Types + //! \{ + + //! First template parameter: The key type of the btree. This is stored in + //! inner nodes. + typedef Key_ key_type; + + //! Second template parameter: The value type associated with each key. + //! Stored in the B+ tree's leaves + typedef Data_ data_type; + + //! Third template parameter: Key comparison function object + typedef Compare_ key_compare; + + //! Fourth template parameter: Traits object used to define more parameters + //! of the B+ tree + typedef Traits_ traits; + + //! Fifth template parameter: STL allocator + typedef Alloc_ allocator_type; + + //! \} + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ + // tree internals. This was added for wxBTreeDemo to be able to draw the + // tree. + TLX_BTREE_FRIENDS; + + public: + //! \name Constructed Types + //! \{ + + //! Typedef of our own type + typedef btree_map self; + + //! Construct the STL-required value_type as a composition pair of key and + //! data types + typedef std::pair value_type; + + //! Key Extractor Struct + struct key_of_value { + //! pull first out of pair + static const key_type &get(const value_type &v) { return v.first; } + }; + + //! Implementation type of the btree_base + typedef BTree btree_impl; + + //! Function class comparing two value_type pairs. + typedef typename btree_impl::value_compare value_compare; + + //! Size type used to count keys + typedef typename btree_impl::size_type size_type; + + //! Small structure containing statistics about the tree + typedef typename btree_impl::tree_stats tree_stats; + + //! \} + + public: + //! \name Static Constant Options and Values of the B+ Tree + //! \{ + + //! Base B+ tree parameter: The number of key/data slots in each leaf + static const unsigned short leaf_slotmax = btree_impl::leaf_slotmax; + + //! Base B+ tree parameter: The number of key slots in each inner node, + //! this can differ from slots in each leaf. + static const unsigned short inner_slotmax = btree_impl::inner_slotmax; + + //! Computed B+ tree parameter: The minimum number of key/data slots used + //! in a leaf. If fewer slots are used, the leaf will be merged or slots + //! shifted from it's siblings. + static const unsigned short leaf_slotmin = btree_impl::leaf_slotmin; + + //! Computed B+ tree parameter: The minimum number of key slots used + //! in an inner node. If fewer slots are used, the inner node will be + //! merged or slots shifted from it's siblings. + static const unsigned short inner_slotmin = btree_impl::inner_slotmin; + + //! Debug parameter: Enables expensive and thorough checking of the B+ tree + //! invariants after each insert/erase operation. + static const bool self_verify = btree_impl::self_verify; + + //! Debug parameter: Prints out lots of debug information about how the + //! algorithms change the tree. Requires the header file to be compiled + //! with TLX_BTREE_DEBUG and the key type must be std::ostream printable. + static const bool debug = btree_impl::debug; + + //! Operational parameter: Allow duplicate keys in the btree. + static const bool allow_duplicates = btree_impl::allow_duplicates; + + //! \} + + public: + //! \name Iterators and Reverse Iterators + //! \{ + + //! STL-like iterator object for B+ tree items. The iterator points to a + //! specific slot number in a leaf. + typedef typename btree_impl::iterator iterator; + + //! STL-like iterator object for B+ tree items. The iterator points to a + //! specific slot number in a leaf. + typedef typename btree_impl::const_iterator const_iterator; + + //! create mutable reverse iterator by using STL magic + typedef typename btree_impl::reverse_iterator reverse_iterator; + + //! create constant reverse iterator by using STL magic + typedef typename btree_impl::const_reverse_iterator const_reverse_iterator; + + //! \} + + private: + //! \name Tree Implementation Object + //! \{ + + //! The contained implementation object + btree_impl tree_; + + //! \} + + public: + //! \name Constructors and Destructor + //! \{ + + //! Default constructor initializing an empty B+ tree with the standard key + //! comparison function + explicit btree_map(const allocator_type &alloc = allocator_type()) : tree_(alloc) {} + + //! Constructor initializing an empty B+ tree with a special key + //! comparison object + explicit btree_map(const key_compare &kcf, const allocator_type &alloc = allocator_type()) : tree_(kcf, alloc) {} + + //! Constructor initializing a B+ tree with the range [first,last) + template + btree_map(InputIterator first, InputIterator last, const allocator_type &alloc = allocator_type()) + : tree_(first, last, alloc) {} + + //! Constructor initializing a B+ tree with the range [first,last) and a + //! special key comparison object + template + btree_map(InputIterator first, InputIterator last, const key_compare &kcf, + const allocator_type &alloc = allocator_type()) + : tree_(first, last, kcf, alloc) {} + + //! Frees up all used B+ tree memory pages + ~btree_map() {} + + //! Fast swapping of two identical B+ tree objects. + void swap(btree_map &from) { std::swap(tree_, from.tree_); } + + //! \} + + public: + //! \name Key and Value Comparison Function Objects + //! \{ + + //! Constant access to the key comparison object sorting the B+ tree + key_compare key_comp() const { return tree_.key_comp(); } + + //! Constant access to a constructed value_type comparison object. required + //! by the STL + value_compare value_comp() const { return tree_.value_comp(); } + + //! \} + + public: + //! \name Allocators + //! \{ + + //! Return the base node allocator provided during construction. + allocator_type get_allocator() const { return tree_.get_allocator(); } + + //! \} + + public: + //! \name Fast Destruction of the B+ Tree + //! \{ + + //! Frees all key/data pairs and all nodes of the tree + void clear() { tree_.clear(); } + + //! \} + + public: + //! \name STL Iterator Construction Functions + //! \{ + + //! Constructs a read/data-write iterator that points to the first slot in + //! the first leaf of the B+ tree. + iterator begin() { return tree_.begin(); } + + //! Constructs a read/data-write iterator that points to the first invalid + //! slot in the last leaf of the B+ tree. + iterator end() { return tree_.end(); } + + //! Constructs a read-only constant iterator that points to the first slot + //! in the first leaf of the B+ tree. + const_iterator begin() const { return tree_.begin(); } + + //! Constructs a read-only constant iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. + const_iterator end() const { return tree_.end(); } + + //! Constructs a read/data-write reverse iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. Uses STL magic. + reverse_iterator rbegin() { return tree_.rbegin(); } + + //! Constructs a read/data-write reverse iterator that points to the first + //! slot in the first leaf of the B+ tree. Uses STL magic. + reverse_iterator rend() { return tree_.rend(); } + + //! Constructs a read-only reverse iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. Uses STL magic. + const_reverse_iterator rbegin() const { return tree_.rbegin(); } + + //! Constructs a read-only reverse iterator that points to the first slot + //! in the first leaf of the B+ tree. Uses STL magic. + const_reverse_iterator rend() const { return tree_.rend(); } + + //! \} + + public: + //! \name Access Functions to the Item Count + //! \{ + + //! Return the number of key/data pairs in the B+ tree + size_type size() const { return tree_.size(); } + + //! Returns true if there is at least one key/data pair in the B+ tree + bool empty() const { return tree_.empty(); } + + //! Returns the largest possible size of the B+ Tree. This is just a + //! function required by the STL standard, the B+ Tree can hold more items. + size_type max_size() const { return tree_.max_size(); } + + //! Return a const reference to the current statistics. + const tree_stats &get_stats() const { return tree_.get_stats(); } + + //! \} + + public: + //! \name STL Access Functions Querying the Tree by Descending to a Leaf + //! \{ + + //! Non-STL function checking whether a key is in the B+ tree. The same as + //! (find(k) != end()) or (count() != 0). + bool exists(const key_type &key) const { return tree_.exists(key); } + + //! Tries to locate a key in the B+ tree and returns an iterator to the + //! key/data slot if found. If unsuccessful it returns end(). + iterator find(const key_type &key) { return tree_.find(key); } + + //! Tries to locate a key in the B+ tree and returns an constant iterator to + //! the key/data slot if found. If unsuccessful it returns end(). + const_iterator find(const key_type &key) const { return tree_.find(key); } + + //! Tries to locate a key in the B+ tree and returns the number of identical + //! key entries found. Since this is a unique map, count() returns either 0 + //! or 1. + size_type count(const key_type &key) const { return tree_.count(key); } + + //! Searches the B+ tree and returns an iterator to the first pair equal to + //! or greater than key, or end() if all keys are smaller. + iterator lower_bound(const key_type &key) { return tree_.lower_bound(key); } + + //! Searches the B+ tree and returns a constant iterator to the first pair + //! equal to or greater than key, or end() if all keys are smaller. + const_iterator lower_bound(const key_type &key) const { return tree_.lower_bound(key); } + + //! Searches the B+ tree and returns an iterator to the first pair greater + //! than key, or end() if all keys are smaller or equal. + iterator upper_bound(const key_type &key) { return tree_.upper_bound(key); } + + //! Searches the B+ tree and returns a constant iterator to the first pair + //! greater than key, or end() if all keys are smaller or equal. + const_iterator upper_bound(const key_type &key) const { return tree_.upper_bound(key); } + + //! Searches the B+ tree and returns both lower_bound() and upper_bound(). + std::pair equal_range(const key_type &key) { return tree_.equal_range(key); } + + //! Searches the B+ tree and returns both lower_bound() and upper_bound(). + std::pair equal_range(const key_type &key) const { return tree_.equal_range(key); } + + //! \} + + public: + //! \name B+ Tree Object Comparison Functions + //! \{ + + //! Equality relation of B+ trees of the same type. B+ trees of the same + //! size and equal elements (both key and data) are considered equal. + bool operator==(const btree_map &other) const { return (tree_ == other.tree_); } + + //! Inequality relation. Based on operator==. + bool operator!=(const btree_map &other) const { return (tree_ != other.tree_); } + + //! Total ordering relation of B+ trees of the same type. It uses + //! std::lexicographical_compare() for the actual comparison of elements. + bool operator<(const btree_map &other) const { return (tree_ < other.tree_); } + + //! Greater relation. Based on operator<. + bool operator>(const btree_map &other) const { return (tree_ > other.tree_); } + + //! Less-equal relation. Based on operator<. + bool operator<=(const btree_map &other) const { return (tree_ <= other.tree_); } + + //! Greater-equal relation. Based on operator<. + bool operator>=(const btree_map &other) const { return (tree_ >= other.tree_); } + + //! \} + + public: + //! \name Fast Copy: Assign Operator and Copy Constructors + //! \{ + + //! Assignment operator. All the key/data pairs are copied + btree_map &operator=(const btree_map &other) { + if (this != &other) tree_ = other.tree_; + return *this; + } + + //! Copy constructor. The newly initialized B+ tree object will contain a + //! copy of all key/data pairs. + btree_map(const btree_map &other) : tree_(other.tree_) {} + + //! \} + + public: + //! \name Public Insertion Functions + //! \{ + + //! Attempt to insert a key/data pair into the B+ tree. Fails if the pair is + //! already present. + std::pair insert(const value_type &x) { return tree_.insert(x); } + + //! Attempt to insert a key/data pair into the B+ tree. This function is the + //! same as the other insert. Fails if the inserted pair is already present. + std::pair insert2(const key_type &key, const data_type &data) { + return tree_.insert(value_type(key, data)); + } + + //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is + //! currently ignored by the B+ tree insertion routine. + iterator insert(iterator hint, const value_type &x) { return tree_.insert(hint, x); } + + //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is + //! currently ignored by the B+ tree insertion routine. + iterator insert2(iterator hint, const key_type &key, const data_type &data) { + return tree_.insert(hint, value_type(key, data)); + } + + //! Returns a reference to the object that is associated with a particular + //! key. If the map does not already contain such an object, operator[] + //! inserts the default object data_type(). + data_type &operator[](const key_type &key) { + iterator i = insert(value_type(key, data_type())).first; + return i->second; + } + + //! Attempt to insert the range [first,last) of value_type pairs into the B+ + //! tree. Each key/data pair is inserted individually. + template + void insert(InputIterator first, InputIterator last) { + return tree_.insert(first, last); + } + + //! Bulk load a sorted range [first,last). Loads items into leaves and + //! constructs a B-tree above them. The tree must be empty when calling this + //! function. + template + void bulk_load(Iterator first, Iterator last) { + return tree_.bulk_load(first, last); + } + + //! \} + + public: + //! \name Public Erase Functions + //! \{ + + //! Erases the key/data pairs associated with the given key. For this + //! unique-associative map there is no difference to erase(). + bool erase_one(const key_type &key) { return tree_.erase_one(key); } + + //! Erases all the key/data pairs associated with the given key. This is + //! implemented using erase_one(). + size_type erase(const key_type &key) { return tree_.erase(key); } + + //! Erase the key/data pair referenced by the iterator. + void erase(iterator iter) { return tree_.erase(iter); } + +#ifdef TLX_BTREE_TODO + //! Erase all key/data pairs in the range [first,last). This function is + //! currently not implemented by the B+ Tree. + void erase(iterator /* first */, iterator /* last */) { abort(); } +#endif + + //! \} + +#ifdef TLX_BTREE_DEBUG + + public: + //! \name Debug Printing + //! \{ + + //! Print out the B+ tree structure with keys onto the given ostream. This + //! function requires that the header is compiled with TLX_BTREE_DEBUG and + //! that key_type is printable via std::ostream. + void print(std::ostream &os) const { tree_.print(os); } + + //! Print out only the leaves via the double linked list. + void print_leaves(std::ostream &os) const { tree_.print_leaves(os); } + + //! \} +#endif + + public: + //! \name Verification of B+ Tree Invariants + //! \{ + + //! Run a thorough verification of all B+ tree invariants. The program + //! aborts via TLX_BTREE_ASSERT() if something is wrong. + void verify() const { tree_.verify(); } + + //! \} +}; + +//! \} + +} // namespace tlx + +#endif // !TLX_CONTAINER_BTREE_MAP_HEADER + +/******************************************************************************/ diff --git a/tests/benchmark/core.hpp b/tests/benchmark/core.hpp new file mode 100644 index 000000000..55e40bbed --- /dev/null +++ b/tests/benchmark/core.hpp @@ -0,0 +1,267 @@ +/******************************************************************************* + * tlx/die/core.hpp + * + * Part of tlx - http://panthema.net/tlx + * + * Copyright (C) 2016-2018 Timo Bingmann + * + * All rights reserved. Published under the Boost Software License, Version 1.0 + ******************************************************************************/ + +#ifndef TLX_DIE_CORE_HEADER +#define TLX_DIE_CORE_HEADER + +#include +#include +#include +#include +#include + +namespace tlx { + +/******************************************************************************/ +// die macros + +//! die with message - either throw an exception or die via std::terminate() +void die_with_message(const std::string &msg); + +//! die with message - either throw an exception or die via std::terminate() +void die_with_message(const char *msg, const char *file, size_t line); + +//! die with message - either throw an exception or die via std::terminate() +void die_with_message(const std::string &msg, const char *file, size_t line); + +//! Instead of std::terminate(), throw the output the message via an exception. +#define tlx_die_with_sstream(msg) \ + do { \ + std::ostringstream oss__; \ + oss__ << msg << " @ " << __FILE__ << ':' << __LINE__; \ + ::tlx::die_with_message(oss__.str()); \ + std::terminate(); /* tell compiler this never returns */ \ + } while (false) + +//! Instead of std::terminate(), throw the output the message via an exception. +#define tlx_die(msg) \ + do { \ + tlx_die_with_sstream("DIE: " << msg); \ + } while (false) + +//! Exception thrown by die_with_message() if +class DieException : public std::runtime_error { + public: + explicit DieException(const std::string &message); +}; + +//! Switch between dying via std::terminate() and throwing an exception. +//! Alternatively define the macro TLX_DIE_WITH_EXCEPTION=1 +bool set_die_with_exception(bool b); + +/******************************************************************************/ +// die_unless() and die_if() + +//! Check condition X and die miserably if false. Same as assert() except this +//! is also active in Release mode. +#define tlx_die_unless(X) \ + do { \ + if (!(X)) { \ + ::tlx::die_with_message("DIE: Assertion \"" #X "\" failed!", __FILE__, __LINE__); \ + } \ + } while (false) + +//! Check condition X and die miserably if true. Opposite of assert() except +//! this is also active in Release mode. +#define tlx_die_if(X) \ + do { \ + if (X) { \ + ::tlx::die_with_message("DIE: Assertion \"" #X "\" succeeded!", __FILE__, __LINE__); \ + } \ + } while (false) + +//! Check condition X and die miserably if false. Same as tlx_die_unless() +//! except the user additionally passes a message. +#define tlx_die_verbose_unless(X, msg) \ + do { \ + if (!(X)) { \ + tlx_die_with_sstream("DIE: Assertion \"" #X "\" failed!\n" << msg << '\n'); \ + } \ + } while (false) + +//! Check condition X and die miserably if false. Same as tlx_die_if() +//! except the user additionally passes a message. +#define tlx_die_verbose_if(X, msg) \ + do { \ + if ((X)) { \ + tlx_die_with_sstream("DIE: Assertion \"" #X "\" succeeded!\n" << msg << '\n'); \ + } \ + } while (false) + +/******************************************************************************/ +// die_unequal() + +//! helper method to compare two values in die_unequal() +template +inline bool die_equal_compare(TypeA a, TypeB b) { + return a == b; +} + +template <> +inline bool die_equal_compare(const char *a, const char *b) { + // compare string contents + return std::strcmp(a, b) == 0; +} + +template <> +inline bool die_equal_compare(float a, float b) { + // special case for NAN + return a != a ? b != b : a == b; +} + +template <> +inline bool die_equal_compare(double a, double b) { + // special case for NAN + return a != a ? b != b : a == b; +} + +//! Check that X == Y or die miserably, but output the values of X and Y for +//! better debugging. +#define tlx_die_unequal(X, Y) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (!::tlx::die_equal_compare(x__, y__)) \ + tlx_die_with_sstream("DIE-UNEQUAL: " #X " != " #Y \ + " : " \ + "\"" \ + << x__ << "\" != \"" << y__ << "\""); \ + } while (false) + +//! Check that X == Y or die miserably, but output the values of X and Y for +//! better debugging. Only active if NDEBUG is not defined. +#ifdef NDEBUG +#define tlx_assert_equal(X, Y) +#else +#define tlx_assert_equal(X, Y) die_unequal(X, Y) +#endif + +//! Check that X == Y or die miserably, but output the values of X and Y for +//! better debugging. Same as tlx_die_unequal() except the user additionally +//! pass a message. +#define tlx_die_verbose_unequal(X, Y, msg) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (!::tlx::die_equal_compare(x__, y__)) \ + tlx_die_with_sstream("DIE-UNEQUAL: " #X " != " #Y \ + " : " \ + "\"" \ + << x__ << "\" != \"" << y__ << "\"\n" \ + << msg << '\n'); \ + } while (false) + +/******************************************************************************/ +// die_unequal_eps() + +//! simple replacement for std::abs +template +inline Type die_unequal_eps_abs(const Type &t) { + return t < 0 ? -t : t; +} + +//! helper method to compare two values in die_unequal_eps() +template +inline bool die_equal_eps_compare(TypeA x, TypeB y, double eps) { + // special case for NAN + return x != x ? y != y : die_unequal_eps_abs(x - y) <= eps; +} + +//! Check that ABS(X - Y) <= eps or die miserably, but output the values of X +//! and Y for better debugging. +#define tlx_die_unequal_eps(X, Y, eps) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (!::tlx::die_equal_eps_compare(x__, y__, eps)) \ + tlx_die("DIE-UNEQUAL-EPS: " #X " != " #Y " : " << std::setprecision(18) << "\"" << x__ << "\" != \"" << y__ \ + << "\""); \ + } while (false) + +//! Check that ABS(X - Y) <= eps or die miserably, but output the values of X +//! and Y for better debugging. Same as tlx_die_unequal_eps() except the user +//! additionally passes a message. +#define tlx_die_verbose_unequal_eps(X, Y, eps, msg) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (!::tlx::die_equal_eps_compare(x__, y__, eps)) \ + tlx_die("DIE-UNEQUAL-EPS: " #X " != " #Y " : " << std::setprecision(18) << "\"" << x__ << "\" != \"" << y__ \ + << "\"\n" \ + << msg << '\n'); \ + } while (false) + +//! Check that ABS(X - Y) <= 0.000001 or die miserably, but output the values of +//! X and Y for better debugging. +#define tlx_die_unequal_eps6(X, Y) die_unequal_eps(X, Y, 1e-6) + +//! Check that ABS(X - Y) <= 0.000001 or die miserably, but output the values of +//! X and Y for better debugging. Same as tlx_die_unequal_eps6() except the user +//! additionally passes a message. +#define tlx_die_verbose_unequal_eps6(X, Y, msg) die_verbose_unequal_eps(X, Y, 1e-6, msg) + +/******************************************************************************/ +// die_equal() + +//! Die miserably if X == Y, but first output the values of X and Y for better +//! debugging. +#define tlx_die_equal(X, Y) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (::tlx::die_equal_compare(x__, y__)) \ + tlx_die_with_sstream("DIE-EQUAL: " #X " == " #Y \ + " : " \ + "\"" \ + << x__ << "\" == \"" << y__ << "\""); \ + } while (false) + +//! Die miserably if X == Y, but first output the values of X and Y for better +//! debugging. Only active if NDEBUG is not defined. +#ifdef NDEBUG +#define tlx_assert_unequal(X, Y) +#else +#define tlx_assert_unequal(X, Y) die_equal(X, Y) +#endif + +//! Die miserably if X == Y, but first output the values of X and Y for better +//! debugging. Same as tlx_die_equal() except the user additionally passes a +//! message. +#define tlx_die_verbose_equal(X, Y, msg) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (::tlx::die_equal_compare(x__, y__)) \ + tlx_die_with_sstream("DIE-EQUAL: " #X " == " #Y \ + " : " \ + "\"" \ + << x__ << "\" == \"" << y__ << "\"\n" \ + << msg << '\n'); \ + } while (false) + +/******************************************************************************/ +// die_unless_throws() + +//! Define to check that [code] throws and exception of given type +#define tlx_die_unless_throws(code, exception_type) \ + do { \ + try { \ + code; \ + } catch (const exception_type &) { \ + break; \ + } \ + ::tlx::die_with_message("DIE-UNLESS-THROWS: " #code " - NO EXCEPTION " #exception_type, __FILE__, __LINE__); \ + } while (false) + +} // namespace tlx + +#endif // !TLX_DIE_CORE_HEADER + +/******************************************************************************/ From 243fa5e4b26dbd21fa347a8566556eb5c19b57c2 Mon Sep 17 00:00:00 2001 From: jbajic Date: Sun, 27 Nov 2022 21:21:55 +0100 Subject: [PATCH 05/93] Add insert benchmark --- src/storage/v3/key_store.hpp | 5 +- src/storage/v3/property_store.cpp | 10 ++ src/storage/v3/property_store.hpp | 4 +- src/storage/v3/vertex.hpp | 2 + tests/benchmark/CMakeLists.txt | 13 +- tests/benchmark/data_structures_insert.cpp | 113 ++++++++++++++++++ ...uctures.cpp => data_structures_random.cpp} | 0 7 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 tests/benchmark/data_structures_insert.cpp rename tests/benchmark/{data_structures.cpp => data_structures_random.cpp} (100%) diff --git a/src/storage/v3/key_store.hpp b/src/storage/v3/key_store.hpp index 4bc3c25e3..1762273fb 100644 --- a/src/storage/v3/key_store.hpp +++ b/src/storage/v3/key_store.hpp @@ -26,11 +26,12 @@ using PrimaryKey = std::vector; class KeyStore { public: + KeyStore() = default; explicit KeyStore(const PrimaryKey &key_values); - KeyStore(const KeyStore &) = delete; + KeyStore(const KeyStore &) = default; KeyStore(KeyStore &&other) noexcept = default; - KeyStore &operator=(const KeyStore &) = delete; + KeyStore &operator=(const KeyStore &) = default; KeyStore &operator=(KeyStore &&other) noexcept = default; ~KeyStore() = default; diff --git a/src/storage/v3/property_store.cpp b/src/storage/v3/property_store.cpp index fc7bd6984..bf8460003 100644 --- a/src/storage/v3/property_store.cpp +++ b/src/storage/v3/property_store.cpp @@ -928,6 +928,16 @@ void SetSizeData(uint8_t *buffer, uint64_t size, uint8_t *data) { PropertyStore::PropertyStore() { memset(buffer_, 0, sizeof(buffer_)); } +PropertyStore::PropertyStore(const PropertyStore &other) { memcpy(buffer_, other.buffer_, sizeof(buffer_)); } + +PropertyStore &PropertyStore::operator=(const PropertyStore &other) { + if (this == &other) { + return *this; + } + memcpy(buffer_, other.buffer_, sizeof(buffer_)); + return *this; +} + PropertyStore::PropertyStore(PropertyStore &&other) noexcept { memcpy(buffer_, other.buffer_, sizeof(buffer_)); memset(other.buffer_, 0, sizeof(other.buffer_)); diff --git a/src/storage/v3/property_store.hpp b/src/storage/v3/property_store.hpp index 477795203..00dc4d40f 100644 --- a/src/storage/v3/property_store.hpp +++ b/src/storage/v3/property_store.hpp @@ -25,9 +25,9 @@ class PropertyStore { public: PropertyStore(); - PropertyStore(const PropertyStore &) = delete; + PropertyStore(const PropertyStore &); PropertyStore(PropertyStore &&other) noexcept; - PropertyStore &operator=(const PropertyStore &) = delete; + PropertyStore &operator=(const PropertyStore &); PropertyStore &operator=(PropertyStore &&other) noexcept; ~PropertyStore(); diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index 2ed4c6e4c..cbfac4ee8 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -31,6 +31,8 @@ namespace memgraph::storage::v3 { struct Vertex { using EdgeLink = std::tuple; + Vertex() = default; + Vertex(Delta *delta, const std::vector &primary_properties) : keys{primary_properties}, delta{delta} { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 46905a4d8..bf32e3071 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -68,5 +68,14 @@ target_link_libraries(${test_prefix}storage_v2_property_store mg-storage-v2) add_benchmark(future.cpp) target_link_libraries(${test_prefix}future mg-io) -add_benchmark(data_structures.cpp) -target_link_libraries(${test_prefix}data_structures mg-utils) +add_benchmark(data_structures_random.cpp) +target_link_libraries(${test_prefix}data_structures_random mg-utils) + +add_benchmark(data_structures_insert.cpp) +target_link_libraries(${test_prefix}data_structures_insert mg-utils mg-storage-v3) + +add_benchmark(data_structures_find.cpp) +target_link_libraries(${test_prefix}data_structures_find mg-utils mg-storage-v3) + +add_benchmark(data_structures_contains.cpp) +target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp new file mode 100644 index 000000000..be2d7457f --- /dev/null +++ b/tests/benchmark/data_structures_insert.cpp @@ -0,0 +1,113 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "btree_map.hpp" +#include "skip_list_common.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +/////////////////////////////////////////////////////////////////////////////// +// Testing Insert Operation +/////////////////////////////////////////////////////////////////////////////// +static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { + utils::SkipList skip_list; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + auto acc = skip_list.access(); + acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + } + } +} + +static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { + std::map std_map; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } + } +} + +static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { + std::set std_set; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + std_set.insert(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + } + } +} + +static void BM_BenchmarkInsertBppTree(::benchmark::State &state) { + tlx::btree_map bpp_tree; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } + } +} + +BENCHMARK(BM_BenchmarkInsertSkipList)->Arg(1000); + +BENCHMARK(BM_BenchmarkInsertStdMap)->Arg(1000); + +BENCHMARK(BM_BenchmarkInsertStdSet)->Arg(1000); + +BENCHMARK(BM_BenchmarkInsertBppTree)->Arg(1000); + +} // namespace memgraph::benchmark + +BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures.cpp b/tests/benchmark/data_structures_random.cpp similarity index 100% rename from tests/benchmark/data_structures.cpp rename to tests/benchmark/data_structures_random.cpp From cceab46a7caca21cb534503cd92961da62c4f00f Mon Sep 17 00:00:00 2001 From: jbajic Date: Sun, 27 Nov 2022 21:59:18 +0100 Subject: [PATCH 06/93] Add find benchmark --- tests/benchmark/data_structures_find.cpp | 155 +++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tests/benchmark/data_structures_find.cpp diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp new file mode 100644 index 000000000..c2b2de83c --- /dev/null +++ b/tests/benchmark/data_structures_find.cpp @@ -0,0 +1,155 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "btree_map.hpp" +#include "skip_list_common.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +/////////////////////////////////////////////////////////////////////////////// +// Testing Find Operation +/////////////////////////////////////////////////////////////////////////////// +static void BM_BenchmarkFindSkipList(::benchmark::State &state) { + utils::SkipList skip_list; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < state.range(0); ++i) { + auto acc = skip_list.access(); + acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + } + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + auto acc = skip_list.access(); + if (acc.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != acc.end()) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkFindStdMap(::benchmark::State &state) { + std::map std_map; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < state.range(0); ++i) { + std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_map.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != std_map.end()) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkFindStdSet(::benchmark::State &state) { + std::set std_set; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < state.range(0); ++i) { + std_set.insert(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + } + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_set.find(storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}}}) != std_set.end()) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkFindBppTree(::benchmark::State &state) { + tlx::btree_map bpp_tree; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < state.range(0); ++i) { + bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (bpp_tree.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != bpp_tree.end()) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +BENCHMARK(BM_BenchmarkFindSkipList)->Arg(1000); + +BENCHMARK(BM_BenchmarkFindStdMap)->Arg(1000); + +BENCHMARK(BM_BenchmarkFindStdSet)->Arg(1000); + +BENCHMARK(BM_BenchmarkFindBppTree)->Arg(1000); + +} // namespace memgraph::benchmark + +BENCHMARK_MAIN(); From 41b06a0a37e13343895e9934458203f1b6c56827 Mon Sep 17 00:00:00 2001 From: jbajic Date: Sun, 27 Nov 2022 22:28:23 +0100 Subject: [PATCH 07/93] Extract common functionalities --- tests/benchmark/CMakeLists.txt | 3 + tests/benchmark/data_structures_common.hpp | 76 +++++++++++ tests/benchmark/data_structures_contains.cpp | 131 +++++++++++++++++++ tests/benchmark/data_structures_find.cpp | 37 +----- tests/benchmark/data_structures_insert.cpp | 1 - 5 files changed, 216 insertions(+), 32 deletions(-) create mode 100644 tests/benchmark/data_structures_common.hpp create mode 100644 tests/benchmark/data_structures_contains.cpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index bf32e3071..6ec3fa644 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -79,3 +79,6 @@ target_link_libraries(${test_prefix}data_structures_find mg-utils mg-storage-v3) add_benchmark(data_structures_contains.cpp) target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) + +add_benchmark(data_structures_remove.cpp) +target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp new file mode 100644 index 000000000..fe6129a22 --- /dev/null +++ b/tests/benchmark/data_structures_common.hpp @@ -0,0 +1,76 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include +#include +#include + +#include "btree_map.hpp" +#include "coordinator/hybrid_logical_clock.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/transaction.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +template +inline void PrepareData(utils::SkipList &skip_list, const int64_t num_elements) { + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < num_elements; ++i) { + auto acc = skip_list.access(); + acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + } +} + +template +inline void PrepareData(std::map &std_map, const int64_t num_elements) { + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < num_elements; ++i) { + std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } +} + +template +inline void PrepareData(std::set &std_set, const int64_t num_elements) { + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < num_elements; ++i) { + std_set.insert(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + } +} + +template +inline void PrepareData(tlx::btree_map &bpp_tree, const int64_t num_elements) { + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < num_elements; ++i) { + bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } +} +} // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp new file mode 100644 index 000000000..08596c7a7 --- /dev/null +++ b/tests/benchmark/data_structures_contains.cpp @@ -0,0 +1,131 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "btree_map.hpp" +#include "data_structures_common.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +/////////////////////////////////////////////////////////////////////////////// +// Testing Contains Operation +/////////////////////////////////////////////////////////////////////////////// +static void BM_BenchmarkContainsSkipList(::benchmark::State &state) { + utils::SkipList skip_list; + PrepareData(skip_list, state.range(0)); + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + auto acc = skip_list.access(); + if (acc.contains(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}})) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkContainsStdMap(::benchmark::State &state) { + std::map std_map; + PrepareData(std_map, state.range(0)); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_map.contains(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}})) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { + std::set std_set; + PrepareData(std_set, state.range(0)); + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_set.contains(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}}})) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { + tlx::btree_map bpp_tree; + PrepareData(bpp_tree, state.range(0)); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (bpp_tree.count(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) > 0) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +BENCHMARK(BM_BenchmarkContainsSkipList)->Arg(1000); + +BENCHMARK(BM_BenchmarkContainsStdMap)->Arg(1000); + +BENCHMARK(BM_BenchmarkContainsStdSet)->Arg(1000); + +BENCHMARK(BM_BenchmarkContainsBppTree)->Arg(1000); + +} // namespace memgraph::benchmark + +BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index c2b2de83c..042066d68 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -24,7 +24,7 @@ #include #include "btree_map.hpp" -#include "skip_list_common.hpp" +#include "data_structures_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" @@ -40,14 +40,7 @@ namespace memgraph::benchmark { /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkFindSkipList(::benchmark::State &state) { utils::SkipList skip_list; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < state.range(0); ++i) { - auto acc = skip_list.access(); - acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); - } + PrepareData(skip_list, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); @@ -66,15 +59,7 @@ static void BM_BenchmarkFindSkipList(::benchmark::State &state) { static void BM_BenchmarkFindStdMap(::benchmark::State &state) { std::map std_map; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < state.range(0); ++i) { - std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, - storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); - } + PrepareData(std_map, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); @@ -92,14 +77,12 @@ static void BM_BenchmarkFindStdMap(::benchmark::State &state) { static void BM_BenchmarkFindStdSet(::benchmark::State &state) { std::set std_set; + PrepareData(std_set, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < state.range(0); ++i) { - std_set.insert(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); - } + // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); @@ -118,15 +101,7 @@ static void BM_BenchmarkFindStdSet(::benchmark::State &state) { static void BM_BenchmarkFindBppTree(::benchmark::State &state) { tlx::btree_map bpp_tree; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < state.range(0); ++i) { - bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, - storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); - } + PrepareData(bpp_tree, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index be2d7457f..4d427985b 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -24,7 +24,6 @@ #include #include "btree_map.hpp" -#include "skip_list_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" From 36a7abb17080de344d9c7fb75e21ec689ee69687 Mon Sep 17 00:00:00 2001 From: jbajic Date: Sun, 27 Nov 2022 22:50:36 +0100 Subject: [PATCH 08/93] Add remove benchmark --- tests/benchmark/CMakeLists.txt | 2 +- tests/benchmark/data_structures_remove.cpp | 138 +++++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 tests/benchmark/data_structures_remove.cpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 6ec3fa644..61a4cd62a 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -81,4 +81,4 @@ add_benchmark(data_structures_contains.cpp) target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) add_benchmark(data_structures_remove.cpp) -target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) +target_link_libraries(${test_prefix}data_structures_remove mg-utils mg-storage-v3) diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp new file mode 100644 index 000000000..3fbd54a38 --- /dev/null +++ b/tests/benchmark/data_structures_remove.cpp @@ -0,0 +1,138 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "btree_map.hpp" +#include "data_structures_common.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +/////////////////////////////////////////////////////////////////////////////// +// Testing Remove Operation +/////////////////////////////////////////////////////////////////////////////// +static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { + utils::SkipList skip_list; + PrepareData(skip_list, state.range(0)); + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t removed_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + auto acc = skip_list.access(); + if (acc.remove(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, storage::v3::PrimaryKey{storage::v3::PropertyValue(value)}}})) { + removed_elems++; + } + } + } + state.SetItemsProcessed(removed_elems); +} + +static void BM_BenchmarkRemoveStdMap(::benchmark::State &state) { + std::map std_map; + PrepareData(std_map, state.range(0)); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t removed_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_map.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) { + removed_elems++; + } + } + } + state.SetItemsProcessed(removed_elems); +} + +static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { + std::set std_set; + PrepareData(std_set, state.range(0)); + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t removed_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_set.erase(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, {storage::v3::PropertyValue{value}}}}) > 0) { + removed_elems++; + } + } + } + state.SetItemsProcessed(removed_elems); +} + +static void BM_BenchmarkRemoveBppTree(::benchmark::State &state) { + tlx::btree_map bpp_tree; + PrepareData(bpp_tree, state.range(0)); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t removed_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (bpp_tree.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) { + removed_elems++; + } + } + } + state.SetItemsProcessed(removed_elems); +} + +BENCHMARK(BM_BenchmarkRemoveSkipList)->Arg(1000); + +BENCHMARK(BM_BenchmarkRemoveStdMap)->Arg(1000); + +BENCHMARK(BM_BenchmarkRemoveStdSet)->Arg(1000); + +BENCHMARK(BM_BenchmarkRemoveBppTree)->Arg(1000); + +} // namespace memgraph::benchmark + +BENCHMARK_MAIN(); From 500691318a005c712ae4a3ca89e4b6e743d92bd4 Mon Sep 17 00:00:00 2001 From: jbajic Date: Mon, 28 Nov 2022 14:15:41 +0100 Subject: [PATCH 09/93] Add analyze script --- tests/benchmark/data_structures_contains.cpp | 10 +- tests/benchmark/data_structures_find.cpp | 8 +- tests/benchmark/data_structures_insert.cpp | 8 +- tests/benchmark/data_structures_remove.cpp | 8 +- tools/plot/benchmark_datastructures.py | 172 +++++++++++++++++++ tools/plot/benchmark_datastructures.sh | 16 ++ 6 files changed, 205 insertions(+), 17 deletions(-) create mode 100644 tools/plot/benchmark_datastructures.py create mode 100755 tools/plot/benchmark_datastructures.sh diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp index 08596c7a7..bf1a74a8d 100644 --- a/tests/benchmark/data_structures_contains.cpp +++ b/tests/benchmark/data_structures_contains.cpp @@ -103,7 +103,7 @@ static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { tlx::btree_map bpp_tree; PrepareData(bpp_tree, state.range(0)); - // So we can also have elements that does don't exist + // So we can also have elements that does don't exists std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); int64_t found_elems{0}; @@ -118,13 +118,13 @@ static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -BENCHMARK(BM_BenchmarkContainsSkipList)->Arg(1000); +BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsStdMap)->Arg(1000); +BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsStdSet)->Arg(1000); +BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsBppTree)->Arg(1000); +BENCHMARK(BM_BenchmarkContainsBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index 042066d68..d3b3bdd60 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -117,13 +117,13 @@ static void BM_BenchmarkFindBppTree(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -BENCHMARK(BM_BenchmarkFindSkipList)->Arg(1000); +BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindStdMap)->Arg(1000); +BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindStdSet)->Arg(1000); +BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindBppTree)->Arg(1000); +BENCHMARK(BM_BenchmarkFindBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 4d427985b..8882b444e 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -99,13 +99,13 @@ static void BM_BenchmarkInsertBppTree(::benchmark::State &state) { } } -BENCHMARK(BM_BenchmarkInsertSkipList)->Arg(1000); +BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertStdMap)->Arg(1000); +BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertStdSet)->Arg(1000); +BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertBppTree)->Arg(1000); +BENCHMARK(BM_BenchmarkInsertBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index 3fbd54a38..5296a9130 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -125,13 +125,13 @@ static void BM_BenchmarkRemoveBppTree(::benchmark::State &state) { state.SetItemsProcessed(removed_elems); } -BENCHMARK(BM_BenchmarkRemoveSkipList)->Arg(1000); +BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveStdMap)->Arg(1000); +BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveStdSet)->Arg(1000); +BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveBppTree)->Arg(1000); +BENCHMARK(BM_BenchmarkRemoveBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tools/plot/benchmark_datastructures.py b/tools/plot/benchmark_datastructures.py new file mode 100644 index 000000000..29fa61be7 --- /dev/null +++ b/tools/plot/benchmark_datastructures.py @@ -0,0 +1,172 @@ +# Copyright 2022 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import argparse +import json +import sys +from dataclasses import dataclass +from enum import Enum +from pathlib import Path +from typing import Any, Dict, List, Optional + +import matplotlib.pyplot as plt + + +class Operation(Enum): + CONTAINS = "contains" + FIND = "find" + INSERT = "insert" + RANDOM = "random" + REMOVE = "remove" + + @classmethod + def to_list(cls) -> List[str]: + return list(map(lambda c: c.value, cls)) + + @staticmethod + def get(s: str) -> Optional["Operation"]: + try: + return Operation[s.upper()] + except ValueError: + return None + + def __str__(self): + return str(self.value) + + +@dataclass(frozen=True) +class BenchmarkRow: + name: str + datastructure: str + operation: Operation + real_time: int + cpu_time: int + iterations: int + time_unit: str + run_arg: Optional[Any] + + +class GoogleBenchmarkResult: + def __init__(self): + self._operation = None + self._datastructures: Dict[str, List[BenchmarkRow]] = dict() + + def add_result(self, row: BenchmarkRow) -> None: + if self._operation is None: + self._operation = row.operation + assert self._operation is row.operation + if row.datastructure not in self._datastructures: + self._datastructures[row.datastructure] = [row] + else: + self._datastructures[row.datastructure].append(row) + + @property + def operation(self) -> Optional[Operation]: + return self._operation + + @property + def datastructures(self) -> Dict[str, List[BenchmarkRow]]: + return self._datastructures + + +def get_operation(s: str) -> Operation: + for op in Operation.to_list(): + if op.lower() in s.lower(): + operation_enum = Operation.get(op) + if operation_enum is not None: + return operation_enum + else: + print("Operation not found!") + sys.exit(1) + print("Operation not found!") + sys.exit(1) + + +def get_row_data(line: Dict[str, Any]) -> BenchmarkRow: + """ + Naming is very important, first must come an Operation name, and then a data + structure to test. + """ + full_name = line["name"].split("BM_Benchmark")[1] + name_with_run_arg = full_name.split("/") + operation = get_operation(name_with_run_arg[0]) + datastructure = name_with_run_arg[0].split(operation.value.capitalize())[1] + + run_arg = None + if len(name_with_run_arg) > 1: + run_arg = name_with_run_arg[1] + + return BenchmarkRow( + name_with_run_arg[0], + datastructure, + operation, + line["real_time"], + line["cpu_time"], + line["iterations"], + line["time_unit"], + run_arg, + ) + + +def get_benchmark_res(args) -> Optional[GoogleBenchmarkResult]: + file_path = Path(args.log_file) + if not file_path.exists(): + print("Error file {file_path} not found!") + return None + with file_path.open("r") as file: + data = json.load(file) + res = GoogleBenchmarkResult() + assert "benchmarks" in data, "There must be a benchmark list inside" + for benchmark in data["benchmarks"]: + res.add_result(get_row_data(benchmark)) + return res + + +def plot_operation(results: GoogleBenchmarkResult, save: bool) -> None: + colors = ["red", "green", "blue", "yellow", "purple", "brown"] + assert results.operation is not None + fig = plt.figure() + for ds, benchmarks in results.datastructures.items(): + if benchmarks: + # Print line chart + x_axis = [elem.real_time for elem in benchmarks] + y_axis = [elem.run_arg for elem in benchmarks] + plt.plot(x_axis, y_axis, marker="", color=colors.pop(0), linewidth="2", label=f"{ds}") + plt.title(f"Benchmark results for operation {results.operation.value}") + plt.xlabel(f"Time [{benchmarks[0].time_unit}]") + plt.legend() + else: + print(f"Nothing to do for {ds}...") + if save: + plt.savefig(f"{results.operation.value}.png") + plt.close(fig) + else: + plt.show() + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Process benchmark results.") + parser.add_argument("--log_file", type=str) + parser.add_argument("--save", type=bool, default=True) + return parser.parse_args() + + +def main(): + args = parse_args() + res = get_benchmark_res(args) + if res is None: + print("Failed to get results from log file!") + sys.exit(1) + plot_operation(res, args.save) + + +if __name__ == "__main__": + main() diff --git a/tools/plot/benchmark_datastructures.sh b/tools/plot/benchmark_datastructures.sh new file mode 100755 index 000000000..450b38466 --- /dev/null +++ b/tools/plot/benchmark_datastructures.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euox pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +WORKSPACE_DIR=${SCRIPT_DIR}/../../ + +BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname data_structures_*) +echo $BENCHMARK_FILES +for bench_file in ${BENCHMARK_FILES}; do + echo "Running $name" + base_name=$(basename $bench_file) + name=${base_name%%.*} + ${WORKSPACE_DIR}/build/tests/benchmark/${name} --benchmark_format=json --benchmark_out=${name}_output.json + python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log-file=${name}_output.json +done From 5f5d839f0c22a4d611deea0aacce8f171328e454 Mon Sep 17 00:00:00 2001 From: jbajic Date: Mon, 28 Nov 2022 15:11:58 +0100 Subject: [PATCH 10/93] Update analyzer script --- tests/benchmark/data_structures_contains.cpp | 8 +++--- tests/benchmark/data_structures_find.cpp | 8 +++--- tests/benchmark/data_structures_insert.cpp | 8 +++--- tests/benchmark/data_structures_remove.cpp | 8 +++--- tools/plot/benchmark_datastructures.py | 13 +++++++++ tools/plot/benchmark_datastructures.sh | 29 ++++++++++++++------ 6 files changed, 50 insertions(+), 24 deletions(-) diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp index bf1a74a8d..604ab6ef9 100644 --- a/tests/benchmark/data_structures_contains.cpp +++ b/tests/benchmark/data_structures_contains.cpp @@ -118,13 +118,13 @@ static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkContainsBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index d3b3bdd60..502be91da 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -117,13 +117,13 @@ static void BM_BenchmarkFindBppTree(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkFindBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 8882b444e..024c23202 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -99,13 +99,13 @@ static void BM_BenchmarkInsertBppTree(::benchmark::State &state) { } } -BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkInsertBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index 5296a9130..aa02820fc 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -125,13 +125,13 @@ static void BM_BenchmarkRemoveBppTree(::benchmark::State &state) { state.SetItemsProcessed(removed_elems); } -BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkRemoveBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tools/plot/benchmark_datastructures.py b/tools/plot/benchmark_datastructures.py index 29fa61be7..f57516d4d 100644 --- a/tools/plot/benchmark_datastructures.py +++ b/tools/plot/benchmark_datastructures.py @@ -9,6 +9,17 @@ # by the Apache License, Version 2.0, included in the file # licenses/APL.txt. +#################################### +# Benchmark datastructures analyzer +#################################### +# This scripts uses the output from dataset benchmark tests to plot charts +# comparing the results of different datastructures on the same operation. +# +# Note: Naming the tests is very important in order for this script to recognize +# which operation is being performed and on which DS, so it should come in this +# form: BM_Benchmark/ +# where run_argument will be added automatically by google benchmark framework + import argparse import json import sys @@ -142,7 +153,9 @@ def plot_operation(results: GoogleBenchmarkResult, save: bool) -> None: plt.plot(x_axis, y_axis, marker="", color=colors.pop(0), linewidth="2", label=f"{ds}") plt.title(f"Benchmark results for operation {results.operation.value}") plt.xlabel(f"Time [{benchmarks[0].time_unit}]") + plt.grid(True) plt.legend() + plt.draw() else: print(f"Nothing to do for {ds}...") if save: diff --git a/tools/plot/benchmark_datastructures.sh b/tools/plot/benchmark_datastructures.sh index 450b38466..3001171c0 100755 --- a/tools/plot/benchmark_datastructures.sh +++ b/tools/plot/benchmark_datastructures.sh @@ -4,13 +4,26 @@ set -euox pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" WORKSPACE_DIR=${SCRIPT_DIR}/../../ +CPUS=$(grep -c processor < /proc/cpuinfo) -BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname data_structures_*) -echo $BENCHMARK_FILES -for bench_file in ${BENCHMARK_FILES}; do - echo "Running $name" - base_name=$(basename $bench_file) - name=${base_name%%.*} - ${WORKSPACE_DIR}/build/tests/benchmark/${name} --benchmark_format=json --benchmark_out=${name}_output.json - python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log-file=${name}_output.json +# Get all benchmark files +BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname "data_structures_*") +echo $(ls ${WORKSPACE_DIR}/tests/benchmark) +for BENCH_FILE in ${BENCHMARK_FILES}; do + BASE_NAME=$(basename $BENCH_FILE) + NAME=${BASE_NAME%%.*} + echo "Running $NAME" + TEST_FILE=${WORKSPACE_DIR}/build/tests/benchmark/${NAME} + if [[ -f "${TEST_FILE}" ]]; then + pushd ${WORKSPACE_DIR}/build + make -j${CPUS} memgraph__benchmark__${NAME} + popd + JSON_OUTPUT=${NAME}_output.json + # Run benchmakr test + ${WORKSPACE_DIR}/build/tests/benchmark/${NAME} --benchmark_format=json --benchmark_out=${JSON_OUTPUT} + # Run analyze script for benchmark test + python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log_file=${JSON_OUTPUT} + else + echo "File ${TEST_FILE} does not exist!" + fi done From b244c4d6ee7f75e291b96940ff8ee3a71f4bb784 Mon Sep 17 00:00:00 2001 From: jeremy Date: Mon, 28 Nov 2022 17:32:29 +0100 Subject: [PATCH 11/93] Impl of Multiframe and iterators --- src/expr/interpret/frame.hpp | 15 +++ src/query/v2/CMakeLists.txt | 3 +- src/query/v2/bindings/frame.hpp | 5 +- src/query/v2/interpreter.cpp | 115 ++++++++++++++++- src/query/v2/multiframe.cpp | 128 +++++++++++++++++++ src/query/v2/multiframe.hpp | 218 ++++++++++++++++++++++++++++++++ src/query/v2/plan/operator.cpp | 40 +++++- src/query/v2/plan/operator.lcp | 7 +- 8 files changed, 523 insertions(+), 8 deletions(-) create mode 100644 src/query/v2/multiframe.cpp create mode 100644 src/query/v2/multiframe.hpp diff --git a/src/expr/interpret/frame.hpp b/src/expr/interpret/frame.hpp index c0619e50e..6a220b6a0 100644 --- a/src/expr/interpret/frame.hpp +++ b/src/expr/interpret/frame.hpp @@ -42,4 +42,19 @@ class Frame { utils::pmr::vector elems_; }; +template +class FrameWithValidity final : public Frame { + public: + explicit FrameWithValidity(int64_t size) : Frame(size), is_valid_(false) {} + + FrameWithValidity(int64_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {} + + bool IsValid() const { return is_valid_; } + void MakeValid() { is_valid_ = true; } + void MakeInvalid() { is_valid_ = false; } + + private: + bool is_valid_; +}; + } // namespace memgraph::expr diff --git a/src/query/v2/CMakeLists.txt b/src/query/v2/CMakeLists.txt index 3c3f780c8..0c6fa5f46 100644 --- a/src/query/v2/CMakeLists.txt +++ b/src/query/v2/CMakeLists.txt @@ -24,7 +24,8 @@ set(mg_query_v2_sources plan/variable_start_planner.cpp serialization/property_value.cpp bindings/typed_value.cpp - accessors.cpp) + accessors.cpp + multiframe.cpp) find_package(Boost REQUIRED) diff --git a/src/query/v2/bindings/frame.hpp b/src/query/v2/bindings/frame.hpp index f5c425f23..688f14121 100644 --- a/src/query/v2/bindings/frame.hpp +++ b/src/query/v2/bindings/frame.hpp @@ -13,9 +13,10 @@ #include "query/v2/bindings/bindings.hpp" -#include "query/v2/bindings/typed_value.hpp" #include "expr/interpret/frame.hpp" +#include "query/v2/bindings/typed_value.hpp" namespace memgraph::query::v2 { using Frame = memgraph::expr::Frame; -} // namespace memgraph::query::v2 +using FrameWithValidity = memgraph::expr::FrameWithValidity; +} // namespace memgraph::query::v2 diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index 045d94709..99eedccb9 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -41,6 +41,7 @@ #include "query/v2/frontend/ast/ast.hpp" #include "query/v2/frontend/semantic/required_privileges.hpp" #include "query/v2/metadata.hpp" +#include "query/v2/multiframe.hpp" #include "query/v2/plan/planner.hpp" #include "query/v2/plan/profile.hpp" #include "query/v2/plan/vertex_count_cache.hpp" @@ -655,11 +656,15 @@ struct PullPlan { std::optional Pull(AnyStream *stream, std::optional n, const std::vector &output_symbols, std::map *summary); + std::optional PullMultiple(AnyStream *stream, std::optional n, + const std::vector &output_symbols, + std::map *summary); private: std::shared_ptr plan_ = nullptr; plan::UniqueCursorPtr cursor_ = nullptr; - expr::Frame frame_; + expr::FrameWithValidity frame_; + MultiFrame multi_frame_; ExecutionContext ctx_; std::optional memory_limit_; @@ -683,6 +688,7 @@ PullPlan::PullPlan(const std::shared_ptr plan, const Parameters &par : plan_(plan), cursor_(plan->plan().MakeCursor(execution_memory)), frame_(plan->symbol_table().max_position(), execution_memory), + multi_frame_(frame_, kNumberOfFramesInMultiframe, execution_memory), memory_limit_(memory_limit) { ctx_.db_accessor = dba; ctx_.symbol_table = plan->symbol_table(); @@ -699,9 +705,116 @@ PullPlan::PullPlan(const std::shared_ptr plan, const Parameters &par ctx_.edge_ids_alloc = &interpreter_context->edge_ids_alloc; } +std::optional PullPlan::PullMultiple(AnyStream *stream, std::optional n, + const std::vector &output_symbols, + std::map *summary) { + // Set up temporary memory for a single Pull. Initial memory comes from the + // stack. 256 KiB should fit on the stack and should be more than enough for a + // single `Pull`. + MG_ASSERT(!n.has_value(), "should pull all!"); + static constexpr size_t stack_size = 256UL * 1024UL; + char stack_data[stack_size]; + utils::ResourceWithOutOfMemoryException resource_with_exception; + utils::MonotonicBufferResource monotonic_memory(&stack_data[0], stack_size, &resource_with_exception); + // We can throw on every query because a simple queries for deleting will use only + // the stack allocated buffer. + // Also, we want to throw only when the query engine requests more memory and not the storage + // so we add the exception to the allocator. + // TODO (mferencevic): Tune the parameters accordingly. + utils::PoolResource pool_memory(128, 1024, &monotonic_memory); + std::optional maybe_limited_resource; + + if (memory_limit_) { + maybe_limited_resource.emplace(&pool_memory, *memory_limit_); + ctx_.evaluation_context.memory = &*maybe_limited_resource; + } else { + ctx_.evaluation_context.memory = &pool_memory; + } + + // Returns true if a result was pulled. + const auto pull_result = [&]() -> bool { + cursor_->PullMultiple(multi_frame_, ctx_); + return multi_frame_.HasValidFrame(); + }; + + const auto stream_values = [&output_symbols, &stream](Frame &frame) { + // TODO: The streamed values should also probably use the above memory. + std::vector values; + values.reserve(output_symbols.size()); + + for (const auto &symbol : output_symbols) { + values.emplace_back(frame[symbol]); + } + + stream->Result(values); + }; + + // Get the execution time of all possible result pulls and streams. + utils::Timer timer; + + int i = 0; + if (has_unsent_results_ && !output_symbols.empty()) { + // stream unsent results from previous pull + + auto iterator_for_valid_frame_only = multi_frame_.GetItOnConstValidFrames(); + for (auto &frame : iterator_for_valid_frame_only) { + stream_values(frame); + ++i; + } + multi_frame_.ResetAllFramesInvalid(); + } + + for (; !n || i < n;) { + if (!pull_result()) { + break; + } + + if (!output_symbols.empty()) { + auto iterator_for_valid_frame_only = multi_frame_.GetItOnConstValidFrames(); + for (auto &frame : iterator_for_valid_frame_only) { + stream_values(frame); + ++i; + } + } + multi_frame_.ResetAllFramesInvalid(); + } + + // If we finished because we streamed the requested n results, + // we try to pull the next result to see if there is more. + // If there is additional result, we leave the pulled result in the frame + // and set the flag to true. + has_unsent_results_ = i == n && pull_result(); + + execution_time_ += timer.Elapsed(); + + if (has_unsent_results_) { + return std::nullopt; + } + summary->insert_or_assign("plan_execution_time", execution_time_.count()); + // We are finished with pulling all the data, therefore we can send any + // metadata about the results i.e. notifications and statistics + const bool is_any_counter_set = + std::any_of(ctx_.execution_stats.counters.begin(), ctx_.execution_stats.counters.end(), + [](const auto &counter) { return counter > 0; }); + if (is_any_counter_set) { + std::map stats; + for (size_t i = 0; i < ctx_.execution_stats.counters.size(); ++i) { + stats.emplace(ExecutionStatsKeyToString(ExecutionStats::Key(i)), ctx_.execution_stats.counters[i]); + } + summary->insert_or_assign("stats", std::move(stats)); + } + cursor_->Shutdown(); + ctx_.profile_execution_time = execution_time_; + return GetStatsWithTotalTime(ctx_); +} + std::optional PullPlan::Pull(AnyStream *stream, std::optional n, const std::vector &output_symbols, std::map *summary) { + auto should_pull_multiple = false; // #NoCommit + if (should_pull_multiple) { + return PullMultiple(stream, n, output_symbols, summary); + } // Set up temporary memory for a single Pull. Initial memory comes from the // stack. 256 KiB should fit on the stack and should be more than enough for a // single `Pull`. diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp new file mode 100644 index 000000000..e6e3d3a98 --- /dev/null +++ b/src/query/v2/multiframe.cpp @@ -0,0 +1,128 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "query/v2/multiframe.hpp" + +#include "query/v2/bindings/frame.hpp" +#include "utils/pmr/vector.hpp" + +namespace memgraph::query::v2 { + +MultiFrame::MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, + utils::MemoryResource *execution_memory) + : default_frame_(default_frame), + frames_(utils::pmr::vector(number_of_frames, default_frame, execution_memory)) { + MG_ASSERT(number_of_frames > 0); + MG_ASSERT(!default_frame.IsValid()); +} + +MultiFrame::~MultiFrame() = default; + +MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_frame_) { + /* + #NoCommit maybe not needed + Do we just copy all frames or do we make distinctions between valid and not valid frames? Does it make any + difference? + */ + frames_.reserve(other.frames_.size()); + std::transform(other.frames_.begin(), other.frames_.end(), std::back_inserter(frames_), + [&default_frame = default_frame_](const auto &other_frame) { + if (other_frame.IsValid()) { + return other_frame; + } else { + return default_frame; + } + }); +} + +MultiFrame::MultiFrame(MultiFrame &&other) noexcept : default_frame_(std::move(other.default_frame_)) { + /* + #NoCommit maybe not needed + Do we just copy all frames or do we make distinctions between valid and not valid frames? Does it make any + difference? + */ + frames_.reserve(other.frames_.size()); + std::transform(make_move_iterator(other.frames_.begin()), make_move_iterator(other.frames_.end()), + std::back_inserter(frames_), [&default_frame = default_frame_](const auto &other_frame) { + if (other_frame.IsValid()) { + return other_frame; + } else { + return default_frame; + } + }); +} + +void MultiFrame::ResetAllFramesInvalid() noexcept { + std::for_each(frames_.begin(), frames_.end(), [](auto &frame) { frame.MakeInvalid(); }); +} + +bool MultiFrame::HasValidFrame() const noexcept { + return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return frame.IsValid(); }); +} + +void MultiFrame::DefragmentValidFrames() noexcept { + /* + from: https://en.cppreference.com/w/cpp/algorithm/remove + "Removing is done by shifting (by means of copy assignment (until C++11)move assignment (since C++11)) the elements + in the range in such a way that the elements that are not to be removed appear in the beginning of the range. + Relative order of the elements that remain is preserved and the physical size of the container is unchanged." + */ + std::remove_if(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); +} + +ItOnConstValidFrames MultiFrame::GetItOnConstValidFrames() { return ItOnConstValidFrames(*this); } + +ItOnNonConstValidFrames MultiFrame::GetItOnNonConstValidFrames() { return ItOnNonConstValidFrames(*this); } + +ItOnNonConstInvalidFrames MultiFrame::GetItOnNonConstInvalidFrames() { return ItOnNonConstInvalidFrames(*this); } + +ItOnConstValidFrames::ItOnConstValidFrames(MultiFrame &multiframe) : multiframe_(multiframe) {} + +ItOnConstValidFrames::~ItOnConstValidFrames() = default; + +ItOnConstValidFrames::Iterator ItOnConstValidFrames::begin() { return Iterator(&multiframe_.frames_[0], *this); } +ItOnConstValidFrames::Iterator ItOnConstValidFrames::end() { + return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); +} + +ItOnNonConstValidFrames::ItOnNonConstValidFrames(MultiFrame &multiframe) : multiframe_(multiframe) {} + +ItOnNonConstValidFrames::~ItOnNonConstValidFrames() { + // #NoCommit possible optimisation: only DefragmentValidFrames if one frame has been invalidated? Only if does not + // cost too much to store it + multiframe_.DefragmentValidFrames(); +} + +ItOnNonConstValidFrames::Iterator ItOnNonConstValidFrames::begin() { return Iterator(&multiframe_.frames_[0], *this); } + +ItOnNonConstValidFrames::Iterator ItOnNonConstValidFrames::end() { + return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); +} + +ItOnNonConstInvalidFrames::ItOnNonConstInvalidFrames(MultiFrame &multiframe) : multiframe_(multiframe) {} + +ItOnNonConstInvalidFrames::~ItOnNonConstInvalidFrames() = default; + +ItOnNonConstInvalidFrames::Iterator ItOnNonConstInvalidFrames::begin() { + for (auto idx = 0UL; idx < multiframe_.frames_.size(); ++idx) { + if (!multiframe_.frames_[idx].IsValid()) { + return Iterator(&multiframe_.frames_[idx]); + } + } + + return end(); +} + +ItOnNonConstInvalidFrames::Iterator ItOnNonConstInvalidFrames::end() { + return Iterator(&multiframe_.frames_[multiframe_.frames_.size()]); +} + +} // namespace memgraph::query::v2 diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp new file mode 100644 index 000000000..5dd5d0e6b --- /dev/null +++ b/src/query/v2/multiframe.hpp @@ -0,0 +1,218 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include + +#include "query/v2/bindings/frame.hpp" + +namespace memgraph::query::v2 { +constexpr unsigned long kNumberOfFramesInMultiframe = 1000; // #NoCommit have it configurable + +class ItOnConstValidFrames; +class ItOnNonConstValidFrames; +class ItOnNonConstInvalidFrames; + +class MultiFrame { + public: + friend class ItOnConstValidFrames; + friend class ItOnNonConstValidFrames; + friend class ItOnNonConstInvalidFrames; + + MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); + ~MultiFrame(); + + MultiFrame(const MultiFrame &other); // copy constructor + MultiFrame(MultiFrame &&other) noexcept; // move constructor + MultiFrame &operator=(const MultiFrame &other) = delete; + MultiFrame &operator=(MultiFrame &&other) noexcept = delete; + + /*! + Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid + state in the multiframe. + Iteration goes in a deterministic order. + One can't modify the validity of the frame with this implementation. + */ + ItOnConstValidFrames GetItOnConstValidFrames(); + + /*! + Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid + state in the multiframe. + Iteration goes in a deterministic order. + One can modify the validity of the frame with this implementation. + If you do not plan to modify the validity of the frames, use GetItOnConstValidFrames instead as this is faster. + */ + ItOnNonConstValidFrames GetItOnNonConstValidFrames(); + + /*! + Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in an invalid + state in the multiframe. + Iteration goes in a deterministic order. + One can modify the validity of the frame with this implementation. + */ + ItOnNonConstInvalidFrames GetItOnNonConstInvalidFrames(); + + void ResetAllFramesInvalid() noexcept; + + bool HasValidFrame() const noexcept; + + inline utils::MemoryResource *GetMemoryResource() { return frames_[0].GetMemoryResource(); } + + private: + void DefragmentValidFrames() noexcept; + + FrameWithValidity default_frame_; + utils::pmr::vector frames_ = + utils::pmr::vector(0, FrameWithValidity{1}, utils::NewDeleteResource()); +}; + +class ItOnConstValidFrames { + public: + ItOnConstValidFrames(MultiFrame &multiframe); + + ~ItOnConstValidFrames(); + ItOnConstValidFrames(const ItOnConstValidFrames &other) = delete; // copy constructor + ItOnConstValidFrames(ItOnConstValidFrames &&other) noexcept = delete; // move constructor + ItOnConstValidFrames &operator=(const ItOnConstValidFrames &other) = delete; // copy assignment + ItOnConstValidFrames &operator=(ItOnConstValidFrames &&other) noexcept = delete; // move assignment + + struct Iterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Frame; + using pointer = value_type *; + using reference = Frame &; + using internal_ptr = FrameWithValidity *; + + Iterator(internal_ptr ptr, ItOnConstValidFrames &iterator_wrapper) + : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} + + reference operator*() const { return *ptr_; } + pointer operator->() { return ptr_; } + + // Prefix increment + Iterator &operator++() { + do { + ptr_++; + } while (!this->ptr_->IsValid() && *this != iterator_wrapper_.end()); + + return *this; + } + + friend bool operator==(const Iterator &a, const Iterator &b) { return a.ptr_ == b.ptr_; }; + friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; + + private: + internal_ptr ptr_; + ItOnConstValidFrames &iterator_wrapper_; + }; + + Iterator begin(); + Iterator end(); + + private: + MultiFrame &multiframe_; +}; + +class ItOnNonConstValidFrames { + public: + ItOnNonConstValidFrames(MultiFrame &multiframe); + + ~ItOnNonConstValidFrames(); + ItOnNonConstValidFrames(const ItOnNonConstValidFrames &other) = delete; // copy constructor + ItOnNonConstValidFrames(ItOnNonConstValidFrames &&other) noexcept = delete; // move constructor + ItOnNonConstValidFrames &operator=(const ItOnNonConstValidFrames &other) = delete; // copy assignment + ItOnNonConstValidFrames &operator=(ItOnNonConstValidFrames &&other) noexcept = delete; // move assignment + + struct Iterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = FrameWithValidity; + using pointer = value_type *; + using reference = FrameWithValidity &; + using internal_ptr = FrameWithValidity *; + + Iterator(internal_ptr ptr, ItOnNonConstValidFrames &iterator_wrapper) + : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} + + reference operator*() const { return *ptr_; } + pointer operator->() { return ptr_; } + + // Prefix increment + Iterator &operator++() { + do { + ptr_++; + } while (!this->ptr_->IsValid() && *this != iterator_wrapper_.end()); + + return *this; + } + + friend bool operator==(const Iterator &a, const Iterator &b) { return a.ptr_ == b.ptr_; }; + friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; + + private: + internal_ptr ptr_; + ItOnNonConstValidFrames &iterator_wrapper_; + }; + + Iterator begin(); + Iterator end(); + + private: + MultiFrame &multiframe_; +}; + +class ItOnNonConstInvalidFrames { + public: + ItOnNonConstInvalidFrames(MultiFrame &multiframe); + ~ItOnNonConstInvalidFrames(); + + ItOnNonConstInvalidFrames(const ItOnNonConstInvalidFrames &other) = delete; // copy constructor + ItOnNonConstInvalidFrames(ItOnNonConstInvalidFrames &&other) noexcept = delete; // move constructor + ItOnNonConstInvalidFrames &operator=(const ItOnNonConstInvalidFrames &other) = delete; // copy assignment + ItOnNonConstInvalidFrames &operator=(ItOnNonConstInvalidFrames &&other) noexcept = delete; // move assignment + + struct Iterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = FrameWithValidity; + using pointer = value_type *; + using reference = FrameWithValidity &; + using internal_ptr = FrameWithValidity *; + + Iterator(internal_ptr ptr) : ptr_(ptr) {} + + reference operator*() const { return *ptr_; } + pointer operator->() { return ptr_; } + + // Prefix increment + Iterator &operator++() { + ptr_->MakeValid(); + ptr_++; + return *this; + } + + friend bool operator==(const Iterator &a, const Iterator &b) { return a.ptr_ == b.ptr_; }; + friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; + + private: + internal_ptr ptr_; + }; + + Iterator begin(); + Iterator end(); + + private: + MultiFrame &multiframe_; +}; + +} // namespace memgraph::query::v2 diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 068ca9192..f7044beac 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -257,13 +257,30 @@ class DistributedCreateNodeCursor : public Cursor { bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) { SCOPED_PROFILE_OP("Once"); - if (!did_pull_) { - did_pull_ = true; + if (pull_count_ < 1) { + pull_count_++; return true; } return false; } +void Once::OnceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { + SCOPED_PROFILE_OP("OnceMF"); + + auto iterator_for_valid_frame_only = multi_frame.GetItOnNonConstInvalidFrames(); + auto first_it = iterator_for_valid_frame_only.begin(); + MG_ASSERT(first_it != iterator_for_valid_frame_only.end()); + if (pull_count_ < 1) { + auto *memory_resource = multi_frame.GetMemoryResource(); + auto &frame = *first_it; + frame.MakeValid(); + for (auto &value : frame.elems()) { + value = TypedValue{memory_resource}; + } + pull_count_++; + } +} + UniqueCursorPtr Once::MakeCursor(utils::MemoryResource *mem) const { EventCounter::IncrementCounter(EventCounter::OnceOperator); @@ -274,7 +291,7 @@ WITHOUT_SINGLE_INPUT(Once); void Once::OnceCursor::Shutdown() {} -void Once::OnceCursor::Reset() { did_pull_ = false; } +void Once::OnceCursor::Reset() { pull_count_ = 0; } CreateNode::CreateNode(const std::shared_ptr &input, const NodeCreationInfo &node_info) : input_(input ? input : std::make_shared()), node_info_(node_info) {} @@ -766,6 +783,23 @@ bool Produce::ProduceCursor::Pull(Frame &frame, ExecutionContext &context) { return false; } +void Produce::ProduceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { + SCOPED_PROFILE_OP("ProduceMF"); + + input_cursor_->PullMultiple(multi_frame, context); + + auto iterator_for_valid_frame_only = multi_frame.GetItOnConstValidFrames(); + for (auto &frame : iterator_for_valid_frame_only) { + // Produce should always yield the latest results. + ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, + context.shard_request_manager, storage::v3::View::NEW); + + for (auto *named_expr : self_.named_expressions_) { + named_expr->Accept(evaluator); + } + } +}; + void Produce::ProduceCursor::Shutdown() { input_cursor_->Shutdown(); } void Produce::ProduceCursor::Reset() { input_cursor_->Reset(); } diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index d6277809c..eb83a9fb2 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -28,6 +28,7 @@ #include "query/v2/bindings/typed_value.hpp" #include "query/v2/bindings/frame.hpp" #include "query/v2/bindings/symbol_table.hpp" +#include "query/v2/multiframe.hpp" #include "storage/v3/id_types.hpp" #include "utils/bound.hpp" #include "utils/fnv.hpp" @@ -71,6 +72,8 @@ class Cursor { /// @throws QueryRuntimeException if something went wrong with execution virtual bool Pull(Frame &, ExecutionContext &) = 0; + virtual void PullMultiple(MultiFrame &, ExecutionContext &) { LOG_FATAL("PullMultipleIsNotImplemented"); } + /// Resets the Cursor to its initial state. virtual void Reset() = 0; @@ -332,12 +335,13 @@ and false on every following Pull.") class OnceCursor : public Cursor { public: OnceCursor() {} + void PullMultiple(MultiFrame &, ExecutionContext &) override; bool Pull(Frame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; private: - bool did_pull_{false}; + size_t pull_count_{false}; }; cpp<#) (:serialize (:slk)) @@ -1207,6 +1211,7 @@ RETURN clause) the Produce's pull succeeds exactly once.") public: ProduceCursor(const Produce &, utils::MemoryResource *); bool Pull(Frame &, ExecutionContext &) override; + void PullMultiple(MultiFrame &, ExecutionContext &) override; void Shutdown() override; void Reset() override; From 11119e540644f4e7b4f41bf893ad39a9d093f403 Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 10:32:52 +0100 Subject: [PATCH 12/93] Rename ItOnConstValidFrames->ValidFramesReader --- src/query/v2/interpreter.cpp | 4 ++-- src/query/v2/multiframe.cpp | 10 +++++----- src/query/v2/multiframe.hpp | 27 +++++++++++++-------------- src/query/v2/plan/operator.cpp | 2 +- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index aa4d39de3..78a560662 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -756,7 +756,7 @@ std::optional PullPlan::PullMultiple(AnyStrea if (has_unsent_results_ && !output_symbols.empty()) { // stream unsent results from previous pull - auto iterator_for_valid_frame_only = multi_frame_.GetItOnConstValidFrames(); + auto iterator_for_valid_frame_only = multi_frame_.GetValidFramesReader(); for (auto &frame : iterator_for_valid_frame_only) { stream_values(frame); ++i; @@ -770,7 +770,7 @@ std::optional PullPlan::PullMultiple(AnyStrea } if (!output_symbols.empty()) { - auto iterator_for_valid_frame_only = multi_frame_.GetItOnConstValidFrames(); + auto iterator_for_valid_frame_only = multi_frame_.GetValidFramesReader(); for (auto &frame : iterator_for_valid_frame_only) { stream_values(frame); ++i; diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index e6e3d3a98..0109a9e39 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -78,18 +78,18 @@ void MultiFrame::DefragmentValidFrames() noexcept { std::remove_if(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); } -ItOnConstValidFrames MultiFrame::GetItOnConstValidFrames() { return ItOnConstValidFrames(*this); } +ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader(*this); } ItOnNonConstValidFrames MultiFrame::GetItOnNonConstValidFrames() { return ItOnNonConstValidFrames(*this); } ItOnNonConstInvalidFrames MultiFrame::GetItOnNonConstInvalidFrames() { return ItOnNonConstInvalidFrames(*this); } -ItOnConstValidFrames::ItOnConstValidFrames(MultiFrame &multiframe) : multiframe_(multiframe) {} +ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multiframe) {} -ItOnConstValidFrames::~ItOnConstValidFrames() = default; +ValidFramesReader::~ValidFramesReader() = default; -ItOnConstValidFrames::Iterator ItOnConstValidFrames::begin() { return Iterator(&multiframe_.frames_[0], *this); } -ItOnConstValidFrames::Iterator ItOnConstValidFrames::end() { +ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator(&multiframe_.frames_[0], *this); } +ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 5dd5d0e6b..0f325f525 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -18,13 +18,13 @@ namespace memgraph::query::v2 { constexpr unsigned long kNumberOfFramesInMultiframe = 1000; // #NoCommit have it configurable -class ItOnConstValidFrames; +class ValidFramesReader; class ItOnNonConstValidFrames; class ItOnNonConstInvalidFrames; class MultiFrame { public: - friend class ItOnConstValidFrames; + friend class ValidFramesReader; friend class ItOnNonConstValidFrames; friend class ItOnNonConstInvalidFrames; @@ -42,14 +42,14 @@ class MultiFrame { Iteration goes in a deterministic order. One can't modify the validity of the frame with this implementation. */ - ItOnConstValidFrames GetItOnConstValidFrames(); + ValidFramesReader GetValidFramesReader(); /*! Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid state in the multiframe. Iteration goes in a deterministic order. One can modify the validity of the frame with this implementation. - If you do not plan to modify the validity of the frames, use GetItOnConstValidFrames instead as this is faster. + If you do not plan to modify the validity of the frames, use GetReader instead as this is faster. */ ItOnNonConstValidFrames GetItOnNonConstValidFrames(); @@ -75,15 +75,15 @@ class MultiFrame { utils::pmr::vector(0, FrameWithValidity{1}, utils::NewDeleteResource()); }; -class ItOnConstValidFrames { +class ValidFramesReader { public: - ItOnConstValidFrames(MultiFrame &multiframe); + ValidFramesReader(MultiFrame &multiframe); - ~ItOnConstValidFrames(); - ItOnConstValidFrames(const ItOnConstValidFrames &other) = delete; // copy constructor - ItOnConstValidFrames(ItOnConstValidFrames &&other) noexcept = delete; // move constructor - ItOnConstValidFrames &operator=(const ItOnConstValidFrames &other) = delete; // copy assignment - ItOnConstValidFrames &operator=(ItOnConstValidFrames &&other) noexcept = delete; // move assignment + ~ValidFramesReader(); + ValidFramesReader(const ValidFramesReader &other) = delete; // copy constructor + ValidFramesReader(ValidFramesReader &&other) noexcept = delete; // move constructor + ValidFramesReader &operator=(const ValidFramesReader &other) = delete; // copy assignment + ValidFramesReader &operator=(ValidFramesReader &&other) noexcept = delete; // move assignment struct Iterator { using iterator_category = std::forward_iterator_tag; @@ -93,8 +93,7 @@ class ItOnConstValidFrames { using reference = Frame &; using internal_ptr = FrameWithValidity *; - Iterator(internal_ptr ptr, ItOnConstValidFrames &iterator_wrapper) - : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} + Iterator(internal_ptr ptr, ValidFramesReader &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } @@ -113,7 +112,7 @@ class ItOnConstValidFrames { private: internal_ptr ptr_; - ItOnConstValidFrames &iterator_wrapper_; + ValidFramesReader &iterator_wrapper_; }; Iterator begin(); diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 81e466d82..bf85c13ad 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -768,7 +768,7 @@ void Produce::ProduceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionCont input_cursor_->PullMultiple(multi_frame, context); - auto iterator_for_valid_frame_only = multi_frame.GetItOnConstValidFrames(); + auto iterator_for_valid_frame_only = multi_frame.GetValidFramesReader(); for (auto &frame : iterator_for_valid_frame_only) { // Produce should always yield the latest results. ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, From 7c37ed23138c0d9c854a4f847044b563ccc5a4fe Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 10:38:01 +0100 Subject: [PATCH 13/93] Rename ItOnNonConstValidFrames -> ValidFramesInvalidator --- src/query/v2/multiframe.cpp | 10 +++++----- src/query/v2/multiframe.hpp | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 0109a9e39..9101a51b0 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -80,7 +80,7 @@ void MultiFrame::DefragmentValidFrames() noexcept { ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader(*this); } -ItOnNonConstValidFrames MultiFrame::GetItOnNonConstValidFrames() { return ItOnNonConstValidFrames(*this); } +ValidFramesInvalidator MultiFrame::GetValidFramesInvalidator() { return ValidFramesInvalidator(*this); } ItOnNonConstInvalidFrames MultiFrame::GetItOnNonConstInvalidFrames() { return ItOnNonConstInvalidFrames(*this); } @@ -93,17 +93,17 @@ ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } -ItOnNonConstValidFrames::ItOnNonConstValidFrames(MultiFrame &multiframe) : multiframe_(multiframe) {} +ValidFramesInvalidator::ValidFramesInvalidator(MultiFrame &multiframe) : multiframe_(multiframe) {} -ItOnNonConstValidFrames::~ItOnNonConstValidFrames() { +ValidFramesInvalidator::~ValidFramesInvalidator() { // #NoCommit possible optimisation: only DefragmentValidFrames if one frame has been invalidated? Only if does not // cost too much to store it multiframe_.DefragmentValidFrames(); } -ItOnNonConstValidFrames::Iterator ItOnNonConstValidFrames::begin() { return Iterator(&multiframe_.frames_[0], *this); } +ValidFramesInvalidator::Iterator ValidFramesInvalidator::begin() { return Iterator(&multiframe_.frames_[0], *this); } -ItOnNonConstValidFrames::Iterator ItOnNonConstValidFrames::end() { +ValidFramesInvalidator::Iterator ValidFramesInvalidator::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 0f325f525..ab373c626 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -19,13 +19,13 @@ namespace memgraph::query::v2 { constexpr unsigned long kNumberOfFramesInMultiframe = 1000; // #NoCommit have it configurable class ValidFramesReader; -class ItOnNonConstValidFrames; +class ValidFramesInvalidator; class ItOnNonConstInvalidFrames; class MultiFrame { public: friend class ValidFramesReader; - friend class ItOnNonConstValidFrames; + friend class ValidFramesInvalidator; friend class ItOnNonConstInvalidFrames; MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); @@ -49,9 +49,9 @@ class MultiFrame { state in the multiframe. Iteration goes in a deterministic order. One can modify the validity of the frame with this implementation. - If you do not plan to modify the validity of the frames, use GetReader instead as this is faster. + If you do not plan to modify the validity of the frames, use GetValidFramesReader instead as this is faster. */ - ItOnNonConstValidFrames GetItOnNonConstValidFrames(); + ValidFramesInvalidator GetValidFramesInvalidator(); /*! Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in an invalid @@ -122,15 +122,15 @@ class ValidFramesReader { MultiFrame &multiframe_; }; -class ItOnNonConstValidFrames { +class ValidFramesInvalidator { public: - ItOnNonConstValidFrames(MultiFrame &multiframe); + ValidFramesInvalidator(MultiFrame &multiframe); - ~ItOnNonConstValidFrames(); - ItOnNonConstValidFrames(const ItOnNonConstValidFrames &other) = delete; // copy constructor - ItOnNonConstValidFrames(ItOnNonConstValidFrames &&other) noexcept = delete; // move constructor - ItOnNonConstValidFrames &operator=(const ItOnNonConstValidFrames &other) = delete; // copy assignment - ItOnNonConstValidFrames &operator=(ItOnNonConstValidFrames &&other) noexcept = delete; // move assignment + ~ValidFramesInvalidator(); + ValidFramesInvalidator(const ValidFramesInvalidator &other) = delete; // copy constructor + ValidFramesInvalidator(ValidFramesInvalidator &&other) noexcept = delete; // move constructor + ValidFramesInvalidator &operator=(const ValidFramesInvalidator &other) = delete; // copy assignment + ValidFramesInvalidator &operator=(ValidFramesInvalidator &&other) noexcept = delete; // move assignment struct Iterator { using iterator_category = std::forward_iterator_tag; @@ -140,7 +140,7 @@ class ItOnNonConstValidFrames { using reference = FrameWithValidity &; using internal_ptr = FrameWithValidity *; - Iterator(internal_ptr ptr, ItOnNonConstValidFrames &iterator_wrapper) + Iterator(internal_ptr ptr, ValidFramesInvalidator &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} reference operator*() const { return *ptr_; } @@ -160,7 +160,7 @@ class ItOnNonConstValidFrames { private: internal_ptr ptr_; - ItOnNonConstValidFrames &iterator_wrapper_; + ValidFramesInvalidator &iterator_wrapper_; }; Iterator begin(); From 8f19ce88d95dba27c293ef4eef06d3d6364566d4 Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 10:40:52 +0100 Subject: [PATCH 14/93] Rename ItOnNonConstInvalidFrames->InvalidFramesPopulator --- src/query/v2/multiframe.cpp | 10 +++++----- src/query/v2/multiframe.hpp | 20 ++++++++++---------- src/query/v2/plan/operator.cpp | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 9101a51b0..d07a4deaf 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -82,7 +82,7 @@ ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader( ValidFramesInvalidator MultiFrame::GetValidFramesInvalidator() { return ValidFramesInvalidator(*this); } -ItOnNonConstInvalidFrames MultiFrame::GetItOnNonConstInvalidFrames() { return ItOnNonConstInvalidFrames(*this); } +InvalidFramesPopulator MultiFrame::GetInvalidFramesPopulator() { return InvalidFramesPopulator(*this); } ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multiframe) {} @@ -107,11 +107,11 @@ ValidFramesInvalidator::Iterator ValidFramesInvalidator::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } -ItOnNonConstInvalidFrames::ItOnNonConstInvalidFrames(MultiFrame &multiframe) : multiframe_(multiframe) {} +InvalidFramesPopulator::InvalidFramesPopulator(MultiFrame &multiframe) : multiframe_(multiframe) {} -ItOnNonConstInvalidFrames::~ItOnNonConstInvalidFrames() = default; +InvalidFramesPopulator::~InvalidFramesPopulator() = default; -ItOnNonConstInvalidFrames::Iterator ItOnNonConstInvalidFrames::begin() { +InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() { for (auto idx = 0UL; idx < multiframe_.frames_.size(); ++idx) { if (!multiframe_.frames_[idx].IsValid()) { return Iterator(&multiframe_.frames_[idx]); @@ -121,7 +121,7 @@ ItOnNonConstInvalidFrames::Iterator ItOnNonConstInvalidFrames::begin() { return end(); } -ItOnNonConstInvalidFrames::Iterator ItOnNonConstInvalidFrames::end() { +InvalidFramesPopulator::Iterator InvalidFramesPopulator::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()]); } diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index ab373c626..23d14a9c3 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -20,13 +20,13 @@ constexpr unsigned long kNumberOfFramesInMultiframe = 1000; // #NoCommit have i class ValidFramesReader; class ValidFramesInvalidator; -class ItOnNonConstInvalidFrames; +class InvalidFramesPopulator; class MultiFrame { public: friend class ValidFramesReader; friend class ValidFramesInvalidator; - friend class ItOnNonConstInvalidFrames; + friend class InvalidFramesPopulator; MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); ~MultiFrame(); @@ -59,7 +59,7 @@ class MultiFrame { Iteration goes in a deterministic order. One can modify the validity of the frame with this implementation. */ - ItOnNonConstInvalidFrames GetItOnNonConstInvalidFrames(); + InvalidFramesPopulator GetInvalidFramesPopulator(); void ResetAllFramesInvalid() noexcept; @@ -170,15 +170,15 @@ class ValidFramesInvalidator { MultiFrame &multiframe_; }; -class ItOnNonConstInvalidFrames { +class InvalidFramesPopulator { public: - ItOnNonConstInvalidFrames(MultiFrame &multiframe); - ~ItOnNonConstInvalidFrames(); + InvalidFramesPopulator(MultiFrame &multiframe); + ~InvalidFramesPopulator(); - ItOnNonConstInvalidFrames(const ItOnNonConstInvalidFrames &other) = delete; // copy constructor - ItOnNonConstInvalidFrames(ItOnNonConstInvalidFrames &&other) noexcept = delete; // move constructor - ItOnNonConstInvalidFrames &operator=(const ItOnNonConstInvalidFrames &other) = delete; // copy assignment - ItOnNonConstInvalidFrames &operator=(ItOnNonConstInvalidFrames &&other) noexcept = delete; // move assignment + InvalidFramesPopulator(const InvalidFramesPopulator &other) = delete; // copy constructor + InvalidFramesPopulator(InvalidFramesPopulator &&other) noexcept = delete; // move constructor + InvalidFramesPopulator &operator=(const InvalidFramesPopulator &other) = delete; // copy assignment + InvalidFramesPopulator &operator=(InvalidFramesPopulator &&other) noexcept = delete; // move assignment struct Iterator { using iterator_category = std::forward_iterator_tag; diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index bf85c13ad..6f4747e2b 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -268,7 +268,7 @@ bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) { void Once::OnceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { SCOPED_PROFILE_OP("OnceMF"); - auto iterator_for_valid_frame_only = multi_frame.GetItOnNonConstInvalidFrames(); + auto iterator_for_valid_frame_only = multi_frame.GetValidFramesInvalidator(); auto first_it = iterator_for_valid_frame_only.begin(); MG_ASSERT(first_it != iterator_for_valid_frame_only.end()); if (pull_count_ < 1) { From e946eb50d2115777927e4aeeb28e77cf3dfa8310 Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 11:05:11 +0100 Subject: [PATCH 15/93] Add version ValidFramesModifier to distinguish between reading-only and reading+modifying --- src/query/v2/interpreter.cpp | 2 +- src/query/v2/multiframe.cpp | 11 ++++++ src/query/v2/multiframe.hpp | 71 +++++++++++++++++++++++++++++++--- src/query/v2/plan/operator.cpp | 2 +- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index 78a560662..86a967de4 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -737,7 +737,7 @@ std::optional PullPlan::PullMultiple(AnyStrea return multi_frame_.HasValidFrame(); }; - const auto stream_values = [&output_symbols, &stream](Frame &frame) { + const auto stream_values = [&output_symbols, &stream](const Frame &frame) { // TODO: The streamed values should also probably use the above memory. std::vector values; values.reserve(output_symbols.size()); diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index d07a4deaf..2d0918b9c 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -80,6 +80,8 @@ void MultiFrame::DefragmentValidFrames() noexcept { ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader(*this); } +ValidFramesModifier MultiFrame::GetValidFramesModifier() { return ValidFramesModifier(*this); } + ValidFramesInvalidator MultiFrame::GetValidFramesInvalidator() { return ValidFramesInvalidator(*this); } InvalidFramesPopulator MultiFrame::GetInvalidFramesPopulator() { return InvalidFramesPopulator(*this); } @@ -93,6 +95,15 @@ ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } +ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {} + +ValidFramesModifier::~ValidFramesModifier() = default; + +ValidFramesModifier::Iterator ValidFramesModifier::begin() { return Iterator(&multiframe_.frames_[0], *this); } +ValidFramesModifier::Iterator ValidFramesModifier::end() { + return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); +} + ValidFramesInvalidator::ValidFramesInvalidator(MultiFrame &multiframe) : multiframe_(multiframe) {} ValidFramesInvalidator::~ValidFramesInvalidator() { diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 23d14a9c3..5b4af87e3 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -18,14 +18,16 @@ namespace memgraph::query::v2 { constexpr unsigned long kNumberOfFramesInMultiframe = 1000; // #NoCommit have it configurable -class ValidFramesReader; class ValidFramesInvalidator; +class ValidFramesModifier; +class ValidFramesReader; class InvalidFramesPopulator; class MultiFrame { public: - friend class ValidFramesReader; friend class ValidFramesInvalidator; + friend class ValidFramesModifier; + friend class ValidFramesReader; friend class InvalidFramesPopulator; MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); @@ -40,16 +42,25 @@ class MultiFrame { Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid state in the multiframe. Iteration goes in a deterministic order. - One can't modify the validity of the frame with this implementation. + One can't modify the validity of the frame nor its content with this implementation. */ ValidFramesReader GetValidFramesReader(); + /*! + Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid + state in the multiframe. + Iteration goes in a deterministic order. + One can't modify the validity of the frame with this implementation. One can modify its content. + */ + ValidFramesModifier GetValidFramesModifier(); + /*! Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid state in the multiframe. Iteration goes in a deterministic order. One can modify the validity of the frame with this implementation. - If you do not plan to modify the validity of the frames, use GetValidFramesReader instead as this is faster. + If you do not plan to modify the validity of the frames, use GetValidFramesReader/GetValidFramesModifer instead as + this is faster. */ ValidFramesInvalidator GetValidFramesInvalidator(); @@ -88,9 +99,9 @@ class ValidFramesReader { struct Iterator { using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; - using value_type = Frame; + using value_type = const Frame; using pointer = value_type *; - using reference = Frame &; + using reference = const Frame &; using internal_ptr = FrameWithValidity *; Iterator(internal_ptr ptr, ValidFramesReader &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} @@ -122,6 +133,54 @@ class ValidFramesReader { MultiFrame &multiframe_; }; +class ValidFramesModifier { + public: + ValidFramesModifier(MultiFrame &multiframe); + + ~ValidFramesModifier(); + ValidFramesModifier(const ValidFramesModifier &other) = delete; // copy constructor + ValidFramesModifier(ValidFramesModifier &&other) noexcept = delete; // move constructor + ValidFramesModifier &operator=(const ValidFramesModifier &other) = delete; // copy assignment + ValidFramesModifier &operator=(ValidFramesModifier &&other) noexcept = delete; // move assignment + + struct Iterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Frame; + using pointer = value_type *; + using reference = Frame &; + using internal_ptr = FrameWithValidity *; + + Iterator(internal_ptr ptr, ValidFramesModifier &iterator_wrapper) + : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} + + reference operator*() const { return *ptr_; } + pointer operator->() { return ptr_; } + + // Prefix increment + Iterator &operator++() { + do { + ptr_++; + } while (!this->ptr_->IsValid() && *this != iterator_wrapper_.end()); + + return *this; + } + + friend bool operator==(const Iterator &a, const Iterator &b) { return a.ptr_ == b.ptr_; }; + friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; + + private: + internal_ptr ptr_; + ValidFramesModifier &iterator_wrapper_; + }; + + Iterator begin(); + Iterator end(); + + private: + MultiFrame &multiframe_; +}; + class ValidFramesInvalidator { public: ValidFramesInvalidator(MultiFrame &multiframe); diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 6f4747e2b..c71d70701 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -768,7 +768,7 @@ void Produce::ProduceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionCont input_cursor_->PullMultiple(multi_frame, context); - auto iterator_for_valid_frame_only = multi_frame.GetValidFramesReader(); + auto iterator_for_valid_frame_only = multi_frame.GetValidFramesModifier(); for (auto &frame : iterator_for_valid_frame_only) { // Produce should always yield the latest results. ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, From 884831ece5849a1ba46fa0132e581f0f7f5f3693 Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 11:56:01 +0100 Subject: [PATCH 16/93] Use pk --- tests/benchmark/data_structures_common.hpp | 6 +++--- tests/benchmark/data_structures_contains.cpp | 7 +++---- tests/benchmark/data_structures_find.cpp | 7 +++---- tests/benchmark/data_structures_insert.cpp | 9 ++++----- tests/benchmark/data_structures_remove.cpp | 10 ++++------ 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp index fe6129a22..fb9053c67 100644 --- a/tests/benchmark/data_structures_common.hpp +++ b/tests/benchmark/data_structures_common.hpp @@ -17,6 +17,7 @@ #include "btree_map.hpp" #include "coordinator/hybrid_logical_clock.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" #include "storage/v3/transaction.hpp" @@ -32,7 +33,7 @@ inline void PrepareData(utils::SkipList &skip_list, const int64_t num_element auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { auto acc = skip_list.access(); - acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); } } @@ -56,8 +57,7 @@ inline void PrepareData(std::set &std_set, const int64_t num_elements) { storage::v3::Transaction transaction{start_timestamp, isolation_level}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { - std_set.insert(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + std_set.insert(std::vector{storage::v3::PropertyValue{i}}); } } diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp index 604ab6ef9..2950aeed3 100644 --- a/tests/benchmark/data_structures_contains.cpp +++ b/tests/benchmark/data_structures_contains.cpp @@ -39,7 +39,7 @@ namespace memgraph::benchmark { // Testing Contains Operation /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkContainsSkipList(::benchmark::State &state) { - utils::SkipList skip_list; + utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -77,7 +77,7 @@ static void BM_BenchmarkContainsStdMap(::benchmark::State &state) { } static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { - std::set std_set; + std::set std_set; PrepareData(std_set, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; @@ -90,8 +90,7 @@ static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { int64_t value = i_distribution(i_generator); - if (std_set.contains(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}}})) { + if (std_set.contains(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}})) { found_elems++; } } diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index 502be91da..f1f1955c4 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -39,7 +39,7 @@ namespace memgraph::benchmark { // Testing Find Operation /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkFindSkipList(::benchmark::State &state) { - utils::SkipList skip_list; + utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -76,7 +76,7 @@ static void BM_BenchmarkFindStdMap(::benchmark::State &state) { } static void BM_BenchmarkFindStdSet(::benchmark::State &state) { - std::set std_set; + std::set std_set; PrepareData(std_set, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; @@ -90,8 +90,7 @@ static void BM_BenchmarkFindStdSet(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { int64_t value = i_distribution(i_generator); - if (std_set.find(storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}}}) != std_set.end()) { + if (std_set.find(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) != std_set.end()) { found_elems++; } } diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 024c23202..1e5361d62 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -38,7 +38,7 @@ namespace memgraph::benchmark { // Testing Insert Operation /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { - utils::SkipList skip_list; + utils::SkipList skip_list; coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; @@ -47,7 +47,7 @@ static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { auto acc = skip_list.access(); - acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); } } } @@ -69,7 +69,7 @@ static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { } static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { - std::set std_set; + std::set std_set; coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; @@ -77,8 +77,7 @@ static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { - std_set.insert(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + std_set.insert(storage::v3::PrimaryKey{std::vector{storage::v3::PropertyValue{i}}}); } } } diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index aa02820fc..17dbd1dec 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -39,7 +39,7 @@ namespace memgraph::benchmark { // Testing Remove Operation /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { - utils::SkipList skip_list; + utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; @@ -54,8 +54,7 @@ static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { for (auto i{0}; i < state.range(0); ++i) { int64_t value = i_distribution(i_generator); auto acc = skip_list.access(); - if (acc.remove(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, storage::v3::PrimaryKey{storage::v3::PropertyValue(value)}}})) { + if (acc.remove(storage::v3::PrimaryKey{storage::v3::PropertyValue(value)})) { removed_elems++; } } @@ -83,7 +82,7 @@ static void BM_BenchmarkRemoveStdMap(::benchmark::State &state) { } static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { - std::set std_set; + std::set std_set; PrepareData(std_set, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; @@ -97,8 +96,7 @@ static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { int64_t value = i_distribution(i_generator); - if (std_set.erase(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, {storage::v3::PropertyValue{value}}}}) > 0) { + if (std_set.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) { removed_elems++; } } From cf388d80fc6ffbc8adf5f7d5c7c0f2bcb6ffc68d Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Tue, 29 Nov 2022 12:29:52 +0100 Subject: [PATCH 17/93] Update src/query/v2/plan/operator.lcp --- src/query/v2/plan/operator.lcp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index eb83a9fb2..9f8b7d6db 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -341,7 +341,7 @@ and false on every following Pull.") void Reset() override; private: - size_t pull_count_{false}; + size_t pull_count_{0}; }; cpp<#) (:serialize (:slk)) From ce5f1c2f1738f6cf978e04da90b853e0d29edbfe Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 13:08:50 +0100 Subject: [PATCH 18/93] Add memory runner for insert --- tools/plot/benchmark_datastructures.sh | 61 ++++++++++++++++++-------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/tools/plot/benchmark_datastructures.sh b/tools/plot/benchmark_datastructures.sh index 3001171c0..2c9885ad4 100755 --- a/tools/plot/benchmark_datastructures.sh +++ b/tools/plot/benchmark_datastructures.sh @@ -8,22 +8,45 @@ CPUS=$(grep -c processor < /proc/cpuinfo) # Get all benchmark files BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname "data_structures_*") -echo $(ls ${WORKSPACE_DIR}/tests/benchmark) -for BENCH_FILE in ${BENCHMARK_FILES}; do - BASE_NAME=$(basename $BENCH_FILE) - NAME=${BASE_NAME%%.*} - echo "Running $NAME" - TEST_FILE=${WORKSPACE_DIR}/build/tests/benchmark/${NAME} - if [[ -f "${TEST_FILE}" ]]; then - pushd ${WORKSPACE_DIR}/build - make -j${CPUS} memgraph__benchmark__${NAME} - popd - JSON_OUTPUT=${NAME}_output.json - # Run benchmakr test - ${WORKSPACE_DIR}/build/tests/benchmark/${NAME} --benchmark_format=json --benchmark_out=${JSON_OUTPUT} - # Run analyze script for benchmark test - python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log_file=${JSON_OUTPUT} - else - echo "File ${TEST_FILE} does not exist!" - fi -done + +function test_all() { + for BENCH_FILE in ${BENCHMARK_FILES}; do + local BASE_NAME=$(basename $BENCH_FILE) + local NAME=${BASE_NAME%%.*} + echo "Running $NAME" + local TEST_FILE=${WORKSPACE_DIR}/build/tests/benchmark/${NAME} + if [[ -f "${TEST_FILE}" ]]; then + pushd ${WORKSPACE_DIR}/build + make -j${CPUS} memgraph__benchmark__${NAME} + popd + local JSON_OUTPUT=${NAME}_output.json + # Run benchmakr test + ${WORKSPACE_DIR}/build/tests/benchmark/${NAME} --benchmark_format=json --benchmark_out=${JSON_OUTPUT} + # Run analyze script for benchmark test + python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log_file=${JSON_OUTPUT} + else + echo "File ${TEST_FILE} does not exist!" + fi + done +} + +function test_memory() { + ## We are testing only insert + local DATA_STRUCTURES=(SkipList StdMap StdSet BppTree) + for DATA_STRUCTURE in ${DATA_STRUCTURES[@]}; do + valgrind --tool=massif --massif-out-file=${DATA_STRUCTURE}.massif.out ${WORKSPACE_DIR}/build/tests/benchmark/data_structures_insert --benchmark_filter=BM_BenchmarkInsert${DATA_STRUCTURE}/10000 --benchmark_format=json --benchmark_out=${DATA_STRUCTURE}.json + done +} + +ARG_1=${1:-"all"} +case ${ARG_1} in + all) + test_all + ;; + memory) + test_memory + ;; + *) + echo "Select either `all` or `memory` benchmark!" + ;; +esac From cead1bcb215f0e1537b928dad7325b8d05c31120 Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 13:49:37 +0100 Subject: [PATCH 19/93] Rename ValidFramesInvalidator->ValidFramesConsumer --- src/query/v2/multiframe.cpp | 10 +++++----- src/query/v2/multiframe.hpp | 24 ++++++++++++------------ src/query/v2/plan/operator.cpp | 6 +++--- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 2d0918b9c..65829fe5e 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -82,7 +82,7 @@ ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader( ValidFramesModifier MultiFrame::GetValidFramesModifier() { return ValidFramesModifier(*this); } -ValidFramesInvalidator MultiFrame::GetValidFramesInvalidator() { return ValidFramesInvalidator(*this); } +ValidFramesConsumer MultiFrame::GetValidFramesConsumer() { return ValidFramesConsumer(*this); } InvalidFramesPopulator MultiFrame::GetInvalidFramesPopulator() { return InvalidFramesPopulator(*this); } @@ -104,17 +104,17 @@ ValidFramesModifier::Iterator ValidFramesModifier::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } -ValidFramesInvalidator::ValidFramesInvalidator(MultiFrame &multiframe) : multiframe_(multiframe) {} +ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(multiframe) {} -ValidFramesInvalidator::~ValidFramesInvalidator() { +ValidFramesConsumer::~ValidFramesConsumer() { // #NoCommit possible optimisation: only DefragmentValidFrames if one frame has been invalidated? Only if does not // cost too much to store it multiframe_.DefragmentValidFrames(); } -ValidFramesInvalidator::Iterator ValidFramesInvalidator::begin() { return Iterator(&multiframe_.frames_[0], *this); } +ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { return Iterator(&multiframe_.frames_[0], *this); } -ValidFramesInvalidator::Iterator ValidFramesInvalidator::end() { +ValidFramesConsumer::Iterator ValidFramesConsumer::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 5b4af87e3..aee4052a6 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -18,14 +18,14 @@ namespace memgraph::query::v2 { constexpr unsigned long kNumberOfFramesInMultiframe = 1000; // #NoCommit have it configurable -class ValidFramesInvalidator; +class ValidFramesConsumer; class ValidFramesModifier; class ValidFramesReader; class InvalidFramesPopulator; class MultiFrame { public: - friend class ValidFramesInvalidator; + friend class ValidFramesConsumer; friend class ValidFramesModifier; friend class ValidFramesReader; friend class InvalidFramesPopulator; @@ -62,7 +62,7 @@ class MultiFrame { If you do not plan to modify the validity of the frames, use GetValidFramesReader/GetValidFramesModifer instead as this is faster. */ - ValidFramesInvalidator GetValidFramesInvalidator(); + ValidFramesConsumer GetValidFramesConsumer(); /*! Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in an invalid @@ -181,15 +181,15 @@ class ValidFramesModifier { MultiFrame &multiframe_; }; -class ValidFramesInvalidator { +class ValidFramesConsumer { public: - ValidFramesInvalidator(MultiFrame &multiframe); + ValidFramesConsumer(MultiFrame &multiframe); - ~ValidFramesInvalidator(); - ValidFramesInvalidator(const ValidFramesInvalidator &other) = delete; // copy constructor - ValidFramesInvalidator(ValidFramesInvalidator &&other) noexcept = delete; // move constructor - ValidFramesInvalidator &operator=(const ValidFramesInvalidator &other) = delete; // copy assignment - ValidFramesInvalidator &operator=(ValidFramesInvalidator &&other) noexcept = delete; // move assignment + ~ValidFramesConsumer(); + ValidFramesConsumer(const ValidFramesConsumer &other) = delete; // copy constructor + ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = delete; // move constructor + ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; // copy assignment + ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = delete; // move assignment struct Iterator { using iterator_category = std::forward_iterator_tag; @@ -199,7 +199,7 @@ class ValidFramesInvalidator { using reference = FrameWithValidity &; using internal_ptr = FrameWithValidity *; - Iterator(internal_ptr ptr, ValidFramesInvalidator &iterator_wrapper) + Iterator(internal_ptr ptr, ValidFramesConsumer &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} reference operator*() const { return *ptr_; } @@ -219,7 +219,7 @@ class ValidFramesInvalidator { private: internal_ptr ptr_; - ValidFramesInvalidator &iterator_wrapper_; + ValidFramesConsumer &iterator_wrapper_; }; Iterator begin(); diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 50cc57ec6..0f1415694 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -268,7 +268,7 @@ bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) { void Once::OnceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { SCOPED_PROFILE_OP("OnceMF"); - auto iterator_for_valid_frame_only = multi_frame.GetValidFramesInvalidator(); + auto iterator_for_valid_frame_only = multi_frame.GetValidFramesConsumer(); auto first_it = iterator_for_valid_frame_only.begin(); MG_ASSERT(first_it != iterator_for_valid_frame_only.end()); if (pull_count_ < 1) { @@ -772,8 +772,8 @@ void Produce::ProduceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionCont auto iterator_for_valid_frame_only = multi_frame.GetValidFramesModifier(); for (auto &frame : iterator_for_valid_frame_only) { // Produce should always yield the latest results. - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, - context.shard_request_manager, storage::v3::View::NEW); + ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.request_router, + storage::v3::View::NEW); for (auto *named_expr : self_.named_expressions_) { named_expr->Accept(evaluator); From bc32a3d305ee772ce3e45d98c1318aba4cc53a0a Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 13:52:29 +0100 Subject: [PATCH 20/93] Adapt comment --- src/query/v2/interpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index fa80ee487..891699312 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -811,7 +811,7 @@ std::optional PullPlan::PullMultiple(AnyStrea std::optional PullPlan::Pull(AnyStream *stream, std::optional n, const std::vector &output_symbols, std::map *summary) { - auto should_pull_multiple = false; // #NoCommit + auto should_pull_multiple = false; // TODO on the long term, we will only use PullMultiple if (should_pull_multiple) { return PullMultiple(stream, n, output_symbols, summary); } From aace5db8ccb48a2901da134bb432b1c5048bd50a Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 13:53:08 +0100 Subject: [PATCH 21/93] Adapt comment --- src/query/v2/multiframe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index aee4052a6..b004bd96f 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -16,7 +16,7 @@ #include "query/v2/bindings/frame.hpp" namespace memgraph::query::v2 { -constexpr unsigned long kNumberOfFramesInMultiframe = 1000; // #NoCommit have it configurable +constexpr uint64 kNumberOfFramesInMultiframe = 1000; // TODO have it configurable class ValidFramesConsumer; class ValidFramesModifier; From 3c0e38aacb8ea10eda1a8d64578868da10170aff Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 13:54:26 +0100 Subject: [PATCH 22/93] Adapt comment --- src/query/v2/multiframe.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 65829fe5e..022e0725d 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -28,7 +28,7 @@ MultiFrame::~MultiFrame() = default; MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_frame_) { /* - #NoCommit maybe not needed + TODO Do we just copy all frames or do we make distinctions between valid and not valid frames? Does it make any difference? */ @@ -45,7 +45,7 @@ MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_f MultiFrame::MultiFrame(MultiFrame &&other) noexcept : default_frame_(std::move(other.default_frame_)) { /* - #NoCommit maybe not needed + TODO Do we just copy all frames or do we make distinctions between valid and not valid frames? Does it make any difference? */ @@ -107,7 +107,7 @@ ValidFramesModifier::Iterator ValidFramesModifier::end() { ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(multiframe) {} ValidFramesConsumer::~ValidFramesConsumer() { - // #NoCommit possible optimisation: only DefragmentValidFrames if one frame has been invalidated? Only if does not + // TODO Possible optimisation: only DefragmentValidFrames if one frame has been invalidated? Only if does not // cost too much to store it multiframe_.DefragmentValidFrames(); } From 632db4175aa6c8556aa7b969a30f72522df40a2d Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 14:01:39 +0100 Subject: [PATCH 23/93] Revert storage changes --- src/storage/v3/key_store.hpp | 5 +- src/storage/v3/property_store.cpp | 10 -- src/storage/v3/property_store.hpp | 4 +- src/storage/v3/vertex.hpp | 2 - tests/benchmark/data_structures_random.cpp | 169 --------------------- tools/plot/benchmark_datastructures.sh | 2 +- 6 files changed, 5 insertions(+), 187 deletions(-) delete mode 100644 tests/benchmark/data_structures_random.cpp diff --git a/src/storage/v3/key_store.hpp b/src/storage/v3/key_store.hpp index 1762273fb..4bc3c25e3 100644 --- a/src/storage/v3/key_store.hpp +++ b/src/storage/v3/key_store.hpp @@ -26,12 +26,11 @@ using PrimaryKey = std::vector; class KeyStore { public: - KeyStore() = default; explicit KeyStore(const PrimaryKey &key_values); - KeyStore(const KeyStore &) = default; + KeyStore(const KeyStore &) = delete; KeyStore(KeyStore &&other) noexcept = default; - KeyStore &operator=(const KeyStore &) = default; + KeyStore &operator=(const KeyStore &) = delete; KeyStore &operator=(KeyStore &&other) noexcept = default; ~KeyStore() = default; diff --git a/src/storage/v3/property_store.cpp b/src/storage/v3/property_store.cpp index bf8460003..fc7bd6984 100644 --- a/src/storage/v3/property_store.cpp +++ b/src/storage/v3/property_store.cpp @@ -928,16 +928,6 @@ void SetSizeData(uint8_t *buffer, uint64_t size, uint8_t *data) { PropertyStore::PropertyStore() { memset(buffer_, 0, sizeof(buffer_)); } -PropertyStore::PropertyStore(const PropertyStore &other) { memcpy(buffer_, other.buffer_, sizeof(buffer_)); } - -PropertyStore &PropertyStore::operator=(const PropertyStore &other) { - if (this == &other) { - return *this; - } - memcpy(buffer_, other.buffer_, sizeof(buffer_)); - return *this; -} - PropertyStore::PropertyStore(PropertyStore &&other) noexcept { memcpy(buffer_, other.buffer_, sizeof(buffer_)); memset(other.buffer_, 0, sizeof(other.buffer_)); diff --git a/src/storage/v3/property_store.hpp b/src/storage/v3/property_store.hpp index 00dc4d40f..477795203 100644 --- a/src/storage/v3/property_store.hpp +++ b/src/storage/v3/property_store.hpp @@ -25,9 +25,9 @@ class PropertyStore { public: PropertyStore(); - PropertyStore(const PropertyStore &); + PropertyStore(const PropertyStore &) = delete; PropertyStore(PropertyStore &&other) noexcept; - PropertyStore &operator=(const PropertyStore &); + PropertyStore &operator=(const PropertyStore &) = delete; PropertyStore &operator=(PropertyStore &&other) noexcept; ~PropertyStore(); diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index cbfac4ee8..2ed4c6e4c 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -31,8 +31,6 @@ namespace memgraph::storage::v3 { struct Vertex { using EdgeLink = std::tuple; - Vertex() = default; - Vertex(Delta *delta, const std::vector &primary_properties) : keys{primary_properties}, delta{delta} { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); diff --git a/tests/benchmark/data_structures_random.cpp b/tests/benchmark/data_structures_random.cpp deleted file mode 100644 index f130d6faf..000000000 --- a/tests/benchmark/data_structures_random.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2022 Memgraph Ltd. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source -// License, and you may not use this file except in compliance with the Business Source License. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "btree_map.hpp" -#include "skip_list_common.hpp" -#include "utils/skip_list.hpp" - -DEFINE_int32(max_element, 20000, "Maximum element in the intial list"); -DEFINE_int32(max_range, 2000000, "Maximum range used for the test"); -DEFINE_string(ds, "skip_list", "Which DS to test"); - -template -concept BasicIndexStructure = requires(ContainerType a, const ContainerType b) { - { a.contains() } -> std::same_as; - { a.find() } -> std::same_as; - { a.insert() } -> std::same_as>; - { a.remove() } -> std::same_as; -}; - -template -class BppStructure final { - using value_type = typename tlx::btree_map::value_type; - using iterator = typename tlx::btree_map::iterator; - - public: - bool contains(const T key) const { return data.exists(key); } - - auto find(const T key) const { return data.find(key); } - - std::pair insert(T val) { return data.insert({val, val}); } - - bool remove(const T key) { return data.erase(key); } - - size_t size() const { return data.size(); }; - - iterator end() { return data.end(); } - - private: - tlx::btree_map data; -}; - -template -class MapStructure final { - using value_type = typename std::map::value_type; - using iterator = typename std::map::iterator; - - public: - bool contains(const T key) const { return data.contains(key); } - - auto find(const T key) const { return data.find(key); } - - std::pair insert(T val) { return data.insert({val, val}); } - - bool remove(const T key) { return data.erase(key); } - - size_t size() const { return data.size(); }; - - iterator end() { return data.end(); } - - private: - std::map data; -}; - -template -void RunDSTest(DataStructure &ds) { - RunTest([&ds](const std::atomic &run, auto &stats) { - std::mt19937 generator(std::random_device{}()); - std::uniform_int_distribution distribution(0, 3); - std::mt19937 i_generator(std::random_device{}()); - std::uniform_int_distribution i_distribution(0, FLAGS_max_range); - - while (run.load(std::memory_order_relaxed)) { - auto value = distribution(generator); - - auto item = i_distribution(i_generator); - switch (value) { - case 0: - stats.succ[OP_INSERT] += static_cast(ds.insert(item).second); - break; - case 1: - stats.succ[OP_CONTAINS] += static_cast(ds.contains(item)); - break; - case 2: - stats.succ[OP_REMOVE] += static_cast(ds.remove(item)); - break; - case 3: - stats.succ[OP_FIND] += static_cast(ds.find(item) != ds.end()); - break; - default: - std::terminate(); - } - ++stats.total; - } - }); -} - -template -void GenerateData(Accessor &acc) { - for (uint64_t i = 0; i <= FLAGS_max_element; ++i) { - MG_ASSERT(acc.insert(i).second); - } -} - -template -void AsserData(Accessor &acc) { - if constexpr (std::is_same_v>) { - uint64_t val = 0; - for (auto item : acc) { - MG_ASSERT(item == val); - ++val; - } - MG_ASSERT(val == FLAGS_max_element + 1); - } else { - MG_ASSERT(acc.size() == FLAGS_max_element + 1); - } -} - -int main(int argc, char **argv) { - gflags::ParseCommandLineFlags(&argc, &argv, true); - - if (FLAGS_ds == "skip_list") { - std::cout << "#### Testing skip list" << std::endl; - memgraph::utils::SkipList list; - auto acc = list.access(); - - GenerateData(acc); - AsserData(acc); - - RunDSTest(acc); - } else if (FLAGS_ds == "map") { - std::cout << "#### Testing map" << std::endl; - MapStructure map; - - GenerateData(map); - AsserData(map); - - RunDSTest(map); - } else if (FLAGS_ds == "bpp") { - std::cout << "#### Testing B+ tree" << std::endl; - BppStructure bpp; - - GenerateData(bpp); - AsserData(bpp); - - RunDSTest(bpp); - } else { - throw std::runtime_error("Select an existing data structure!"); - } - - return 0; -} diff --git a/tools/plot/benchmark_datastructures.sh b/tools/plot/benchmark_datastructures.sh index 2c9885ad4..2ab15be31 100755 --- a/tools/plot/benchmark_datastructures.sh +++ b/tools/plot/benchmark_datastructures.sh @@ -10,7 +10,7 @@ CPUS=$(grep -c processor < /proc/cpuinfo) BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname "data_structures_*") function test_all() { - for BENCH_FILE in ${BENCHMARK_FILES}; do + for BENCH_FILE in ${BENCHMARK_FILES[@]}; do local BASE_NAME=$(basename $BENCH_FILE) local NAME=${BASE_NAME%%.*} echo "Running $NAME" From 8c5edaaeb95512fe155f6791b15fef4b817523ec Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 14:07:15 +0100 Subject: [PATCH 24/93] Update type --- src/query/v2/multiframe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index b004bd96f..64c41f43d 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -16,7 +16,7 @@ #include "query/v2/bindings/frame.hpp" namespace memgraph::query::v2 { -constexpr uint64 kNumberOfFramesInMultiframe = 1000; // TODO have it configurable +constexpr uint64_t kNumberOfFramesInMultiframe = 1000; // TODO have it configurable class ValidFramesConsumer; class ValidFramesModifier; From 86f7b82bdc86d2992dad91acdb03ea91b619fbea Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 14:26:17 +0100 Subject: [PATCH 25/93] Clang tidy --- src/query/v2/interpreter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index 891699312..bfa7dfe0e 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -757,7 +757,7 @@ std::optional PullPlan::PullMultiple(AnyStrea // stream unsent results from previous pull auto iterator_for_valid_frame_only = multi_frame_.GetValidFramesReader(); - for (auto &frame : iterator_for_valid_frame_only) { + for (const auto &frame : iterator_for_valid_frame_only) { stream_values(frame); ++i; } @@ -771,7 +771,7 @@ std::optional PullPlan::PullMultiple(AnyStrea if (!output_symbols.empty()) { auto iterator_for_valid_frame_only = multi_frame_.GetValidFramesReader(); - for (auto &frame : iterator_for_valid_frame_only) { + for (const auto &frame : iterator_for_valid_frame_only) { stream_values(frame); ++i; } From 1f98d33fa6d42a4a320e1518e57ad9440155474d Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 14:40:44 +0100 Subject: [PATCH 26/93] Remove b++ tree --- tests/benchmark/CMakeLists.txt | 3 - tests/benchmark/btree.hpp | 3134 ------------------ tests/benchmark/btree_map.hpp | 475 --- tests/benchmark/core.hpp | 267 -- tests/benchmark/data_structures_common.hpp | 15 - tests/benchmark/data_structures_contains.cpp | 27 +- tests/benchmark/data_structures_find.cpp | 25 - tests/benchmark/data_structures_insert.cpp | 19 - tests/benchmark/data_structures_remove.cpp | 30 - 9 files changed, 1 insertion(+), 3994 deletions(-) delete mode 100644 tests/benchmark/btree.hpp delete mode 100644 tests/benchmark/btree_map.hpp delete mode 100644 tests/benchmark/core.hpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 61a4cd62a..c7cededd8 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -68,9 +68,6 @@ target_link_libraries(${test_prefix}storage_v2_property_store mg-storage-v2) add_benchmark(future.cpp) target_link_libraries(${test_prefix}future mg-io) -add_benchmark(data_structures_random.cpp) -target_link_libraries(${test_prefix}data_structures_random mg-utils) - add_benchmark(data_structures_insert.cpp) target_link_libraries(${test_prefix}data_structures_insert mg-utils mg-storage-v3) diff --git a/tests/benchmark/btree.hpp b/tests/benchmark/btree.hpp deleted file mode 100644 index 97b912fb0..000000000 --- a/tests/benchmark/btree.hpp +++ /dev/null @@ -1,3134 +0,0 @@ -/******************************************************************************* - * tlx/container/btree.hpp - * - * Part of tlx - http://panthema.net/tlx - * - * Copyright (C) 2008-2017 Timo Bingmann - * - * All rights reserved. Published under the Boost Software License, Version 1.0 - ******************************************************************************/ - -#ifndef TLX_CONTAINER_BTREE_HEADER -#define TLX_CONTAINER_BTREE_HEADER - -#include "core.hpp" - -// *** Required Headers from the STL - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace tlx { - -//! \addtogroup tlx_container -//! \{ -//! \defgroup tlx_container_btree B+ Trees -//! B+ tree variants -//! \{ - -// *** Debugging Macros - -#ifdef TLX_BTREE_DEBUG - -#include - -//! Print out debug information to std::cout if TLX_BTREE_DEBUG is defined. -#define TLX_BTREE_PRINT(x) \ - do { \ - if (debug) (std::cout << x << std::endl); \ - } while (0) - -//! Assertion only if TLX_BTREE_DEBUG is defined. This is not used in verify(). -#define TLX_BTREE_ASSERT(x) \ - do { \ - assert(x); \ - } while (0) - -#else - -//! Print out debug information to std::cout if TLX_BTREE_DEBUG is defined. -#define TLX_BTREE_PRINT(x) \ - do { \ - } while (0) - -//! Assertion only if TLX_BTREE_DEBUG is defined. This is not used in verify(). -#define TLX_BTREE_ASSERT(x) \ - do { \ - } while (0) - -#endif - -//! The maximum of a and b. Used in some compile-time formulas. -#define TLX_BTREE_MAX(a, b) ((a) < (b) ? (b) : (a)) - -#ifndef TLX_BTREE_FRIENDS -//! The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ -//! tree internals. This was added for wxBTreeDemo to be able to draw the -//! tree. -#define TLX_BTREE_FRIENDS friend class btree_friend -#endif - -/*! - * Generates default traits for a B+ tree used as a set or map. It estimates - * leaf and inner node sizes by assuming a cache line multiple of 256 bytes. - */ -template -struct btree_default_traits { - //! If true, the tree will self verify its invariants after each insert() or - //! erase(). The header must have been compiled with TLX_BTREE_DEBUG - //! defined. - static const bool self_verify = false; - - //! If true, the tree will print out debug information and a tree dump - //! during insert() or erase() operation. The header must have been - //! compiled with TLX_BTREE_DEBUG defined and key_type must be std::ostream - //! printable. - static const bool debug = false; - - //! Number of slots in each leaf of the tree. Estimated so that each node - //! has a size of about 256 bytes. - static const int leaf_slots = TLX_BTREE_MAX(8, 256 / (sizeof(Value))); - - //! Number of slots in each inner node of the tree. Estimated so that each - //! node has a size of about 256 bytes. - static const int inner_slots = TLX_BTREE_MAX(8, 256 / (sizeof(Key) + sizeof(void *))); - - //! As of stx-btree-0.9, the code does linear search in find_lower() and - //! find_upper() instead of binary_search, unless the node size is larger - //! than this threshold. See notes at - //! http://panthema.net/2013/0504-STX-B+Tree-Binary-vs-Linear-Search - static const size_t binsearch_threshold = 256; -}; - -/*! - * Basic class implementing a B+ tree data structure in memory. - * - * The base implementation of an in-memory B+ tree. It is based on the - * implementation in Cormen's Introduction into Algorithms, Jan Jannink's paper - * and other algorithm resources. Almost all STL-required function calls are - * implemented. The asymptotic time requirements of the STL are not always - * fulfilled in theory, however, in practice this B+ tree performs better than a - * red-black tree and almost always uses less memory. The insertion function - * splits the nodes on the recursion unroll. Erase is largely based on Jannink's - * ideas. - * - * This class is specialized into btree_set, btree_multiset, btree_map and - * btree_multimap using default template parameters and facade functions. - */ -template , - typename Traits = btree_default_traits, bool Duplicates = false, - typename Allocator = std::allocator> -class BTree { - public: - //! \name Template Parameter Types - //! \{ - - //! First template parameter: The key type of the B+ tree. This is stored in - //! inner nodes. - typedef Key key_type; - - //! Second template parameter: Composition pair of key and data types, or - //! just the key for set containers. This data type is stored in the leaves. - typedef Value value_type; - - //! Third template: key extractor class to pull key_type from value_type. - typedef KeyOfValue key_of_value; - - //! Fourth template parameter: key_type comparison function object - typedef Compare key_compare; - - //! Fifth template parameter: Traits object used to define more parameters - //! of the B+ tree - typedef Traits traits; - - //! Sixth template parameter: Allow duplicate keys in the B+ tree. Used to - //! implement multiset and multimap. - static const bool allow_duplicates = Duplicates; - - //! Seventh template parameter: STL allocator for tree nodes - typedef Allocator allocator_type; - - //! \} - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ - // tree internals. This was added for wxBTreeDemo to be able to draw the - // tree. - TLX_BTREE_FRIENDS; - - public: - //! \name Constructed Types - //! \{ - - //! Typedef of our own type - typedef BTree Self; - - //! Size type used to count keys - typedef size_t size_type; - - //! \} - - public: - //! \name Static Constant Options and Values of the B+ Tree - //! \{ - - //! Base B+ tree parameter: The number of key/data slots in each leaf - static const unsigned short leaf_slotmax = traits::leaf_slots; - - //! Base B+ tree parameter: The number of key slots in each inner node, - //! this can differ from slots in each leaf. - static const unsigned short inner_slotmax = traits::inner_slots; - - //! Computed B+ tree parameter: The minimum number of key/data slots used - //! in a leaf. If fewer slots are used, the leaf will be merged or slots - //! shifted from it's siblings. - static const unsigned short leaf_slotmin = (leaf_slotmax / 2); - - //! Computed B+ tree parameter: The minimum number of key slots used - //! in an inner node. If fewer slots are used, the inner node will be - //! merged or slots shifted from it's siblings. - static const unsigned short inner_slotmin = (inner_slotmax / 2); - - //! Debug parameter: Enables expensive and thorough checking of the B+ tree - //! invariants after each insert/erase operation. - static const bool self_verify = traits::self_verify; - - //! Debug parameter: Prints out lots of debug information about how the - //! algorithms change the tree. Requires the header file to be compiled - //! with TLX_BTREE_DEBUG and the key type must be std::ostream printable. - static const bool debug = traits::debug; - - //! \} - - private: - //! \name Node Classes for In-Memory Nodes - //! \{ - - //! The header structure of each node in-memory. This structure is extended - //! by InnerNode or LeafNode. - struct node { - //! Level in the b-tree, if level == 0 -> leaf node - unsigned short level; - - //! Number of key slotuse use, so the number of valid children or data - //! pointers - unsigned short slotuse; - - //! Delayed initialisation of constructed node. - void initialize(const unsigned short l) { - level = l; - slotuse = 0; - } - - //! True if this is a leaf node. - bool is_leafnode() const { return (level == 0); } - }; - - //! Extended structure of a inner node in-memory. Contains only keys and no - //! data items. - struct InnerNode : public node { - //! Define an related allocator for the InnerNode structs. - typedef typename std::allocator_traits::template rebind_alloc alloc_type; - - //! Keys of children or data pointers - key_type slotkey[inner_slotmax]; // NOLINT - - //! Pointers to children - node *childid[inner_slotmax + 1]; // NOLINT - - //! Set variables to initial values. - void initialize(const unsigned short l) { node::initialize(l); } - - //! Return key in slot s - const key_type &key(size_t s) const { return slotkey[s]; } - - //! True if the node's slots are full. - bool is_full() const { return (node::slotuse == inner_slotmax); } - - //! True if few used entries, less than half full. - bool is_few() const { return (node::slotuse <= inner_slotmin); } - - //! True if node has too few entries. - bool is_underflow() const { return (node::slotuse < inner_slotmin); } - }; - - //! Extended structure of a leaf node in memory. Contains pairs of keys and - //! data items. Key and data slots are kept together in value_type. - struct LeafNode : public node { - //! Define an related allocator for the LeafNode structs. - typedef typename std::allocator_traits::template rebind_alloc alloc_type; - - //! Double linked list pointers to traverse the leaves - LeafNode *prev_leaf; - - //! Double linked list pointers to traverse the leaves - LeafNode *next_leaf; - - //! Array of (key, data) pairs - value_type slotdata[leaf_slotmax]; // NOLINT - - //! Set variables to initial values - void initialize() { - node::initialize(0); - prev_leaf = next_leaf = nullptr; - } - - //! Return key in slot s. - const key_type &key(size_t s) const { return key_of_value::get(slotdata[s]); } - - //! True if the node's slots are full. - bool is_full() const { return (node::slotuse == leaf_slotmax); } - - //! True if few used entries, less than half full. - bool is_few() const { return (node::slotuse <= leaf_slotmin); } - - //! True if node has too few entries. - bool is_underflow() const { return (node::slotuse < leaf_slotmin); } - - //! Set the (key,data) pair in slot. Overloaded function used by - //! bulk_load(). - void set_slot(unsigned short slot, const value_type &value) { - TLX_BTREE_ASSERT(slot < node::slotuse); - slotdata[slot] = value; - } - }; - - //! \} - - public: - //! \name Iterators and Reverse Iterators - //! \{ - - class iterator; - class const_iterator; - class reverse_iterator; - class const_reverse_iterator; - - //! STL-like iterator object for B+ tree items. The iterator points to a - //! specific slot number in a leaf. - class iterator { - public: - // *** Types - - //! The key type of the btree. Returned by key(). - typedef typename BTree::key_type key_type; - - //! The value type of the btree. Returned by operator*(). - typedef typename BTree::value_type value_type; - - //! Reference to the value_type. STL required. - typedef value_type &reference; - - //! Pointer to the value_type. STL required. - typedef value_type *pointer; - - //! STL-magic iterator category - typedef std::bidirectional_iterator_tag iterator_category; - - //! STL-magic - typedef ptrdiff_t difference_type; - - //! Our own type - typedef iterator self; - - private: - // *** Members - - //! The currently referenced leaf node of the tree - typename BTree::LeafNode *curr_leaf; - - //! Current key/data slot referenced - unsigned short curr_slot; - - //! Friendly to the const_iterator, so it may access the two data items - //! directly. - friend class const_iterator; - - //! Also friendly to the reverse_iterator, so it may access the two - //! data items directly. - friend class reverse_iterator; - - //! Also friendly to the const_reverse_iterator, so it may access the - //! two data items directly. - friend class const_reverse_iterator; - - //! Also friendly to the base btree class, because erase_iter() needs - //! to read the curr_leaf and curr_slot values directly. - friend class BTree; - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access - // the B+ tree internals. This was added for wxBTreeDemo to be able to - // draw the tree. - TLX_BTREE_FRIENDS; - - public: - // *** Methods - - //! Default-Constructor of a mutable iterator - iterator() : curr_leaf(nullptr), curr_slot(0) {} - - //! Initializing-Constructor of a mutable iterator - iterator(typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} - - //! Copy-constructor from a reverse iterator - iterator(const reverse_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Dereference the iterator. - reference operator*() const { return curr_leaf->slotdata[curr_slot]; } - - //! Dereference the iterator. - pointer operator->() const { return &curr_leaf->slotdata[curr_slot]; } - - //! Key of the current slot. - const key_type &key() const { return curr_leaf->key(curr_slot); } - - //! Prefix++ advance the iterator to the next slot. - iterator &operator++() { - if (curr_slot + 1u < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 0; - } else { - // this is end() - curr_slot = curr_leaf->slotuse; - } - - return *this; - } - - //! Postfix++ advance the iterator to the next slot. - iterator operator++(int) { - iterator tmp = *this; // copy ourselves - - if (curr_slot + 1u < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 0; - } else { - // this is end() - curr_slot = curr_leaf->slotuse; - } - - return tmp; - } - - //! Prefix-- backstep the iterator to the last slot. - iterator &operator--() { - if (curr_slot > 0) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse - 1; - } else { - // this is begin() - curr_slot = 0; - } - - return *this; - } - - //! Postfix-- backstep the iterator to the last slot. - iterator operator--(int) { - iterator tmp = *this; // copy ourselves - - if (curr_slot > 0) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse - 1; - } else { - // this is begin() - curr_slot = 0; - } - - return tmp; - } - - //! Equality of iterators. - bool operator==(const iterator &x) const { return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); } - - //! Inequality of iterators. - bool operator!=(const iterator &x) const { return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); } - }; - - //! STL-like read-only iterator object for B+ tree items. The iterator - //! points to a specific slot number in a leaf. - class const_iterator { - public: - // *** Types - - //! The key type of the btree. Returned by key(). - typedef typename BTree::key_type key_type; - - //! The value type of the btree. Returned by operator*(). - typedef typename BTree::value_type value_type; - - //! Reference to the value_type. STL required. - typedef const value_type &reference; - - //! Pointer to the value_type. STL required. - typedef const value_type *pointer; - - //! STL-magic iterator category - typedef std::bidirectional_iterator_tag iterator_category; - - //! STL-magic - typedef ptrdiff_t difference_type; - - //! Our own type - typedef const_iterator self; - - private: - // *** Members - - //! The currently referenced leaf node of the tree - const typename BTree::LeafNode *curr_leaf; - - //! Current key/data slot referenced - unsigned short curr_slot; - - //! Friendly to the reverse_const_iterator, so it may access the two - //! data items directly - friend class const_reverse_iterator; - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access - // the B+ tree internals. This was added for wxBTreeDemo to be able to - // draw the tree. - TLX_BTREE_FRIENDS; - - public: - // *** Methods - - //! Default-Constructor of a const iterator - const_iterator() : curr_leaf(nullptr), curr_slot(0) {} - - //! Initializing-Constructor of a const iterator - const_iterator(const typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} - - //! Copy-constructor from a mutable iterator - const_iterator(const iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Copy-constructor from a mutable reverse iterator - const_iterator(const reverse_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Copy-constructor from a const reverse iterator - const_iterator(const const_reverse_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Dereference the iterator. - reference operator*() const { return curr_leaf->slotdata[curr_slot]; } - - //! Dereference the iterator. - pointer operator->() const { return &curr_leaf->slotdata[curr_slot]; } - - //! Key of the current slot. - const key_type &key() const { return curr_leaf->key(curr_slot); } - - //! Prefix++ advance the iterator to the next slot. - const_iterator &operator++() { - if (curr_slot + 1u < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 0; - } else { - // this is end() - curr_slot = curr_leaf->slotuse; - } - - return *this; - } - - //! Postfix++ advance the iterator to the next slot. - const_iterator operator++(int) { - const_iterator tmp = *this; // copy ourselves - - if (curr_slot + 1u < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 0; - } else { - // this is end() - curr_slot = curr_leaf->slotuse; - } - - return tmp; - } - - //! Prefix-- backstep the iterator to the last slot. - const_iterator &operator--() { - if (curr_slot > 0) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse - 1; - } else { - // this is begin() - curr_slot = 0; - } - - return *this; - } - - //! Postfix-- backstep the iterator to the last slot. - const_iterator operator--(int) { - const_iterator tmp = *this; // copy ourselves - - if (curr_slot > 0) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse - 1; - } else { - // this is begin() - curr_slot = 0; - } - - return tmp; - } - - //! Equality of iterators. - bool operator==(const const_iterator &x) const { return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); } - - //! Inequality of iterators. - bool operator!=(const const_iterator &x) const { return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); } - }; - - //! STL-like mutable reverse iterator object for B+ tree items. The - //! iterator points to a specific slot number in a leaf. - class reverse_iterator { - public: - // *** Types - - //! The key type of the btree. Returned by key(). - typedef typename BTree::key_type key_type; - - //! The value type of the btree. Returned by operator*(). - typedef typename BTree::value_type value_type; - - //! Reference to the value_type. STL required. - typedef value_type &reference; - - //! Pointer to the value_type. STL required. - typedef value_type *pointer; - - //! STL-magic iterator category - typedef std::bidirectional_iterator_tag iterator_category; - - //! STL-magic - typedef ptrdiff_t difference_type; - - //! Our own type - typedef reverse_iterator self; - - private: - // *** Members - - //! The currently referenced leaf node of the tree - typename BTree::LeafNode *curr_leaf; - - //! One slot past the current key/data slot referenced. - unsigned short curr_slot; - - //! Friendly to the const_iterator, so it may access the two data items - //! directly - friend class iterator; - - //! Also friendly to the const_iterator, so it may access the two data - //! items directly - friend class const_iterator; - - //! Also friendly to the const_iterator, so it may access the two data - //! items directly - friend class const_reverse_iterator; - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access - // the B+ tree internals. This was added for wxBTreeDemo to be able to - // draw the tree. - TLX_BTREE_FRIENDS; - - public: - // *** Methods - - //! Default-Constructor of a reverse iterator - reverse_iterator() : curr_leaf(nullptr), curr_slot(0) {} - - //! Initializing-Constructor of a mutable reverse iterator - reverse_iterator(typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} - - //! Copy-constructor from a mutable iterator - reverse_iterator(const iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Dereference the iterator. - reference operator*() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return curr_leaf->slotdata[curr_slot - 1]; - } - - //! Dereference the iterator. - pointer operator->() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return &curr_leaf->slotdata[curr_slot - 1]; - } - - //! Key of the current slot. - const key_type &key() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return curr_leaf->key(curr_slot - 1); - } - - //! Prefix++ advance the iterator to the next slot. - reverse_iterator &operator++() { - if (curr_slot > 1) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse; - } else { - // this is begin() == rend() - curr_slot = 0; - } - - return *this; - } - - //! Postfix++ advance the iterator to the next slot. - reverse_iterator operator++(int) { - reverse_iterator tmp = *this; // copy ourselves - - if (curr_slot > 1) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse; - } else { - // this is begin() == rend() - curr_slot = 0; - } - - return tmp; - } - - //! Prefix-- backstep the iterator to the last slot. - reverse_iterator &operator--() { - if (curr_slot < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 1; - } else { - // this is end() == rbegin() - curr_slot = curr_leaf->slotuse; - } - - return *this; - } - - //! Postfix-- backstep the iterator to the last slot. - reverse_iterator operator--(int) { - reverse_iterator tmp = *this; // copy ourselves - - if (curr_slot < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 1; - } else { - // this is end() == rbegin() - curr_slot = curr_leaf->slotuse; - } - - return tmp; - } - - //! Equality of iterators. - bool operator==(const reverse_iterator &x) const { - return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); - } - - //! Inequality of iterators. - bool operator!=(const reverse_iterator &x) const { - return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); - } - }; - - //! STL-like read-only reverse iterator object for B+ tree items. The - //! iterator points to a specific slot number in a leaf. - class const_reverse_iterator { - public: - // *** Types - - //! The key type of the btree. Returned by key(). - typedef typename BTree::key_type key_type; - - //! The value type of the btree. Returned by operator*(). - typedef typename BTree::value_type value_type; - - //! Reference to the value_type. STL required. - typedef const value_type &reference; - - //! Pointer to the value_type. STL required. - typedef const value_type *pointer; - - //! STL-magic iterator category - typedef std::bidirectional_iterator_tag iterator_category; - - //! STL-magic - typedef ptrdiff_t difference_type; - - //! Our own type - typedef const_reverse_iterator self; - - private: - // *** Members - - //! The currently referenced leaf node of the tree - const typename BTree::LeafNode *curr_leaf; - - //! One slot past the current key/data slot referenced. - unsigned short curr_slot; - - //! Friendly to the const_iterator, so it may access the two data items - //! directly. - friend class reverse_iterator; - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access - // the B+ tree internals. This was added for wxBTreeDemo to be able to - // draw the tree. - TLX_BTREE_FRIENDS; - - public: - // *** Methods - - //! Default-Constructor of a const reverse iterator. - const_reverse_iterator() : curr_leaf(nullptr), curr_slot(0) {} - - //! Initializing-Constructor of a const reverse iterator. - const_reverse_iterator(const typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} - - //! Copy-constructor from a mutable iterator. - const_reverse_iterator(const iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Copy-constructor from a const iterator. - const_reverse_iterator(const const_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Copy-constructor from a mutable reverse iterator. - const_reverse_iterator(const reverse_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Dereference the iterator. - reference operator*() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return curr_leaf->slotdata[curr_slot - 1]; - } - - //! Dereference the iterator. - pointer operator->() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return &curr_leaf->slotdata[curr_slot - 1]; - } - - //! Key of the current slot. - const key_type &key() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return curr_leaf->key(curr_slot - 1); - } - - //! Prefix++ advance the iterator to the previous slot. - const_reverse_iterator &operator++() { - if (curr_slot > 1) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse; - } else { - // this is begin() == rend() - curr_slot = 0; - } - - return *this; - } - - //! Postfix++ advance the iterator to the previous slot. - const_reverse_iterator operator++(int) { - const_reverse_iterator tmp = *this; // copy ourselves - - if (curr_slot > 1) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse; - } else { - // this is begin() == rend() - curr_slot = 0; - } - - return tmp; - } - - //! Prefix-- backstep the iterator to the next slot. - const_reverse_iterator &operator--() { - if (curr_slot < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 1; - } else { - // this is end() == rbegin() - curr_slot = curr_leaf->slotuse; - } - - return *this; - } - - //! Postfix-- backstep the iterator to the next slot. - const_reverse_iterator operator--(int) { - const_reverse_iterator tmp = *this; // copy ourselves - - if (curr_slot < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 1; - } else { - // this is end() == rbegin() - curr_slot = curr_leaf->slotuse; - } - - return tmp; - } - - //! Equality of iterators. - bool operator==(const const_reverse_iterator &x) const { - return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); - } - - //! Inequality of iterators. - bool operator!=(const const_reverse_iterator &x) const { - return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); - } - }; - - //! \} - - public: - //! \name Small Statistics Structure - //! \{ - - /*! - * A small struct containing basic statistics about the B+ tree. It can be - * fetched using get_stats(). - */ - struct tree_stats { - //! Number of items in the B+ tree - size_type size; - - //! Number of leaves in the B+ tree - size_type leaves; - - //! Number of inner nodes in the B+ tree - size_type inner_nodes; - - //! Base B+ tree parameter: The number of key/data slots in each leaf - static const unsigned short leaf_slots = Self::leaf_slotmax; - - //! Base B+ tree parameter: The number of key slots in each inner node. - static const unsigned short inner_slots = Self::inner_slotmax; - - //! Zero initialized - tree_stats() : size(0), leaves(0), inner_nodes(0) {} - - //! Return the total number of nodes - size_type nodes() const { return inner_nodes + leaves; } - - //! Return the average fill of leaves - double avgfill_leaves() const { return static_cast(size) / (leaves * leaf_slots); } - }; - - //! \} - - private: - //! \name Tree Object Data Members - //! \{ - - //! Pointer to the B+ tree's root node, either leaf or inner node. - node *root_; - - //! Pointer to first leaf in the double linked leaf chain. - LeafNode *head_leaf_; - - //! Pointer to last leaf in the double linked leaf chain. - LeafNode *tail_leaf_; - - //! Other small statistics about the B+ tree. - tree_stats stats_; - - //! Key comparison object. More comparison functions are generated from - //! this < relation. - key_compare key_less_; - - //! Memory allocator. - allocator_type allocator_; - - //! \} - - public: - //! \name Constructors and Destructor - //! \{ - - //! Default constructor initializing an empty B+ tree with the standard key - //! comparison function. - explicit BTree(const allocator_type &alloc = allocator_type()) - : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), allocator_(alloc) {} - - //! Constructor initializing an empty B+ tree with a special key - //! comparison object. - explicit BTree(const key_compare &kcf, const allocator_type &alloc = allocator_type()) - : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), key_less_(kcf), allocator_(alloc) {} - - //! Constructor initializing a B+ tree with the range [first,last). The - //! range need not be sorted. To create a B+ tree from a sorted range, use - //! bulk_load(). - template - BTree(InputIterator first, InputIterator last, const allocator_type &alloc = allocator_type()) - : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), allocator_(alloc) { - insert(first, last); - } - - //! Constructor initializing a B+ tree with the range [first,last) and a - //! special key comparison object. The range need not be sorted. To create - //! a B+ tree from a sorted range, use bulk_load(). - template - BTree(InputIterator first, InputIterator last, const key_compare &kcf, const allocator_type &alloc = allocator_type()) - : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), key_less_(kcf), allocator_(alloc) { - insert(first, last); - } - - //! Frees up all used B+ tree memory pages - ~BTree() { clear(); } - - //! Fast swapping of two identical B+ tree objects. - void swap(BTree &from) { - std::swap(root_, from.root_); - std::swap(head_leaf_, from.head_leaf_); - std::swap(tail_leaf_, from.tail_leaf_); - std::swap(stats_, from.stats_); - std::swap(key_less_, from.key_less_); - std::swap(allocator_, from.allocator_); - } - - //! \} - - public: - //! \name Key and Value Comparison Function Objects - //! \{ - - //! Function class to compare value_type objects. Required by the STL - class value_compare { - protected: - //! Key comparison function from the template parameter - key_compare key_comp; - - //! Constructor called from BTree::value_comp() - explicit value_compare(key_compare kc) : key_comp(kc) {} - - //! Friendly to the btree class so it may call the constructor - friend class BTree; - - public: - //! Function call "less"-operator resulting in true if x < y. - bool operator()(const value_type &x, const value_type &y) const { return key_comp(x.first, y.first); } - }; - - //! Constant access to the key comparison object sorting the B+ tree. - key_compare key_comp() const { return key_less_; } - - //! Constant access to a constructed value_type comparison object. Required - //! by the STL. - value_compare value_comp() const { return value_compare(key_less_); } - - //! \} - - private: - //! \name Convenient Key Comparison Functions Generated From key_less - //! \{ - - //! True if a < b ? "constructed" from key_less_() - bool key_less(const key_type &a, const key_type &b) const { return key_less_(a, b); } - - //! True if a <= b ? constructed from key_less() - bool key_lessequal(const key_type &a, const key_type &b) const { return !key_less_(b, a); } - - //! True if a > b ? constructed from key_less() - bool key_greater(const key_type &a, const key_type &b) const { return key_less_(b, a); } - - //! True if a >= b ? constructed from key_less() - bool key_greaterequal(const key_type &a, const key_type &b) const { return !key_less_(a, b); } - - //! True if a == b ? constructed from key_less(). This requires the < - //! relation to be a total order, otherwise the B+ tree cannot be sorted. - bool key_equal(const key_type &a, const key_type &b) const { return !key_less_(a, b) && !key_less_(b, a); } - - //! \} - - public: - //! \name Allocators - //! \{ - - //! Return the base node allocator provided during construction. - allocator_type get_allocator() const { return allocator_; } - - //! \} - - private: - //! \name Node Object Allocation and Deallocation Functions - //! \{ - - //! Return an allocator for LeafNode objects. - typename LeafNode::alloc_type leaf_node_allocator() { return typename LeafNode::alloc_type(allocator_); } - - //! Return an allocator for InnerNode objects. - typename InnerNode::alloc_type inner_node_allocator() { return typename InnerNode::alloc_type(allocator_); } - - //! Allocate and initialize a leaf node - LeafNode *allocate_leaf() { - LeafNode *n = new (leaf_node_allocator().allocate(1)) LeafNode(); - n->initialize(); - stats_.leaves++; - return n; - } - - //! Allocate and initialize an inner node - InnerNode *allocate_inner(unsigned short level) { - InnerNode *n = new (inner_node_allocator().allocate(1)) InnerNode(); - n->initialize(level); - stats_.inner_nodes++; - return n; - } - - //! Correctly free either inner or leaf node, destructs all contained key - //! and value objects. - void free_node(node *n) { - if (n->is_leafnode()) { - LeafNode *ln = static_cast(n); - typename LeafNode::alloc_type a(leaf_node_allocator()); - std::allocator_traits::destroy(a, ln); - std::allocator_traits::deallocate(a, ln, 1); - stats_.leaves--; - } else { - InnerNode *in = static_cast(n); - typename InnerNode::alloc_type a(inner_node_allocator()); - std::allocator_traits::destroy(a, in); - std::allocator_traits::deallocate(a, in, 1); - stats_.inner_nodes--; - } - } - - //! \} - - public: - //! \name Fast Destruction of the B+ Tree - //! \{ - - //! Frees all key/data pairs and all nodes of the tree. - void clear() { - if (root_) { - clear_recursive(root_); - free_node(root_); - - root_ = nullptr; - head_leaf_ = tail_leaf_ = nullptr; - - stats_ = tree_stats(); - } - - TLX_BTREE_ASSERT(stats_.size == 0); - } - - private: - //! Recursively free up nodes. - void clear_recursive(node *n) { - if (n->is_leafnode()) { - LeafNode *leafnode = static_cast(n); - - for (unsigned short slot = 0; slot < leafnode->slotuse; ++slot) { - // data objects are deleted by LeafNode's destructor - } - } else { - InnerNode *innernode = static_cast(n); - - for (unsigned short slot = 0; slot < innernode->slotuse + 1; ++slot) { - clear_recursive(innernode->childid[slot]); - free_node(innernode->childid[slot]); - } - } - } - - //! \} - - public: - //! \name STL Iterator Construction Functions - //! \{ - - //! Constructs a read/data-write iterator that points to the first slot in - //! the first leaf of the B+ tree. - iterator begin() { return iterator(head_leaf_, 0); } - - //! Constructs a read/data-write iterator that points to the first invalid - //! slot in the last leaf of the B+ tree. - iterator end() { return iterator(tail_leaf_, tail_leaf_ ? tail_leaf_->slotuse : 0); } - - //! Constructs a read-only constant iterator that points to the first slot - //! in the first leaf of the B+ tree. - const_iterator begin() const { return const_iterator(head_leaf_, 0); } - - //! Constructs a read-only constant iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. - const_iterator end() const { return const_iterator(tail_leaf_, tail_leaf_ ? tail_leaf_->slotuse : 0); } - - //! Constructs a read/data-write reverse iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. Uses STL magic. - reverse_iterator rbegin() { return reverse_iterator(end()); } - - //! Constructs a read/data-write reverse iterator that points to the first - //! slot in the first leaf of the B+ tree. Uses STL magic. - reverse_iterator rend() { return reverse_iterator(begin()); } - - //! Constructs a read-only reverse iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. Uses STL magic. - const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } - - //! Constructs a read-only reverse iterator that points to the first slot - //! in the first leaf of the B+ tree. Uses STL magic. - const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } - - //! \} - - private: - //! \name B+ Tree Node Binary Search Functions - //! \{ - - //! Searches for the first key in the node n greater or equal to key. Uses - //! binary search with an optional linear self-verification. This is a - //! template function, because the slotkey array is located at different - //! places in LeafNode and InnerNode. - template - unsigned short find_lower(const node_type *n, const key_type &key) const { - if (sizeof(*n) > traits::binsearch_threshold) { - if (n->slotuse == 0) return 0; - - unsigned short lo = 0, hi = n->slotuse; - - while (lo < hi) { - unsigned short mid = (lo + hi) >> 1; - - if (key_lessequal(key, n->key(mid))) { - hi = mid; // key <= mid - } else { - lo = mid + 1; // key > mid - } - } - - TLX_BTREE_PRINT("BTree::find_lower: on " << n << " key " << key << " -> " << lo << " / " << hi); - - // verify result using simple linear search - if (self_verify) { - unsigned short i = 0; - while (i < n->slotuse && key_less(n->key(i), key)) ++i; - - TLX_BTREE_PRINT("BTree::find_lower: testfind: " << i); - TLX_BTREE_ASSERT(i == lo); - } - - return lo; - } else // for nodes <= binsearch_threshold do linear search. - { - unsigned short lo = 0; - while (lo < n->slotuse && key_less(n->key(lo), key)) ++lo; - return lo; - } - } - - //! Searches for the first key in the node n greater than key. Uses binary - //! search with an optional linear self-verification. This is a template - //! function, because the slotkey array is located at different places in - //! LeafNode and InnerNode. - template - unsigned short find_upper(const node_type *n, const key_type &key) const { - if (sizeof(*n) > traits::binsearch_threshold) { - if (n->slotuse == 0) return 0; - - unsigned short lo = 0, hi = n->slotuse; - - while (lo < hi) { - unsigned short mid = (lo + hi) >> 1; - - if (key_less(key, n->key(mid))) { - hi = mid; // key < mid - } else { - lo = mid + 1; // key >= mid - } - } - - TLX_BTREE_PRINT("BTree::find_upper: on " << n << " key " << key << " -> " << lo << " / " << hi); - - // verify result using simple linear search - if (self_verify) { - unsigned short i = 0; - while (i < n->slotuse && key_lessequal(n->key(i), key)) ++i; - - TLX_BTREE_PRINT("BTree::find_upper testfind: " << i); - TLX_BTREE_ASSERT(i == hi); - } - - return lo; - } else // for nodes <= binsearch_threshold do linear search. - { - unsigned short lo = 0; - while (lo < n->slotuse && key_lessequal(n->key(lo), key)) ++lo; - return lo; - } - } - - //! \} - - public: - //! \name Access Functions to the Item Count - //! \{ - - //! Return the number of key/data pairs in the B+ tree - size_type size() const { return stats_.size; } - - //! Returns true if there is at least one key/data pair in the B+ tree - bool empty() const { return (size() == size_type(0)); } - - //! Returns the largest possible size of the B+ Tree. This is just a - //! function required by the STL standard, the B+ Tree can hold more items. - size_type max_size() const { return size_type(-1); } - - //! Return a const reference to the current statistics. - const struct tree_stats &get_stats() const { return stats_; } - - //! \} - - public: - //! \name STL Access Functions Querying the Tree by Descending to a Leaf - //! \{ - - //! Non-STL function checking whether a key is in the B+ tree. The same as - //! (find(k) != end()) or (count() != 0). - bool exists(const key_type &key) const { - const node *n = root_; - if (!n) return false; - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))); - } - - //! Tries to locate a key in the B+ tree and returns an iterator to the - //! key/data slot if found. If unsuccessful it returns end(). - iterator find(const key_type &key) { - node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))) ? iterator(leaf, slot) : end(); - } - - //! Tries to locate a key in the B+ tree and returns an constant iterator to - //! the key/data slot if found. If unsuccessful it returns end(). - const_iterator find(const key_type &key) const { - const node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))) ? const_iterator(leaf, slot) : end(); - } - - //! Tries to locate a key in the B+ tree and returns the number of identical - //! key entries found. - size_type count(const key_type &key) const { - const node *n = root_; - if (!n) return 0; - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - size_type num = 0; - - while (leaf && slot < leaf->slotuse && key_equal(key, leaf->key(slot))) { - ++num; - if (++slot >= leaf->slotuse) { - leaf = leaf->next_leaf; - slot = 0; - } - } - - return num; - } - - //! Searches the B+ tree and returns an iterator to the first pair equal to - //! or greater than key, or end() if all keys are smaller. - iterator lower_bound(const key_type &key) { - node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return iterator(leaf, slot); - } - - //! Searches the B+ tree and returns a constant iterator to the first pair - //! equal to or greater than key, or end() if all keys are smaller. - const_iterator lower_bound(const key_type &key) const { - const node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return const_iterator(leaf, slot); - } - - //! Searches the B+ tree and returns an iterator to the first pair greater - //! than key, or end() if all keys are smaller or equal. - iterator upper_bound(const key_type &key) { - node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_upper(inner, key); - - n = inner->childid[slot]; - } - - LeafNode *leaf = static_cast(n); - - unsigned short slot = find_upper(leaf, key); - return iterator(leaf, slot); - } - - //! Searches the B+ tree and returns a constant iterator to the first pair - //! greater than key, or end() if all keys are smaller or equal. - const_iterator upper_bound(const key_type &key) const { - const node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_upper(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_upper(leaf, key); - return const_iterator(leaf, slot); - } - - //! Searches the B+ tree and returns both lower_bound() and upper_bound(). - std::pair equal_range(const key_type &key) { - return std::pair(lower_bound(key), upper_bound(key)); - } - - //! Searches the B+ tree and returns both lower_bound() and upper_bound(). - std::pair equal_range(const key_type &key) const { - return std::pair(lower_bound(key), upper_bound(key)); - } - - //! \} - - public: - //! \name B+ Tree Object Comparison Functions - //! \{ - - //! Equality relation of B+ trees of the same type. B+ trees of the same - //! size and equal elements (both key and data) are considered equal. Beware - //! of the random ordering of duplicate keys. - bool operator==(const BTree &other) const { - return (size() == other.size()) && std::equal(begin(), end(), other.begin()); - } - - //! Inequality relation. Based on operator==. - bool operator!=(const BTree &other) const { return !(*this == other); } - - //! Total ordering relation of B+ trees of the same type. It uses - //! std::lexicographical_compare() for the actual comparison of elements. - bool operator<(const BTree &other) const { - return std::lexicographical_compare(begin(), end(), other.begin(), other.end()); - } - - //! Greater relation. Based on operator<. - bool operator>(const BTree &other) const { return other < *this; } - - //! Less-equal relation. Based on operator<. - bool operator<=(const BTree &other) const { return !(other < *this); } - - //! Greater-equal relation. Based on operator<. - bool operator>=(const BTree &other) const { return !(*this < other); } - - //! \} - - public: - //! \name Fast Copy: Assign Operator and Copy Constructors - //! \{ - - //! Assignment operator. All the key/data pairs are copied. - BTree &operator=(const BTree &other) { - if (this != &other) { - clear(); - - key_less_ = other.key_comp(); - allocator_ = other.get_allocator(); - - if (other.size() != 0) { - stats_.leaves = stats_.inner_nodes = 0; - if (other.root_) { - root_ = copy_recursive(other.root_); - } - stats_ = other.stats_; - } - - if (self_verify) verify(); - } - return *this; - } - - //! Copy constructor. The newly initialized B+ tree object will contain a - //! copy of all key/data pairs. - BTree(const BTree &other) - : root_(nullptr), - head_leaf_(nullptr), - tail_leaf_(nullptr), - stats_(other.stats_), - key_less_(other.key_comp()), - allocator_(other.get_allocator()) { - if (size() > 0) { - stats_.leaves = stats_.inner_nodes = 0; - if (other.root_) { - root_ = copy_recursive(other.root_); - } - if (self_verify) verify(); - } - } - - private: - //! Recursively copy nodes from another B+ tree object - struct node *copy_recursive(const node *n) { - if (n->is_leafnode()) { - const LeafNode *leaf = static_cast(n); - LeafNode *newleaf = allocate_leaf(); - - newleaf->slotuse = leaf->slotuse; - std::copy(leaf->slotdata, leaf->slotdata + leaf->slotuse, newleaf->slotdata); - - if (head_leaf_ == nullptr) { - head_leaf_ = tail_leaf_ = newleaf; - newleaf->prev_leaf = newleaf->next_leaf = nullptr; - } else { - newleaf->prev_leaf = tail_leaf_; - tail_leaf_->next_leaf = newleaf; - tail_leaf_ = newleaf; - } - - return newleaf; - } else { - const InnerNode *inner = static_cast(n); - InnerNode *newinner = allocate_inner(inner->level); - - newinner->slotuse = inner->slotuse; - std::copy(inner->slotkey, inner->slotkey + inner->slotuse, newinner->slotkey); - - for (unsigned short slot = 0; slot <= inner->slotuse; ++slot) { - newinner->childid[slot] = copy_recursive(inner->childid[slot]); - } - - return newinner; - } - } - - //! \} - - public: - //! \name Public Insertion Functions - //! \{ - - //! Attempt to insert a key/data pair into the B+ tree. If the tree does not - //! allow duplicate keys, then the insert may fail if it is already present. - std::pair insert(const value_type &x) { return insert_start(key_of_value::get(x), x); } - - //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is - //! currently ignored by the B+ tree insertion routine. - iterator insert(iterator /* hint */, const value_type &x) { return insert_start(key_of_value::get(x), x).first; } - - //! Attempt to insert the range [first,last) of value_type pairs into the B+ - //! tree. Each key/data pair is inserted individually; to bulk load the - //! tree, use a constructor with range. - template - void insert(InputIterator first, InputIterator last) { - InputIterator iter = first; - while (iter != last) { - insert(*iter); - ++iter; - } - } - - //! \} - - private: - //! \name Private Insertion Functions - //! \{ - - //! Start the insertion descent at the current root and handle root splits. - //! Returns true if the item was inserted - std::pair insert_start(const key_type &key, const value_type &value) { - node *newchild = nullptr; - key_type newkey = key_type(); - - if (root_ == nullptr) { - root_ = head_leaf_ = tail_leaf_ = allocate_leaf(); - } - - std::pair r = insert_descend(root_, key, value, &newkey, &newchild); - - if (newchild) { - // this only occurs if insert_descend() could not insert the key - // into the root node, this mean the root is full and a new root - // needs to be created. - InnerNode *newroot = allocate_inner(root_->level + 1); - newroot->slotkey[0] = newkey; - - newroot->childid[0] = root_; - newroot->childid[1] = newchild; - - newroot->slotuse = 1; - - root_ = newroot; - } - - // increment size if the item was inserted - if (r.second) ++stats_.size; - -#ifdef TLX_BTREE_DEBUG - if (debug) print(std::cout); -#endif - - if (self_verify) { - verify(); - TLX_BTREE_ASSERT(exists(key)); - } - - return r; - } - - /*! - * Insert an item into the B+ tree. - * - * Descend down the nodes to a leaf, insert the key/data pair in a free - * slot. If the node overflows, then it must be split and the new split node - * inserted into the parent. Unroll / this splitting up to the root. - */ - std::pair insert_descend(node *n, const key_type &key, const value_type &value, key_type *splitkey, - node **splitnode) { - if (!n->is_leafnode()) { - InnerNode *inner = static_cast(n); - - key_type newkey = key_type(); - node *newchild = nullptr; - - unsigned short slot = find_lower(inner, key); - - TLX_BTREE_PRINT("BTree::insert_descend into " << inner->childid[slot]); - - std::pair r = insert_descend(inner->childid[slot], key, value, &newkey, &newchild); - - if (newchild) { - TLX_BTREE_PRINT("BTree::insert_descend newchild" - << " with key " << newkey << " node " << newchild << " at slot " << slot); - - if (inner->is_full()) { - split_inner_node(inner, splitkey, splitnode, slot); - - TLX_BTREE_PRINT("BTree::insert_descend done split_inner:" - << " putslot: " << slot << " putkey: " << newkey << " upkey: " << *splitkey); - -#ifdef TLX_BTREE_DEBUG - if (debug) { - print_node(std::cout, inner); - print_node(std::cout, *splitnode); - } -#endif - - // check if insert slot is in the split sibling node - TLX_BTREE_PRINT("BTree::insert_descend switch: " << slot << " > " << inner->slotuse + 1); - - if (slot == inner->slotuse + 1 && inner->slotuse < (*splitnode)->slotuse) { - // special case when the insert slot matches the split - // place between the two nodes, then the insert key - // becomes the split key. - - TLX_BTREE_ASSERT(inner->slotuse + 1 < inner_slotmax); - - InnerNode *split = static_cast(*splitnode); - - // move the split key and it's datum into the left node - inner->slotkey[inner->slotuse] = *splitkey; - inner->childid[inner->slotuse + 1] = split->childid[0]; - inner->slotuse++; - - // set new split key and move corresponding datum into - // right node - split->childid[0] = newchild; - *splitkey = newkey; - - return r; - } else if (slot >= inner->slotuse + 1) { - // in case the insert slot is in the newly create split - // node, we reuse the code below. - - slot -= inner->slotuse + 1; - inner = static_cast(*splitnode); - TLX_BTREE_PRINT( - "BTree::insert_descend switching to " - "splitted node " - << inner << " slot " << slot); - } - } - - // move items and put pointer to child node into correct slot - TLX_BTREE_ASSERT(slot >= 0 && slot <= inner->slotuse); - - std::copy_backward(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + inner->slotuse + 1); - std::copy_backward(inner->childid + slot, inner->childid + inner->slotuse + 1, - inner->childid + inner->slotuse + 2); - - inner->slotkey[slot] = newkey; - inner->childid[slot + 1] = newchild; - inner->slotuse++; - } - - return r; - } else // n->is_leafnode() == true - { - LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - - if (!allow_duplicates && slot < leaf->slotuse && key_equal(key, leaf->key(slot))) { - return std::pair(iterator(leaf, slot), false); - } - - if (leaf->is_full()) { - split_leaf_node(leaf, splitkey, splitnode); - - // check if insert slot is in the split sibling node - if (slot >= leaf->slotuse) { - slot -= leaf->slotuse; - leaf = static_cast(*splitnode); - } - } - - // move items and put data item into correct data slot - TLX_BTREE_ASSERT(slot >= 0 && slot <= leaf->slotuse); - - std::copy_backward(leaf->slotdata + slot, leaf->slotdata + leaf->slotuse, leaf->slotdata + leaf->slotuse + 1); - - leaf->slotdata[slot] = value; - leaf->slotuse++; - - if (splitnode && leaf != *splitnode && slot == leaf->slotuse - 1) { - // special case: the node was split, and the insert is at the - // last slot of the old node. then the splitkey must be updated. - *splitkey = key; - } - - return std::pair(iterator(leaf, slot), true); - } - } - - //! Split up a leaf node into two equally-filled sibling leaves. Returns the - //! new nodes and it's insertion key in the two parameters. - void split_leaf_node(LeafNode *leaf, key_type *out_newkey, node **out_newleaf) { - TLX_BTREE_ASSERT(leaf->is_full()); - - unsigned short mid = (leaf->slotuse >> 1); - - TLX_BTREE_PRINT("BTree::split_leaf_node on " << leaf); - - LeafNode *newleaf = allocate_leaf(); - - newleaf->slotuse = leaf->slotuse - mid; - - newleaf->next_leaf = leaf->next_leaf; - if (newleaf->next_leaf == nullptr) { - TLX_BTREE_ASSERT(leaf == tail_leaf_); - tail_leaf_ = newleaf; - } else { - newleaf->next_leaf->prev_leaf = newleaf; - } - - std::copy(leaf->slotdata + mid, leaf->slotdata + leaf->slotuse, newleaf->slotdata); - - leaf->slotuse = mid; - leaf->next_leaf = newleaf; - newleaf->prev_leaf = leaf; - - *out_newkey = leaf->key(leaf->slotuse - 1); - *out_newleaf = newleaf; - } - - //! Split up an inner node into two equally-filled sibling nodes. Returns - //! the new nodes and it's insertion key in the two parameters. Requires the - //! slot of the item will be inserted, so the nodes will be the same size - //! after the insert. - void split_inner_node(InnerNode *inner, key_type *out_newkey, node **out_newinner, unsigned int addslot) { - TLX_BTREE_ASSERT(inner->is_full()); - - unsigned short mid = (inner->slotuse >> 1); - - TLX_BTREE_PRINT("BTree::split_inner: mid " << mid << " addslot " << addslot); - - // if the split is uneven and the overflowing item will be put into the - // larger node, then the smaller split node may underflow - if (addslot <= mid && mid > inner->slotuse - (mid + 1)) mid--; - - TLX_BTREE_PRINT("BTree::split_inner: mid " << mid << " addslot " << addslot); - - TLX_BTREE_PRINT("BTree::split_inner_node on " << inner << " into two nodes " << mid << " and " - << inner->slotuse - (mid + 1) << " sized"); - - InnerNode *newinner = allocate_inner(inner->level); - - newinner->slotuse = inner->slotuse - (mid + 1); - - std::copy(inner->slotkey + mid + 1, inner->slotkey + inner->slotuse, newinner->slotkey); - std::copy(inner->childid + mid + 1, inner->childid + inner->slotuse + 1, newinner->childid); - - inner->slotuse = mid; - - *out_newkey = inner->key(mid); - *out_newinner = newinner; - } - - //! \} - - public: - //! \name Bulk Loader - Construct Tree from Sorted Sequence - //! \{ - - //! Bulk load a sorted range. Loads items into leaves and constructs a - //! B-tree above them. The tree must be empty when calling this function. - template - void bulk_load(Iterator ibegin, Iterator iend) { - TLX_BTREE_ASSERT(empty()); - - stats_.size = iend - ibegin; - - // calculate number of leaves needed, round up. - size_t num_items = iend - ibegin; - size_t num_leaves = (num_items + leaf_slotmax - 1) / leaf_slotmax; - - TLX_BTREE_PRINT("BTree::bulk_load, level 0: " - << stats_.size << " items into " << num_leaves << " leaves with up to " - << ((iend - ibegin + num_leaves - 1) / num_leaves) << " items per leaf."); - - Iterator it = ibegin; - for (size_t i = 0; i < num_leaves; ++i) { - // allocate new leaf node - LeafNode *leaf = allocate_leaf(); - - // copy keys or (key,value) pairs into leaf nodes, uses template - // switch leaf->set_slot(). - leaf->slotuse = static_cast(num_items / (num_leaves - i)); - for (size_t s = 0; s < leaf->slotuse; ++s, ++it) leaf->set_slot(s, *it); - - if (tail_leaf_ != nullptr) { - tail_leaf_->next_leaf = leaf; - leaf->prev_leaf = tail_leaf_; - } else { - head_leaf_ = leaf; - } - tail_leaf_ = leaf; - - num_items -= leaf->slotuse; - } - - TLX_BTREE_ASSERT(it == iend && num_items == 0); - - // if the btree is so small to fit into one leaf, then we're done. - if (head_leaf_ == tail_leaf_) { - root_ = head_leaf_; - return; - } - - TLX_BTREE_ASSERT(stats_.leaves == num_leaves); - - // create first level of inner nodes, pointing to the leaves. - size_t num_parents = (num_leaves + (inner_slotmax + 1) - 1) / (inner_slotmax + 1); - - TLX_BTREE_PRINT("BTree::bulk_load, level 1: " - << num_leaves << " leaves in " << num_parents << " inner nodes with up to " - << ((num_leaves + num_parents - 1) / num_parents) << " leaves per inner node."); - - // save inner nodes and maxkey for next level. - typedef std::pair nextlevel_type; - nextlevel_type *nextlevel = new nextlevel_type[num_parents]; - - LeafNode *leaf = head_leaf_; - for (size_t i = 0; i < num_parents; ++i) { - // allocate new inner node at level 1 - InnerNode *n = allocate_inner(1); - - n->slotuse = static_cast(num_leaves / (num_parents - i)); - TLX_BTREE_ASSERT(n->slotuse > 0); - // this counts keys, but an inner node has keys+1 children. - --n->slotuse; - - // copy last key from each leaf and set child - for (unsigned short s = 0; s < n->slotuse; ++s) { - n->slotkey[s] = leaf->key(leaf->slotuse - 1); - n->childid[s] = leaf; - leaf = leaf->next_leaf; - } - n->childid[n->slotuse] = leaf; - - // track max key of any descendant. - nextlevel[i].first = n; - nextlevel[i].second = &leaf->key(leaf->slotuse - 1); - - leaf = leaf->next_leaf; - num_leaves -= n->slotuse + 1; - } - - TLX_BTREE_ASSERT(leaf == nullptr && num_leaves == 0); - - // recursively build inner nodes pointing to inner nodes. - for (int level = 2; num_parents != 1; ++level) { - size_t num_children = num_parents; - num_parents = (num_children + (inner_slotmax + 1) - 1) / (inner_slotmax + 1); - - TLX_BTREE_PRINT("BTree::bulk_load, level " - << level << ": " << num_children << " children in " << num_parents << " inner nodes with up to " - << ((num_children + num_parents - 1) / num_parents) << " children per inner node."); - - size_t inner_index = 0; - for (size_t i = 0; i < num_parents; ++i) { - // allocate new inner node at level - InnerNode *n = allocate_inner(level); - - n->slotuse = static_cast(num_children / (num_parents - i)); - TLX_BTREE_ASSERT(n->slotuse > 0); - // this counts keys, but an inner node has keys+1 children. - --n->slotuse; - - // copy children and maxkeys from nextlevel - for (unsigned short s = 0; s < n->slotuse; ++s) { - n->slotkey[s] = *nextlevel[inner_index].second; - n->childid[s] = nextlevel[inner_index].first; - ++inner_index; - } - n->childid[n->slotuse] = nextlevel[inner_index].first; - - // reuse nextlevel array for parents, because we can overwrite - // slots we've already consumed. - nextlevel[i].first = n; - nextlevel[i].second = nextlevel[inner_index].second; - - ++inner_index; - num_children -= n->slotuse + 1; - } - - TLX_BTREE_ASSERT(num_children == 0); - } - - root_ = nextlevel[0].first; - delete[] nextlevel; - - if (self_verify) verify(); - } - - //! \} - - private: - //! \name Support Class Encapsulating Deletion Results - //! \{ - - //! Result flags of recursive deletion. - enum result_flags_t { - //! Deletion successful and no fix-ups necessary. - btree_ok = 0, - - //! Deletion not successful because key was not found. - btree_not_found = 1, - - //! Deletion successful, the last key was updated so parent slotkeys - //! need updates. - btree_update_lastkey = 2, - - //! Deletion successful, children nodes were merged and the parent needs - //! to remove the empty node. - btree_fixmerge = 4 - }; - - //! B+ tree recursive deletion has much information which is needs to be - //! passed upward. - struct result_t { - //! Merged result flags - result_flags_t flags; - - //! The key to be updated at the parent's slot - key_type lastkey; - - //! Constructor of a result with a specific flag, this can also be used - //! as for implicit conversion. - result_t(result_flags_t f = btree_ok) // NOLINT - : flags(f), lastkey() {} - - //! Constructor with a lastkey value. - result_t(result_flags_t f, const key_type &k) : flags(f), lastkey(k) {} - - //! Test if this result object has a given flag set. - bool has(result_flags_t f) const { return (flags & f) != 0; } - - //! Merge two results OR-ing the result flags and overwriting lastkeys. - result_t &operator|=(const result_t &other) { - flags = result_flags_t(flags | other.flags); - - // we overwrite existing lastkeys on purpose - if (other.has(btree_update_lastkey)) lastkey = other.lastkey; - - return *this; - } - }; - - //! \} - - public: - //! \name Public Erase Functions - //! \{ - - //! Erases one (the first) of the key/data pairs associated with the given - //! key. - bool erase_one(const key_type &key) { - TLX_BTREE_PRINT("BTree::erase_one(" << key << ") on btree size " << size()); - - if (self_verify) verify(); - - if (!root_) return false; - - result_t result = erase_one_descend(key, root_, nullptr, nullptr, nullptr, nullptr, nullptr, 0); - - if (!result.has(btree_not_found)) --stats_.size; - -#ifdef TLX_BTREE_DEBUG - if (debug) print(std::cout); -#endif - if (self_verify) verify(); - - return !result.has(btree_not_found); - } - - //! Erases all the key/data pairs associated with the given key. This is - //! implemented using erase_one(). - size_type erase(const key_type &key) { - size_type c = 0; - - while (erase_one(key)) { - ++c; - if (!allow_duplicates) break; - } - - return c; - } - - //! Erase the key/data pair referenced by the iterator. - void erase(iterator iter) { - TLX_BTREE_PRINT("BTree::erase_iter(" << iter.curr_leaf << "," << iter.curr_slot << ") on btree size " << size()); - - if (self_verify) verify(); - - if (!root_) return; - - result_t result = erase_iter_descend(iter, root_, nullptr, nullptr, nullptr, nullptr, nullptr, 0); - - if (!result.has(btree_not_found)) --stats_.size; - -#ifdef TLX_BTREE_DEBUG - if (debug) print(std::cout); -#endif - if (self_verify) verify(); - } - -#ifdef BTREE_TODO - //! Erase all key/data pairs in the range [first,last). This function is - //! currently not implemented by the B+ Tree. - void erase(iterator /* first */, iterator /* last */) { abort(); } -#endif - - //! \} - - private: - //! \name Private Erase Functions - //! \{ - - /*! - * Erase one (the first) key/data pair in the B+ tree matching key. - * - * Descends down the tree in search of key. During the descent the parent, - * left and right siblings and their parents are computed and passed - * down. Once the key/data pair is found, it is removed from the leaf. If - * the leaf underflows 6 different cases are handled. These cases resolve - * the underflow by shifting key/data pairs from adjacent sibling nodes, - * merging two sibling nodes or trimming the tree. - */ - result_t erase_one_descend(const key_type &key, node *curr, node *left, node *right, InnerNode *left_parent, - InnerNode *right_parent, InnerNode *parent, unsigned int parentslot) { - if (curr->is_leafnode()) { - LeafNode *leaf = static_cast(curr); - LeafNode *left_leaf = static_cast(left); - LeafNode *right_leaf = static_cast(right); - - unsigned short slot = find_lower(leaf, key); - - if (slot >= leaf->slotuse || !key_equal(key, leaf->key(slot))) { - TLX_BTREE_PRINT("Could not find key " << key << " to erase."); - - return btree_not_found; - } - - TLX_BTREE_PRINT("Found key in leaf " << curr << " at slot " << slot); - - std::copy(leaf->slotdata + slot + 1, leaf->slotdata + leaf->slotuse, leaf->slotdata + slot); - - leaf->slotuse--; - - result_t myres = btree_ok; - - // if the last key of the leaf was changed, the parent is notified - // and updates the key of this leaf - if (slot == leaf->slotuse) { - if (parent && parentslot < parent->slotuse) { - TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); - parent->slotkey[parentslot] = leaf->key(leaf->slotuse - 1); - } else { - if (leaf->slotuse >= 1) { - TLX_BTREE_PRINT("Scheduling lastkeyupdate: key " << leaf->key(leaf->slotuse - 1)); - myres |= result_t(btree_update_lastkey, leaf->key(leaf->slotuse - 1)); - } else { - TLX_BTREE_ASSERT(leaf == root_); - } - } - } - - if (leaf->is_underflow() && !(leaf == root_ && leaf->slotuse >= 1)) { - // determine what to do about the underflow - - // case : if this empty leaf is the root, then delete all nodes - // and set root to nullptr. - if (left_leaf == nullptr && right_leaf == nullptr) { - TLX_BTREE_ASSERT(leaf == root_); - TLX_BTREE_ASSERT(leaf->slotuse == 0); - - free_node(root_); - - root_ = leaf = nullptr; - head_leaf_ = tail_leaf_ = nullptr; - - // will be decremented soon by insert_start() - TLX_BTREE_ASSERT(stats_.size == 1); - TLX_BTREE_ASSERT(stats_.leaves == 0); - TLX_BTREE_ASSERT(stats_.inner_nodes == 0); - - return btree_ok; - } - // case : if both left and right leaves would underflow in case - // of a shift, then merging is necessary. choose the more local - // merger with our parent - else if ((left_leaf == nullptr || left_leaf->is_few()) && (right_leaf == nullptr || right_leaf->is_few())) { - if (left_parent == parent) - myres |= merge_leaves(left_leaf, leaf, left_parent); - else - myres |= merge_leaves(leaf, right_leaf, right_parent); - } - // case : the right leaf has extra data, so balance right with - // current - else if ((left_leaf != nullptr && left_leaf->is_few()) && (right_leaf != nullptr && !right_leaf->is_few())) { - if (right_parent == parent) - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - else - myres |= merge_leaves(left_leaf, leaf, left_parent); - } - // case : the left leaf has extra data, so balance left with - // current - else if ((left_leaf != nullptr && !left_leaf->is_few()) && (right_leaf != nullptr && right_leaf->is_few())) { - if (left_parent == parent) - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - else - myres |= merge_leaves(leaf, right_leaf, right_parent); - } - // case : both the leaf and right leaves have extra data and our - // parent, choose the leaf with more data - else if (left_parent == right_parent) { - if (left_leaf->slotuse <= right_leaf->slotuse) - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - else - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - } else { - if (left_parent == parent) - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - else - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - } - } - - return myres; - } else // !curr->is_leafnode() - { - InnerNode *inner = static_cast(curr); - InnerNode *left_inner = static_cast(left); - InnerNode *right_inner = static_cast(right); - - node *myleft, *myright; - InnerNode *myleft_parent, *myright_parent; - - unsigned short slot = find_lower(inner, key); - - if (slot == 0) { - myleft = (left == nullptr) ? nullptr : static_cast(left)->childid[left->slotuse - 1]; - myleft_parent = left_parent; - } else { - myleft = inner->childid[slot - 1]; - myleft_parent = inner; - } - - if (slot == inner->slotuse) { - myright = (right == nullptr) ? nullptr : static_cast(right)->childid[0]; - myright_parent = right_parent; - } else { - myright = inner->childid[slot + 1]; - myright_parent = inner; - } - - TLX_BTREE_PRINT("erase_one_descend into " << inner->childid[slot]); - - result_t result = - erase_one_descend(key, inner->childid[slot], myleft, myright, myleft_parent, myright_parent, inner, slot); - - result_t myres = btree_ok; - - if (result.has(btree_not_found)) { - return result; - } - - if (result.has(btree_update_lastkey)) { - if (parent && parentslot < parent->slotuse) { - TLX_BTREE_PRINT("Fixing lastkeyupdate: key " << result.lastkey << " into parent " << parent - << " at parentslot " << parentslot); - - TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); - parent->slotkey[parentslot] = result.lastkey; - } else { - TLX_BTREE_PRINT("Forwarding lastkeyupdate: key " << result.lastkey); - myres |= result_t(btree_update_lastkey, result.lastkey); - } - } - - if (result.has(btree_fixmerge)) { - // either the current node or the next is empty and should be - // removed - if (inner->childid[slot]->slotuse != 0) slot++; - - // this is the child slot invalidated by the merge - TLX_BTREE_ASSERT(inner->childid[slot]->slotuse == 0); - - free_node(inner->childid[slot]); - - std::copy(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + slot - 1); - std::copy(inner->childid + slot + 1, inner->childid + inner->slotuse + 1, inner->childid + slot); - - inner->slotuse--; - - if (inner->level == 1) { - // fix split key for children leaves - slot--; - LeafNode *child = static_cast(inner->childid[slot]); - inner->slotkey[slot] = child->key(child->slotuse - 1); - } - } - - if (inner->is_underflow() && !(inner == root_ && inner->slotuse >= 1)) { - // case: the inner node is the root and has just one child. that - // child becomes the new root - if (left_inner == nullptr && right_inner == nullptr) { - TLX_BTREE_ASSERT(inner == root_); - TLX_BTREE_ASSERT(inner->slotuse == 0); - - root_ = inner->childid[0]; - - inner->slotuse = 0; - free_node(inner); - - return btree_ok; - } - // case : if both left and right leaves would underflow in case - // of a shift, then merging is necessary. choose the more local - // merger with our parent - else if ((left_inner == nullptr || left_inner->is_few()) && (right_inner == nullptr || right_inner->is_few())) { - if (left_parent == parent) - myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); - else - myres |= merge_inner(inner, right_inner, right_parent, parentslot); - } - // case : the right leaf has extra data, so balance right with - // current - else if ((left_inner != nullptr && left_inner->is_few()) && - (right_inner != nullptr && !right_inner->is_few())) { - if (right_parent == parent) - shift_left_inner(inner, right_inner, right_parent, parentslot); - else - myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); - } - // case : the left leaf has extra data, so balance left with - // current - else if ((left_inner != nullptr && !left_inner->is_few()) && - (right_inner != nullptr && right_inner->is_few())) { - if (left_parent == parent) - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - else - myres |= merge_inner(inner, right_inner, right_parent, parentslot); - } - // case : both the leaf and right leaves have extra data and our - // parent, choose the leaf with more data - else if (left_parent == right_parent) { - if (left_inner->slotuse <= right_inner->slotuse) - shift_left_inner(inner, right_inner, right_parent, parentslot); - else - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - } else { - if (left_parent == parent) - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - else - shift_left_inner(inner, right_inner, right_parent, parentslot); - } - } - - return myres; - } - } - - /*! - * Erase one key/data pair referenced by an iterator in the B+ tree. - * - * Descends down the tree in search of an iterator. During the descent the - * parent, left and right siblings and their parents are computed and passed - * down. The difficulty is that the iterator contains only a pointer to a - * LeafNode, which means that this function must do a recursive depth first - * search for that leaf node in the subtree containing all pairs of the same - * key. This subtree can be very large, even the whole tree, though in - * practice it would not make sense to have so many duplicate keys. - * - * Once the referenced key/data pair is found, it is removed from the leaf - * and the same underflow cases are handled as in erase_one_descend. - */ - result_t erase_iter_descend(const iterator &iter, node *curr, node *left, node *right, InnerNode *left_parent, - InnerNode *right_parent, InnerNode *parent, unsigned int parentslot) { - if (curr->is_leafnode()) { - LeafNode *leaf = static_cast(curr); - LeafNode *left_leaf = static_cast(left); - LeafNode *right_leaf = static_cast(right); - - // if this is not the correct leaf, get next step in recursive - // search - if (leaf != iter.curr_leaf) { - return btree_not_found; - } - - if (iter.curr_slot >= leaf->slotuse) { - TLX_BTREE_PRINT("Could not find iterator (" << iter.curr_leaf << "," << iter.curr_slot - << ") to erase. Invalid leaf node?"); - - return btree_not_found; - } - - unsigned short slot = iter.curr_slot; - - TLX_BTREE_PRINT("Found iterator in leaf " << curr << " at slot " << slot); - - std::copy(leaf->slotdata + slot + 1, leaf->slotdata + leaf->slotuse, leaf->slotdata + slot); - - leaf->slotuse--; - - result_t myres = btree_ok; - - // if the last key of the leaf was changed, the parent is notified - // and updates the key of this leaf - if (slot == leaf->slotuse) { - if (parent && parentslot < parent->slotuse) { - TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); - parent->slotkey[parentslot] = leaf->key(leaf->slotuse - 1); - } else { - if (leaf->slotuse >= 1) { - TLX_BTREE_PRINT("Scheduling lastkeyupdate: key " << leaf->key(leaf->slotuse - 1)); - myres |= result_t(btree_update_lastkey, leaf->key(leaf->slotuse - 1)); - } else { - TLX_BTREE_ASSERT(leaf == root_); - } - } - } - - if (leaf->is_underflow() && !(leaf == root_ && leaf->slotuse >= 1)) { - // determine what to do about the underflow - - // case : if this empty leaf is the root, then delete all nodes - // and set root to nullptr. - if (left_leaf == nullptr && right_leaf == nullptr) { - TLX_BTREE_ASSERT(leaf == root_); - TLX_BTREE_ASSERT(leaf->slotuse == 0); - - free_node(root_); - - root_ = leaf = nullptr; - head_leaf_ = tail_leaf_ = nullptr; - - // will be decremented soon by insert_start() - TLX_BTREE_ASSERT(stats_.size == 1); - TLX_BTREE_ASSERT(stats_.leaves == 0); - TLX_BTREE_ASSERT(stats_.inner_nodes == 0); - - return btree_ok; - } - // case : if both left and right leaves would underflow in case - // of a shift, then merging is necessary. choose the more local - // merger with our parent - else if ((left_leaf == nullptr || left_leaf->is_few()) && (right_leaf == nullptr || right_leaf->is_few())) { - if (left_parent == parent) - myres |= merge_leaves(left_leaf, leaf, left_parent); - else - myres |= merge_leaves(leaf, right_leaf, right_parent); - } - // case : the right leaf has extra data, so balance right with - // current - else if ((left_leaf != nullptr && left_leaf->is_few()) && (right_leaf != nullptr && !right_leaf->is_few())) { - if (right_parent == parent) { - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - } else { - myres |= merge_leaves(left_leaf, leaf, left_parent); - } - } - // case : the left leaf has extra data, so balance left with - // current - else if ((left_leaf != nullptr && !left_leaf->is_few()) && (right_leaf != nullptr && right_leaf->is_few())) { - if (left_parent == parent) { - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - } else { - myres |= merge_leaves(leaf, right_leaf, right_parent); - } - } - // case : both the leaf and right leaves have extra data and our - // parent, choose the leaf with more data - else if (left_parent == right_parent) { - if (left_leaf->slotuse <= right_leaf->slotuse) { - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - } else { - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - } - } else { - if (left_parent == parent) { - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - } else { - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - } - } - } - - return myres; - } else // !curr->is_leafnode() - { - InnerNode *inner = static_cast(curr); - InnerNode *left_inner = static_cast(left); - InnerNode *right_inner = static_cast(right); - - // find first slot below which the searched iterator might be - // located. - - result_t result; - unsigned short slot = find_lower(inner, iter.key()); - - while (slot <= inner->slotuse) { - node *myleft, *myright; - InnerNode *myleft_parent, *myright_parent; - - if (slot == 0) { - myleft = (left == nullptr) ? nullptr : static_cast(left)->childid[left->slotuse - 1]; - myleft_parent = left_parent; - } else { - myleft = inner->childid[slot - 1]; - myleft_parent = inner; - } - - if (slot == inner->slotuse) { - myright = (right == nullptr) ? nullptr : static_cast(right)->childid[0]; - myright_parent = right_parent; - } else { - myright = inner->childid[slot + 1]; - myright_parent = inner; - } - - TLX_BTREE_PRINT("erase_iter_descend into " << inner->childid[slot]); - - result = - erase_iter_descend(iter, inner->childid[slot], myleft, myright, myleft_parent, myright_parent, inner, slot); - - if (!result.has(btree_not_found)) break; - - // continue recursive search for leaf on next slot - - if (slot < inner->slotuse && key_less(inner->slotkey[slot], iter.key())) return btree_not_found; - - ++slot; - } - - if (slot > inner->slotuse) return btree_not_found; - - result_t myres = btree_ok; - - if (result.has(btree_update_lastkey)) { - if (parent && parentslot < parent->slotuse) { - TLX_BTREE_PRINT("Fixing lastkeyupdate: key " << result.lastkey << " into parent " << parent - << " at parentslot " << parentslot); - - TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); - parent->slotkey[parentslot] = result.lastkey; - } else { - TLX_BTREE_PRINT("Forwarding lastkeyupdate: key " << result.lastkey); - myres |= result_t(btree_update_lastkey, result.lastkey); - } - } - - if (result.has(btree_fixmerge)) { - // either the current node or the next is empty and should be - // removed - if (inner->childid[slot]->slotuse != 0) slot++; - - // this is the child slot invalidated by the merge - TLX_BTREE_ASSERT(inner->childid[slot]->slotuse == 0); - - free_node(inner->childid[slot]); - - std::copy(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + slot - 1); - std::copy(inner->childid + slot + 1, inner->childid + inner->slotuse + 1, inner->childid + slot); - - inner->slotuse--; - - if (inner->level == 1) { - // fix split key for children leaves - slot--; - LeafNode *child = static_cast(inner->childid[slot]); - inner->slotkey[slot] = child->key(child->slotuse - 1); - } - } - - if (inner->is_underflow() && !(inner == root_ && inner->slotuse >= 1)) { - // case: the inner node is the root and has just one - // child. that child becomes the new root - if (left_inner == nullptr && right_inner == nullptr) { - TLX_BTREE_ASSERT(inner == root_); - TLX_BTREE_ASSERT(inner->slotuse == 0); - - root_ = inner->childid[0]; - - inner->slotuse = 0; - free_node(inner); - - return btree_ok; - } - // case : if both left and right leaves would underflow in case - // of a shift, then merging is necessary. choose the more local - // merger with our parent - else if ((left_inner == nullptr || left_inner->is_few()) && (right_inner == nullptr || right_inner->is_few())) { - if (left_parent == parent) { - myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); - } else { - myres |= merge_inner(inner, right_inner, right_parent, parentslot); - } - } - // case : the right leaf has extra data, so balance right with - // current - else if ((left_inner != nullptr && left_inner->is_few()) && - (right_inner != nullptr && !right_inner->is_few())) { - if (right_parent == parent) { - shift_left_inner(inner, right_inner, right_parent, parentslot); - } else { - myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); - } - } - // case : the left leaf has extra data, so balance left with - // current - else if ((left_inner != nullptr && !left_inner->is_few()) && - (right_inner != nullptr && right_inner->is_few())) { - if (left_parent == parent) { - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - } else { - myres |= merge_inner(inner, right_inner, right_parent, parentslot); - } - } - // case : both the leaf and right leaves have extra data and our - // parent, choose the leaf with more data - else if (left_parent == right_parent) { - if (left_inner->slotuse <= right_inner->slotuse) { - shift_left_inner(inner, right_inner, right_parent, parentslot); - } else { - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - } - } else { - if (left_parent == parent) { - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - } else { - shift_left_inner(inner, right_inner, right_parent, parentslot); - } - } - } - - return myres; - } - } - - //! Merge two leaf nodes. The function moves all key/data pairs from right - //! to left and sets right's slotuse to zero. The right slot is then removed - //! by the calling parent node. - result_t merge_leaves(LeafNode *left, LeafNode *right, InnerNode *parent) { - TLX_BTREE_PRINT("Merge leaf nodes " << left << " and " << right << " with common parent " << parent << "."); - (void)parent; - - TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); - TLX_BTREE_ASSERT(parent->level == 1); - - TLX_BTREE_ASSERT(left->slotuse + right->slotuse < leaf_slotmax); - - std::copy(right->slotdata, right->slotdata + right->slotuse, left->slotdata + left->slotuse); - - left->slotuse += right->slotuse; - - left->next_leaf = right->next_leaf; - if (left->next_leaf) - left->next_leaf->prev_leaf = left; - else - tail_leaf_ = left; - - right->slotuse = 0; - - return btree_fixmerge; - } - - //! Merge two inner nodes. The function moves all key/childid pairs from - //! right to left and sets right's slotuse to zero. The right slot is then - //! removed by the calling parent node. - static result_t merge_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_PRINT("Merge inner nodes " << left << " and " << right << " with common parent " << parent << "."); - - TLX_BTREE_ASSERT(left->level == right->level); - TLX_BTREE_ASSERT(parent->level == left->level + 1); - - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - TLX_BTREE_ASSERT(left->slotuse + right->slotuse < inner_slotmax); - - if (self_verify) { - // find the left node's slot in the parent's children - unsigned int leftslot = 0; - while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; - - TLX_BTREE_ASSERT(leftslot < parent->slotuse); - TLX_BTREE_ASSERT(parent->childid[leftslot] == left); - TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); - - TLX_BTREE_ASSERT(parentslot == leftslot); - } - - // retrieve the decision key from parent - left->slotkey[left->slotuse] = parent->slotkey[parentslot]; - left->slotuse++; - - // copy over keys and children from right - std::copy(right->slotkey, right->slotkey + right->slotuse, left->slotkey + left->slotuse); - std::copy(right->childid, right->childid + right->slotuse + 1, left->childid + left->slotuse); - - left->slotuse += right->slotuse; - right->slotuse = 0; - - return btree_fixmerge; - } - - //! Balance two leaf nodes. The function moves key/data pairs from right to - //! left so that both nodes are equally filled. The parent node is updated - //! if possible. - static result_t shift_left_leaf(LeafNode *left, LeafNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); - TLX_BTREE_ASSERT(parent->level == 1); - - TLX_BTREE_ASSERT(left->next_leaf == right); - TLX_BTREE_ASSERT(left == right->prev_leaf); - - TLX_BTREE_ASSERT(left->slotuse < right->slotuse); - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - unsigned int shiftnum = (right->slotuse - left->slotuse) >> 1; - - TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to left " << left << " from right " << right - << " with common parent " << parent << "."); - - TLX_BTREE_ASSERT(left->slotuse + shiftnum < leaf_slotmax); - - // copy the first items from the right node to the last slot in the left - // node. - - std::copy(right->slotdata, right->slotdata + shiftnum, left->slotdata + left->slotuse); - - left->slotuse += shiftnum; - - // shift all slots in the right node to the left - - std::copy(right->slotdata + shiftnum, right->slotdata + right->slotuse, right->slotdata); - - right->slotuse -= shiftnum; - - // fixup parent - if (parentslot < parent->slotuse) { - parent->slotkey[parentslot] = left->key(left->slotuse - 1); - return btree_ok; - } else { // the update is further up the tree - return result_t(btree_update_lastkey, left->key(left->slotuse - 1)); - } - } - - //! Balance two inner nodes. The function moves key/data pairs from right to - //! left so that both nodes are equally filled. The parent node is updated - //! if possible. - static void shift_left_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_ASSERT(left->level == right->level); - TLX_BTREE_ASSERT(parent->level == left->level + 1); - - TLX_BTREE_ASSERT(left->slotuse < right->slotuse); - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - unsigned int shiftnum = (right->slotuse - left->slotuse) >> 1; - - TLX_BTREE_PRINT("Shifting (inner) " << shiftnum << " entries to left " << left << " from right " << right - << " with common parent " << parent << "."); - - TLX_BTREE_ASSERT(left->slotuse + shiftnum < inner_slotmax); - - if (self_verify) { - // find the left node's slot in the parent's children and compare to - // parentslot - - unsigned int leftslot = 0; - while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; - - TLX_BTREE_ASSERT(leftslot < parent->slotuse); - TLX_BTREE_ASSERT(parent->childid[leftslot] == left); - TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); - - TLX_BTREE_ASSERT(leftslot == parentslot); - } - - // copy the parent's decision slotkey and childid to the first new key - // on the left - left->slotkey[left->slotuse] = parent->slotkey[parentslot]; - left->slotuse++; - - // copy the other items from the right node to the last slots in the - // left node. - std::copy(right->slotkey, right->slotkey + shiftnum - 1, left->slotkey + left->slotuse); - std::copy(right->childid, right->childid + shiftnum, left->childid + left->slotuse); - - left->slotuse += shiftnum - 1; - - // fixup parent - parent->slotkey[parentslot] = right->slotkey[shiftnum - 1]; - - // shift all slots in the right node - std::copy(right->slotkey + shiftnum, right->slotkey + right->slotuse, right->slotkey); - std::copy(right->childid + shiftnum, right->childid + right->slotuse + 1, right->childid); - - right->slotuse -= shiftnum; - } - - //! Balance two leaf nodes. The function moves key/data pairs from left to - //! right so that both nodes are equally filled. The parent node is updated - //! if possible. - static void shift_right_leaf(LeafNode *left, LeafNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); - TLX_BTREE_ASSERT(parent->level == 1); - - TLX_BTREE_ASSERT(left->next_leaf == right); - TLX_BTREE_ASSERT(left == right->prev_leaf); - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - TLX_BTREE_ASSERT(left->slotuse > right->slotuse); - - unsigned int shiftnum = (left->slotuse - right->slotuse) >> 1; - - TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to right " << right << " from left " << left - << " with common parent " << parent << "."); - - if (self_verify) { - // find the left node's slot in the parent's children - unsigned int leftslot = 0; - while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; - - TLX_BTREE_ASSERT(leftslot < parent->slotuse); - TLX_BTREE_ASSERT(parent->childid[leftslot] == left); - TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); - - TLX_BTREE_ASSERT(leftslot == parentslot); - } - - // shift all slots in the right node - - TLX_BTREE_ASSERT(right->slotuse + shiftnum < leaf_slotmax); - - std::copy_backward(right->slotdata, right->slotdata + right->slotuse, right->slotdata + right->slotuse + shiftnum); - - right->slotuse += shiftnum; - - // copy the last items from the left node to the first slot in the right - // node. - std::copy(left->slotdata + left->slotuse - shiftnum, left->slotdata + left->slotuse, right->slotdata); - - left->slotuse -= shiftnum; - - parent->slotkey[parentslot] = left->key(left->slotuse - 1); - } - - //! Balance two inner nodes. The function moves key/data pairs from left to - //! right so that both nodes are equally filled. The parent node is updated - //! if possible. - static void shift_right_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_ASSERT(left->level == right->level); - TLX_BTREE_ASSERT(parent->level == left->level + 1); - - TLX_BTREE_ASSERT(left->slotuse > right->slotuse); - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - unsigned int shiftnum = (left->slotuse - right->slotuse) >> 1; - - TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to right " << right << " from left " << left - << " with common parent " << parent << "."); - - if (self_verify) { - // find the left node's slot in the parent's children - unsigned int leftslot = 0; - while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; - - TLX_BTREE_ASSERT(leftslot < parent->slotuse); - TLX_BTREE_ASSERT(parent->childid[leftslot] == left); - TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); - - TLX_BTREE_ASSERT(leftslot == parentslot); - } - - // shift all slots in the right node - - TLX_BTREE_ASSERT(right->slotuse + shiftnum < inner_slotmax); - - std::copy_backward(right->slotkey, right->slotkey + right->slotuse, right->slotkey + right->slotuse + shiftnum); - std::copy_backward(right->childid, right->childid + right->slotuse + 1, - right->childid + right->slotuse + 1 + shiftnum); - - right->slotuse += shiftnum; - - // copy the parent's decision slotkey and childid to the last new key on - // the right - right->slotkey[shiftnum - 1] = parent->slotkey[parentslot]; - - // copy the remaining last items from the left node to the first slot in - // the right node. - std::copy(left->slotkey + left->slotuse - shiftnum + 1, left->slotkey + left->slotuse, right->slotkey); - std::copy(left->childid + left->slotuse - shiftnum + 1, left->childid + left->slotuse + 1, right->childid); - - // copy the first to-be-removed key from the left node to the parent's - // decision slot - parent->slotkey[parentslot] = left->slotkey[left->slotuse - shiftnum]; - - left->slotuse -= shiftnum; - } - - //! \} - -#ifdef TLX_BTREE_DEBUG - - public: - //! \name Debug Printing - //! \{ - - //! Print out the B+ tree structure with keys onto the given ostream. This - //! function requires that the header is compiled with TLX_BTREE_DEBUG and - //! that key_type is printable via std::ostream. - void print(std::ostream &os) const { - if (root_) { - print_node(os, root_, 0, true); - } - } - - //! Print out only the leaves via the double linked list. - void print_leaves(std::ostream &os) const { - os << "leaves:" << std::endl; - - const LeafNode *n = head_leaf_; - - while (n) { - os << " " << n << std::endl; - - n = n->next_leaf; - } - } - - private: - //! Recursively descend down the tree and print out nodes. - static void print_node(std::ostream &os, const node *node, unsigned int depth = 0, bool recursive = false) { - for (unsigned int i = 0; i < depth; i++) os << " "; - - os << "node " << node << " level " << node->level << " slotuse " << node->slotuse << std::endl; - - if (node->is_leafnode()) { - const LeafNode *leafnode = static_cast(node); - - for (unsigned int i = 0; i < depth; i++) os << " "; - os << " leaf prev " << leafnode->prev_leaf << " next " << leafnode->next_leaf << std::endl; - - for (unsigned int i = 0; i < depth; i++) os << " "; - - for (unsigned short slot = 0; slot < leafnode->slotuse; ++slot) { - // os << leafnode->key(slot) << " " - // << "(data: " << leafnode->slotdata[slot] << ") "; - os << leafnode->key(slot) << " "; - } - os << std::endl; - } else { - const InnerNode *innernode = static_cast(node); - - for (unsigned int i = 0; i < depth; i++) os << " "; - - for (unsigned short slot = 0; slot < innernode->slotuse; ++slot) { - os << "(" << innernode->childid[slot] << ") " << innernode->slotkey[slot] << " "; - } - os << "(" << innernode->childid[innernode->slotuse] << ")" << std::endl; - - if (recursive) { - for (unsigned short slot = 0; slot < innernode->slotuse + 1; ++slot) { - print_node(os, innernode->childid[slot], depth + 1, recursive); - } - } - } - } - - //! \} -#endif - - public: - //! \name Verification of B+ Tree Invariants - //! \{ - - //! Run a thorough verification of all B+ tree invariants. The program - //! aborts via tlx_die_unless() if something is wrong. - void verify() const { - key_type minkey, maxkey; - tree_stats vstats; - - if (root_) { - verify_node(root_, &minkey, &maxkey, vstats); - - tlx_die_unless(vstats.size == stats_.size); - tlx_die_unless(vstats.leaves == stats_.leaves); - tlx_die_unless(vstats.inner_nodes == stats_.inner_nodes); - - verify_leaflinks(); - } - } - - private: - //! Recursively descend down the tree and verify each node - void verify_node(const node *n, key_type *minkey, key_type *maxkey, tree_stats &vstats) const { - TLX_BTREE_PRINT("verifynode " << n); - - if (n->is_leafnode()) { - const LeafNode *leaf = static_cast(n); - - tlx_die_unless(leaf == root_ || !leaf->is_underflow()); - tlx_die_unless(leaf->slotuse > 0); - - for (unsigned short slot = 0; slot < leaf->slotuse - 1; ++slot) { - tlx_die_unless(key_lessequal(leaf->key(slot), leaf->key(slot + 1))); - } - - *minkey = leaf->key(0); - *maxkey = leaf->key(leaf->slotuse - 1); - - vstats.leaves++; - vstats.size += leaf->slotuse; - } else // !n->is_leafnode() - { - const InnerNode *inner = static_cast(n); - vstats.inner_nodes++; - - tlx_die_unless(inner == root_ || !inner->is_underflow()); - tlx_die_unless(inner->slotuse > 0); - - for (unsigned short slot = 0; slot < inner->slotuse - 1; ++slot) { - tlx_die_unless(key_lessequal(inner->key(slot), inner->key(slot + 1))); - } - - for (unsigned short slot = 0; slot <= inner->slotuse; ++slot) { - const node *subnode = inner->childid[slot]; - key_type subminkey = key_type(); - key_type submaxkey = key_type(); - - tlx_die_unless(subnode->level + 1 == inner->level); - verify_node(subnode, &subminkey, &submaxkey, vstats); - - TLX_BTREE_PRINT("verify subnode " << subnode << ": " << subminkey << " - " << submaxkey); - - if (slot == 0) - *minkey = subminkey; - else - tlx_die_unless(key_greaterequal(subminkey, inner->key(slot - 1))); - - if (slot == inner->slotuse) - *maxkey = submaxkey; - else - tlx_die_unless(key_equal(inner->key(slot), submaxkey)); - - if (inner->level == 1 && slot < inner->slotuse) { - // children are leaves and must be linked together in the - // correct order - const LeafNode *leafa = static_cast(inner->childid[slot]); - const LeafNode *leafb = static_cast(inner->childid[slot + 1]); - - tlx_die_unless(leafa->next_leaf == leafb); - tlx_die_unless(leafa == leafb->prev_leaf); - } - if (inner->level == 2 && slot < inner->slotuse) { - // verify leaf links between the adjacent inner nodes - const InnerNode *parenta = static_cast(inner->childid[slot]); - const InnerNode *parentb = static_cast(inner->childid[slot + 1]); - - const LeafNode *leafa = static_cast(parenta->childid[parenta->slotuse]); - const LeafNode *leafb = static_cast(parentb->childid[0]); - - tlx_die_unless(leafa->next_leaf == leafb); - tlx_die_unless(leafa == leafb->prev_leaf); - } - } - } - } - - //! Verify the double linked list of leaves. - void verify_leaflinks() const { - const LeafNode *n = head_leaf_; - - tlx_die_unless(n->level == 0); - tlx_die_unless(!n || n->prev_leaf == nullptr); - - unsigned int testcount = 0; - - while (n) { - tlx_die_unless(n->level == 0); - tlx_die_unless(n->slotuse > 0); - - for (unsigned short slot = 0; slot < n->slotuse - 1; ++slot) { - tlx_die_unless(key_lessequal(n->key(slot), n->key(slot + 1))); - } - - testcount += n->slotuse; - - if (n->next_leaf) { - tlx_die_unless(key_lessequal(n->key(n->slotuse - 1), n->next_leaf->key(0))); - - tlx_die_unless(n == n->next_leaf->prev_leaf); - } else { - tlx_die_unless(tail_leaf_ == n); - } - - n = n->next_leaf; - } - - tlx_die_unless(testcount == size()); - } - - //! \} -}; - -//! \} -//! \} - -} // namespace tlx - -#endif // !TLX_CONTAINER_BTREE_HEADER - -/******************************************************************************/ diff --git a/tests/benchmark/btree_map.hpp b/tests/benchmark/btree_map.hpp deleted file mode 100644 index 3f718677b..000000000 --- a/tests/benchmark/btree_map.hpp +++ /dev/null @@ -1,475 +0,0 @@ -/******************************************************************************* - * tlx/container/btree_map.hpp - * - * Part of tlx - http://panthema.net/tlx - * - * Copyright (C) 2008-2017 Timo Bingmann - * - * All rights reserved. Published under the Boost Software License, Version 1.0 - ******************************************************************************/ - -#ifndef TLX_CONTAINER_BTREE_MAP_HEADER -#define TLX_CONTAINER_BTREE_MAP_HEADER - -#include -#include -#include - -#include "btree.hpp" - -namespace tlx { - -//! \addtogroup tlx_container_btree -//! \{ - -/*! - * Specialized B+ tree template class implementing STL's map container. - * - * Implements the STL map using a B+ tree. It can be used as a drop-in - * replacement for std::map. Not all asymptotic time requirements are met in - * theory. The class has a traits class defining B+ tree properties like slots - * and self-verification. Furthermore an allocator can be specified for tree - * nodes. - */ -template , - typename Traits_ = btree_default_traits>, - typename Alloc_ = std::allocator>> -class btree_map { - public: - //! \name Template Parameter Types - //! \{ - - //! First template parameter: The key type of the btree. This is stored in - //! inner nodes. - typedef Key_ key_type; - - //! Second template parameter: The value type associated with each key. - //! Stored in the B+ tree's leaves - typedef Data_ data_type; - - //! Third template parameter: Key comparison function object - typedef Compare_ key_compare; - - //! Fourth template parameter: Traits object used to define more parameters - //! of the B+ tree - typedef Traits_ traits; - - //! Fifth template parameter: STL allocator - typedef Alloc_ allocator_type; - - //! \} - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ - // tree internals. This was added for wxBTreeDemo to be able to draw the - // tree. - TLX_BTREE_FRIENDS; - - public: - //! \name Constructed Types - //! \{ - - //! Typedef of our own type - typedef btree_map self; - - //! Construct the STL-required value_type as a composition pair of key and - //! data types - typedef std::pair value_type; - - //! Key Extractor Struct - struct key_of_value { - //! pull first out of pair - static const key_type &get(const value_type &v) { return v.first; } - }; - - //! Implementation type of the btree_base - typedef BTree btree_impl; - - //! Function class comparing two value_type pairs. - typedef typename btree_impl::value_compare value_compare; - - //! Size type used to count keys - typedef typename btree_impl::size_type size_type; - - //! Small structure containing statistics about the tree - typedef typename btree_impl::tree_stats tree_stats; - - //! \} - - public: - //! \name Static Constant Options and Values of the B+ Tree - //! \{ - - //! Base B+ tree parameter: The number of key/data slots in each leaf - static const unsigned short leaf_slotmax = btree_impl::leaf_slotmax; - - //! Base B+ tree parameter: The number of key slots in each inner node, - //! this can differ from slots in each leaf. - static const unsigned short inner_slotmax = btree_impl::inner_slotmax; - - //! Computed B+ tree parameter: The minimum number of key/data slots used - //! in a leaf. If fewer slots are used, the leaf will be merged or slots - //! shifted from it's siblings. - static const unsigned short leaf_slotmin = btree_impl::leaf_slotmin; - - //! Computed B+ tree parameter: The minimum number of key slots used - //! in an inner node. If fewer slots are used, the inner node will be - //! merged or slots shifted from it's siblings. - static const unsigned short inner_slotmin = btree_impl::inner_slotmin; - - //! Debug parameter: Enables expensive and thorough checking of the B+ tree - //! invariants after each insert/erase operation. - static const bool self_verify = btree_impl::self_verify; - - //! Debug parameter: Prints out lots of debug information about how the - //! algorithms change the tree. Requires the header file to be compiled - //! with TLX_BTREE_DEBUG and the key type must be std::ostream printable. - static const bool debug = btree_impl::debug; - - //! Operational parameter: Allow duplicate keys in the btree. - static const bool allow_duplicates = btree_impl::allow_duplicates; - - //! \} - - public: - //! \name Iterators and Reverse Iterators - //! \{ - - //! STL-like iterator object for B+ tree items. The iterator points to a - //! specific slot number in a leaf. - typedef typename btree_impl::iterator iterator; - - //! STL-like iterator object for B+ tree items. The iterator points to a - //! specific slot number in a leaf. - typedef typename btree_impl::const_iterator const_iterator; - - //! create mutable reverse iterator by using STL magic - typedef typename btree_impl::reverse_iterator reverse_iterator; - - //! create constant reverse iterator by using STL magic - typedef typename btree_impl::const_reverse_iterator const_reverse_iterator; - - //! \} - - private: - //! \name Tree Implementation Object - //! \{ - - //! The contained implementation object - btree_impl tree_; - - //! \} - - public: - //! \name Constructors and Destructor - //! \{ - - //! Default constructor initializing an empty B+ tree with the standard key - //! comparison function - explicit btree_map(const allocator_type &alloc = allocator_type()) : tree_(alloc) {} - - //! Constructor initializing an empty B+ tree with a special key - //! comparison object - explicit btree_map(const key_compare &kcf, const allocator_type &alloc = allocator_type()) : tree_(kcf, alloc) {} - - //! Constructor initializing a B+ tree with the range [first,last) - template - btree_map(InputIterator first, InputIterator last, const allocator_type &alloc = allocator_type()) - : tree_(first, last, alloc) {} - - //! Constructor initializing a B+ tree with the range [first,last) and a - //! special key comparison object - template - btree_map(InputIterator first, InputIterator last, const key_compare &kcf, - const allocator_type &alloc = allocator_type()) - : tree_(first, last, kcf, alloc) {} - - //! Frees up all used B+ tree memory pages - ~btree_map() {} - - //! Fast swapping of two identical B+ tree objects. - void swap(btree_map &from) { std::swap(tree_, from.tree_); } - - //! \} - - public: - //! \name Key and Value Comparison Function Objects - //! \{ - - //! Constant access to the key comparison object sorting the B+ tree - key_compare key_comp() const { return tree_.key_comp(); } - - //! Constant access to a constructed value_type comparison object. required - //! by the STL - value_compare value_comp() const { return tree_.value_comp(); } - - //! \} - - public: - //! \name Allocators - //! \{ - - //! Return the base node allocator provided during construction. - allocator_type get_allocator() const { return tree_.get_allocator(); } - - //! \} - - public: - //! \name Fast Destruction of the B+ Tree - //! \{ - - //! Frees all key/data pairs and all nodes of the tree - void clear() { tree_.clear(); } - - //! \} - - public: - //! \name STL Iterator Construction Functions - //! \{ - - //! Constructs a read/data-write iterator that points to the first slot in - //! the first leaf of the B+ tree. - iterator begin() { return tree_.begin(); } - - //! Constructs a read/data-write iterator that points to the first invalid - //! slot in the last leaf of the B+ tree. - iterator end() { return tree_.end(); } - - //! Constructs a read-only constant iterator that points to the first slot - //! in the first leaf of the B+ tree. - const_iterator begin() const { return tree_.begin(); } - - //! Constructs a read-only constant iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. - const_iterator end() const { return tree_.end(); } - - //! Constructs a read/data-write reverse iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. Uses STL magic. - reverse_iterator rbegin() { return tree_.rbegin(); } - - //! Constructs a read/data-write reverse iterator that points to the first - //! slot in the first leaf of the B+ tree. Uses STL magic. - reverse_iterator rend() { return tree_.rend(); } - - //! Constructs a read-only reverse iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. Uses STL magic. - const_reverse_iterator rbegin() const { return tree_.rbegin(); } - - //! Constructs a read-only reverse iterator that points to the first slot - //! in the first leaf of the B+ tree. Uses STL magic. - const_reverse_iterator rend() const { return tree_.rend(); } - - //! \} - - public: - //! \name Access Functions to the Item Count - //! \{ - - //! Return the number of key/data pairs in the B+ tree - size_type size() const { return tree_.size(); } - - //! Returns true if there is at least one key/data pair in the B+ tree - bool empty() const { return tree_.empty(); } - - //! Returns the largest possible size of the B+ Tree. This is just a - //! function required by the STL standard, the B+ Tree can hold more items. - size_type max_size() const { return tree_.max_size(); } - - //! Return a const reference to the current statistics. - const tree_stats &get_stats() const { return tree_.get_stats(); } - - //! \} - - public: - //! \name STL Access Functions Querying the Tree by Descending to a Leaf - //! \{ - - //! Non-STL function checking whether a key is in the B+ tree. The same as - //! (find(k) != end()) or (count() != 0). - bool exists(const key_type &key) const { return tree_.exists(key); } - - //! Tries to locate a key in the B+ tree and returns an iterator to the - //! key/data slot if found. If unsuccessful it returns end(). - iterator find(const key_type &key) { return tree_.find(key); } - - //! Tries to locate a key in the B+ tree and returns an constant iterator to - //! the key/data slot if found. If unsuccessful it returns end(). - const_iterator find(const key_type &key) const { return tree_.find(key); } - - //! Tries to locate a key in the B+ tree and returns the number of identical - //! key entries found. Since this is a unique map, count() returns either 0 - //! or 1. - size_type count(const key_type &key) const { return tree_.count(key); } - - //! Searches the B+ tree and returns an iterator to the first pair equal to - //! or greater than key, or end() if all keys are smaller. - iterator lower_bound(const key_type &key) { return tree_.lower_bound(key); } - - //! Searches the B+ tree and returns a constant iterator to the first pair - //! equal to or greater than key, or end() if all keys are smaller. - const_iterator lower_bound(const key_type &key) const { return tree_.lower_bound(key); } - - //! Searches the B+ tree and returns an iterator to the first pair greater - //! than key, or end() if all keys are smaller or equal. - iterator upper_bound(const key_type &key) { return tree_.upper_bound(key); } - - //! Searches the B+ tree and returns a constant iterator to the first pair - //! greater than key, or end() if all keys are smaller or equal. - const_iterator upper_bound(const key_type &key) const { return tree_.upper_bound(key); } - - //! Searches the B+ tree and returns both lower_bound() and upper_bound(). - std::pair equal_range(const key_type &key) { return tree_.equal_range(key); } - - //! Searches the B+ tree and returns both lower_bound() and upper_bound(). - std::pair equal_range(const key_type &key) const { return tree_.equal_range(key); } - - //! \} - - public: - //! \name B+ Tree Object Comparison Functions - //! \{ - - //! Equality relation of B+ trees of the same type. B+ trees of the same - //! size and equal elements (both key and data) are considered equal. - bool operator==(const btree_map &other) const { return (tree_ == other.tree_); } - - //! Inequality relation. Based on operator==. - bool operator!=(const btree_map &other) const { return (tree_ != other.tree_); } - - //! Total ordering relation of B+ trees of the same type. It uses - //! std::lexicographical_compare() for the actual comparison of elements. - bool operator<(const btree_map &other) const { return (tree_ < other.tree_); } - - //! Greater relation. Based on operator<. - bool operator>(const btree_map &other) const { return (tree_ > other.tree_); } - - //! Less-equal relation. Based on operator<. - bool operator<=(const btree_map &other) const { return (tree_ <= other.tree_); } - - //! Greater-equal relation. Based on operator<. - bool operator>=(const btree_map &other) const { return (tree_ >= other.tree_); } - - //! \} - - public: - //! \name Fast Copy: Assign Operator and Copy Constructors - //! \{ - - //! Assignment operator. All the key/data pairs are copied - btree_map &operator=(const btree_map &other) { - if (this != &other) tree_ = other.tree_; - return *this; - } - - //! Copy constructor. The newly initialized B+ tree object will contain a - //! copy of all key/data pairs. - btree_map(const btree_map &other) : tree_(other.tree_) {} - - //! \} - - public: - //! \name Public Insertion Functions - //! \{ - - //! Attempt to insert a key/data pair into the B+ tree. Fails if the pair is - //! already present. - std::pair insert(const value_type &x) { return tree_.insert(x); } - - //! Attempt to insert a key/data pair into the B+ tree. This function is the - //! same as the other insert. Fails if the inserted pair is already present. - std::pair insert2(const key_type &key, const data_type &data) { - return tree_.insert(value_type(key, data)); - } - - //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is - //! currently ignored by the B+ tree insertion routine. - iterator insert(iterator hint, const value_type &x) { return tree_.insert(hint, x); } - - //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is - //! currently ignored by the B+ tree insertion routine. - iterator insert2(iterator hint, const key_type &key, const data_type &data) { - return tree_.insert(hint, value_type(key, data)); - } - - //! Returns a reference to the object that is associated with a particular - //! key. If the map does not already contain such an object, operator[] - //! inserts the default object data_type(). - data_type &operator[](const key_type &key) { - iterator i = insert(value_type(key, data_type())).first; - return i->second; - } - - //! Attempt to insert the range [first,last) of value_type pairs into the B+ - //! tree. Each key/data pair is inserted individually. - template - void insert(InputIterator first, InputIterator last) { - return tree_.insert(first, last); - } - - //! Bulk load a sorted range [first,last). Loads items into leaves and - //! constructs a B-tree above them. The tree must be empty when calling this - //! function. - template - void bulk_load(Iterator first, Iterator last) { - return tree_.bulk_load(first, last); - } - - //! \} - - public: - //! \name Public Erase Functions - //! \{ - - //! Erases the key/data pairs associated with the given key. For this - //! unique-associative map there is no difference to erase(). - bool erase_one(const key_type &key) { return tree_.erase_one(key); } - - //! Erases all the key/data pairs associated with the given key. This is - //! implemented using erase_one(). - size_type erase(const key_type &key) { return tree_.erase(key); } - - //! Erase the key/data pair referenced by the iterator. - void erase(iterator iter) { return tree_.erase(iter); } - -#ifdef TLX_BTREE_TODO - //! Erase all key/data pairs in the range [first,last). This function is - //! currently not implemented by the B+ Tree. - void erase(iterator /* first */, iterator /* last */) { abort(); } -#endif - - //! \} - -#ifdef TLX_BTREE_DEBUG - - public: - //! \name Debug Printing - //! \{ - - //! Print out the B+ tree structure with keys onto the given ostream. This - //! function requires that the header is compiled with TLX_BTREE_DEBUG and - //! that key_type is printable via std::ostream. - void print(std::ostream &os) const { tree_.print(os); } - - //! Print out only the leaves via the double linked list. - void print_leaves(std::ostream &os) const { tree_.print_leaves(os); } - - //! \} -#endif - - public: - //! \name Verification of B+ Tree Invariants - //! \{ - - //! Run a thorough verification of all B+ tree invariants. The program - //! aborts via TLX_BTREE_ASSERT() if something is wrong. - void verify() const { tree_.verify(); } - - //! \} -}; - -//! \} - -} // namespace tlx - -#endif // !TLX_CONTAINER_BTREE_MAP_HEADER - -/******************************************************************************/ diff --git a/tests/benchmark/core.hpp b/tests/benchmark/core.hpp deleted file mode 100644 index 55e40bbed..000000000 --- a/tests/benchmark/core.hpp +++ /dev/null @@ -1,267 +0,0 @@ -/******************************************************************************* - * tlx/die/core.hpp - * - * Part of tlx - http://panthema.net/tlx - * - * Copyright (C) 2016-2018 Timo Bingmann - * - * All rights reserved. Published under the Boost Software License, Version 1.0 - ******************************************************************************/ - -#ifndef TLX_DIE_CORE_HEADER -#define TLX_DIE_CORE_HEADER - -#include -#include -#include -#include -#include - -namespace tlx { - -/******************************************************************************/ -// die macros - -//! die with message - either throw an exception or die via std::terminate() -void die_with_message(const std::string &msg); - -//! die with message - either throw an exception or die via std::terminate() -void die_with_message(const char *msg, const char *file, size_t line); - -//! die with message - either throw an exception or die via std::terminate() -void die_with_message(const std::string &msg, const char *file, size_t line); - -//! Instead of std::terminate(), throw the output the message via an exception. -#define tlx_die_with_sstream(msg) \ - do { \ - std::ostringstream oss__; \ - oss__ << msg << " @ " << __FILE__ << ':' << __LINE__; \ - ::tlx::die_with_message(oss__.str()); \ - std::terminate(); /* tell compiler this never returns */ \ - } while (false) - -//! Instead of std::terminate(), throw the output the message via an exception. -#define tlx_die(msg) \ - do { \ - tlx_die_with_sstream("DIE: " << msg); \ - } while (false) - -//! Exception thrown by die_with_message() if -class DieException : public std::runtime_error { - public: - explicit DieException(const std::string &message); -}; - -//! Switch between dying via std::terminate() and throwing an exception. -//! Alternatively define the macro TLX_DIE_WITH_EXCEPTION=1 -bool set_die_with_exception(bool b); - -/******************************************************************************/ -// die_unless() and die_if() - -//! Check condition X and die miserably if false. Same as assert() except this -//! is also active in Release mode. -#define tlx_die_unless(X) \ - do { \ - if (!(X)) { \ - ::tlx::die_with_message("DIE: Assertion \"" #X "\" failed!", __FILE__, __LINE__); \ - } \ - } while (false) - -//! Check condition X and die miserably if true. Opposite of assert() except -//! this is also active in Release mode. -#define tlx_die_if(X) \ - do { \ - if (X) { \ - ::tlx::die_with_message("DIE: Assertion \"" #X "\" succeeded!", __FILE__, __LINE__); \ - } \ - } while (false) - -//! Check condition X and die miserably if false. Same as tlx_die_unless() -//! except the user additionally passes a message. -#define tlx_die_verbose_unless(X, msg) \ - do { \ - if (!(X)) { \ - tlx_die_with_sstream("DIE: Assertion \"" #X "\" failed!\n" << msg << '\n'); \ - } \ - } while (false) - -//! Check condition X and die miserably if false. Same as tlx_die_if() -//! except the user additionally passes a message. -#define tlx_die_verbose_if(X, msg) \ - do { \ - if ((X)) { \ - tlx_die_with_sstream("DIE: Assertion \"" #X "\" succeeded!\n" << msg << '\n'); \ - } \ - } while (false) - -/******************************************************************************/ -// die_unequal() - -//! helper method to compare two values in die_unequal() -template -inline bool die_equal_compare(TypeA a, TypeB b) { - return a == b; -} - -template <> -inline bool die_equal_compare(const char *a, const char *b) { - // compare string contents - return std::strcmp(a, b) == 0; -} - -template <> -inline bool die_equal_compare(float a, float b) { - // special case for NAN - return a != a ? b != b : a == b; -} - -template <> -inline bool die_equal_compare(double a, double b) { - // special case for NAN - return a != a ? b != b : a == b; -} - -//! Check that X == Y or die miserably, but output the values of X and Y for -//! better debugging. -#define tlx_die_unequal(X, Y) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (!::tlx::die_equal_compare(x__, y__)) \ - tlx_die_with_sstream("DIE-UNEQUAL: " #X " != " #Y \ - " : " \ - "\"" \ - << x__ << "\" != \"" << y__ << "\""); \ - } while (false) - -//! Check that X == Y or die miserably, but output the values of X and Y for -//! better debugging. Only active if NDEBUG is not defined. -#ifdef NDEBUG -#define tlx_assert_equal(X, Y) -#else -#define tlx_assert_equal(X, Y) die_unequal(X, Y) -#endif - -//! Check that X == Y or die miserably, but output the values of X and Y for -//! better debugging. Same as tlx_die_unequal() except the user additionally -//! pass a message. -#define tlx_die_verbose_unequal(X, Y, msg) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (!::tlx::die_equal_compare(x__, y__)) \ - tlx_die_with_sstream("DIE-UNEQUAL: " #X " != " #Y \ - " : " \ - "\"" \ - << x__ << "\" != \"" << y__ << "\"\n" \ - << msg << '\n'); \ - } while (false) - -/******************************************************************************/ -// die_unequal_eps() - -//! simple replacement for std::abs -template -inline Type die_unequal_eps_abs(const Type &t) { - return t < 0 ? -t : t; -} - -//! helper method to compare two values in die_unequal_eps() -template -inline bool die_equal_eps_compare(TypeA x, TypeB y, double eps) { - // special case for NAN - return x != x ? y != y : die_unequal_eps_abs(x - y) <= eps; -} - -//! Check that ABS(X - Y) <= eps or die miserably, but output the values of X -//! and Y for better debugging. -#define tlx_die_unequal_eps(X, Y, eps) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (!::tlx::die_equal_eps_compare(x__, y__, eps)) \ - tlx_die("DIE-UNEQUAL-EPS: " #X " != " #Y " : " << std::setprecision(18) << "\"" << x__ << "\" != \"" << y__ \ - << "\""); \ - } while (false) - -//! Check that ABS(X - Y) <= eps or die miserably, but output the values of X -//! and Y for better debugging. Same as tlx_die_unequal_eps() except the user -//! additionally passes a message. -#define tlx_die_verbose_unequal_eps(X, Y, eps, msg) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (!::tlx::die_equal_eps_compare(x__, y__, eps)) \ - tlx_die("DIE-UNEQUAL-EPS: " #X " != " #Y " : " << std::setprecision(18) << "\"" << x__ << "\" != \"" << y__ \ - << "\"\n" \ - << msg << '\n'); \ - } while (false) - -//! Check that ABS(X - Y) <= 0.000001 or die miserably, but output the values of -//! X and Y for better debugging. -#define tlx_die_unequal_eps6(X, Y) die_unequal_eps(X, Y, 1e-6) - -//! Check that ABS(X - Y) <= 0.000001 or die miserably, but output the values of -//! X and Y for better debugging. Same as tlx_die_unequal_eps6() except the user -//! additionally passes a message. -#define tlx_die_verbose_unequal_eps6(X, Y, msg) die_verbose_unequal_eps(X, Y, 1e-6, msg) - -/******************************************************************************/ -// die_equal() - -//! Die miserably if X == Y, but first output the values of X and Y for better -//! debugging. -#define tlx_die_equal(X, Y) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (::tlx::die_equal_compare(x__, y__)) \ - tlx_die_with_sstream("DIE-EQUAL: " #X " == " #Y \ - " : " \ - "\"" \ - << x__ << "\" == \"" << y__ << "\""); \ - } while (false) - -//! Die miserably if X == Y, but first output the values of X and Y for better -//! debugging. Only active if NDEBUG is not defined. -#ifdef NDEBUG -#define tlx_assert_unequal(X, Y) -#else -#define tlx_assert_unequal(X, Y) die_equal(X, Y) -#endif - -//! Die miserably if X == Y, but first output the values of X and Y for better -//! debugging. Same as tlx_die_equal() except the user additionally passes a -//! message. -#define tlx_die_verbose_equal(X, Y, msg) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (::tlx::die_equal_compare(x__, y__)) \ - tlx_die_with_sstream("DIE-EQUAL: " #X " == " #Y \ - " : " \ - "\"" \ - << x__ << "\" == \"" << y__ << "\"\n" \ - << msg << '\n'); \ - } while (false) - -/******************************************************************************/ -// die_unless_throws() - -//! Define to check that [code] throws and exception of given type -#define tlx_die_unless_throws(code, exception_type) \ - do { \ - try { \ - code; \ - } catch (const exception_type &) { \ - break; \ - } \ - ::tlx::die_with_message("DIE-UNLESS-THROWS: " #code " - NO EXCEPTION " #exception_type, __FILE__, __LINE__); \ - } while (false) - -} // namespace tlx - -#endif // !TLX_DIE_CORE_HEADER - -/******************************************************************************/ diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp index fb9053c67..d6426dcfe 100644 --- a/tests/benchmark/data_structures_common.hpp +++ b/tests/benchmark/data_structures_common.hpp @@ -15,7 +15,6 @@ #include #include -#include "btree_map.hpp" #include "coordinator/hybrid_logical_clock.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" @@ -30,7 +29,6 @@ inline void PrepareData(utils::SkipList &skip_list, const int64_t num_element coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { auto acc = skip_list.access(); acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); @@ -55,22 +53,9 @@ inline void PrepareData(std::set &std_set, const int64_t num_elements) { coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { std_set.insert(std::vector{storage::v3::PropertyValue{i}}); } } -template -inline void PrepareData(tlx::btree_map &bpp_tree, const int64_t num_elements) { - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < num_elements; ++i) { - bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, - storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); - } -} } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp index 2950aeed3..47af24a1b 100644 --- a/tests/benchmark/data_structures_contains.cpp +++ b/tests/benchmark/data_structures_contains.cpp @@ -23,7 +23,6 @@ #include #include -#include "btree_map.hpp" #include "data_structures_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" @@ -79,10 +78,7 @@ static void BM_BenchmarkContainsStdMap(::benchmark::State &state) { static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { std::set std_set; PrepareData(std_set, state.range(0)); - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); @@ -98,33 +94,12 @@ static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { - tlx::btree_map bpp_tree; - PrepareData(bpp_tree, state.range(0)); - - // So we can also have elements that does don't exists - std::mt19937 i_generator(std::random_device{}()); - std::uniform_int_distribution i_distribution(0, state.range(0) * 2); - int64_t found_elems{0}; - for (auto _ : state) { - for (auto i{0}; i < state.range(0); ++i) { - int64_t value = i_distribution(i_generator); - if (bpp_tree.count(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) > 0) { - found_elems++; - } - } - } - state.SetItemsProcessed(found_elems); -} - BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); - } // namespace memgraph::benchmark BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index f1f1955c4..d51771be5 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -23,7 +23,6 @@ #include #include -#include "btree_map.hpp" #include "data_structures_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" @@ -78,10 +77,6 @@ static void BM_BenchmarkFindStdMap(::benchmark::State &state) { static void BM_BenchmarkFindStdSet(::benchmark::State &state) { std::set std_set; PrepareData(std_set, state.range(0)); - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -98,32 +93,12 @@ static void BM_BenchmarkFindStdSet(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -static void BM_BenchmarkFindBppTree(::benchmark::State &state) { - tlx::btree_map bpp_tree; - PrepareData(bpp_tree, state.range(0)); - // So we can also have elements that does don't exist - std::mt19937 i_generator(std::random_device{}()); - std::uniform_int_distribution i_distribution(0, state.range(0) * 2); - int64_t found_elems{0}; - for (auto _ : state) { - for (auto i{0}; i < state.range(0); ++i) { - int64_t value = i_distribution(i_generator); - if (bpp_tree.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != bpp_tree.end()) { - found_elems++; - } - } - } - state.SetItemsProcessed(found_elems); -} - BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); - } // namespace memgraph::benchmark BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 1e5361d62..6700eaa93 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -23,7 +23,6 @@ #include #include -#include "btree_map.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" @@ -82,30 +81,12 @@ static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { } } -static void BM_BenchmarkInsertBppTree(::benchmark::State &state) { - tlx::btree_map bpp_tree; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - - for (auto _ : state) { - for (auto i{0}; i < state.range(0); ++i) { - bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, - storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); - } - } -} - BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); - } // namespace memgraph::benchmark BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index 17dbd1dec..0d7baaccf 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -23,7 +23,6 @@ #include #include -#include "btree_map.hpp" #include "data_structures_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" @@ -41,10 +40,6 @@ namespace memgraph::benchmark { static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -84,10 +79,6 @@ static void BM_BenchmarkRemoveStdMap(::benchmark::State &state) { static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { std::set std_set; PrepareData(std_set, state.range(0)); - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -104,33 +95,12 @@ static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { state.SetItemsProcessed(removed_elems); } -static void BM_BenchmarkRemoveBppTree(::benchmark::State &state) { - tlx::btree_map bpp_tree; - PrepareData(bpp_tree, state.range(0)); - - // So we can also have elements that does don't exist - std::mt19937 i_generator(std::random_device{}()); - std::uniform_int_distribution i_distribution(0, state.range(0) * 2); - int64_t removed_elems{0}; - for (auto _ : state) { - for (auto i{0}; i < state.range(0); ++i) { - int64_t value = i_distribution(i_generator); - if (bpp_tree.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) { - removed_elems++; - } - } - } - state.SetItemsProcessed(removed_elems); -} - BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); - } // namespace memgraph::benchmark BENCHMARK_MAIN(); From 1b97bb085678e8be8e9a122b006e13c6ba5bdc87 Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 14:57:53 +0100 Subject: [PATCH 27/93] Remove redundant line from skiplist --- src/utils/skip_list.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/skip_list.hpp b/src/utils/skip_list.hpp index 7d4897884..486aade57 100644 --- a/src/utils/skip_list.hpp +++ b/src/utils/skip_list.hpp @@ -621,8 +621,6 @@ class SkipList final { TNode *node_; }; - using iterator = Iterator; - class Accessor final { private: friend class SkipList; From 9faa206f95b582674baf9ec4dbbccf944c2ea62e Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 15:03:03 +0100 Subject: [PATCH 28/93] Clang tidy --- src/query/v2/multiframe.cpp | 33 ++++++++++++++------------------- src/query/v2/multiframe.hpp | 10 +++++----- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 022e0725d..5c39390c9 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -37,13 +37,12 @@ MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_f [&default_frame = default_frame_](const auto &other_frame) { if (other_frame.IsValid()) { return other_frame; - } else { - return default_frame; } + return default_frame; }); } -MultiFrame::MultiFrame(MultiFrame &&other) noexcept : default_frame_(std::move(other.default_frame_)) { +MultiFrame::MultiFrame(MultiFrame &&other) : default_frame_(std::move(other.default_frame_)) { /* TODO Do we just copy all frames or do we make distinctions between valid and not valid frames? Does it make any @@ -54,9 +53,8 @@ MultiFrame::MultiFrame(MultiFrame &&other) noexcept : default_frame_(std::move(o std::back_inserter(frames_), [&default_frame = default_frame_](const auto &other_frame) { if (other_frame.IsValid()) { return other_frame; - } else { - return default_frame; } + return default_frame; }); } @@ -68,38 +66,35 @@ bool MultiFrame::HasValidFrame() const noexcept { return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return frame.IsValid(); }); } -void MultiFrame::DefragmentValidFrames() noexcept { +void MultiFrame::DefragmentValidFrames() { /* from: https://en.cppreference.com/w/cpp/algorithm/remove "Removing is done by shifting (by means of copy assignment (until C++11)move assignment (since C++11)) the elements in the range in such a way that the elements that are not to be removed appear in the beginning of the range. Relative order of the elements that remain is preserved and the physical size of the container is unchanged." */ - std::remove_if(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); + [[maybe_unused]] const auto it = + std::remove_if(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); } -ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader(*this); } +ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader{*this}; } -ValidFramesModifier MultiFrame::GetValidFramesModifier() { return ValidFramesModifier(*this); } +ValidFramesModifier MultiFrame::GetValidFramesModifier() { return ValidFramesModifier{*this}; } -ValidFramesConsumer MultiFrame::GetValidFramesConsumer() { return ValidFramesConsumer(*this); } +ValidFramesConsumer MultiFrame::GetValidFramesConsumer() { return ValidFramesConsumer{*this}; } -InvalidFramesPopulator MultiFrame::GetInvalidFramesPopulator() { return InvalidFramesPopulator(*this); } +InvalidFramesPopulator MultiFrame::GetInvalidFramesPopulator() { return InvalidFramesPopulator{*this}; } ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multiframe) {} -ValidFramesReader::~ValidFramesReader() = default; - -ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator(&multiframe_.frames_[0], *this); } +ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {} -ValidFramesModifier::~ValidFramesModifier() = default; - -ValidFramesModifier::Iterator ValidFramesModifier::begin() { return Iterator(&multiframe_.frames_[0], *this); } +ValidFramesModifier::Iterator ValidFramesModifier::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesModifier::Iterator ValidFramesModifier::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); } @@ -112,7 +107,7 @@ ValidFramesConsumer::~ValidFramesConsumer() { multiframe_.DefragmentValidFrames(); } -ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { return Iterator(&multiframe_.frames_[0], *this); } +ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesConsumer::Iterator ValidFramesConsumer::end() { return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); @@ -133,7 +128,7 @@ InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() { } InvalidFramesPopulator::Iterator InvalidFramesPopulator::end() { - return Iterator(&multiframe_.frames_[multiframe_.frames_.size()]); + return Iterator{&multiframe_.frames_[multiframe_.frames_.size()]}; } } // namespace memgraph::query::v2 diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 64c41f43d..3fe95888a 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -33,8 +33,8 @@ class MultiFrame { MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); ~MultiFrame(); - MultiFrame(const MultiFrame &other); // copy constructor - MultiFrame(MultiFrame &&other) noexcept; // move constructor + MultiFrame(const MultiFrame &other); // copy constructor + MultiFrame(MultiFrame &&other); // move constructor MultiFrame &operator=(const MultiFrame &other) = delete; MultiFrame &operator=(MultiFrame &&other) noexcept = delete; @@ -79,7 +79,7 @@ class MultiFrame { inline utils::MemoryResource *GetMemoryResource() { return frames_[0].GetMemoryResource(); } private: - void DefragmentValidFrames() noexcept; + void DefragmentValidFrames(); FrameWithValidity default_frame_; utils::pmr::vector frames_ = @@ -90,7 +90,7 @@ class ValidFramesReader { public: ValidFramesReader(MultiFrame &multiframe); - ~ValidFramesReader(); + ~ValidFramesReader() = default; ValidFramesReader(const ValidFramesReader &other) = delete; // copy constructor ValidFramesReader(ValidFramesReader &&other) noexcept = delete; // move constructor ValidFramesReader &operator=(const ValidFramesReader &other) = delete; // copy assignment @@ -137,7 +137,7 @@ class ValidFramesModifier { public: ValidFramesModifier(MultiFrame &multiframe); - ~ValidFramesModifier(); + ~ValidFramesModifier() = default; ValidFramesModifier(const ValidFramesModifier &other) = delete; // copy constructor ValidFramesModifier(ValidFramesModifier &&other) noexcept = delete; // move constructor ValidFramesModifier &operator=(const ValidFramesModifier &other) = delete; // copy assignment From cc3bcf1dc2337ef869977f032f1449cf45e63656 Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 15:43:24 +0100 Subject: [PATCH 29/93] Clang tidy --- src/query/v2/multiframe.cpp | 14 +++++--------- src/query/v2/multiframe.hpp | 18 +++++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 5c39390c9..dba5d3ee5 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -24,8 +24,6 @@ MultiFrame::MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, MG_ASSERT(!default_frame.IsValid()); } -MultiFrame::~MultiFrame() = default; - MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_frame_) { /* TODO @@ -42,7 +40,7 @@ MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_f }); } -MultiFrame::MultiFrame(MultiFrame &&other) : default_frame_(std::move(other.default_frame_)) { +MultiFrame::MultiFrame(MultiFrame &&other) noexcept : default_frame_(std::move(other.default_frame_)) { /* TODO Do we just copy all frames or do we make distinctions between valid and not valid frames? Does it make any @@ -89,14 +87,14 @@ ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multi ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesReader::Iterator ValidFramesReader::end() { - return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); + return Iterator{&multiframe_.frames_[multiframe_.frames_.size()], *this}; } ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {} ValidFramesModifier::Iterator ValidFramesModifier::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesModifier::Iterator ValidFramesModifier::end() { - return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); + return Iterator{&multiframe_.frames_[multiframe_.frames_.size()], *this}; } ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(multiframe) {} @@ -110,17 +108,15 @@ ValidFramesConsumer::~ValidFramesConsumer() { ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesConsumer::Iterator ValidFramesConsumer::end() { - return Iterator(&multiframe_.frames_[multiframe_.frames_.size()], *this); + return Iterator{&multiframe_.frames_[multiframe_.frames_.size()], *this}; } InvalidFramesPopulator::InvalidFramesPopulator(MultiFrame &multiframe) : multiframe_(multiframe) {} -InvalidFramesPopulator::~InvalidFramesPopulator() = default; - InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() { for (auto idx = 0UL; idx < multiframe_.frames_.size(); ++idx) { if (!multiframe_.frames_[idx].IsValid()) { - return Iterator(&multiframe_.frames_[idx]); + return Iterator{&multiframe_.frames_[idx]}; } } diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 3fe95888a..9ed707002 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -31,10 +31,10 @@ class MultiFrame { friend class InvalidFramesPopulator; MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); - ~MultiFrame(); + ~MultiFrame() = default; - MultiFrame(const MultiFrame &other); // copy constructor - MultiFrame(MultiFrame &&other); // move constructor + MultiFrame(const MultiFrame &other); // copy constructor + MultiFrame(MultiFrame &&other) noexcept; // move constructor MultiFrame &operator=(const MultiFrame &other) = delete; MultiFrame &operator=(MultiFrame &&other) noexcept = delete; @@ -88,7 +88,7 @@ class MultiFrame { class ValidFramesReader { public: - ValidFramesReader(MultiFrame &multiframe); + explicit ValidFramesReader(MultiFrame &multiframe); ~ValidFramesReader() = default; ValidFramesReader(const ValidFramesReader &other) = delete; // copy constructor @@ -135,7 +135,7 @@ class ValidFramesReader { class ValidFramesModifier { public: - ValidFramesModifier(MultiFrame &multiframe); + explicit ValidFramesModifier(MultiFrame &multiframe); ~ValidFramesModifier() = default; ValidFramesModifier(const ValidFramesModifier &other) = delete; // copy constructor @@ -183,7 +183,7 @@ class ValidFramesModifier { class ValidFramesConsumer { public: - ValidFramesConsumer(MultiFrame &multiframe); + explicit ValidFramesConsumer(MultiFrame &multiframe); ~ValidFramesConsumer(); ValidFramesConsumer(const ValidFramesConsumer &other) = delete; // copy constructor @@ -231,8 +231,8 @@ class ValidFramesConsumer { class InvalidFramesPopulator { public: - InvalidFramesPopulator(MultiFrame &multiframe); - ~InvalidFramesPopulator(); + explicit InvalidFramesPopulator(MultiFrame &multiframe); + ~InvalidFramesPopulator() = default; InvalidFramesPopulator(const InvalidFramesPopulator &other) = delete; // copy constructor InvalidFramesPopulator(InvalidFramesPopulator &&other) noexcept = delete; // move constructor @@ -247,7 +247,7 @@ class InvalidFramesPopulator { using reference = FrameWithValidity &; using internal_ptr = FrameWithValidity *; - Iterator(internal_ptr ptr) : ptr_(ptr) {} + explicit Iterator(internal_ptr ptr) : ptr_(ptr) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } From f107ef8aea4f8dabd210a494b378baf842d48c3f Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 16:12:50 +0100 Subject: [PATCH 30/93] Default destructor in header --- src/query/v2/multiframe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 9ed707002..e26b138c8 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -185,7 +185,7 @@ class ValidFramesConsumer { public: explicit ValidFramesConsumer(MultiFrame &multiframe); - ~ValidFramesConsumer(); + ~ValidFramesConsumer() = default; ValidFramesConsumer(const ValidFramesConsumer &other) = delete; // copy constructor ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = delete; // move constructor ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; // copy assignment From 00a4127e4e563eac97d4dda487fceac9ebab7dce Mon Sep 17 00:00:00 2001 From: jeremy Date: Tue, 29 Nov 2022 17:15:52 +0100 Subject: [PATCH 31/93] Remove incorrect = default --- src/query/v2/multiframe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index e26b138c8..9ed707002 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -185,7 +185,7 @@ class ValidFramesConsumer { public: explicit ValidFramesConsumer(MultiFrame &multiframe); - ~ValidFramesConsumer() = default; + ~ValidFramesConsumer(); ValidFramesConsumer(const ValidFramesConsumer &other) = delete; // copy constructor ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = delete; // move constructor ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; // copy assignment From 9c0c0a2d1cf134197bebb5af25b767d88f498035 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 10:14:07 +0100 Subject: [PATCH 32/93] Add clang warning suppress --- src/query/v2/multiframe.cpp | 21 +++++---------------- src/query/v2/multiframe.hpp | 2 +- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index dba5d3ee5..fea74d141 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -40,21 +40,9 @@ MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_f }); } -MultiFrame::MultiFrame(MultiFrame &&other) noexcept : default_frame_(std::move(other.default_frame_)) { - /* - TODO - Do we just copy all frames or do we make distinctions between valid and not valid frames? Does it make any - difference? - */ - frames_.reserve(other.frames_.size()); - std::transform(make_move_iterator(other.frames_.begin()), make_move_iterator(other.frames_.end()), - std::back_inserter(frames_), [&default_frame = default_frame_](const auto &other_frame) { - if (other_frame.IsValid()) { - return other_frame; - } - return default_frame; - }); -} +// NOLINTNEXTLINE (bugprone-exception-escape) +MultiFrame::MultiFrame(MultiFrame &&other) noexcept + : default_frame_(std::move(other.default_frame_)), frames_(std::move(other.frames_)) {} void MultiFrame::ResetAllFramesInvalid() noexcept { std::for_each(frames_.begin(), frames_.end(), [](auto &frame) { frame.MakeInvalid(); }); @@ -64,7 +52,8 @@ bool MultiFrame::HasValidFrame() const noexcept { return std::any_of(frames_.begin(), frames_.end(), [](auto &frame) { return frame.IsValid(); }); } -void MultiFrame::DefragmentValidFrames() { +// NOLINTNEXTLINE (bugprone-exception-escape) +void MultiFrame::DefragmentValidFrames() noexcept { /* from: https://en.cppreference.com/w/cpp/algorithm/remove "Removing is done by shifting (by means of copy assignment (until C++11)move assignment (since C++11)) the elements diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 9ed707002..e7c5aa8cd 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -79,7 +79,7 @@ class MultiFrame { inline utils::MemoryResource *GetMemoryResource() { return frames_[0].GetMemoryResource(); } private: - void DefragmentValidFrames(); + void DefragmentValidFrames() noexcept; FrameWithValidity default_frame_; utils::pmr::vector frames_ = From a10c254caaad47c831320a72f9a2a6720ea6407e Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 11:43:16 +0100 Subject: [PATCH 33/93] Add // NOLINTNEXTLINE to correct place --- src/query/v2/multiframe.cpp | 3 ++- src/query/v2/multiframe.hpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index fea74d141..4573beaea 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -88,7 +88,8 @@ ValidFramesModifier::Iterator ValidFramesModifier::end() { ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(multiframe) {} -ValidFramesConsumer::~ValidFramesConsumer() { +// NOLINTNEXTLINE (bugprone-exception-escape) +ValidFramesConsumer::~ValidFramesConsumer() noexcept { // TODO Possible optimisation: only DefragmentValidFrames if one frame has been invalidated? Only if does not // cost too much to store it multiframe_.DefragmentValidFrames(); diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index e7c5aa8cd..7c59300de 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -79,6 +79,7 @@ class MultiFrame { inline utils::MemoryResource *GetMemoryResource() { return frames_[0].GetMemoryResource(); } private: + // NOLINTNEXTLINE (bugprone-exception-escape) void DefragmentValidFrames() noexcept; FrameWithValidity default_frame_; @@ -185,7 +186,7 @@ class ValidFramesConsumer { public: explicit ValidFramesConsumer(MultiFrame &multiframe); - ~ValidFramesConsumer(); + ~ValidFramesConsumer() noexcept; ValidFramesConsumer(const ValidFramesConsumer &other) = delete; // copy constructor ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = delete; // move constructor ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; // copy assignment From 9621532d3d1993b07ee6a6de77d66a48440fb4c0 Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Wed, 30 Nov 2022 14:59:00 +0200 Subject: [PATCH 34/93] Prototype suggested changes and polish PR --- src/query/v2/requests.hpp | 22 +-- src/storage/v3/request_helper.cpp | 145 ++++++++--------- src/storage/v3/request_helper.hpp | 72 +++++++-- src/storage/v3/shard_rsm.cpp | 251 +++++++++++++++++++----------- tests/simulation/shard_rsm.cpp | 145 +++++++++-------- 5 files changed, 360 insertions(+), 275 deletions(-) diff --git a/src/query/v2/requests.hpp b/src/query/v2/requests.hpp index c751b16ec..9ff9a1bae 100644 --- a/src/query/v2/requests.hpp +++ b/src/query/v2/requests.hpp @@ -368,16 +368,12 @@ struct ScanVerticesResponse { std::vector results; }; -struct VertexAndEdgeId { - VertexId vertex; - std::optional edge; -}; - struct GetPropertiesRequest { Hlc transaction_id; - std::vector vertices_and_edges; + std::vector vertex_ids; + std::vector> vertices_and_edges; - std::vector property_ids; + std::optional> property_ids; std::vector expressions; std::vector order_by; @@ -388,22 +384,16 @@ struct GetPropertiesRequest { std::optional filter; }; -struct PropIdValue { - std::vector ids; - std::vector properties; -}; - struct GetPropertiesResultRow { - VertexAndEdgeId vertex_and_edge; + VertexId vertex; + std::optional edge; - PropIdValue properies_and_ids; + std::vector> props; std::vector evaluated_expressions; }; struct GetPropertiesResponse { std::vector result_row; - enum RequestResult : uint16_t { OUT_OF_SHARD_RANGE, SUCCESS, FAILURE }; - RequestResult result; std::optional error; }; diff --git a/src/storage/v3/request_helper.cpp b/src/storage/v3/request_helper.cpp index 7d367d850..260147a25 100644 --- a/src/storage/v3/request_helper.cpp +++ b/src/storage/v3/request_helper.cpp @@ -14,6 +14,7 @@ #include #include +#include "pretty_print_ast_to_original_expression.hpp" #include "storage/v3/bindings/db_accessor.hpp" #include "storage/v3/bindings/pretty_print_ast_to_original_expression.hpp" #include "storage/v3/expr.hpp" @@ -221,30 +222,39 @@ std::vector EvaluateVertexExpressions(DbAccessor &dba, const VertexA return evaluated_expressions; } +std::vector EvaluateEdgeExpressions(DbAccessor &dba, const VertexAccessor &v_acc, const EdgeAccessor &e_acc, + const std::vector &expressions) { + std::vector evaluated_expressions; + evaluated_expressions.reserve(expressions.size()); + + std::transform(expressions.begin(), expressions.end(), std::back_inserter(evaluated_expressions), + [&dba, &v_acc, &e_acc](const auto &expression) { + return ComputeExpression(dba, v_acc, e_acc, expression, expr::identifier_node_symbol, + expr::identifier_edge_symbol); + }); + + return evaluated_expressions; +} + ShardResult> CollectAllPropertiesFromAccessor(const VertexAccessor &acc, View view, const Schemas::Schema &schema) { - std::map ret; - auto props = acc.Properties(view); - if (props.HasError()) { - spdlog::debug("Encountered an error while trying to get vertex properties."); - return props.GetError(); + auto ret = impl::CollectAllPropertiesImpl(acc, view); + if (ret.HasError()) { + return ret.GetError(); } - auto &properties = props.GetValue(); - std::transform(properties.begin(), properties.end(), std::inserter(ret, ret.begin()), - [](std::pair &pair) { - return std::make_pair(pair.first, FromPropertyValueToValue(std::move(pair.second))); - }); - properties.clear(); - auto pks = PrimaryKeysFromAccessor(acc, view, schema); if (pks) { - ret.merge(*pks); + ret.GetValue().merge(*pks); } return ret; } +ShardResult> CollectAllPropertiesFromAccessor(const VertexAccessor &acc, View view) { + return impl::CollectAllPropertiesImpl(acc, view); +} + EdgeUniquenessFunction InitializeEdgeUniquenessFunction(bool only_unique_neighbor_rows) { // Functions to select connecting edges based on uniquness EdgeUniquenessFunction maybe_filter_based_on_edge_uniquness; @@ -351,15 +361,19 @@ EdgeFiller InitializeEdgeFillerFunction(const msgs::ExpandOneRequest &req) { return edge_filler; } -bool FilterOnVertex(DbAccessor &dba, const storage::v3::VertexAccessor &v_acc, const std::vector &filters, - const std::string_view node_name, const std::optional &e_acc) { - return std::ranges::all_of(filters, [&node_name, &dba, &v_acc, &e_acc](const auto &filter_expr) { - TypedValue result; - if (e_acc) { - result = ComputeExpression(dba, v_acc, e_acc, filter_expr, "", node_name); - } else { - result = ComputeExpression(dba, v_acc, e_acc, filter_expr, node_name, ""); - } +bool FilterOnVertex(DbAccessor &dba, const storage::v3::VertexAccessor &v_acc, + const std::vector &filters) { + return std::ranges::all_of(filters, [&dba, &v_acc](const auto &filter_expr) { + const auto result = ComputeExpression(dba, v_acc, std::nullopt, filter_expr, expr::identifier_node_symbol, ""); + return result.IsBool() && result.ValueBool(); + }); +} + +bool FilterOnEdge(DbAccessor &dba, const storage::v3::VertexAccessor &v_acc, const EdgeAccessor &e_acc, + const std::vector &filters) { + return std::ranges::all_of(filters, [&dba, &v_acc, &e_acc](const auto &filter_expr) { + const auto result = + ComputeExpression(dba, v_acc, e_acc, filter_expr, expr::identifier_node_symbol, expr::identifier_edge_symbol); return result.IsBool() && result.ValueBool(); }); } @@ -444,61 +458,6 @@ ShardResult GetExpandOneResult( return result_row; } -std::vector OrderByElements(DbAccessor &dba, std::vector &order_by, - std::vector &&vertices) { - std::vector ordering; - ordering.reserve(order_by.size()); - for (const auto &order : order_by) { - switch (order.direction) { - case memgraph::msgs::OrderingDirection::ASCENDING: { - ordering.push_back(Ordering::ASC); - break; - } - case memgraph::msgs::OrderingDirection::DESCENDING: { - ordering.push_back(Ordering::DESC); - break; - } - } - } - struct PropElement { - std::vector properties_order_by; - VertexAccessor vertex_acc; - GetPropElement *original_element; - }; - - std::vector ordered; - auto compare_typed_values = TypedValueVectorCompare(ordering); - for (auto &vertex : vertices) { - std::vector properties; - properties.reserve(order_by.size()); - const auto *symbol = (vertex.edge_acc) ? expr::identifier_edge_symbol : expr::identifier_node_symbol; - for (const auto &order : order_by) { - TypedValue val; - if (vertex.edge_acc) { - val = ComputeExpression(dba, vertex.vertex_acc, vertex.edge_acc, order.expression.expression, "", symbol); - } else { - val = ComputeExpression(dba, vertex.vertex_acc, vertex.edge_acc, order.expression.expression, symbol, ""); - } - properties.push_back(std::move(val)); - } - - ordered.push_back({std::move(properties), vertex.vertex_acc, &vertex}); - } - - std::sort(ordered.begin(), ordered.end(), [&compare_typed_values](const auto &lhs, const auto &rhs) { - return compare_typed_values(lhs.properties_order_by, rhs.properties_order_by); - }); - - std::vector results_ordered; - results_ordered.reserve(ordered.size()); - - for (auto &elem : ordered) { - results_ordered.push_back(std::move(*elem.original_element)); - } - - return results_ordered; -} - VerticesIterable::Iterator GetStartVertexIterator(VerticesIterable &vertex_iterable, const std::vector &primary_key, const View view) { auto it = vertex_iterable.begin(); @@ -584,4 +543,36 @@ std::vector> OrderByEdges(DbAccessor &dba, std::vector>> OrderByEdges( + DbAccessor &dba, std::vector &iterable, std::vector &order_by_edges, + const std::vector &vertex_acc) { + MG_ASSERT(vertex_acc.size() == iterable.size()); + std::vector ordering; + ordering.reserve(order_by_edges.size()); + std::transform(order_by_edges.begin(), order_by_edges.end(), std::back_inserter(ordering), + [](const auto &order_by) { return ConvertMsgsOrderByToOrdering(order_by.direction); }); + + std::vector>> ordered; + VertexAccessor current = vertex_acc.front(); + size_t id = 0; + for (auto it = iterable.begin(); it != iterable.end(); it++, id++) { + current = vertex_acc[id]; + std::vector properties_order_by; + properties_order_by.reserve(order_by_edges.size()); + std::transform(order_by_edges.begin(), order_by_edges.end(), std::back_inserter(properties_order_by), + [&dba, it, current](const auto &order_by) { + return ComputeExpression(dba, current, *it, order_by.expression.expression, + expr::identifier_node_symbol, expr::identifier_edge_symbol); + }); + + ordered.push_back({std::move(properties_order_by), {current, *it}}); + } + + auto compare_typed_values = TypedValueVectorCompare(ordering); + std::sort(ordered.begin(), ordered.end(), [compare_typed_values](const auto &pair1, const auto &pair2) { + return compare_typed_values(pair1.properties_order_by, pair2.properties_order_by); + }); + return ordered; +} + } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/request_helper.hpp b/src/storage/v3/request_helper.hpp index f14535beb..13871d219 100644 --- a/src/storage/v3/request_helper.hpp +++ b/src/storage/v3/request_helper.hpp @@ -20,6 +20,7 @@ #include "storage/v3/edge_accessor.hpp" #include "storage/v3/expr.hpp" #include "storage/v3/shard.hpp" +#include "storage/v3/value_conversions.hpp" #include "storage/v3/vertex_accessor.hpp" #include "utils/template_utils.hpp" @@ -31,7 +32,7 @@ using EdgeFiller = using msgs::Value; template -concept ObjectAccessor = utils::SameAsAnyOf; +concept ObjectAccessor = utils::SameAsAnyOf>; inline bool TypedValueCompare(const TypedValue &a, const TypedValue &b) { // in ordering null comes after everything else @@ -131,13 +132,6 @@ struct Element { TObjectAccessor object_acc; }; -struct GetPropElement { - std::vector properties_order_by; - std::vector ids; - VertexAccessor vertex_acc; - std::optional edge_acc; -}; - template concept VerticesIt = utils::SameAsAnyOf>; @@ -170,12 +164,16 @@ std::vector> OrderByVertices(DbAccessor &dba, TIterable return ordered; } +template +concept EdgeObjectAccessor = utils::SameAsAnyOf>; + std::vector> OrderByEdges(DbAccessor &dba, std::vector &iterable, std::vector &order_by_edges, const VertexAccessor &vertex_acc); -std::vector OrderByElements(DbAccessor &dba, std::vector &order_bys, - std::vector &&vertices); +std::vector>> OrderByEdges( + DbAccessor &dba, std::vector &iterable, std::vector &order_by_edges, + const std::vector &vertex_acc); VerticesIterable::Iterator GetStartVertexIterator(VerticesIterable &vertex_iterable, const std::vector &primary_key, View view); @@ -187,19 +185,65 @@ std::vector>::const_iterator GetStartOrderedElementsIter std::array, 2> GetEdgesFromVertex(const VertexAccessor &vertex_accessor, msgs::EdgeDirection direction); -bool FilterOnVertex(DbAccessor &dba, const storage::v3::VertexAccessor &v_acc, const std::vector &filters, - const std::string_view node_name, const std::optional &e_acc = std::nullopt); +bool FilterOnVertex(DbAccessor &dba, const storage::v3::VertexAccessor &v_acc, const std::vector &filters); + +bool FilterOnEdge(DbAccessor &dba, const storage::v3::VertexAccessor &v_acc, const EdgeAccessor &e_acc, + const std::vector &filters); std::vector EvaluateVertexExpressions(DbAccessor &dba, const VertexAccessor &v_acc, const std::vector &expressions, std::string_view node_name); -ShardResult> CollectSpecificPropertiesFromAccessor(const VertexAccessor &acc, +std::vector EvaluateEdgeExpressions(DbAccessor &dba, const VertexAccessor &v_acc, const EdgeAccessor &e_acc, + const std::vector &expressions); + +template +concept TAccessor = utils::SameAsAnyOf; + +template +ShardResult> CollectSpecificPropertiesFromAccessor(const TAccessor &acc, const std::vector &props, - View view); + View view) { + std::map ret; + + for (const auto &prop : props) { + auto result = acc.GetProperty(prop, view); + if (result.HasError()) { + spdlog::debug("Encountered an Error while trying to get a vertex property."); + return result.GetError(); + } + auto &value = result.GetValue(); + ret.emplace(std::make_pair(prop, FromPropertyValueToValue(std::move(value)))); + } + + return ret; +} ShardResult> CollectAllPropertiesFromAccessor(const VertexAccessor &acc, View view, const Schemas::Schema &schema); +namespace impl { +template +ShardResult> CollectAllPropertiesImpl(const TAccessor &acc, View view) { + std::map ret; + auto props = acc.Properties(view); + if (props.HasError()) { + spdlog::debug("Encountered an error while trying to get vertex properties."); + return props.GetError(); + } + + auto &properties = props.GetValue(); + std::transform(properties.begin(), properties.end(), std::inserter(ret, ret.begin()), + [](std::pair &pair) { + return std::make_pair(pair.first, conversions::FromPropertyValueToValue(std::move(pair.second))); + }); + return ret; +} +} // namespace impl + +template +ShardResult> CollectAllPropertiesFromAccessor(const TAccessor &acc, View view) { + return impl::CollectAllPropertiesImpl(acc, view); +} EdgeUniquenessFunction InitializeEdgeUniquenessFunction(bool only_unique_neighbor_rows); diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index 3188458ce..b628127af 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -10,6 +10,8 @@ // licenses/APL.txt. #include +#include +#include #include #include #include @@ -19,7 +21,6 @@ #include "common/errors.hpp" #include "parser/opencypher/parser.hpp" -#include "pretty_print_ast_to_original_expression.hpp" #include "query/v2/requests.hpp" #include "storage/v2/vertex.hpp" #include "storage/v2/view.hpp" @@ -330,7 +331,7 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) { std::vector expression_results; if (!req.filter_expressions.empty()) { // NOTE - DbAccessor might get removed in the future. - const bool eval = FilterOnVertex(dba, vertex, req.filter_expressions, expr::identifier_node_symbol); + const bool eval = FilterOnVertex(dba, vertex, req.filter_expressions); if (!eval) { return; } @@ -435,7 +436,7 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ExpandOneRequest &&req) { } if (!req.filters.empty()) { // NOTE - DbAccessor might get removed in the future. - const bool eval = FilterOnVertex(dba, src_vertex_acc_opt.value(), req.filters, expr::identifier_node_symbol); + const bool eval = FilterOnVertex(dba, src_vertex_acc_opt.value(), req.filters); if (!eval) { continue; } @@ -515,124 +516,184 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CommitRequest &&req) { }; msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { - if (req.vertices_and_edges.empty()) { - return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::FAILURE}; + if (!req.vertex_ids.empty() && !req.vertices_and_edges.empty()) { + auto error = CreateErrorResponse( + {common::ErrorCode::VERTEX_HAS_EDGES, std::experimental::source_location::current()}, req.transaction_id, ""); + return msgs::GetPropertiesResponse{.error = {}}; } auto shard_acc = shard_->Access(req.transaction_id); auto dba = DbAccessor{&shard_acc}; const auto view = storage::v3::View::NEW; - auto collect_props = [](const VertexAccessor &acc, const std::vector &props, View view, - const std::optional &e_acc) mutable -> std::optional { - std::vector properties; - std::vector ids; - for (const auto &prop : props) { - ShardResult result{PropertyValue()}; - if (e_acc) { - result = e_acc->GetProperty(prop, view); - } else { - result = acc.GetProperty(prop, view); - } - if (result.HasError() && result.GetError() == common::ErrorCode::NONEXISTENT_OBJECT) { - continue; - } - if (result.HasError()) { - spdlog::debug("Encountered an Error while trying to get a vertex property."); - return std::nullopt; - } - properties.push_back(PropertyToTypedValue(result.GetValue())); - ids.push_back(prop); + auto transform_props = [](std::map &&value) { + std::vector> result; + result.reserve(value.size()); + for (auto &[id, val] : value) { + result.push_back({id, std::move(val)}); } - GetPropElement element{std::move(properties), std::move(ids), acc, e_acc}; - return {std::move(element)}; + return result; }; - auto find_edge = [](const VertexAccessor &v, const EdgeTypeId &e) -> std::optional { - auto in = v.InEdges(view, {e}); + auto collect_props = [&req](const VertexAccessor &v_acc, const std::optional &e_acc) { + if (req.property_ids) { + if (e_acc) { + return CollectAllPropertiesFromAccessor(*e_acc, view); + } + return CollectAllPropertiesFromAccessor(v_acc, view); + } + + if (e_acc) { + return CollectSpecificPropertiesFromAccessor(v_acc, *req.property_ids, view); + } + return CollectSpecificPropertiesFromAccessor(*e_acc, *req.property_ids, view); + }; + + auto find_edge = [](const VertexAccessor &v, msgs::EdgeId e) -> std::optional { + auto in = v.InEdges(view); MG_ASSERT(in.HasValue()); for (auto &edge : in.GetValue()) { - if (edge.EdgeType() == e) { + if (edge.Gid().AsUint() == e.gid) { return edge; } } - - auto out = v.OutEdges(view, {e}); + auto out = v.OutEdges(view); MG_ASSERT(out.HasValue()); for (auto &edge : out.GetValue()) { - if (edge.EdgeType() == e) { + if (edge.Gid().AsUint() == e.gid) { return edge; } } return std::nullopt; }; - std::vector elements; - - for (const auto &[vertex, maybe_edge] : req.vertices_and_edges) { - const auto &[label, pk_v] = vertex; - auto pk = ConvertPropertyVector(pk_v); - auto v_acc = dba.FindVertex(pk, view); - if (!v_acc) { - return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::OUT_OF_SHARD_RANGE}; - } - std::optional e_acc; - if (maybe_edge) { - e_acc = find_edge(*v_acc, *maybe_edge); - if (!e_acc) { - return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::OUT_OF_SHARD_RANGE}; - } - } - - const auto *symbol = (maybe_edge) ? expr::identifier_edge_symbol : expr::identifier_node_symbol; - if (req.filter && !FilterOnVertex(dba, *v_acc, {*req.filter}, symbol, e_acc)) { - continue; - } - - std::optional collected_properties; - collected_properties = collect_props(*v_acc, req.property_ids, view, e_acc); - if (!collected_properties) { - return msgs::GetPropertiesResponse{.result = msgs::GetPropertiesResponse::FAILURE}; - } - if (collected_properties->ids.empty()) { - continue; - } - elements.push_back(std::move(*collected_properties)); - } - - if (!req.order_by.empty()) { - elements = OrderByElements(dba, req.order_by, std::move(elements)); - } - - std::vector results; - results.reserve(elements.size()); - const auto has_expr_to_evaluate = !req.expressions.empty(); - size_t limit = elements.size(); - if (req.limit && *req.limit < elements.size()) { - limit = *req.limit; - } - for (size_t index = 0; index != limit; ++index) { - auto &element = elements.at(index); - const auto id = element.vertex_acc.Id(view).GetValue(); - std::optional e_type = - (element.edge_acc) ? std::make_optional(element.edge_acc->EdgeType()) : std::nullopt; - msgs::VertexId v_id{msgs::Label{id.primary_label}, ConvertValueVector(id.primary_key)}; - results.push_back(msgs::GetPropertiesResultRow{ - .vertex_and_edge = {.vertex = std::move(v_id), .edge = e_type}, - .properies_and_ids = { - .ids = std::move(element.ids), - .properties = ConvertToValueVectorFromTypedValueVector(std::move(element.properties_order_by))}}); - if (has_expr_to_evaluate) { - auto expression_results = ConvertToValueVectorFromTypedValueVector( - EvaluateVertexExpressions(dba, element.vertex_acc, req.expressions, expr::identifier_node_symbol)); - results.back().evaluated_expressions = std::move(expression_results); + auto emplace_result_row = + [dba, transform_props, collect_props, has_expr_to_evaluate, &req]( + const VertexAccessor &v_acc, + const std::optional e_acc) mutable -> ShardResult { + auto maybe_id = v_acc.Id(view); + if (maybe_id.HasError()) { + return {maybe_id.GetError()}; } + const auto &id = maybe_id.GetValue(); + std::optional e_type; + if (e_acc) { + e_type = msgs::EdgeId{e_acc->Gid().AsUint()}; + } + msgs::VertexId v_id{msgs::Label{id.primary_label}, ConvertValueVector(id.primary_key)}; + auto maybe_props = collect_props(v_acc, e_acc); + if (maybe_props.HasError()) { + return {maybe_props.GetError()}; + } + auto props = transform_props(std::move(maybe_props.GetValue())); + auto result = msgs::GetPropertiesResultRow{.vertex = std::move(v_id), .edge = e_type, .props = std::move(props)}; + if (has_expr_to_evaluate) { + std::vector e_results; + if (e_acc) { + e_results = + ConvertToValueVectorFromTypedValueVector(EvaluateEdgeExpressions(dba, v_acc, *e_acc, req.expressions)); + } else { + e_results = ConvertToValueVectorFromTypedValueVector( + EvaluateVertexExpressions(dba, v_acc, req.expressions, expr::identifier_node_symbol)); + } + result.evaluated_expressions = std::move(e_results); + } + return {std::move(result)}; + }; + + auto get_limit = [&req](const auto &elements) { + size_t limit = elements.size(); + if (req.limit && *req.limit < elements.size()) { + limit = *req.limit; + } + return limit; + }; + + auto collect_response = [get_limit, &req](auto &elements, auto result_row_functor) { + msgs::GetPropertiesResponse response; + const auto limit = get_limit(elements); + for (size_t index = 0; index != limit; ++index) { + auto result_row = result_row_functor(elements[index]); + if (result_row.HasError()) { + return msgs::GetPropertiesResponse{.error = CreateErrorResponse(result_row.GetError(), req.transaction_id, "")}; + } + response.result_row.push_back(std::move(result_row.GetValue())); + } + return response; + }; + + std::vector vertices; + std::vector edges; + + auto parse_and_filter = [dba, &vertices](auto &cont, auto projection, auto filter, auto maybe_get_edge) mutable { + for (const auto &elem : cont) { + const auto &[label, pk_v] = projection(elem); + auto pk = ConvertPropertyVector(pk_v); + auto v_acc = dba.FindVertex(pk, view); + if (!v_acc || filter(*v_acc, maybe_get_edge(elem))) { + continue; + } + vertices.push_back({*v_acc}); + } + }; + auto identity = [](auto &elem) { return elem; }; + + auto filter_vertex = [dba, req](const auto &acc, const auto & /*edge*/) mutable { + if (!req.filter) { + return false; + } + return !FilterOnVertex(dba, acc, {*req.filter}); + }; + + auto filter_edge = [dba, &edges, &req, find_edge](const auto &acc, const auto &edge) mutable { + auto e_acc = find_edge(acc, edge); + if (!req.filter || !e_acc || !FilterOnEdge(dba, acc, *e_acc, {*req.filter})) { + return false; + } + edges.push_back(*e_acc); + return true; + }; + + // Handler logic here + if (!req.vertex_ids.empty()) { + parse_and_filter(req.vertex_ids, identity, filter_vertex, identity); + } else { + parse_and_filter( + req.vertices_and_edges, [](auto &e) { return e.first; }, filter_edge, [](auto &e) { return e.second; }); } - return msgs::GetPropertiesResponse{std::move(results), msgs::GetPropertiesResponse::SUCCESS}; + if (!req.vertex_ids.empty()) { + if (!req.order_by.empty()) { + auto elements = OrderByVertices(dba, vertices, req.order_by); + return collect_response(elements, [emplace_result_row](auto &element) mutable { + return emplace_result_row(element.object_acc, std::nullopt); + }); + } + return collect_response(vertices, + [emplace_result_row](auto &acc) mutable { return emplace_result_row(acc, std::nullopt); }); + } + if (!req.order_by.empty()) { + auto elements = OrderByEdges(dba, edges, req.order_by, vertices); + return collect_response(elements, [emplace_result_row](auto &element) mutable { + return emplace_result_row(element.object_acc.first, element.object_acc.second); + }); + } + + struct ZipView { + ZipView(std::vector &v, std::vector &e) : v(v), e(e) {} + size_t size() const { return v.size(); } + auto operator[](size_t index) { return std::make_pair(v[index], e[index]); } + + private: + std::vector &v; + std::vector &e; + }; + + ZipView vertices_and_edges(vertices, edges); + return collect_response(vertices_and_edges, [emplace_result_row](const auto &acc) mutable { + return emplace_result_row(acc.first, acc.second); + }); } -// TODO(kostasrim) Handle edges - } // namespace memgraph::storage::v3 diff --git a/tests/simulation/shard_rsm.cpp b/tests/simulation/shard_rsm.cpp index 224a84090..80a5cd87d 100644 --- a/tests/simulation/shard_rsm.cpp +++ b/tests/simulation/shard_rsm.cpp @@ -482,7 +482,7 @@ std::tuple> AttemptToScanAllWithExpression msgs::GetPropertiesResponse AttemptToGetProperties(ShardClient &client, std::vector properties, std::vector vertices, - std::vector edges, + std::vector edges, std::optional limit = std::nullopt, std::optional filter_prop = std::nullopt, bool edge = false, @@ -508,14 +508,25 @@ msgs::GetPropertiesResponse AttemptToGetProperties(ShardClient &client, std::vec req.limit = limit; } req.expressions = {std::string("5 = 5")}; - std::vector req_v; + std::vector req_v; + std::vector req_e; for (auto &v : vertices) { - req_v.push_back(msgs::VertexAndEdgeId{.vertex = std::move(v)}); + req_v.push_back(std::move(v)); } - for (auto index = 0; index != edges.size(); ++index) { - req_v[index].edge = edges[index]; + for (auto &e : edges) { + req_e.push_back(std::move(e)); + } + + if (!edges.empty()) { + MG_ASSERT(edges.size() == vertices.size()); + size_t id = 0; + req.vertices_and_edges.reserve(req_v.size()); + for (auto &v : req_v) { + req.vertices_and_edges.push_back({std::move(v), std::move(req_e[id++])}); + } + } else { + req.vertex_ids = std::move(req_v); } - req.vertices_and_edges = std::move(req_v); while (true) { auto read_res = client.SendReadRequest(req); @@ -1285,93 +1296,86 @@ void TestGetProperties(ShardClient &client) { { // No properties const auto result = AttemptToGetProperties(client, {}, {v_id, v_id_2}, {}, std::nullopt, unique_prop_val_2); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.empty()); } { // All properties const auto result = AttemptToGetProperties(client, {prop_id_2, prop_id_4, prop_id_5}, {v_id, v_id_2, v_id_3}, {}); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + MG_ASSERT(!result.error); MG_ASSERT(!result.result_row.empty()); MG_ASSERT(result.result_row.size() == 3); for (const auto &elem : result.result_row) { - MG_ASSERT(elem.properies_and_ids.ids.size() == 3); - MG_ASSERT(elem.properies_and_ids.properties.size() == 3); + MG_ASSERT(elem.props.size() == 3); } } { // Two properties from two vertices with a filter on unique_prop_5 const auto result = AttemptToGetProperties(client, {prop_id_2, prop_id_4}, {v_id, v_id_2, v_id_5}, {}, std::nullopt, unique_prop_val_5); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 1); } { // One property from three vertices. const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 3); - MG_ASSERT(result.result_row[0].properies_and_ids.ids.size() == 1); - MG_ASSERT(result.result_row[1].properies_and_ids.ids.size() == 1); - MG_ASSERT(result.result_row[2].properies_and_ids.ids.size() == 1); + MG_ASSERT(result.result_row[0].props.size() == 1); + MG_ASSERT(result.result_row[1].props.size() == 1); + MG_ASSERT(result.result_row[2].props.size() == 1); } { // Same as before but with limit of 1 row const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::make_optional(1)); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 1); } { // Same as before but with a limit greater than the elements returned const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::make_optional(5)); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 3); } { // Order by on `prop1` (descending) const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::nullopt, std::nullopt, false, "prop1"); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 3); - MG_ASSERT(result.result_row[0].vertex_and_edge.vertex == v_id_3); - MG_ASSERT(result.result_row[1].vertex_and_edge.vertex == v_id_2); - MG_ASSERT(result.result_row[2].vertex_and_edge.vertex == v_id); + MG_ASSERT(result.result_row[0].vertex == v_id_3); + MG_ASSERT(result.result_row[1].vertex == v_id_2); + MG_ASSERT(result.result_row[2].vertex == v_id); } { // Order by and filter on >= unique_prop_val_3 && assert result row data members const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3, v_id_4, v_id_5}, {}, std::nullopt, unique_prop_val_3, false, "prop1"); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 3); - MG_ASSERT(result.result_row[0].vertex_and_edge.vertex == v_id_5); - MG_ASSERT(result.result_row[0].properies_and_ids.properties.size() == 1); - MG_ASSERT(result.result_row[0].properies_and_ids.properties.front() == prim_key_5.front()); - MG_ASSERT(result.result_row[0].properies_and_ids.ids.size() == 1); - MG_ASSERT(result.result_row[0].properies_and_ids.ids.front() == prop_id_2); + MG_ASSERT(result.result_row[0].vertex == v_id_5); + MG_ASSERT(result.result_row[0].props.size() == 1); + MG_ASSERT(result.result_row[0].props.front().second == prim_key_5.front()); + MG_ASSERT(result.result_row[0].props.size() == 1); + MG_ASSERT(result.result_row[0].props.front().first == prop_id_2); MG_ASSERT(result.result_row[0].evaluated_expressions.size() == 1); MG_ASSERT(result.result_row[0].evaluated_expressions.front() == msgs::Value(true)); - MG_ASSERT(result.result_row[1].vertex_and_edge.vertex == v_id_4); - MG_ASSERT(result.result_row[1].properies_and_ids.properties.size() == 1); - MG_ASSERT(result.result_row[1].properies_and_ids.properties.front() == prim_key_4.front()); - MG_ASSERT(result.result_row[1].properies_and_ids.ids.size() == 1); - MG_ASSERT(result.result_row[1].properies_and_ids.ids.front() == prop_id_2); + MG_ASSERT(result.result_row[1].vertex == v_id_4); + MG_ASSERT(result.result_row[1].props.size() == 1); + MG_ASSERT(result.result_row[1].props.front().second == prim_key_4.front()); + MG_ASSERT(result.result_row[1].props.size() == 1); + MG_ASSERT(result.result_row[1].props.front().first == prop_id_2); MG_ASSERT(result.result_row[1].evaluated_expressions.size() == 1); MG_ASSERT(result.result_row[1].evaluated_expressions.front() == msgs::Value(true)); - MG_ASSERT(result.result_row[2].vertex_and_edge.vertex == v_id_3); - MG_ASSERT(result.result_row[2].properies_and_ids.properties.size() == 1); - MG_ASSERT(result.result_row[2].properies_and_ids.properties.front() == prim_key_3.front()); - MG_ASSERT(result.result_row[2].properies_and_ids.ids.size() == 1); - MG_ASSERT(result.result_row[2].properies_and_ids.ids.front() == prop_id_2); + MG_ASSERT(result.result_row[2].vertex == v_id_3); + MG_ASSERT(result.result_row[2].props.size() == 1); + MG_ASSERT(result.result_row[2].props.front().second == prim_key_3.front()); + MG_ASSERT(result.result_row[2].props.size() == 1); + MG_ASSERT(result.result_row[2].props.front().first == prop_id_2); MG_ASSERT(result.result_row[2].evaluated_expressions.size() == 1); MG_ASSERT(result.result_row[2].evaluated_expressions.front() == msgs::Value(true)); } @@ -1388,54 +1392,49 @@ void TestGetProperties(ShardClient &client) { MG_ASSERT(AttemptToAddEdgeWithProperties(client, unique_prop_val_3, unique_prop_val_4, edge_gid_2, unique_edge_prop_id, edge_prop_val_2, {edge_type_id})); const auto edge_prop_id = PropertyId::FromUint(unique_edge_prop_id); + std::vector edge_ids = {{edge_gid}, {edge_gid_2}}; // no properties { - const auto result = AttemptToGetProperties(client, {}, {v_id_2, v_id_3}, {edge_type_id, edge_type_id}); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); + const auto result = AttemptToGetProperties(client, {}, {v_id_2, v_id_3}, edge_ids); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.empty()); } // properties for two vertices { - const auto result = AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, {edge_type_id, edge_type_id}); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + const auto result = AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, edge_ids); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 2); } // filter { - const auto result = AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, {edge_type_id, edge_type_id}, - {}, {edge_prop_val}, true); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + const auto result = + AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, edge_ids, {}, {edge_prop_val}, true); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 1); - MG_ASSERT(result.result_row.front().vertex_and_edge.edge); - MG_ASSERT(result.result_row.front().vertex_and_edge.edge.value() == edge_type_id); - MG_ASSERT(result.result_row.front().properies_and_ids.properties.size() == 1); - MG_ASSERT(result.result_row.front().properies_and_ids.properties.front() == - msgs::Value(static_cast(edge_prop_val))); + MG_ASSERT(result.result_row.front().edge); + MG_ASSERT(result.result_row.front().edge.value().gid == edge_gid); + MG_ASSERT(result.result_row.front().props.size() == 1); + MG_ASSERT(result.result_row.front().props.front().second == msgs::Value(static_cast(edge_prop_val))); } // Order by { - const auto result = AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, {edge_type_id, edge_type_id}, - {}, {}, true, "e_prop"); - MG_ASSERT(result.result == msgs::GetPropertiesResponse::SUCCESS); - MG_ASSERT(!result.result_row.empty()); + const auto result = + AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, edge_ids, {}, {}, true, "e_prop"); + MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 2); - MG_ASSERT(result.result_row[0].vertex_and_edge.vertex == v_id_3); - MG_ASSERT(result.result_row[0].vertex_and_edge.edge); - MG_ASSERT(result.result_row[0].vertex_and_edge.edge.value() == edge_type_id); - MG_ASSERT(result.result_row[0].properies_and_ids.properties.size() == 1); - MG_ASSERT(result.result_row[0].properies_and_ids.properties.front() == - msgs::Value(static_cast(edge_prop_val_2))); + MG_ASSERT(result.result_row[0].vertex == v_id_3); + MG_ASSERT(result.result_row[0].edge); + MG_ASSERT(result.result_row[0].edge.value().gid == edge_gid_2); + MG_ASSERT(result.result_row[0].props.size() == 1); + MG_ASSERT(result.result_row[0].props.front().second == msgs::Value(static_cast(edge_prop_val_2))); MG_ASSERT(result.result_row[0].evaluated_expressions.size() == 1); MG_ASSERT(result.result_row[0].evaluated_expressions.front() == msgs::Value(true)); - MG_ASSERT(result.result_row[1].vertex_and_edge.vertex == v_id_2); - MG_ASSERT(result.result_row[1].vertex_and_edge.edge); - MG_ASSERT(result.result_row[1].vertex_and_edge.edge.value() == edge_type_id); - MG_ASSERT(result.result_row[1].properies_and_ids.properties.size() == 1); - MG_ASSERT(result.result_row[1].properies_and_ids.properties.front() == - msgs::Value(static_cast(edge_prop_val))); + MG_ASSERT(result.result_row[1].vertex == v_id_2); + MG_ASSERT(result.result_row[1].edge); + MG_ASSERT(result.result_row[1].edge.value().gid == edge_gid); + MG_ASSERT(result.result_row[1].props.size() == 1); + MG_ASSERT(result.result_row[1].props.front().second == msgs::Value(static_cast(edge_prop_val))); MG_ASSERT(result.result_row[1].evaluated_expressions.size() == 1); MG_ASSERT(result.result_row[1].evaluated_expressions.front() == msgs::Value(true)); } From 02ca6734c1b6467460f5441a6566e4b9ecbd83b5 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 14:11:38 +0100 Subject: [PATCH 35/93] Correct comment to follow common style --- src/query/v2/multiframe.hpp | 51 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 7c59300de..5ea8d1c65 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -38,38 +38,37 @@ class MultiFrame { MultiFrame &operator=(const MultiFrame &other) = delete; MultiFrame &operator=(MultiFrame &&other) noexcept = delete; - /*! - Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid - state in the multiframe. - Iteration goes in a deterministic order. - One can't modify the validity of the frame nor its content with this implementation. - */ + /* + * Returns a object on which one can iterate in a for-loop. By doing so, you will only get Frames that are in a valid + * state in the MultiFrame. + * Iteration goes in a deterministic order. + * One can't modify the validity of the Frame nor its content with this implementation. + */ ValidFramesReader GetValidFramesReader(); - /*! - Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid - state in the multiframe. - Iteration goes in a deterministic order. - One can't modify the validity of the frame with this implementation. One can modify its content. - */ + /* + * Returns a object on which one can iterate in a for-loop. By doing so, you will only get Frames that are in a valid + * state in the MultiFrame. + * Iteration goes in a deterministic order. + * One can't modify the validity of the Frame with this implementation. One can modify its content. + */ ValidFramesModifier GetValidFramesModifier(); - /*! - Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in a valid - state in the multiframe. - Iteration goes in a deterministic order. - One can modify the validity of the frame with this implementation. - If you do not plan to modify the validity of the frames, use GetValidFramesReader/GetValidFramesModifer instead as - this is faster. - */ + /* + * Returns a object on which one can iterate in a for-loop. By doing so, you will only get Frames that are in a valid + * state in the MultiFrame. + * Iteration goes in a deterministic order. + * One can modify the validity of the Frame with this implementation. + * If you do not plan to modify the validity of the Frames, use GetValidFramesReader/GetValidFramesModifer instead as + * this is faster. + */ ValidFramesConsumer GetValidFramesConsumer(); - /*! - Returns a object on which one can iterate in a for-loop. By doing so, you will only get frames that are in an invalid - state in the multiframe. - Iteration goes in a deterministic order. - One can modify the validity of the frame with this implementation. - */ + /* + * Returns a object on which one can iterate in a for-loop. By doing so, you will only get Frames that are in an + * invalid state in the MultiFrame. Iteration goes in a deterministic order. One can modify the validity of + * the Frame with this implementation. + */ InvalidFramesPopulator GetInvalidFramesPopulator(); void ResetAllFramesInvalid() noexcept; From 55008a2927bcfe2e596783f9bf17a079c5ab9093 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 14:17:01 +0100 Subject: [PATCH 36/93] Rename func ResetAllFramesInvalid->MakeAllFramesInvalid --- src/query/v2/interpreter.cpp | 4 ++-- src/query/v2/multiframe.cpp | 2 +- src/query/v2/multiframe.hpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index bfa7dfe0e..bf28ef875 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -761,7 +761,7 @@ std::optional PullPlan::PullMultiple(AnyStrea stream_values(frame); ++i; } - multi_frame_.ResetAllFramesInvalid(); + multi_frame_.MakeAllFramesInvalid(); } for (; !n || i < n;) { @@ -776,7 +776,7 @@ std::optional PullPlan::PullMultiple(AnyStrea ++i; } } - multi_frame_.ResetAllFramesInvalid(); + multi_frame_.MakeAllFramesInvalid(); } // If we finished because we streamed the requested n results, diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 4573beaea..3a180ab89 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -44,7 +44,7 @@ MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_f MultiFrame::MultiFrame(MultiFrame &&other) noexcept : default_frame_(std::move(other.default_frame_)), frames_(std::move(other.frames_)) {} -void MultiFrame::ResetAllFramesInvalid() noexcept { +void MultiFrame::MakeAllFramesInvalid() noexcept { std::for_each(frames_.begin(), frames_.end(), [](auto &frame) { frame.MakeInvalid(); }); } diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 5ea8d1c65..27d010fd4 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -71,7 +71,7 @@ class MultiFrame { */ InvalidFramesPopulator GetInvalidFramesPopulator(); - void ResetAllFramesInvalid() noexcept; + void MakeAllFramesInvalid() noexcept; bool HasValidFrame() const noexcept; From 969b8f0da79ff238d87b9ed1afa7c35447ddd8c0 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 14:32:19 +0100 Subject: [PATCH 37/93] Remove un-necessary internal_ptr --- src/query/v2/multiframe.hpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 27d010fd4..fb6cbe9e6 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -102,9 +102,9 @@ class ValidFramesReader { using value_type = const Frame; using pointer = value_type *; using reference = const Frame &; - using internal_ptr = FrameWithValidity *; - Iterator(internal_ptr ptr, ValidFramesReader &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} + Iterator(FrameWithValidity *ptr, ValidFramesReader &iterator_wrapper) + : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } @@ -122,7 +122,7 @@ class ValidFramesReader { friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; private: - internal_ptr ptr_; + FrameWithValidity *ptr_; ValidFramesReader &iterator_wrapper_; }; @@ -149,9 +149,8 @@ class ValidFramesModifier { using value_type = Frame; using pointer = value_type *; using reference = Frame &; - using internal_ptr = FrameWithValidity *; - Iterator(internal_ptr ptr, ValidFramesModifier &iterator_wrapper) + Iterator(FrameWithValidity *ptr, ValidFramesModifier &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} reference operator*() const { return *ptr_; } @@ -170,7 +169,7 @@ class ValidFramesModifier { friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; private: - internal_ptr ptr_; + FrameWithValidity *ptr_; ValidFramesModifier &iterator_wrapper_; }; @@ -197,9 +196,8 @@ class ValidFramesConsumer { using value_type = FrameWithValidity; using pointer = value_type *; using reference = FrameWithValidity &; - using internal_ptr = FrameWithValidity *; - Iterator(internal_ptr ptr, ValidFramesConsumer &iterator_wrapper) + Iterator(FrameWithValidity *ptr, ValidFramesConsumer &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} reference operator*() const { return *ptr_; } @@ -218,7 +216,7 @@ class ValidFramesConsumer { friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; private: - internal_ptr ptr_; + FrameWithValidity *ptr_; ValidFramesConsumer &iterator_wrapper_; }; @@ -245,9 +243,8 @@ class InvalidFramesPopulator { using value_type = FrameWithValidity; using pointer = value_type *; using reference = FrameWithValidity &; - using internal_ptr = FrameWithValidity *; - explicit Iterator(internal_ptr ptr) : ptr_(ptr) {} + explicit Iterator(FrameWithValidity *ptr) : ptr_(ptr) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } @@ -263,7 +260,7 @@ class InvalidFramesPopulator { friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; private: - internal_ptr ptr_; + FrameWithValidity *ptr_; }; Iterator begin(); From 072bc58b1eac7462ee9de6b53c287253d2c51a3c Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 14:50:28 +0100 Subject: [PATCH 38/93] Reverse condition in while() + comment --- src/query/v2/multiframe.cpp | 10 ++++++++++ src/query/v2/multiframe.hpp | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 3a180ab89..7873969ac 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -16,6 +16,16 @@ namespace memgraph::query::v2 { +// #NoCommit uncomment https://github.com/memgraph/memgraph/pull/676#discussion_r1035704661 +// static_assert(std::forward_iterator && +// std::equality_comparable); +// static_assert(std::forward_iterator && +// std::equality_comparable); +// static_assert(std::forward_iterator && +// std::equality_comparable); +// static_assert(std::forward_iterator && +// std::equality_comparable); + MultiFrame::MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, utils::MemoryResource *execution_memory) : default_frame_(default_frame), diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index fb6cbe9e6..9186d739c 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -113,7 +113,7 @@ class ValidFramesReader { Iterator &operator++() { do { ptr_++; - } while (!this->ptr_->IsValid() && *this != iterator_wrapper_.end()); + } while (*this != iterator_wrapper_.end() && !this->ptr_->IsValid()); return *this; } @@ -160,7 +160,7 @@ class ValidFramesModifier { Iterator &operator++() { do { ptr_++; - } while (!this->ptr_->IsValid() && *this != iterator_wrapper_.end()); + } while (*this != iterator_wrapper_.end() && !this->ptr_->IsValid()); return *this; } @@ -207,7 +207,7 @@ class ValidFramesConsumer { Iterator &operator++() { do { ptr_++; - } while (!this->ptr_->IsValid() && *this != iterator_wrapper_.end()); + } while (*this != iterator_wrapper_.end() && !this->ptr_->IsValid()); return *this; } From deb31e4b77045df14b4ec70d8b6e82a1d960509d Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 16:00:24 +0100 Subject: [PATCH 39/93] Multiframe only expects size of frame instead of default frame --- src/query/v2/interpreter.cpp | 2 +- src/query/v2/multiframe.cpp | 9 ++++----- src/query/v2/multiframe.hpp | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index bf28ef875..b5f916e9f 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -688,7 +688,7 @@ PullPlan::PullPlan(const std::shared_ptr plan, const Parameters &par : plan_(plan), cursor_(plan->plan().MakeCursor(execution_memory)), frame_(plan->symbol_table().max_position(), execution_memory), - multi_frame_(frame_, kNumberOfFramesInMultiframe, execution_memory), + multi_frame_(plan->symbol_table().max_position(), kNumberOfFramesInMultiframe, execution_memory), memory_limit_(memory_limit) { ctx_.db_accessor = dba; ctx_.symbol_table = plan->symbol_table(); diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 7873969ac..640e63510 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -26,12 +26,11 @@ namespace memgraph::query::v2 { // static_assert(std::forward_iterator && // std::equality_comparable); -MultiFrame::MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, - utils::MemoryResource *execution_memory) - : default_frame_(default_frame), - frames_(utils::pmr::vector(number_of_frames, default_frame, execution_memory)) { +MultiFrame::MultiFrame(size_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory) + : default_frame_(FrameWithValidity(size_of_frame, execution_memory)), + frames_(utils::pmr::vector(number_of_frames, default_frame_, execution_memory)) { MG_ASSERT(number_of_frames > 0); - MG_ASSERT(!default_frame.IsValid()); + MG_ASSERT(!default_frame_.IsValid()); } MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_frame_) { diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 9186d739c..8f6109ab4 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -30,7 +30,7 @@ class MultiFrame { friend class ValidFramesReader; friend class InvalidFramesPopulator; - MultiFrame(FrameWithValidity default_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); + MultiFrame(size_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); ~MultiFrame() = default; MultiFrame(const MultiFrame &other); // copy constructor From a2027fc6ac8e3fcd08c6d726ab4fb26d412598d9 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 16:09:06 +0100 Subject: [PATCH 40/93] Remove default_frame --- src/query/v2/multiframe.cpp | 22 +++++----------------- src/query/v2/multiframe.hpp | 1 - 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 640e63510..a4ec3be4f 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -27,31 +27,19 @@ namespace memgraph::query::v2 { // std::equality_comparable); MultiFrame::MultiFrame(size_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory) - : default_frame_(FrameWithValidity(size_of_frame, execution_memory)), - frames_(utils::pmr::vector(number_of_frames, default_frame_, execution_memory)) { + : frames_(utils::pmr::vector( + number_of_frames, FrameWithValidity(size_of_frame, execution_memory), execution_memory)) { MG_ASSERT(number_of_frames > 0); - MG_ASSERT(!default_frame_.IsValid()); } -MultiFrame::MultiFrame(const MultiFrame &other) : default_frame_(other.default_frame_) { - /* - TODO - Do we just copy all frames or do we make distinctions between valid and not valid frames? Does it make any - difference? - */ +MultiFrame::MultiFrame(const MultiFrame &other) { frames_.reserve(other.frames_.size()); std::transform(other.frames_.begin(), other.frames_.end(), std::back_inserter(frames_), - [&default_frame = default_frame_](const auto &other_frame) { - if (other_frame.IsValid()) { - return other_frame; - } - return default_frame; - }); + [](const auto &other_frame) { return other_frame; }); } // NOLINTNEXTLINE (bugprone-exception-escape) -MultiFrame::MultiFrame(MultiFrame &&other) noexcept - : default_frame_(std::move(other.default_frame_)), frames_(std::move(other.frames_)) {} +MultiFrame::MultiFrame(MultiFrame &&other) noexcept : frames_(std::move(other.frames_)) {} void MultiFrame::MakeAllFramesInvalid() noexcept { std::for_each(frames_.begin(), frames_.end(), [](auto &frame) { frame.MakeInvalid(); }); diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 8f6109ab4..0da5c1717 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -81,7 +81,6 @@ class MultiFrame { // NOLINTNEXTLINE (bugprone-exception-escape) void DefragmentValidFrames() noexcept; - FrameWithValidity default_frame_; utils::pmr::vector frames_ = utils::pmr::vector(0, FrameWithValidity{1}, utils::NewDeleteResource()); }; From 56556f7c2d41d221cb069f7a620c5f436a60d757 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 16:22:39 +0100 Subject: [PATCH 41/93] Update incorrect de-referencing --- src/query/v2/multiframe.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index a4ec3be4f..5bae5c108 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -73,14 +73,14 @@ ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multi ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesReader::Iterator ValidFramesReader::end() { - return Iterator{&multiframe_.frames_[multiframe_.frames_.size()], *this}; + return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this}; } ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {} ValidFramesModifier::Iterator ValidFramesModifier::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesModifier::Iterator ValidFramesModifier::end() { - return Iterator{&multiframe_.frames_[multiframe_.frames_.size()], *this}; + return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this}; } ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(multiframe) {} @@ -95,7 +95,7 @@ ValidFramesConsumer::~ValidFramesConsumer() noexcept { ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { return Iterator{&multiframe_.frames_[0], *this}; } ValidFramesConsumer::Iterator ValidFramesConsumer::end() { - return Iterator{&multiframe_.frames_[multiframe_.frames_.size()], *this}; + return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this}; } InvalidFramesPopulator::InvalidFramesPopulator(MultiFrame &multiframe) : multiframe_(multiframe) {} @@ -111,7 +111,7 @@ InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() { } InvalidFramesPopulator::Iterator InvalidFramesPopulator::end() { - return Iterator{&multiframe_.frames_[multiframe_.frames_.size()]}; + return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size()}; } } // namespace memgraph::query::v2 From 94ef57c459eb2d1919bfa015d81137f6007c1f93 Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Wed, 30 Nov 2022 17:24:46 +0200 Subject: [PATCH 42/93] Fix small bugs --- src/storage/v3/request_helper.cpp | 3 +-- src/storage/v3/request_helper.hpp | 15 +++++------ src/storage/v3/shard_rsm.cpp | 43 +++++++++++++++++++------------ tests/simulation/shard_rsm.cpp | 21 ++++++++++----- 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/storage/v3/request_helper.cpp b/src/storage/v3/request_helper.cpp index 260147a25..07ea99d9d 100644 --- a/src/storage/v3/request_helper.cpp +++ b/src/storage/v3/request_helper.cpp @@ -14,7 +14,6 @@ #include #include -#include "pretty_print_ast_to_original_expression.hpp" #include "storage/v3/bindings/db_accessor.hpp" #include "storage/v3/bindings/pretty_print_ast_to_original_expression.hpp" #include "storage/v3/expr.hpp" @@ -245,7 +244,7 @@ ShardResult> CollectAllPropertiesFromAccessor(const auto pks = PrimaryKeysFromAccessor(acc, view, schema); if (pks) { - ret.GetValue().merge(*pks); + ret.GetValue().merge(std::move(*pks)); } return ret; diff --git a/src/storage/v3/request_helper.hpp b/src/storage/v3/request_helper.hpp index 13871d219..bbe4894e9 100644 --- a/src/storage/v3/request_helper.hpp +++ b/src/storage/v3/request_helper.hpp @@ -32,7 +32,7 @@ using EdgeFiller = using msgs::Value; template -concept ObjectAccessor = utils::SameAsAnyOf>; +concept OrderableObject = utils::SameAsAnyOf>; inline bool TypedValueCompare(const TypedValue &a, const TypedValue &b) { // in ordering null comes after everything else @@ -126,7 +126,7 @@ class TypedValueVectorCompare final { std::vector ordering_; }; -template +template struct Element { std::vector properties_order_by; TObjectAccessor object_acc; @@ -164,9 +164,6 @@ std::vector> OrderByVertices(DbAccessor &dba, TIterable return ordered; } -template -concept EdgeObjectAccessor = utils::SameAsAnyOf>; - std::vector> OrderByEdges(DbAccessor &dba, std::vector &iterable, std::vector &order_by_edges, const VertexAccessor &vertex_acc); @@ -198,9 +195,9 @@ std::vector EvaluateEdgeExpressions(DbAccessor &dba, const VertexAcc const std::vector &expressions); template -concept TAccessor = utils::SameAsAnyOf; +concept PropertiesAccessor = utils::SameAsAnyOf; -template +template ShardResult> CollectSpecificPropertiesFromAccessor(const TAccessor &acc, const std::vector &props, View view) { @@ -222,7 +219,7 @@ ShardResult> CollectSpecificPropertiesFromAccessor(c ShardResult> CollectAllPropertiesFromAccessor(const VertexAccessor &acc, View view, const Schemas::Schema &schema); namespace impl { -template +template ShardResult> CollectAllPropertiesImpl(const TAccessor &acc, View view) { std::map ret; auto props = acc.Properties(view); @@ -240,7 +237,7 @@ ShardResult> CollectAllPropertiesImpl(const TAccesso } } // namespace impl -template +template ShardResult> CollectAllPropertiesFromAccessor(const TAccessor &acc, View view) { return impl::CollectAllPropertiesImpl(acc, view); } diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index b628127af..4ea874770 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -517,10 +517,13 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CommitRequest &&req) { msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { if (!req.vertex_ids.empty() && !req.vertices_and_edges.empty()) { - auto error = CreateErrorResponse( - {common::ErrorCode::VERTEX_HAS_EDGES, std::experimental::source_location::current()}, req.transaction_id, ""); + auto shard_error = SHARD_ERROR(ErrorCode::NONEXISTENT_OBJECT); + auto error = CreateErrorResponse(shard_error, req.transaction_id, ""); return msgs::GetPropertiesResponse{.error = {}}; } + if (req.property_ids && req.property_ids->empty()) { + return {}; + } auto shard_acc = shard_->Access(req.transaction_id); auto dba = DbAccessor{&shard_acc}; @@ -535,8 +538,9 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { return result; }; - auto collect_props = [&req](const VertexAccessor &v_acc, const std::optional &e_acc) { - if (req.property_ids) { + auto collect_props = [&req](const VertexAccessor &v_acc, + const std::optional &e_acc) -> ShardResult> { + if (!req.property_ids) { if (e_acc) { return CollectAllPropertiesFromAccessor(*e_acc, view); } @@ -544,9 +548,9 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { } if (e_acc) { - return CollectSpecificPropertiesFromAccessor(v_acc, *req.property_ids, view); + return CollectSpecificPropertiesFromAccessor(*e_acc, *req.property_ids, view); } - return CollectSpecificPropertiesFromAccessor(*e_acc, *req.property_ids, view); + return CollectSpecificPropertiesFromAccessor(v_acc, *req.property_ids, view); }; auto find_edge = [](const VertexAccessor &v, msgs::EdgeId e) -> std::optional { @@ -577,9 +581,9 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { return {maybe_id.GetError()}; } const auto &id = maybe_id.GetValue(); - std::optional e_type; + std::optional e_id; if (e_acc) { - e_type = msgs::EdgeId{e_acc->Gid().AsUint()}; + e_id = msgs::EdgeId{e_acc->Gid().AsUint()}; } msgs::VertexId v_id{msgs::Label{id.primary_label}, ConvertValueVector(id.primary_key)}; auto maybe_props = collect_props(v_acc, e_acc); @@ -587,7 +591,7 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { return {maybe_props.GetError()}; } auto props = transform_props(std::move(maybe_props.GetValue())); - auto result = msgs::GetPropertiesResultRow{.vertex = std::move(v_id), .edge = e_type, .props = std::move(props)}; + auto result = msgs::GetPropertiesResultRow{.vertex = std::move(v_id), .edge = e_id, .props = std::move(props)}; if (has_expr_to_evaluate) { std::vector e_results; if (e_acc) { @@ -610,11 +614,11 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { return limit; }; - auto collect_response = [get_limit, &req](auto &elements, auto result_row_functor) { + auto collect_response = [get_limit, &req](auto &elements, auto create_result_row) { msgs::GetPropertiesResponse response; const auto limit = get_limit(elements); for (size_t index = 0; index != limit; ++index) { - auto result_row = result_row_functor(elements[index]); + auto result_row = create_result_row(elements[index]); if (result_row.HasError()) { return msgs::GetPropertiesResponse{.error = CreateErrorResponse(result_row.GetError(), req.transaction_id, "")}; } @@ -626,15 +630,15 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { std::vector vertices; std::vector edges; - auto parse_and_filter = [dba, &vertices](auto &cont, auto projection, auto filter, auto maybe_get_edge) mutable { - for (const auto &elem : cont) { + auto parse_and_filter = [dba, &vertices](auto &container, auto projection, auto filter, auto maybe_get_edge) mutable { + for (const auto &elem : container) { const auto &[label, pk_v] = projection(elem); auto pk = ConvertPropertyVector(pk_v); auto v_acc = dba.FindVertex(pk, view); if (!v_acc || filter(*v_acc, maybe_get_edge(elem))) { continue; } - vertices.push_back({*v_acc}); + vertices.push_back(*v_acc); } }; auto identity = [](auto &elem) { return elem; }; @@ -648,11 +652,15 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { auto filter_edge = [dba, &edges, &req, find_edge](const auto &acc, const auto &edge) mutable { auto e_acc = find_edge(acc, edge); - if (!req.filter || !e_acc || !FilterOnEdge(dba, acc, *e_acc, {*req.filter})) { - return false; + if (!e_acc) { + return true; + } + + if (req.filter && !FilterOnEdge(dba, acc, *e_acc, {*req.filter})) { + return true; } edges.push_back(*e_acc); - return true; + return false; }; // Handler logic here @@ -673,6 +681,7 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { return collect_response(vertices, [emplace_result_row](auto &acc) mutable { return emplace_result_row(acc, std::nullopt); }); } + if (!req.order_by.empty()) { auto elements = OrderByEdges(dba, edges, req.order_by, vertices); return collect_response(elements, [emplace_result_row](auto &element) mutable { diff --git a/tests/simulation/shard_rsm.cpp b/tests/simulation/shard_rsm.cpp index 80a5cd87d..ba36157d0 100644 --- a/tests/simulation/shard_rsm.cpp +++ b/tests/simulation/shard_rsm.cpp @@ -489,7 +489,9 @@ msgs::GetPropertiesResponse AttemptToGetProperties(ShardClient &client, std::vec std::optional order_by = std::nullopt) { msgs::GetPropertiesRequest req{}; req.transaction_id.logical_id = GetTransactionId(); - req.property_ids = std::move(properties); + if (!properties.empty()) { + req.property_ids = std::move(properties); + } if (filter_prop) { std::string filter_expr = (!edge) ? "MG_SYMBOL_NODE.prop1 >= " : "MG_SYMBOL_EDGE.e_prop = "; @@ -1294,13 +1296,15 @@ void TestGetProperties(ShardClient &client) { const auto prop_id_5 = PropertyId::FromUint(5); // Vertices { - // No properties - const auto result = AttemptToGetProperties(client, {}, {v_id, v_id_2}, {}, std::nullopt, unique_prop_val_2); + const auto result = AttemptToGetProperties(client, {}, {v_id, v_id_2}, {}); MG_ASSERT(!result.error); - MG_ASSERT(result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 2); + for (const auto &elem : result.result_row) { + MG_ASSERT(elem.props.size() == 3); + } } { - // All properties + // Specific properties const auto result = AttemptToGetProperties(client, {prop_id_2, prop_id_4, prop_id_5}, {v_id, v_id_2, v_id_3}, {}); MG_ASSERT(!result.error); MG_ASSERT(!result.result_row.empty()); @@ -1393,11 +1397,14 @@ void TestGetProperties(ShardClient &client) { unique_edge_prop_id, edge_prop_val_2, {edge_type_id})); const auto edge_prop_id = PropertyId::FromUint(unique_edge_prop_id); std::vector edge_ids = {{edge_gid}, {edge_gid_2}}; - // no properties + // all properties { const auto result = AttemptToGetProperties(client, {}, {v_id_2, v_id_3}, edge_ids); MG_ASSERT(!result.error); - MG_ASSERT(result.result_row.empty()); + MG_ASSERT(result.result_row.size() == 2); + for (const auto &elem : result.result_row) { + MG_ASSERT(elem.props.size() == 1); + } } // properties for two vertices { From 38f3a4cacbd4ed1c4b37c7fb7c0b6c1c7bc1661c Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 16:28:01 +0100 Subject: [PATCH 43/93] Use range for loop instead of idx based --- src/query/v2/multiframe.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 5bae5c108..e6e8685fc 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -101,12 +101,11 @@ ValidFramesConsumer::Iterator ValidFramesConsumer::end() { InvalidFramesPopulator::InvalidFramesPopulator(MultiFrame &multiframe) : multiframe_(multiframe) {} InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() { - for (auto idx = 0UL; idx < multiframe_.frames_.size(); ++idx) { - if (!multiframe_.frames_[idx].IsValid()) { - return Iterator{&multiframe_.frames_[idx]}; + for (auto &frame : multiframe_.frames_) { + if (!frame.IsValid()) { + return Iterator{&frame}; } } - return end(); } From 5e64b19745f89133827b8966bb674ba4fa8d1ad2 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 16:30:55 +0100 Subject: [PATCH 44/93] Replace pull_count_->did_pull_ --- src/query/v2/plan/operator.cpp | 10 +++++----- src/query/v2/plan/operator.lcp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index db2a1372d..c9fd05609 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -257,8 +257,8 @@ class DistributedCreateNodeCursor : public Cursor { bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) { SCOPED_PROFILE_OP("Once"); - if (pull_count_ < 1) { - pull_count_++; + if (!did_pull_) { + did_pull_ = true; return true; } return false; @@ -270,14 +270,14 @@ void Once::OnceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &c auto iterator_for_valid_frame_only = multi_frame.GetValidFramesConsumer(); auto first_it = iterator_for_valid_frame_only.begin(); MG_ASSERT(first_it != iterator_for_valid_frame_only.end()); - if (pull_count_ < 1) { + if (!did_pull_) { auto *memory_resource = multi_frame.GetMemoryResource(); auto &frame = *first_it; frame.MakeValid(); for (auto &value : frame.elems()) { value = TypedValue{memory_resource}; } - pull_count_++; + did_pull_ = true; } } @@ -291,7 +291,7 @@ WITHOUT_SINGLE_INPUT(Once); void Once::OnceCursor::Shutdown() {} -void Once::OnceCursor::Reset() { pull_count_ = 0; } +void Once::OnceCursor::Reset() { did_pull_ = false; } CreateNode::CreateNode(const std::shared_ptr &input, const NodeCreationInfo &node_info) : input_(input ? input : std::make_shared()), node_info_(node_info) {} diff --git a/src/query/v2/plan/operator.lcp b/src/query/v2/plan/operator.lcp index 9f8b7d6db..efa0d5df0 100644 --- a/src/query/v2/plan/operator.lcp +++ b/src/query/v2/plan/operator.lcp @@ -341,7 +341,7 @@ and false on every following Pull.") void Reset() override; private: - size_t pull_count_{0}; + bool did_pull_{false}; }; cpp<#) (:serialize (:slk)) From 8af635c8d70efce8482a5d8bd0c8174d76270b6f Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Wed, 30 Nov 2022 17:44:37 +0200 Subject: [PATCH 45/93] Fix clang-tidy warnings --- src/storage/v3/shard_rsm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index 4ea874770..e533008a1 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -533,7 +533,7 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { std::vector> result; result.reserve(value.size()); for (auto &[id, val] : value) { - result.push_back({id, std::move(val)}); + result.emplace_back(std::make_pair(id, std::move(val))); } return result; }; From b0b8c0a5c94cbeb84137fd04e68dff714435d936 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 16:49:32 +0100 Subject: [PATCH 46/93] Add noexcept to basic functions --- src/expr/interpret/frame.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/expr/interpret/frame.hpp b/src/expr/interpret/frame.hpp index 6a220b6a0..1bc5bba83 100644 --- a/src/expr/interpret/frame.hpp +++ b/src/expr/interpret/frame.hpp @@ -49,9 +49,9 @@ class FrameWithValidity final : public Frame { FrameWithValidity(int64_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {} - bool IsValid() const { return is_valid_; } - void MakeValid() { is_valid_ = true; } - void MakeInvalid() { is_valid_ = false; } + bool IsValid() const noexcept { return is_valid_; } + void MakeValid() noexcept { is_valid_ = true; } + void MakeInvalid() noexcept { is_valid_ = false; } private: bool is_valid_; From 9f9a81455f4c29902faee3d80d45de8f8a405f99 Mon Sep 17 00:00:00 2001 From: jeremy Date: Wed, 30 Nov 2022 16:56:35 +0100 Subject: [PATCH 47/93] Change type size_t->int64_t --- src/query/v2/multiframe.cpp | 2 +- src/query/v2/multiframe.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index e6e8685fc..27b0872ed 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -26,7 +26,7 @@ namespace memgraph::query::v2 { // static_assert(std::forward_iterator && // std::equality_comparable); -MultiFrame::MultiFrame(size_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory) +MultiFrame::MultiFrame(int64_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory) : frames_(utils::pmr::vector( number_of_frames, FrameWithValidity(size_of_frame, execution_memory), execution_memory)) { MG_ASSERT(number_of_frames > 0); diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 0da5c1717..5583e80f3 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -30,7 +30,7 @@ class MultiFrame { friend class ValidFramesReader; friend class InvalidFramesPopulator; - MultiFrame(size_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); + MultiFrame(int64_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); ~MultiFrame() = default; MultiFrame(const MultiFrame &other); // copy constructor From e5d892683c3790351674a7c0121aad309a4de660 Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 11:14:54 +0100 Subject: [PATCH 48/93] Keep wraper as ptr instead of ref --- src/query/v2/multiframe.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 5583e80f3..7c0f366eb 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -103,7 +103,7 @@ class ValidFramesReader { using reference = const Frame &; Iterator(FrameWithValidity *ptr, ValidFramesReader &iterator_wrapper) - : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} + : ptr_(ptr), iterator_wrapper_(&iterator_wrapper) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } @@ -112,7 +112,7 @@ class ValidFramesReader { Iterator &operator++() { do { ptr_++; - } while (*this != iterator_wrapper_.end() && !this->ptr_->IsValid()); + } while (*this != iterator_wrapper_->end() && !this->ptr_->IsValid()); return *this; } @@ -122,7 +122,7 @@ class ValidFramesReader { private: FrameWithValidity *ptr_; - ValidFramesReader &iterator_wrapper_; + ValidFramesReader *iterator_wrapper_; }; Iterator begin(); @@ -150,7 +150,7 @@ class ValidFramesModifier { using reference = Frame &; Iterator(FrameWithValidity *ptr, ValidFramesModifier &iterator_wrapper) - : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} + : ptr_(ptr), iterator_wrapper_(&iterator_wrapper) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } @@ -159,7 +159,7 @@ class ValidFramesModifier { Iterator &operator++() { do { ptr_++; - } while (*this != iterator_wrapper_.end() && !this->ptr_->IsValid()); + } while (*this != iterator_wrapper_->end() && !this->ptr_->IsValid()); return *this; } @@ -169,7 +169,7 @@ class ValidFramesModifier { private: FrameWithValidity *ptr_; - ValidFramesModifier &iterator_wrapper_; + ValidFramesModifier *iterator_wrapper_; }; Iterator begin(); @@ -197,7 +197,7 @@ class ValidFramesConsumer { using reference = FrameWithValidity &; Iterator(FrameWithValidity *ptr, ValidFramesConsumer &iterator_wrapper) - : ptr_(ptr), iterator_wrapper_(iterator_wrapper) {} + : ptr_(ptr), iterator_wrapper_(&iterator_wrapper) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } @@ -206,7 +206,7 @@ class ValidFramesConsumer { Iterator &operator++() { do { ptr_++; - } while (*this != iterator_wrapper_.end() && !this->ptr_->IsValid()); + } while (*this != iterator_wrapper_->end() && !this->ptr_->IsValid()); return *this; } @@ -216,7 +216,7 @@ class ValidFramesConsumer { private: FrameWithValidity *ptr_; - ValidFramesConsumer &iterator_wrapper_; + ValidFramesConsumer *iterator_wrapper_; }; Iterator begin(); From f8cbaaf362237da45683dcd5b35194ef63538047 Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Thu, 1 Dec 2022 14:41:21 +0200 Subject: [PATCH 49/93] Allow requests with zero properties --- src/storage/v3/shard_rsm.cpp | 3 -- tests/simulation/shard_rsm.cpp | 77 ++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index e533008a1..639ffd6d8 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -521,9 +521,6 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { auto error = CreateErrorResponse(shard_error, req.transaction_id, ""); return msgs::GetPropertiesResponse{.error = {}}; } - if (req.property_ids && req.property_ids->empty()) { - return {}; - } auto shard_acc = shard_->Access(req.transaction_id); auto dba = DbAccessor{&shard_acc}; diff --git a/tests/simulation/shard_rsm.cpp b/tests/simulation/shard_rsm.cpp index ba36157d0..768217945 100644 --- a/tests/simulation/shard_rsm.cpp +++ b/tests/simulation/shard_rsm.cpp @@ -480,18 +480,14 @@ std::tuple> AttemptToScanAllWithExpression } } -msgs::GetPropertiesResponse AttemptToGetProperties(ShardClient &client, std::vector properties, - std::vector vertices, - std::vector edges, - std::optional limit = std::nullopt, - std::optional filter_prop = std::nullopt, - bool edge = false, - std::optional order_by = std::nullopt) { +msgs::GetPropertiesResponse AttemptToGetProperties( + ShardClient &client, std::optional> properties, std::vector vertices, + std::vector edges, std::optional limit = std::nullopt, + std::optional filter_prop = std::nullopt, bool edge = false, + std::optional order_by = std::nullopt) { msgs::GetPropertiesRequest req{}; req.transaction_id.logical_id = GetTransactionId(); - if (!properties.empty()) { - req.property_ids = std::move(properties); - } + req.property_ids = std::move(properties); if (filter_prop) { std::string filter_expr = (!edge) ? "MG_SYMBOL_NODE.prop1 >= " : "MG_SYMBOL_EDGE.e_prop = "; @@ -1294,9 +1290,18 @@ void TestGetProperties(ShardClient &client) { const auto prop_id_2 = PropertyId::FromUint(2); const auto prop_id_4 = PropertyId::FromUint(4); const auto prop_id_5 = PropertyId::FromUint(5); - // Vertices + // No properties { - const auto result = AttemptToGetProperties(client, {}, {v_id, v_id_2}, {}); + const auto result = AttemptToGetProperties(client, {{}}, {v_id, v_id_2}, {}); + MG_ASSERT(!result.error); + MG_ASSERT(result.result_row.size() == 2); + for (const auto &elem : result.result_row) { + MG_ASSERT(elem.props.size() == 0); + } + } + // All properties + { + const auto result = AttemptToGetProperties(client, std::nullopt, {v_id, v_id_2}, {}); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 2); for (const auto &elem : result.result_row) { @@ -1305,7 +1310,8 @@ void TestGetProperties(ShardClient &client) { } { // Specific properties - const auto result = AttemptToGetProperties(client, {prop_id_2, prop_id_4, prop_id_5}, {v_id, v_id_2, v_id_3}, {}); + const auto result = + AttemptToGetProperties(client, std::vector{prop_id_2, prop_id_4, prop_id_5}, {v_id, v_id_2, v_id_3}, {}); MG_ASSERT(!result.error); MG_ASSERT(!result.result_row.empty()); MG_ASSERT(result.result_row.size() == 3); @@ -1315,14 +1321,14 @@ void TestGetProperties(ShardClient &client) { } { // Two properties from two vertices with a filter on unique_prop_5 - const auto result = AttemptToGetProperties(client, {prop_id_2, prop_id_4}, {v_id, v_id_2, v_id_5}, {}, std::nullopt, - unique_prop_val_5); + const auto result = AttemptToGetProperties(client, std::vector{prop_id_2, prop_id_4}, {v_id, v_id_2, v_id_5}, {}, + std::nullopt, unique_prop_val_5); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 1); } { // One property from three vertices. - const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}); + const auto result = AttemptToGetProperties(client, std::vector{prop_id_2}, {v_id, v_id_2, v_id_3}, {}); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 3); MG_ASSERT(result.result_row[0].props.size() == 1); @@ -1331,21 +1337,21 @@ void TestGetProperties(ShardClient &client) { } { // Same as before but with limit of 1 row - const auto result = - AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::make_optional(1)); + const auto result = AttemptToGetProperties(client, std::vector{prop_id_2}, {v_id, v_id_2, v_id_3}, {}, + std::make_optional(1)); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 1); } { // Same as before but with a limit greater than the elements returned - const auto result = - AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::make_optional(5)); + const auto result = AttemptToGetProperties(client, std::vector{prop_id_2}, std::vector{v_id, v_id_2, v_id_3}, {}, + std::make_optional(5)); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 3); } { // Order by on `prop1` (descending) - const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::nullopt, + const auto result = AttemptToGetProperties(client, std::vector{prop_id_2}, {v_id, v_id_2, v_id_3}, {}, std::nullopt, std::nullopt, false, "prop1"); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 3); @@ -1355,8 +1361,8 @@ void TestGetProperties(ShardClient &client) { } { // Order by and filter on >= unique_prop_val_3 && assert result row data members - const auto result = AttemptToGetProperties(client, {prop_id_2}, {v_id, v_id_2, v_id_3, v_id_4, v_id_5}, {}, - std::nullopt, unique_prop_val_3, false, "prop1"); + const auto result = AttemptToGetProperties(client, std::vector{prop_id_2}, {v_id, v_id_2, v_id_3, v_id_4, v_id_5}, + {}, std::nullopt, unique_prop_val_3, false, "prop1"); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 3); MG_ASSERT(result.result_row[0].vertex == v_id_5); @@ -1397,25 +1403,34 @@ void TestGetProperties(ShardClient &client) { unique_edge_prop_id, edge_prop_val_2, {edge_type_id})); const auto edge_prop_id = PropertyId::FromUint(unique_edge_prop_id); std::vector edge_ids = {{edge_gid}, {edge_gid_2}}; - // all properties + // No properties { - const auto result = AttemptToGetProperties(client, {}, {v_id_2, v_id_3}, edge_ids); + const auto result = AttemptToGetProperties(client, {{}}, {v_id_2, v_id_3}, edge_ids); + MG_ASSERT(!result.error); + MG_ASSERT(result.result_row.size() == 2); + for (const auto &elem : result.result_row) { + MG_ASSERT(elem.props.size() == 0); + } + } + // All properties + { + const auto result = AttemptToGetProperties(client, std::nullopt, {v_id_2, v_id_3}, edge_ids); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 2); for (const auto &elem : result.result_row) { MG_ASSERT(elem.props.size() == 1); } } - // properties for two vertices + // Properties for two vertices { - const auto result = AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, edge_ids); + const auto result = AttemptToGetProperties(client, std::vector{edge_prop_id}, {v_id_2, v_id_3}, edge_ids); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 2); } - // filter + // Filter { - const auto result = - AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, edge_ids, {}, {edge_prop_val}, true); + const auto result = AttemptToGetProperties(client, std::vector{edge_prop_id}, {v_id_2, v_id_3}, edge_ids, {}, + {edge_prop_val}, true); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 1); MG_ASSERT(result.result_row.front().edge); @@ -1426,7 +1441,7 @@ void TestGetProperties(ShardClient &client) { // Order by { const auto result = - AttemptToGetProperties(client, {edge_prop_id}, {v_id_2, v_id_3}, edge_ids, {}, {}, true, "e_prop"); + AttemptToGetProperties(client, std::vector{edge_prop_id}, {v_id_2, v_id_3}, edge_ids, {}, {}, true, "e_prop"); MG_ASSERT(!result.error); MG_ASSERT(result.result_row.size() == 2); MG_ASSERT(result.result_row[0].vertex == v_id_3); From 23bfd7f4fca4eb29aaff4c21404c357b7b10b450 Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 13:45:24 +0100 Subject: [PATCH 50/93] Updated OnceCursor --- src/query/v2/multiframe.cpp | 5 +++++ src/query/v2/multiframe.hpp | 7 +++++++ src/query/v2/plan/operator.cpp | 11 ++++------- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 27b0872ed..74ccf8478 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -41,6 +41,11 @@ MultiFrame::MultiFrame(const MultiFrame &other) { // NOLINTNEXTLINE (bugprone-exception-escape) MultiFrame::MultiFrame(MultiFrame &&other) noexcept : frames_(std::move(other.frames_)) {} +FrameWithValidity &MultiFrame::GetFirstFrame() { + MG_ASSERT(!frames_.empty()); + return frames_.front(); +} + void MultiFrame::MakeAllFramesInvalid() noexcept { std::for_each(frames_.begin(), frames_.end(), [](auto &frame) { frame.MakeInvalid(); }); } diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 7c0f366eb..e76f34c5a 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -71,6 +71,13 @@ class MultiFrame { */ InvalidFramesPopulator GetInvalidFramesPopulator(); + /** + * Return the first Frame of the MultiFrame. This is only meant to be used in very specific cases. Please consider + * using the iterators instead. + * The Frame can be valid or invalid. + */ + FrameWithValidity &GetFirstFrame(); + void MakeAllFramesInvalid() noexcept; bool HasValidFrame() const noexcept; diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index c9fd05609..9a000face 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -267,14 +267,11 @@ bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) { void Once::OnceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &context) { SCOPED_PROFILE_OP("OnceMF"); - auto iterator_for_valid_frame_only = multi_frame.GetValidFramesConsumer(); - auto first_it = iterator_for_valid_frame_only.begin(); - MG_ASSERT(first_it != iterator_for_valid_frame_only.end()); if (!did_pull_) { - auto *memory_resource = multi_frame.GetMemoryResource(); - auto &frame = *first_it; - frame.MakeValid(); - for (auto &value : frame.elems()) { + auto &first_frame = multi_frame.GetFirstFrame(); + auto *memory_resource = first_frame.GetMemoryResource(); + first_frame.MakeValid(); + for (auto &value : first_frame.elems()) { value = TypedValue{memory_resource}; } did_pull_ = true; From 54907d2a1aefba1a4fb34ebd26b9227534908a1c Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Thu, 1 Dec 2022 14:19:15 +0100 Subject: [PATCH 51/93] Update src/query/v2/multiframe.cpp Co-authored-by: Kostas Kyrimis --- src/query/v2/multiframe.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 27b0872ed..05d2a755b 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -57,7 +57,6 @@ void MultiFrame::DefragmentValidFrames() noexcept { in the range in such a way that the elements that are not to be removed appear in the beginning of the range. Relative order of the elements that remain is preserved and the physical size of the container is unchanged." */ - [[maybe_unused]] const auto it = std::remove_if(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); } From 00fd69c17090ca259dfb0c1916b4c34e8ba2482f Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 14:19:41 +0100 Subject: [PATCH 52/93] Add statement to ignroe clang warning --- src/query/v2/multiframe.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 74ccf8478..41ac7c6ed 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -62,8 +62,8 @@ void MultiFrame::DefragmentValidFrames() noexcept { in the range in such a way that the elements that are not to be removed appear in the beginning of the range. Relative order of the elements that remain is preserved and the physical size of the container is unchanged." */ - [[maybe_unused]] const auto it = - std::remove_if(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); + // NOLINTNEXTLINE (bugprone-unused-return-value) + std::remove_if(frames_.begin(), frames_.end(), [](auto &frame) { return !frame.IsValid(); }); } ValidFramesReader MultiFrame::GetValidFramesReader() { return ValidFramesReader{*this}; } From d0c960e90036dcf4a8422441039a3108009010ea Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Thu, 1 Dec 2022 14:20:19 +0100 Subject: [PATCH 53/93] Update src/query/v2/multiframe.hpp Co-authored-by: Kostas Kyrimis --- src/query/v2/multiframe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 5583e80f3..5651365a7 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -33,7 +33,7 @@ class MultiFrame { MultiFrame(int64_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory); ~MultiFrame() = default; - MultiFrame(const MultiFrame &other); // copy constructor + MultiFrame(const MultiFrame &other); MultiFrame(MultiFrame &&other) noexcept; // move constructor MultiFrame &operator=(const MultiFrame &other) = delete; MultiFrame &operator=(MultiFrame &&other) noexcept = delete; From ee9ba1a7f8a2f5f6559c05a0673e4f27dba2b92e Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Thu, 1 Dec 2022 14:20:26 +0100 Subject: [PATCH 54/93] Update src/query/v2/multiframe.hpp Co-authored-by: Kostas Kyrimis --- src/query/v2/multiframe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 5651365a7..782a1e8fc 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -34,7 +34,7 @@ class MultiFrame { ~MultiFrame() = default; MultiFrame(const MultiFrame &other); - MultiFrame(MultiFrame &&other) noexcept; // move constructor + MultiFrame(MultiFrame &&other) noexcept; MultiFrame &operator=(const MultiFrame &other) = delete; MultiFrame &operator=(MultiFrame &&other) noexcept = delete; From 6c441b80ec3b2f0bbf53b318807fc9b743304561 Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Thu, 1 Dec 2022 14:20:57 +0100 Subject: [PATCH 55/93] Update src/query/v2/multiframe.hpp Co-authored-by: Kostas Kyrimis --- src/query/v2/multiframe.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 782a1e8fc..f0219c7df 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -90,10 +90,10 @@ class ValidFramesReader { explicit ValidFramesReader(MultiFrame &multiframe); ~ValidFramesReader() = default; - ValidFramesReader(const ValidFramesReader &other) = delete; // copy constructor - ValidFramesReader(ValidFramesReader &&other) noexcept = delete; // move constructor - ValidFramesReader &operator=(const ValidFramesReader &other) = delete; // copy assignment - ValidFramesReader &operator=(ValidFramesReader &&other) noexcept = delete; // move assignment + ValidFramesReader(const ValidFramesReader &other) = delete; + ValidFramesReader(ValidFramesReader &&other) noexcept = delete; + ValidFramesReader &operator=(const ValidFramesReader &other) = delete; + ValidFramesReader &operator=(ValidFramesReader &&other) noexcept = delete; struct Iterator { using iterator_category = std::forward_iterator_tag; From 452722f4f8067831996a2d576eb48ec62b98ad90 Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Thu, 1 Dec 2022 14:52:30 +0100 Subject: [PATCH 56/93] Update src/query/v2/multiframe.hpp Co-authored-by: Kostas Kyrimis --- src/query/v2/multiframe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index f0219c7df..852674c8c 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -159,7 +159,7 @@ class ValidFramesModifier { Iterator &operator++() { do { ptr_++; - } while (*this != iterator_wrapper_.end() && !this->ptr_->IsValid()); + } while (*this != iterator_wrapper_.end() && ptr_->IsValid()); return *this; } From 29347c83e79505f51d1d3e7d2b1f2111612b01df Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 14:54:26 +0100 Subject: [PATCH 57/93] Remove unneeded tag --- src/query/v2/multiframe.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index e76f34c5a..fae95ccca 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -85,7 +85,6 @@ class MultiFrame { inline utils::MemoryResource *GetMemoryResource() { return frames_[0].GetMemoryResource(); } private: - // NOLINTNEXTLINE (bugprone-exception-escape) void DefragmentValidFrames() noexcept; utils::pmr::vector frames_ = From 8eec8399a3539d282e59b95032b4ed10202cf40f Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 15:04:51 +0100 Subject: [PATCH 58/93] Rmove unneeded "this" --- src/query/v2/multiframe.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index a82c4a242..5a0a5af8b 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -118,7 +118,7 @@ class ValidFramesReader { Iterator &operator++() { do { ptr_++; - } while (*this != iterator_wrapper_->end() && !this->ptr_->IsValid()); + } while (*this != iterator_wrapper_->end() && !ptr_->IsValid()); return *this; } @@ -165,7 +165,7 @@ class ValidFramesModifier { Iterator &operator++() { do { ptr_++; - } while (*this != iterator_wrapper_.end() && ptr_->IsValid()); + } while (*this != iterator_wrapper_->end() && ptr_->IsValid()); return *this; } @@ -212,7 +212,7 @@ class ValidFramesConsumer { Iterator &operator++() { do { ptr_++; - } while (*this != iterator_wrapper_->end() && !this->ptr_->IsValid()); + } while (*this != iterator_wrapper_->end() && !ptr_->IsValid()); return *this; } From 5cd0d5137eb266dfc75fa71593faa38cb6ff94fd Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Thu, 1 Dec 2022 15:18:50 +0100 Subject: [PATCH 59/93] Update src/query/v2/multiframe.hpp Co-authored-by: Kostas Kyrimis --- src/query/v2/multiframe.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 852674c8c..edeb9094d 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -81,8 +81,7 @@ class MultiFrame { // NOLINTNEXTLINE (bugprone-exception-escape) void DefragmentValidFrames() noexcept; - utils::pmr::vector frames_ = - utils::pmr::vector(0, FrameWithValidity{1}, utils::NewDeleteResource()); + utils::pmr::vector frames_; }; class ValidFramesReader { From 4bbf3c95ca8a87834983ce2f2955f6a2e8878eec Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Thu, 1 Dec 2022 15:49:17 +0100 Subject: [PATCH 60/93] Update src/query/v2/multiframe.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: János Benjamin Antal --- src/query/v2/multiframe.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 904961213..ab47ee19f 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -32,11 +32,7 @@ MultiFrame::MultiFrame(int64_t size_of_frame, size_t number_of_frames, utils::Me MG_ASSERT(number_of_frames > 0); } -MultiFrame::MultiFrame(const MultiFrame &other) { - frames_.reserve(other.frames_.size()); - std::transform(other.frames_.begin(), other.frames_.end(), std::back_inserter(frames_), - [](const auto &other_frame) { return other_frame; }); -} +MultiFrame::MultiFrame(const MultiFrame &other) :frames_{other.frames_} {} // NOLINTNEXTLINE (bugprone-exception-escape) MultiFrame::MultiFrame(MultiFrame &&other) noexcept : frames_(std::move(other.frames_)) {} From db45845619eddbe7470fbad2b148d0f62710c071 Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 15:52:35 +0100 Subject: [PATCH 61/93] format --- src/query/v2/multiframe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index ab47ee19f..e5aaca883 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -32,7 +32,7 @@ MultiFrame::MultiFrame(int64_t size_of_frame, size_t number_of_frames, utils::Me MG_ASSERT(number_of_frames > 0); } -MultiFrame::MultiFrame(const MultiFrame &other) :frames_{other.frames_} {} +MultiFrame::MultiFrame(const MultiFrame &other) : frames_{other.frames_} {} // NOLINTNEXTLINE (bugprone-exception-escape) MultiFrame::MultiFrame(MultiFrame &&other) noexcept : frames_(std::move(other.frames_)) {} From 13cabcaab5362157f84ca5f0dda0017e9dad1650 Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 16:31:21 +0100 Subject: [PATCH 62/93] Re-implement ValidFramesReader and iterators --- src/query/v2/multiframe.cpp | 22 ++++++++++++++++++---- src/query/v2/multiframe.hpp | 10 +++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index e5aaca883..c007baf7c 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -11,6 +11,9 @@ #include "query/v2/multiframe.hpp" +#include +#include + #include "query/v2/bindings/frame.hpp" #include "utils/pmr/vector.hpp" @@ -71,12 +74,23 @@ ValidFramesConsumer MultiFrame::GetValidFramesConsumer() { return ValidFramesCon InvalidFramesPopulator MultiFrame::GetInvalidFramesPopulator() { return InvalidFramesPopulator{*this}; } -ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multiframe) {} +ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multiframe) { + /* + From: https://en.cppreference.com/w/cpp/algorithm/find + Returns an iterator to the first element in the range [first, last) that satisfies specific criteria: + find_if searches for an element for which predicate p returns true + Return value + Iterator to the first element satisfying the condition or last if no such element is found. + + -> this is what we want. We want the "after" last valid frame (weather this is vector::end or and invalid frame). + */ + auto it = std::find_if(multiframe.frames_.begin(), multiframe.frames_.end(), + [](const auto &frame) { return !frame.IsValid(); }); + after_last_valid_frame_ = multiframe_.frames_.data() + std::distance(multiframe.frames_.begin(), it); +} ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator{&multiframe_.frames_[0], *this}; } -ValidFramesReader::Iterator ValidFramesReader::end() { - return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this}; -} +ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator{after_last_valid_frame_, *this}; } ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {} diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index cc3dbd12a..c1bf9cdf1 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -107,18 +107,14 @@ class ValidFramesReader { using pointer = value_type *; using reference = const Frame &; - Iterator(FrameWithValidity *ptr, ValidFramesReader &iterator_wrapper) - : ptr_(ptr), iterator_wrapper_(&iterator_wrapper) {} + explicit Iterator(FrameWithValidity *ptr, ValidFramesReader &iterator_wrapper) : ptr_(ptr) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } // Prefix increment Iterator &operator++() { - do { - ptr_++; - } while (*this != iterator_wrapper_->end() && !ptr_->IsValid()); - + ptr_++; return *this; } @@ -127,13 +123,13 @@ class ValidFramesReader { private: FrameWithValidity *ptr_; - ValidFramesReader *iterator_wrapper_; }; Iterator begin(); Iterator end(); private: + FrameWithValidity *after_last_valid_frame_; MultiFrame &multiframe_; }; From 2120645d6a2a7802b76d2dbb8a1fbaa81b53019c Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Thu, 1 Dec 2022 17:38:24 +0200 Subject: [PATCH 63/93] Remove dead code in request_router simulation test --- tests/simulation/request_router.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/simulation/request_router.cpp b/tests/simulation/request_router.cpp index 526e6365b..e990c867e 100644 --- a/tests/simulation/request_router.cpp +++ b/tests/simulation/request_router.cpp @@ -160,13 +160,6 @@ void TestScanVertices(query::v2::RequestRouterInterface &request_router) { prop = result[1].GetProperty(msgs::PropertyId::FromUint(0)); MG_ASSERT(prop.int_v == 444); } - - // result = request_router.ScanVertices("test_label"); - // { - // MG_ASSERT(result.size() == 1); - // auto prop = result[0].GetProperty(msgs::PropertyId::FromUint(0)); - // MG_ASSERT(prop.int_v == 1); - // } } void TestCreateVertices(query::v2::RequestRouterInterface &request_router) { From c15e75b48cea40e980706a44647753842e63fb61 Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Thu, 1 Dec 2022 17:40:58 +0200 Subject: [PATCH 64/93] Remove old shard request manager header --- src/query/v2/shard_request_manager.hpp | 812 ------------------------- 1 file changed, 812 deletions(-) delete mode 100644 src/query/v2/shard_request_manager.hpp diff --git a/src/query/v2/shard_request_manager.hpp b/src/query/v2/shard_request_manager.hpp deleted file mode 100644 index 4db77e645..000000000 --- a/src/query/v2/shard_request_manager.hpp +++ /dev/null @@ -1,812 +0,0 @@ -// Copyright 2022 Memgraph Ltd. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source -// License, and you may not use this file except in compliance with the Business Source License. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coordinator/coordinator.hpp" -#include "coordinator/coordinator_client.hpp" -#include "coordinator/coordinator_rsm.hpp" -#include "coordinator/shard_map.hpp" -#include "io/address.hpp" -#include "io/errors.hpp" -#include "io/rsm/raft.hpp" -#include "io/rsm/rsm_client.hpp" -#include "io/rsm/shard_rsm.hpp" -#include "io/simulator/simulator.hpp" -#include "io/simulator/simulator_transport.hpp" -#include "query/v2/accessors.hpp" -#include "query/v2/requests.hpp" -#include "storage/v3/id_types.hpp" -#include "storage/v3/value_conversions.hpp" -#include "utils/result.hpp" - -namespace memgraph::msgs { -template -class RsmStorageClientManager { - public: - using CompoundKey = memgraph::io::rsm::ShardRsmKey; - using Shard = memgraph::coordinator::Shard; - using LabelId = memgraph::storage::v3::LabelId; - RsmStorageClientManager() = default; - RsmStorageClientManager(const RsmStorageClientManager &) = delete; - RsmStorageClientManager(RsmStorageClientManager &&) = delete; - RsmStorageClientManager &operator=(const RsmStorageClientManager &) = delete; - RsmStorageClientManager &operator=(RsmStorageClientManager &&) = delete; - ~RsmStorageClientManager() = default; - - void AddClient(Shard key, TStorageClient client) { cli_cache_.emplace(std::move(key), std::move(client)); } - - bool Exists(const Shard &key) { return cli_cache_.contains(key); } - - void PurgeCache() { cli_cache_.clear(); } - - TStorageClient &GetClient(const Shard &key) { - auto it = cli_cache_.find(key); - MG_ASSERT(it != cli_cache_.end(), "Non-existing shard client"); - return it->second; - } - - private: - std::map cli_cache_; -}; - -template -struct ExecutionState { - using CompoundKey = memgraph::io::rsm::ShardRsmKey; - using Shard = memgraph::coordinator::Shard; - - enum State : int8_t { INITIALIZING, EXECUTING, COMPLETED }; - // label is optional because some operators can create/remove etc, vertices. These kind of requests contain the label - // on the request itself. - std::optional label; - // CompoundKey is optional because some operators require to iterate over all the available keys - // of a shard. One example is ScanAll, where we only require the field label. - std::optional key; - // Transaction id to be filled by the ShardRequestManager implementation - memgraph::coordinator::Hlc transaction_id; - // Initialized by ShardRequestManager implementation. This vector is filled with the shards that - // the ShardRequestManager impl will send requests to. When a request to a shard exhausts it, meaning that - // it pulled all the requested data from the given Shard, it will be removed from the Vector. When the Vector becomes - // empty, it means that all of the requests have completed succefully. - // TODO(gvolfing) - // Maybe make this into a more complex object to be able to keep track of paginated resutls. E.g. instead of a vector - // of Shards make it into a std::vector> (probably a struct instead of a pair) - // where PaginatedResultType is an enum signaling the progress on the given request. This way we can easily check if - // a partial response on a shard(if there is one) is finished and we can send off the request for the next batch. - std::vector shard_cache; - // 1-1 mapping with `shard_cache`. - // A vector that tracks request metadata for each shard (For example, next_id for a ScanAll on Shard A) - std::vector requests; - State state = INITIALIZING; -}; - -class ShardRequestManagerInterface { - public: - using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor; - using EdgeAccessor = memgraph::query::v2::accessors::EdgeAccessor; - ShardRequestManagerInterface() = default; - ShardRequestManagerInterface(const ShardRequestManagerInterface &) = delete; - ShardRequestManagerInterface(ShardRequestManagerInterface &&) = delete; - ShardRequestManagerInterface &operator=(const ShardRequestManagerInterface &) = delete; - ShardRequestManagerInterface &&operator=(ShardRequestManagerInterface &&) = delete; - - virtual ~ShardRequestManagerInterface() = default; - - virtual void StartTransaction() = 0; - virtual void Commit() = 0; - virtual std::vector Request(ExecutionState &state) = 0; - virtual std::vector Request(ExecutionState &state, - std::vector new_vertices) = 0; - virtual std::vector Request(ExecutionState &state, - ExpandOneRequest request) = 0; - virtual std::vector Request(ExecutionState &state, - std::vector new_edges) = 0; - virtual std::vector Request(ExecutionState &state, - GetPropertiesRequest request) = 0; - virtual storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) const = 0; - virtual storage::v3::PropertyId NameToProperty(const std::string &name) const = 0; - virtual storage::v3::LabelId NameToLabel(const std::string &name) const = 0; - virtual const std::string &PropertyToName(memgraph::storage::v3::PropertyId prop) const = 0; - virtual const std::string &LabelToName(memgraph::storage::v3::LabelId label) const = 0; - virtual const std::string &EdgeTypeToName(memgraph::storage::v3::EdgeTypeId type) const = 0; - virtual bool IsPrimaryLabel(LabelId label) const = 0; - virtual bool IsPrimaryKey(LabelId primary_label, PropertyId property) const = 0; -}; - -// TODO(kostasrim)rename this class template -template -class ShardRequestManager : public ShardRequestManagerInterface { - public: - using StorageClient = - memgraph::coordinator::RsmClient; - using CoordinatorWriteRequests = memgraph::coordinator::CoordinatorWriteRequests; - using CoordinatorClient = memgraph::coordinator::CoordinatorClient; - using Address = memgraph::io::Address; - using Shard = memgraph::coordinator::Shard; - using ShardMap = memgraph::coordinator::ShardMap; - using CompoundKey = memgraph::coordinator::PrimaryKey; - using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor; - using EdgeAccessor = memgraph::query::v2::accessors::EdgeAccessor; - ShardRequestManager(CoordinatorClient coord, memgraph::io::Io &&io) - : coord_cli_(std::move(coord)), io_(std::move(io)) {} - - ShardRequestManager(const ShardRequestManager &) = delete; - ShardRequestManager(ShardRequestManager &&) = delete; - ShardRequestManager &operator=(const ShardRequestManager &) = delete; - ShardRequestManager &operator=(ShardRequestManager &&) = delete; - - ~ShardRequestManager() override {} - - void StartTransaction() override { - memgraph::coordinator::HlcRequest req{.last_shard_map_version = shards_map_.GetHlc()}; - CoordinatorWriteRequests write_req = req; - auto write_res = coord_cli_.SendWriteRequest(write_req); - if (write_res.HasError()) { - throw std::runtime_error("HLC request failed"); - } - auto coordinator_write_response = write_res.GetValue(); - auto hlc_response = std::get(coordinator_write_response); - - // Transaction ID to be used later... - transaction_id_ = hlc_response.new_hlc; - - if (hlc_response.fresher_shard_map) { - shards_map_ = hlc_response.fresher_shard_map.value(); - SetUpNameIdMappers(); - } - } - - void Commit() override { - memgraph::coordinator::HlcRequest req{.last_shard_map_version = shards_map_.GetHlc()}; - CoordinatorWriteRequests write_req = req; - auto write_res = coord_cli_.SendWriteRequest(write_req); - if (write_res.HasError()) { - throw std::runtime_error("HLC request for commit failed"); - } - auto coordinator_write_response = write_res.GetValue(); - auto hlc_response = std::get(coordinator_write_response); - - if (hlc_response.fresher_shard_map) { - shards_map_ = hlc_response.fresher_shard_map.value(); - SetUpNameIdMappers(); - } - auto commit_timestamp = hlc_response.new_hlc; - - msgs::CommitRequest commit_req{.transaction_id = transaction_id_, .commit_timestamp = commit_timestamp}; - - for (const auto &[label, space] : shards_map_.label_spaces) { - for (const auto &[key, shard] : space.shards) { - auto &storage_client = GetStorageClientForShard(shard); - // TODO(kostasrim) Currently requests return the result directly. Adjust this when the API works MgFuture - // instead. - auto commit_response = storage_client.SendWriteRequest(commit_req); - // RETRY on timeouts? - // Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map test - if (commit_response.HasError()) { - throw std::runtime_error("Commit request timed out"); - } - WriteResponses write_response_variant = commit_response.GetValue(); - auto &response = std::get(write_response_variant); - if (response.error) { - throw std::runtime_error("Commit request did not succeed"); - } - } - } - } - - storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) const override { - return shards_map_.GetEdgeTypeId(name).value(); - } - - storage::v3::PropertyId NameToProperty(const std::string &name) const override { - return shards_map_.GetPropertyId(name).value(); - } - - storage::v3::LabelId NameToLabel(const std::string &name) const override { - return shards_map_.GetLabelId(name).value(); - } - - const std::string &PropertyToName(memgraph::storage::v3::PropertyId id) const override { - return properties_.IdToName(id.AsUint()); - } - const std::string &LabelToName(memgraph::storage::v3::LabelId id) const override { - return labels_.IdToName(id.AsUint()); - } - const std::string &EdgeTypeToName(memgraph::storage::v3::EdgeTypeId id) const override { - return edge_types_.IdToName(id.AsUint()); - } - - bool IsPrimaryKey(LabelId primary_label, PropertyId property) const override { - const auto schema_it = shards_map_.schemas.find(primary_label); - MG_ASSERT(schema_it != shards_map_.schemas.end(), "Invalid primary label id: {}", primary_label.AsUint()); - - return std::find_if(schema_it->second.begin(), schema_it->second.end(), [property](const auto &schema_prop) { - return schema_prop.property_id == property; - }) != schema_it->second.end(); - } - - bool IsPrimaryLabel(LabelId label) const override { return shards_map_.label_spaces.contains(label); } - - // TODO(kostasrim) Simplify return result - std::vector Request(ExecutionState &state) override { - MaybeInitializeExecutionState(state); - std::vector responses; - - SendAllRequests(state); - auto all_requests_gathered = [](auto &paginated_rsp_tracker) { - return std::ranges::all_of(paginated_rsp_tracker, [](const auto &state) { - return state.second == PaginatedResponseState::PartiallyFinished; - }); - }; - - std::map paginated_response_tracker; - for (const auto &shard : state.shard_cache) { - paginated_response_tracker.insert(std::make_pair(shard, PaginatedResponseState::Pending)); - } - - do { - AwaitOnPaginatedRequests(state, responses, paginated_response_tracker); - } while (!all_requests_gathered(paginated_response_tracker)); - - MaybeCompleteState(state); - // TODO(kostasrim) Before returning start prefetching the batch (this shall be done once we get MgFuture as return - // result of storage_client.SendReadRequest()). - return PostProcess(std::move(responses)); - } - - std::vector Request(ExecutionState &state, - std::vector new_vertices) override { - MG_ASSERT(!new_vertices.empty()); - MaybeInitializeExecutionState(state, new_vertices); - std::vector responses; - auto &shard_cache_ref = state.shard_cache; - - // 1. Send the requests. - SendAllRequests(state, shard_cache_ref); - - // 2. Block untill all the futures are exhausted - do { - AwaitOnResponses(state, responses); - } while (!state.shard_cache.empty()); - - MaybeCompleteState(state); - // TODO(kostasrim) Before returning start prefetching the batch (this shall be done once we get MgFuture as return - // result of storage_client.SendReadRequest()). - return responses; - } - - std::vector Request(ExecutionState &state, - std::vector new_edges) override { - MG_ASSERT(!new_edges.empty()); - MaybeInitializeExecutionState(state, new_edges); - std::vector responses; - auto &shard_cache_ref = state.shard_cache; - size_t id{0}; - for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) { - auto &storage_client = GetStorageClientForShard(*shard_it); - WriteRequests req = state.requests[id]; - auto write_response_result = storage_client.SendWriteRequest(std::move(req)); - if (write_response_result.HasError()) { - throw std::runtime_error("CreateVertices request timedout"); - } - WriteResponses response_variant = write_response_result.GetValue(); - CreateExpandResponse mapped_response = std::get(response_variant); - - if (mapped_response.error) { - throw std::runtime_error("CreateExpand request did not succeed"); - } - responses.push_back(mapped_response); - shard_it = shard_cache_ref.erase(shard_it); - } - // We are done with this state - MaybeCompleteState(state); - return responses; - } - - std::vector Request(ExecutionState &state, ExpandOneRequest request) override { - // TODO(kostasrim)Update to limit the batch size here - // Expansions of the destination must be handled by the caller. For example - // match (u:L1 { prop : 1 })-[:Friend]-(v:L1) - // For each vertex U, the ExpandOne will result in . The destination vertex and its properties - // must be fetched again with an ExpandOne(Edges.dst) - MaybeInitializeExecutionState(state, std::move(request)); - std::vector responses; - auto &shard_cache_ref = state.shard_cache; - - // 1. Send the requests. - SendAllRequests(state, shard_cache_ref); - - // 2. Block untill all the futures are exhausted - do { - AwaitOnResponses(state, responses); - } while (!state.shard_cache.empty()); - std::vector result_rows; - const auto total_row_count = std::accumulate( - responses.begin(), responses.end(), 0, - [](const int64_t partial_count, const ExpandOneResponse &resp) { return partial_count + resp.result.size(); }); - result_rows.reserve(total_row_count); - - for (auto &response : responses) { - result_rows.insert(result_rows.end(), std::make_move_iterator(response.result.begin()), - std::make_move_iterator(response.result.end())); - } - MaybeCompleteState(state); - return result_rows; - } - - std::vector Request(ExecutionState &state, - GetPropertiesRequest requests) override { - MaybeInitializeExecutionState(state, std::move(requests)); - SendAllRequests(state); - - std::vector responses; - // 2. Block untill all the futures are exhausted - do { - AwaitOnResponses(state, responses); - } while (!state.shard_cache.empty()); - - MaybeCompleteState(state); - return responses; - } - - private: - enum class PaginatedResponseState { Pending, PartiallyFinished }; - - std::vector PostProcess(std::vector &&responses) const { - std::vector accessors; - for (auto &response : responses) { - for (auto &result_row : response.results) { - accessors.emplace_back(VertexAccessor(std::move(result_row.vertex), std::move(result_row.props), this)); - } - } - return accessors; - } - - template - void ThrowIfStateCompleted(ExecutionState &state) const { - if (state.state == ExecutionState::COMPLETED) [[unlikely]] { - throw std::runtime_error("State is completed and must be reset"); - } - } - - template - void ThrowIfStateExecuting(ExecutionState &state) const { - if (state.state == ExecutionState::EXECUTING) [[unlikely]] { - throw std::runtime_error("State is completed and must be reset"); - } - } - - template - void MaybeCompleteState(ExecutionState &state) const { - if (state.requests.empty()) { - state.state = ExecutionState::COMPLETED; - } - } - - template - bool ShallNotInitializeState(ExecutionState &state) const { - return state.state != ExecutionState::INITIALIZING; - } - - void MaybeInitializeExecutionState(ExecutionState &state, - std::vector new_vertices) { - ThrowIfStateCompleted(state); - if (ShallNotInitializeState(state)) { - return; - } - state.transaction_id = transaction_id_; - - std::map per_shard_request_table; - - for (auto &new_vertex : new_vertices) { - MG_ASSERT(!new_vertex.label_ids.empty(), "This is error!"); - auto shard = shards_map_.GetShardForKey(new_vertex.label_ids[0].id, - storage::conversions::ConvertPropertyVector(new_vertex.primary_key)); - if (!per_shard_request_table.contains(shard)) { - CreateVerticesRequest create_v_rqst{.transaction_id = transaction_id_}; - per_shard_request_table.insert(std::pair(shard, std::move(create_v_rqst))); - state.shard_cache.push_back(shard); - } - per_shard_request_table[shard].new_vertices.push_back(std::move(new_vertex)); - } - - for (auto &[shard, rqst] : per_shard_request_table) { - state.requests.push_back(std::move(rqst)); - } - state.state = ExecutionState::EXECUTING; - } - - void MaybeInitializeExecutionState(ExecutionState &state, std::vector new_expands) { - ThrowIfStateCompleted(state); - if (ShallNotInitializeState(state)) { - return; - } - state.transaction_id = transaction_id_; - - std::map per_shard_request_table; - auto ensure_shard_exists_in_table = [&per_shard_request_table, - transaction_id = transaction_id_](const Shard &shard) { - if (!per_shard_request_table.contains(shard)) { - CreateExpandRequest create_expand_request{.transaction_id = transaction_id}; - per_shard_request_table.insert({shard, std::move(create_expand_request)}); - } - }; - - for (auto &new_expand : new_expands) { - const auto shard_src_vertex = shards_map_.GetShardForKey( - new_expand.src_vertex.first.id, storage::conversions::ConvertPropertyVector(new_expand.src_vertex.second)); - const auto shard_dest_vertex = shards_map_.GetShardForKey( - new_expand.dest_vertex.first.id, storage::conversions::ConvertPropertyVector(new_expand.dest_vertex.second)); - - ensure_shard_exists_in_table(shard_src_vertex); - - if (shard_src_vertex != shard_dest_vertex) { - ensure_shard_exists_in_table(shard_dest_vertex); - per_shard_request_table[shard_dest_vertex].new_expands.push_back(new_expand); - } - per_shard_request_table[shard_src_vertex].new_expands.push_back(std::move(new_expand)); - } - - for (auto &[shard, request] : per_shard_request_table) { - state.shard_cache.push_back(shard); - state.requests.push_back(std::move(request)); - } - state.state = ExecutionState::EXECUTING; - } - - void MaybeInitializeExecutionState(ExecutionState &state) { - ThrowIfStateCompleted(state); - if (ShallNotInitializeState(state)) { - return; - } - - std::vector multi_shards; - state.transaction_id = transaction_id_; - if (!state.label) { - multi_shards = shards_map_.GetAllShards(); - } else { - const auto label_id = shards_map_.GetLabelId(*state.label); - MG_ASSERT(label_id); - MG_ASSERT(IsPrimaryLabel(*label_id)); - multi_shards = {shards_map_.GetShardsForLabel(*state.label)}; - } - for (auto &shards : multi_shards) { - for (auto &[key, shard] : shards) { - MG_ASSERT(!shard.empty()); - state.shard_cache.push_back(std::move(shard)); - ScanVerticesRequest rqst; - rqst.transaction_id = transaction_id_; - rqst.start_id.second = storage::conversions::ConvertValueVector(key); - state.requests.push_back(std::move(rqst)); - } - } - state.state = ExecutionState::EXECUTING; - } - - void MaybeInitializeExecutionState(ExecutionState &state, ExpandOneRequest request) { - ThrowIfStateCompleted(state); - if (ShallNotInitializeState(state)) { - return; - } - state.transaction_id = transaction_id_; - - std::map per_shard_request_table; - auto top_level_rqst_template = request; - top_level_rqst_template.transaction_id = transaction_id_; - top_level_rqst_template.src_vertices.clear(); - state.requests.clear(); - for (auto &vertex : request.src_vertices) { - auto shard = - shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second)); - if (!per_shard_request_table.contains(shard)) { - per_shard_request_table.insert(std::pair(shard, top_level_rqst_template)); - state.shard_cache.push_back(shard); - } - per_shard_request_table[shard].src_vertices.push_back(vertex); - } - - for (auto &[shard, rqst] : per_shard_request_table) { - state.requests.push_back(std::move(rqst)); - } - state.state = ExecutionState::EXECUTING; - } - - void MaybeInitializeExecutionState(ExecutionState &state, GetPropertiesRequest request) { - ThrowIfStateCompleted(state); - ThrowIfStateExecuting(state); - - std::map per_shard_request_table; - auto top_level_rqst_template = request; - top_level_rqst_template.transaction_id = transaction_id_; - top_level_rqst_template.vertices_and_edges.clear(); - - state.transaction_id = transaction_id_; - - for (auto &[vertex, maybe_edge] : request.vertices_and_edges) { - auto shard = - shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second)); - if (!per_shard_request_table.contains(shard)) { - per_shard_request_table.insert(std::pair(shard, top_level_rqst_template)); - state.shard_cache.push_back(shard); - } - per_shard_request_table[shard].vertices_and_edges.push_back({std::move(vertex), maybe_edge}); - } - - for (auto &[shard, rqst] : per_shard_request_table) { - state.requests.push_back(std::move(rqst)); - } - state.state = ExecutionState::EXECUTING; - } - - StorageClient &GetStorageClientForShard(Shard shard) { - if (!storage_cli_manager_.Exists(shard)) { - AddStorageClientToManager(shard); - } - return storage_cli_manager_.GetClient(shard); - } - - StorageClient &GetStorageClientForShard(const std::string &label, const CompoundKey &key) { - auto shard = shards_map_.GetShardForKey(label, key); - return GetStorageClientForShard(std::move(shard)); - } - - void AddStorageClientToManager(Shard target_shard) { - MG_ASSERT(!target_shard.empty()); - auto leader_addr = target_shard.front(); - std::vector
addresses; - addresses.reserve(target_shard.size()); - for (auto &address : target_shard) { - addresses.push_back(std::move(address.address)); - } - auto cli = StorageClient(io_, std::move(leader_addr.address), std::move(addresses)); - storage_cli_manager_.AddClient(target_shard, std::move(cli)); - } - - template - void SendAllRequests(ExecutionState &state) { - int64_t shard_idx = 0; - for (const auto &request : state.requests) { - const auto ¤t_shard = state.shard_cache[shard_idx]; - - auto &storage_client = GetStorageClientForShard(current_shard); - ReadRequests req = request; - storage_client.SendAsyncReadRequest(request); - - ++shard_idx; - } - } - - void SendAllRequests(ExecutionState &state, - std::vector &shard_cache_ref) { - size_t id = 0; - for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++shard_it) { - // This is fine because all new_vertices of each request end up on the same shard - const auto labels = state.requests[id].new_vertices[0].label_ids; - auto req_deep_copy = state.requests[id]; - - for (auto &new_vertex : req_deep_copy.new_vertices) { - new_vertex.label_ids.erase(new_vertex.label_ids.begin()); - } - - auto &storage_client = GetStorageClientForShard(*shard_it); - - WriteRequests req = req_deep_copy; - storage_client.SendAsyncWriteRequest(req); - ++id; - } - } - - void SendAllRequests(ExecutionState &state, - std::vector &shard_cache_ref) { - size_t id = 0; - for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++shard_it) { - auto &storage_client = GetStorageClientForShard(*shard_it); - ReadRequests req = state.requests[id]; - storage_client.SendAsyncReadRequest(req); - ++id; - } - } - - void AwaitOnResponses(ExecutionState &state, std::vector &responses) { - auto &shard_cache_ref = state.shard_cache; - int64_t request_idx = 0; - - for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) { - auto &storage_client = GetStorageClientForShard(*shard_it); - - auto poll_result = storage_client.AwaitAsyncWriteRequest(); - if (!poll_result) { - ++shard_it; - ++request_idx; - - continue; - } - - if (poll_result->HasError()) { - throw std::runtime_error("CreateVertices request timed out"); - } - - WriteResponses response_variant = poll_result->GetValue(); - auto response = std::get(response_variant); - - if (response.error) { - throw std::runtime_error("CreateVertices request did not succeed"); - } - responses.push_back(response); - - shard_it = shard_cache_ref.erase(shard_it); - // Needed to maintain the 1-1 mapping between the ShardCache and the requests. - auto it = state.requests.begin() + request_idx; - state.requests.erase(it); - } - } - - void AwaitOnResponses(ExecutionState &state, std::vector &responses) { - auto &shard_cache_ref = state.shard_cache; - int64_t request_idx = 0; - - for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) { - auto &storage_client = GetStorageClientForShard(*shard_it); - - auto poll_result = storage_client.PollAsyncReadRequest(); - if (!poll_result) { - ++shard_it; - ++request_idx; - continue; - } - - if (poll_result->HasError()) { - throw std::runtime_error("ExpandOne request timed out"); - } - - ReadResponses response_variant = poll_result->GetValue(); - auto response = std::get(response_variant); - // -NOTE- - // Currently a boolean flag for signaling the overall success of the - // ExpandOne request does not exist. But it should, so here we assume - // that it is already in place. - if (response.error) { - throw std::runtime_error("ExpandOne request did not succeed"); - } - - responses.push_back(std::move(response)); - shard_it = shard_cache_ref.erase(shard_it); - // Needed to maintain the 1-1 mapping between the ShardCache and the requests. - auto it = state.requests.begin() + request_idx; - state.requests.erase(it); - } - } - - void AwaitOnResponses(ExecutionState &state, std::vector &responses) { - auto &shard_cache_ref = state.shard_cache; - int64_t request_idx = 0; - - for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) { - auto &storage_client = GetStorageClientForShard(*shard_it); - - auto poll_result = storage_client.PollAsyncReadRequest(); - if (!poll_result) { - ++shard_it; - ++request_idx; - continue; - } - - if (poll_result->HasError()) { - throw std::runtime_error("GetProperties request timed out"); - } - - ReadResponses response_variant = poll_result->GetValue(); - auto response = std::get(response_variant); - if (response.result != GetPropertiesResponse::SUCCESS) { - throw std::runtime_error("GetProperties request did not succeed"); - } - - responses.push_back(std::move(response)); - shard_it = shard_cache_ref.erase(shard_it); - // Needed to maintain the 1-1 mapping between the ShardCache and the requests. - auto it = state.requests.begin() + request_idx; - state.requests.erase(it); - } - } - - void AwaitOnPaginatedRequests(ExecutionState &state, - std::vector &responses, - std::map &paginated_response_tracker) { - auto &shard_cache_ref = state.shard_cache; - - // Find the first request that is not holding a paginated response. - int64_t request_idx = 0; - for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) { - if (paginated_response_tracker.at(*shard_it) != PaginatedResponseState::Pending) { - ++shard_it; - ++request_idx; - continue; - } - - auto &storage_client = GetStorageClientForShard(*shard_it); - - auto await_result = storage_client.AwaitAsyncReadRequest(); - - if (!await_result) { - // Redirection has occured. - ++shard_it; - ++request_idx; - continue; - } - - if (await_result->HasError()) { - throw std::runtime_error("ScanAll request timed out"); - } - - ReadResponses read_response_variant = await_result->GetValue(); - auto response = std::get(read_response_variant); - if (response.error) { - throw std::runtime_error("ScanAll request did not succeed"); - } - - if (!response.next_start_id) { - paginated_response_tracker.erase((*shard_it)); - shard_cache_ref.erase(shard_it); - // Needed to maintain the 1-1 mapping between the ShardCache and the requests. - auto it = state.requests.begin() + request_idx; - state.requests.erase(it); - - } else { - state.requests[request_idx].start_id.second = response.next_start_id->second; - paginated_response_tracker[*shard_it] = PaginatedResponseState::PartiallyFinished; - } - responses.push_back(std::move(response)); - } - } - - void SetUpNameIdMappers() { - std::unordered_map id_to_name; - for (const auto &[name, id] : shards_map_.labels) { - id_to_name.emplace(id.AsUint(), name); - } - labels_.StoreMapping(std::move(id_to_name)); - id_to_name.clear(); - for (const auto &[name, id] : shards_map_.properties) { - id_to_name.emplace(id.AsUint(), name); - } - properties_.StoreMapping(std::move(id_to_name)); - id_to_name.clear(); - for (const auto &[name, id] : shards_map_.edge_types) { - id_to_name.emplace(id.AsUint(), name); - } - edge_types_.StoreMapping(std::move(id_to_name)); - } - - ShardMap shards_map_; - storage::v3::NameIdMapper properties_; - storage::v3::NameIdMapper edge_types_; - storage::v3::NameIdMapper labels_; - CoordinatorClient coord_cli_; - RsmStorageClientManager storage_cli_manager_; - memgraph::io::Io io_; - memgraph::coordinator::Hlc transaction_id_; - // TODO(kostasrim) Add batch prefetching -}; -} // namespace memgraph::msgs From 366a4e2b9a08d75dbbe64460ce28c78e1e3085e6 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Thu, 1 Dec 2022 15:56:16 +0000 Subject: [PATCH 65/93] Add support for efficiently executing multiple asynchronous requests out-of-order from the RequestRouter --- src/io/future.hpp | 26 +- src/io/local_transport/local_transport.hpp | 7 +- .../local_transport_handle.hpp | 8 +- src/io/notifier.hpp | 69 +++++ src/io/rsm/rsm_client.hpp | 100 ++++---- src/io/simulator/simulator_handle.hpp | 10 +- src/io/simulator/simulator_transport.hpp | 7 +- src/io/transport.hpp | 30 ++- src/query/v2/request_router.hpp | 238 ++++++++++-------- tests/unit/future.cpp | 12 +- tests/unit/high_density_shard_create_scan.cpp | 3 +- tests/unit/query_v2_expression_evaluator.cpp | 7 +- 12 files changed, 329 insertions(+), 188 deletions(-) create mode 100644 src/io/notifier.hpp diff --git a/src/io/future.hpp b/src/io/future.hpp index 98437b496..585f18938 100644 --- a/src/io/future.hpp +++ b/src/io/future.hpp @@ -35,10 +35,13 @@ class Shared { std::optional item_; bool consumed_ = false; bool waiting_ = false; - std::function simulator_notifier_ = nullptr; + bool filled_ = false; + std::function wait_notifier_ = nullptr; + std::function fill_notifier_ = nullptr; public: - explicit Shared(std::function simulator_notifier) : simulator_notifier_(simulator_notifier) {} + explicit Shared(std::function wait_notifier, std::function fill_notifier) + : wait_notifier_(wait_notifier), fill_notifier_(fill_notifier) {} Shared() = default; Shared(Shared &&) = delete; Shared &operator=(Shared &&) = delete; @@ -64,7 +67,7 @@ class Shared { waiting_ = true; while (!item_) { - if (simulator_notifier_) [[unlikely]] { + if (wait_notifier_) [[unlikely]] { // We can't hold our own lock while notifying // the simulator because notifying the simulator // involves acquiring the simulator's mutex @@ -76,7 +79,7 @@ class Shared { // so we have to get out of its way to avoid // a cyclical deadlock. lock.unlock(); - std::invoke(simulator_notifier_); + std::invoke(wait_notifier_); lock.lock(); if (item_) { // item may have been filled while we @@ -115,11 +118,19 @@ class Shared { std::unique_lock lock(mu_); MG_ASSERT(!consumed_, "Promise filled after it was already consumed!"); - MG_ASSERT(!item_, "Promise filled twice!"); + MG_ASSERT(!filled_, "Promise filled twice!"); item_ = item; + filled_ = true; } // lock released before condition variable notification + if (fill_notifier_) { + spdlog::trace("calling fill notifier"); + std::invoke(fill_notifier_); + } else { + spdlog::trace("not calling fill notifier"); + } + cv_.notify_all(); } @@ -251,8 +262,9 @@ std::pair, Promise> FuturePromisePair() { } template -std::pair, Promise> FuturePromisePairWithNotifier(std::function simulator_notifier) { - std::shared_ptr> shared = std::make_shared>(simulator_notifier); +std::pair, Promise> FuturePromisePairWithNotifications(std::function wait_notifier, + std::function fill_notifier) { + std::shared_ptr> shared = std::make_shared>(wait_notifier, fill_notifier); Future future = Future(shared); Promise promise = Promise(shared); diff --git a/src/io/local_transport/local_transport.hpp b/src/io/local_transport/local_transport.hpp index 258df6385..b64cabf1d 100644 --- a/src/io/local_transport/local_transport.hpp +++ b/src/io/local_transport/local_transport.hpp @@ -31,9 +31,10 @@ class LocalTransport { : local_transport_handle_(std::move(local_transport_handle)) {} template - ResponseFuture Request(Address to_address, Address from_address, RequestT request, Duration timeout) { - return local_transport_handle_->template SubmitRequest(to_address, from_address, - std::move(request), timeout); + ResponseFuture Request(Address to_address, Address from_address, RequestT request, + std::function fill_notifier, Duration timeout) { + return local_transport_handle_->template SubmitRequest( + to_address, from_address, std::move(request), timeout, fill_notifier); } template diff --git a/src/io/local_transport/local_transport_handle.hpp b/src/io/local_transport/local_transport_handle.hpp index 2303ae735..38538620f 100644 --- a/src/io/local_transport/local_transport_handle.hpp +++ b/src/io/local_transport/local_transport_handle.hpp @@ -140,8 +140,12 @@ class LocalTransportHandle { template ResponseFuture SubmitRequest(Address to_address, Address from_address, RequestT &&request, - Duration timeout) { - auto [future, promise] = memgraph::io::FuturePromisePair>(); + Duration timeout, std::function fill_notifier) { + auto [future, promise] = memgraph::io::FuturePromisePairWithNotifications>( + // set null notifier for when the Future::Wait is called + nullptr, + // set notifier for when Promise::Fill is called + std::forward>(fill_notifier)); const bool port_matches = to_address.last_known_port == from_address.last_known_port; const bool ip_matches = to_address.last_known_ip == from_address.last_known_ip; diff --git a/src/io/notifier.hpp b/src/io/notifier.hpp new file mode 100644 index 000000000..e6b073046 --- /dev/null +++ b/src/io/notifier.hpp @@ -0,0 +1,69 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +namespace memgraph::io { + +class ReadinessToken { + size_t id_; + + public: + explicit ReadinessToken(size_t id) : id_(id) {} + size_t GetId() const { return id_; } +}; + +class Inner { + std::condition_variable cv_; + std::mutex mu_; + std::vector ready_; + + public: + void Notify(ReadinessToken readiness_token) { + { + std::unique_lock lock(mu_); + spdlog::trace("Notifier notifying token {}", readiness_token.GetId()); + ready_.emplace_back(readiness_token); + } // mutex dropped + + cv_.notify_all(); + } + + ReadinessToken Await() { + std::unique_lock lock(mu_); + + while (ready_.empty()) { + cv_.wait(lock); + } + + ReadinessToken ret = ready_.back(); + ready_.pop_back(); + return ret; + } +}; + +class Notifier { + std::shared_ptr inner_; + + public: + Notifier() : inner_(std::make_shared()) {} + Notifier(const Notifier &) = default; + Notifier &operator=(const Notifier &) = default; + Notifier(Notifier &&old) = default; + Notifier &operator=(Notifier &&old) = default; + ~Notifier() = default; + + void Notify(ReadinessToken readiness_token) { inner_->Notify(readiness_token); } + + ReadinessToken Await() { return inner_->Await(); } +}; + +} // namespace memgraph::io diff --git a/src/io/rsm/rsm_client.hpp b/src/io/rsm/rsm_client.hpp index 920866c7a..1283ec3dc 100644 --- a/src/io/rsm/rsm_client.hpp +++ b/src/io/rsm/rsm_client.hpp @@ -19,6 +19,7 @@ #include "io/address.hpp" #include "io/errors.hpp" +#include "io/notifier.hpp" #include "io/rsm/raft.hpp" #include "utils/result.hpp" @@ -37,18 +38,11 @@ using memgraph::io::rsm::WriteRequest; using memgraph::io::rsm::WriteResponse; using memgraph::utils::BasicResult; -class AsyncRequestToken { - size_t id_; - - public: - explicit AsyncRequestToken(size_t id) : id_(id) {} - size_t GetId() const { return id_; } -}; - template struct AsyncRequest { Time start_time; RequestT request; + Notifier notifier; ResponseFuture future; }; @@ -66,8 +60,6 @@ class RsmClient { std::unordered_map>> async_reads_; std::unordered_map>> async_writes_; - size_t async_token_generator_ = 0; - void SelectRandomLeader() { std::uniform_int_distribution addr_distrib(0, (server_addrs_.size() - 1)); size_t addr_index = io_.Rand(addr_distrib); @@ -81,6 +73,7 @@ class RsmClient { if (response.retry_leader) { MG_ASSERT(!response.success, "retry_leader should never be set for successful responses"); leader_ = response.retry_leader.value(); + spdlog::error("client redirected to leader server {}", leader_.ToString()); spdlog::debug("client redirected to leader server {}", leader_.ToString()); } if (!response.success) { @@ -101,61 +94,63 @@ class RsmClient { ~RsmClient() = default; BasicResult SendWriteRequest(WriteRequestT req) { - auto token = SendAsyncWriteRequest(req); - auto poll_result = AwaitAsyncWriteRequest(token); + Notifier notifier; + ReadinessToken readiness_token{0}; + SendAsyncWriteRequest(req, notifier, readiness_token); + auto poll_result = AwaitAsyncWriteRequest(readiness_token); while (!poll_result) { - poll_result = AwaitAsyncWriteRequest(token); + poll_result = AwaitAsyncWriteRequest(readiness_token); } return poll_result.value(); } BasicResult SendReadRequest(ReadRequestT req) { - auto token = SendAsyncReadRequest(req); - auto poll_result = AwaitAsyncReadRequest(token); + Notifier notifier; + ReadinessToken readiness_token{0}; + SendAsyncReadRequest(req, notifier, readiness_token); + auto poll_result = AwaitAsyncReadRequest(readiness_token); while (!poll_result) { - poll_result = AwaitAsyncReadRequest(token); + poll_result = AwaitAsyncReadRequest(readiness_token); } return poll_result.value(); } /// AsyncRead methods - AsyncRequestToken SendAsyncReadRequest(const ReadRequestT &req) { - size_t token = async_token_generator_++; - + void SendAsyncReadRequest(const ReadRequestT &req, Notifier notifier, ReadinessToken readiness_token) { ReadRequest read_req = {.operation = req}; AsyncRequest> async_request{ .start_time = io_.Now(), .request = std::move(req), - .future = io_.template Request, ReadResponse>(leader_, read_req), + .notifier = notifier, + .future = io_.template RequestWithNotification, ReadResponse>( + leader_, read_req, notifier, readiness_token), }; - async_reads_.emplace(token, std::move(async_request)); - - return AsyncRequestToken{token}; + async_reads_.emplace(readiness_token.GetId(), std::move(async_request)); } - void ResendAsyncReadRequest(const AsyncRequestToken &token) { - auto &async_request = async_reads_.at(token.GetId()); + void ResendAsyncReadRequest(const ReadinessToken &readiness_token) { + auto &async_request = async_reads_.at(readiness_token.GetId()); ReadRequest read_req = {.operation = async_request.request}; - async_request.future = - io_.template Request, ReadResponse>(leader_, read_req); + async_request.future = io_.template RequestWithNotification, ReadResponse>( + leader_, read_req, async_request.notifier, readiness_token); } - std::optional> PollAsyncReadRequest(const AsyncRequestToken &token) { - auto &async_request = async_reads_.at(token.GetId()); + std::optional> PollAsyncReadRequest(const ReadinessToken &readiness_token) { + auto &async_request = async_reads_.at(readiness_token.GetId()); if (!async_request.future.IsReady()) { return std::nullopt; } - return AwaitAsyncReadRequest(); + return AwaitAsyncReadRequest(readiness_token); } - std::optional> AwaitAsyncReadRequest(const AsyncRequestToken &token) { - auto &async_request = async_reads_.at(token.GetId()); + std::optional> AwaitAsyncReadRequest(const ReadinessToken &readiness_token) { + auto &async_request = async_reads_.at(readiness_token.GetId()); ResponseResult> get_response_result = std::move(async_request.future).Wait(); const Duration overall_timeout = io_.GetDefaultTimeout(); @@ -165,7 +160,7 @@ class RsmClient { if (result_has_error && past_time_out) { // TODO static assert the exact type of error. spdlog::debug("client timed out while trying to communicate with leader server {}", leader_.ToString()); - async_reads_.erase(token.GetId()); + async_reads_.erase(readiness_token.GetId()); return TimedOut{}; } @@ -176,7 +171,7 @@ class RsmClient { PossiblyRedirectLeader(read_get_response); if (read_get_response.success) { - async_reads_.erase(token.GetId()); + async_reads_.erase(readiness_token.GetId()); spdlog::debug("returning read_return for RSM request"); return std::move(read_get_response.read_return); } @@ -184,49 +179,48 @@ class RsmClient { SelectRandomLeader(); } - ResendAsyncReadRequest(token); + ResendAsyncReadRequest(readiness_token); return std::nullopt; } /// AsyncWrite methods - AsyncRequestToken SendAsyncWriteRequest(const WriteRequestT &req) { - size_t token = async_token_generator_++; - + void SendAsyncWriteRequest(const WriteRequestT &req, Notifier notifier, ReadinessToken readiness_token) { WriteRequest write_req = {.operation = req}; AsyncRequest> async_request{ .start_time = io_.Now(), .request = std::move(req), - .future = io_.template Request, WriteResponse>(leader_, write_req), + .notifier = notifier, + .future = io_.template RequestWithNotification, WriteResponse>( + leader_, write_req, notifier, readiness_token), }; - async_writes_.emplace(token, std::move(async_request)); - - return AsyncRequestToken{token}; + async_writes_.emplace(readiness_token.GetId(), std::move(async_request)); } - void ResendAsyncWriteRequest(const AsyncRequestToken &token) { - auto &async_request = async_writes_.at(token.GetId()); + void ResendAsyncWriteRequest(const ReadinessToken &readiness_token) { + auto &async_request = async_writes_.at(readiness_token.GetId()); WriteRequest write_req = {.operation = async_request.request}; async_request.future = - io_.template Request, WriteResponse>(leader_, write_req); + io_.template RequestWithNotification, WriteResponse>( + leader_, write_req, async_request.notifier, readiness_token); } - std::optional> PollAsyncWriteRequest(const AsyncRequestToken &token) { - auto &async_request = async_writes_.at(token.GetId()); + std::optional> PollAsyncWriteRequest(const ReadinessToken &readiness_token) { + auto &async_request = async_writes_.at(readiness_token.GetId()); if (!async_request.future.IsReady()) { return std::nullopt; } - return AwaitAsyncWriteRequest(); + return AwaitAsyncWriteRequest(readiness_token); } - std::optional> AwaitAsyncWriteRequest(const AsyncRequestToken &token) { - auto &async_request = async_writes_.at(token.GetId()); + std::optional> AwaitAsyncWriteRequest(const ReadinessToken &readiness_token) { + auto &async_request = async_writes_.at(readiness_token.GetId()); ResponseResult> get_response_result = std::move(async_request.future).Wait(); const Duration overall_timeout = io_.GetDefaultTimeout(); @@ -236,7 +230,7 @@ class RsmClient { if (result_has_error && past_time_out) { // TODO static assert the exact type of error. spdlog::debug("client timed out while trying to communicate with leader server {}", leader_.ToString()); - async_writes_.erase(token.GetId()); + async_writes_.erase(readiness_token.GetId()); return TimedOut{}; } @@ -248,14 +242,14 @@ class RsmClient { PossiblyRedirectLeader(write_get_response); if (write_get_response.success) { - async_writes_.erase(token.GetId()); + async_writes_.erase(readiness_token.GetId()); return std::move(write_get_response.write_return); } } else { SelectRandomLeader(); } - ResendAsyncWriteRequest(token); + ResendAsyncWriteRequest(readiness_token); return std::nullopt; } diff --git a/src/io/simulator/simulator_handle.hpp b/src/io/simulator/simulator_handle.hpp index 5a5ad1ec0..0cd6b77b0 100644 --- a/src/io/simulator/simulator_handle.hpp +++ b/src/io/simulator/simulator_handle.hpp @@ -105,12 +105,16 @@ class SimulatorHandle { template ResponseFuture SubmitRequest(Address to_address, Address from_address, Request &&request, Duration timeout, - std::function &&maybe_tick_simulator) { + std::function &&maybe_tick_simulator, + std::function &&fill_notifier) { spdlog::trace("submitting request to {}", to_address.last_known_port); auto type_info = TypeInfoFor(request); - auto [future, promise] = memgraph::io::FuturePromisePairWithNotifier>( - std::forward>(maybe_tick_simulator)); + auto [future, promise] = memgraph::io::FuturePromisePairWithNotifications>( + // set notifier for when the Future::Wait is called + std::forward>(maybe_tick_simulator), + // set notifier for when Promise::Fill is called + std::forward>(fill_notifier)); std::unique_lock lock(mu_); diff --git a/src/io/simulator/simulator_transport.hpp b/src/io/simulator/simulator_transport.hpp index 5e5a24aa9..2107c34ca 100644 --- a/src/io/simulator/simulator_transport.hpp +++ b/src/io/simulator/simulator_transport.hpp @@ -15,6 +15,7 @@ #include #include "io/address.hpp" +#include "io/notifier.hpp" #include "io/simulator/simulator_handle.hpp" #include "io/time.hpp" @@ -33,11 +34,13 @@ class SimulatorTransport { : simulator_handle_(simulator_handle), address_(address), rng_(std::mt19937{seed}) {} template - ResponseFuture Request(Address to_address, Address from_address, RequestT request, Duration timeout) { + ResponseFuture Request(Address to_address, Address from_address, RequestT request, + std::function notification, Duration timeout) { std::function maybe_tick_simulator = [this] { return simulator_handle_->MaybeTickSimulator(); }; return simulator_handle_->template SubmitRequest(to_address, from_address, std::move(request), - timeout, std::move(maybe_tick_simulator)); + timeout, std::move(maybe_tick_simulator), + std::move(notification)); } template diff --git a/src/io/transport.hpp b/src/io/transport.hpp index 4994cb436..2c2e79060 100644 --- a/src/io/transport.hpp +++ b/src/io/transport.hpp @@ -20,6 +20,7 @@ #include "io/errors.hpp" #include "io/future.hpp" #include "io/message_histogram_collector.hpp" +#include "io/notifier.hpp" #include "io/time.hpp" #include "utils/result.hpp" @@ -84,7 +85,9 @@ class Io { template ResponseFuture RequestWithTimeout(Address address, RequestT request, Duration timeout) { const Address from_address = address_; - return implementation_.template Request(address, from_address, request, timeout); + std::function fill_notifier = nullptr; + return implementation_.template Request(address, from_address, request, fill_notifier, + timeout); } /// Issue a request that times out after the default timeout. This tends @@ -93,7 +96,30 @@ class Io { ResponseFuture Request(Address to_address, RequestT request) { const Duration timeout = default_timeout_; const Address from_address = address_; - return implementation_.template Request(to_address, from_address, std::move(request), timeout); + std::function fill_notifier = nullptr; + return implementation_.template Request(to_address, from_address, std::move(request), + fill_notifier, timeout); + } + + /// Issue a request that will notify a Notifier when it is filled or times out. + template + ResponseFuture RequestWithNotification(Address to_address, RequestT request, Notifier notifier, + ReadinessToken readiness_token) { + const Duration timeout = default_timeout_; + const Address from_address = address_; + std::function fill_notifier = std::bind(&Notifier::Notify, notifier, readiness_token); + return implementation_.template Request(to_address, from_address, std::move(request), + fill_notifier, timeout); + } + + /// Issue a request that will notify a Notifier when it is filled or times out. + template + ResponseFuture RequestWithNotificationAndTimeout(Address to_address, RequestT request, Notifier notifier, + ReadinessToken readiness_token, Duration timeout) { + const Address from_address = address_; + std::function fill_notifier = std::bind(&Notifier::Notify, notifier, readiness_token); + return implementation_.template Request(to_address, from_address, std::move(request), + fill_notifier, timeout); } /// Wait for an explicit number of microseconds for a request of one of the diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 996272fdc..2d563ade0 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -31,6 +31,7 @@ #include "coordinator/shard_map.hpp" #include "io/address.hpp" #include "io/errors.hpp" +#include "io/notifier.hpp" #include "io/rsm/raft.hpp" #include "io/rsm/rsm_client.hpp" #include "io/rsm/shard_rsm.hpp" @@ -75,25 +76,11 @@ template struct ShardRequestState { memgraph::coordinator::Shard shard; TRequest request; - std::optional async_request_token; }; +// maps from ReadinessToken's internal size_t to the associated state template -struct ExecutionState { - using CompoundKey = io::rsm::ShardRsmKey; - using Shard = coordinator::Shard; - - // label is optional because some operators can create/remove etc, vertices. These kind of requests contain the label - // on the request itself. - std::optional label; - // Transaction id to be filled by the RequestRouter implementation - coordinator::Hlc transaction_id; - // Initialized by RequestRouter implementation. This vector is filled with the shards that - // the RequestRouter impl will send requests to. When a request to a shard exhausts it, meaning that - // it pulled all the requested data from the given Shard, it will be removed from the Vector. When the Vector becomes - // empty, it means that all of the requests have completed succefully. - std::vector> requests; -}; +using RunningRequests = std::unordered_map>; class RequestRouterInterface { public: @@ -238,26 +225,25 @@ class RequestRouter : public RequestRouterInterface { // TODO(kostasrim) Simplify return result std::vector ScanVertices(std::optional label) override { - ExecutionState state = {}; - state.label = label; - // create requests - InitializeExecutionState(state); + std::vector> unsent_requests = RequestsForScanVertices(label); + spdlog::error("created {} ScanVertices requests", unsent_requests.size()); // begin all requests in parallel - for (auto &request : state.requests) { + RunningRequests running_requests = {}; + running_requests.reserve(unsent_requests.size()); + for (size_t i = 0; i < unsent_requests.size(); i++) { + auto &request = unsent_requests[i]; + io::ReadinessToken readiness_token{i}; auto &storage_client = GetStorageClientForShard(request.shard); - msgs::ReadRequests req = request.request; - - request.async_request_token = storage_client.SendAsyncReadRequest(request.request); + storage_client.SendAsyncReadRequest(request.request, notifier_, readiness_token); + running_requests.emplace(readiness_token.GetId(), request); } + spdlog::error("sent {} ScanVertices requests in parallel", running_requests.size()); // drive requests to completion - std::vector responses; - responses.reserve(state.requests.size()); - do { - DriveReadResponses(state, responses); - } while (!state.requests.empty()); + auto responses = DriveReadResponses(running_requests); + spdlog::error("got back {} ScanVertices responses after driving to completion", responses.size()); // convert responses into VertexAccessor objects to return std::vector accessors; @@ -272,62 +258,53 @@ class RequestRouter : public RequestRouterInterface { } std::vector CreateVertices(std::vector new_vertices) override { - ExecutionState state = {}; MG_ASSERT(!new_vertices.empty()); // create requests - InitializeExecutionState(state, new_vertices); + std::vector> unsent_requests = + RequestsForCreateVertices(new_vertices); // begin all requests in parallel - for (auto &request : state.requests) { - auto req_deep_copy = request.request; - - for (auto &new_vertex : req_deep_copy.new_vertices) { + RunningRequests running_requests = {}; + running_requests.reserve(unsent_requests.size()); + for (size_t i = 0; i < unsent_requests.size(); i++) { + auto &request = unsent_requests[i]; + io::ReadinessToken readiness_token{i}; + for (auto &new_vertex : request.request.new_vertices) { new_vertex.label_ids.erase(new_vertex.label_ids.begin()); } - auto &storage_client = GetStorageClientForShard(request.shard); - - msgs::WriteRequests req = req_deep_copy; - request.async_request_token = storage_client.SendAsyncWriteRequest(req); + storage_client.SendAsyncWriteRequest(request.request, notifier_, readiness_token); + running_requests.emplace(readiness_token.GetId(), request); } // drive requests to completion - std::vector responses; - responses.reserve(state.requests.size()); - do { - DriveWriteResponses(state, responses); - } while (!state.requests.empty()); - - return responses; + return DriveWriteResponses(running_requests); } std::vector CreateExpand(std::vector new_edges) override { - ExecutionState state = {}; MG_ASSERT(!new_edges.empty()); // create requests - InitializeExecutionState(state, new_edges); + std::vector> unsent_requests = RequestsForCreateExpand(new_edges); // begin all requests in parallel - for (auto &request : state.requests) { + RunningRequests running_requests = {}; + running_requests.reserve(unsent_requests.size()); + for (size_t i = 0; i < unsent_requests.size(); i++) { + auto &request = unsent_requests[i]; + io::ReadinessToken readiness_token{i}; auto &storage_client = GetStorageClientForShard(request.shard); msgs::WriteRequests req = request.request; - request.async_request_token = storage_client.SendAsyncWriteRequest(req); + storage_client.SendAsyncWriteRequest(req, notifier_, readiness_token); + running_requests.emplace(readiness_token.GetId(), request); } // drive requests to completion - std::vector responses; - responses.reserve(state.requests.size()); - do { - DriveWriteResponses(state, responses); - } while (!state.requests.empty()); - - return responses; + return DriveWriteResponses(running_requests); } std::vector ExpandOne(msgs::ExpandOneRequest request) override { - ExecutionState state = {}; // TODO(kostasrim)Update to limit the batch size here // Expansions of the destination must be handled by the caller. For example // match (u:L1 { prop : 1 })-[:Friend]-(v:L1) @@ -335,21 +312,22 @@ class RequestRouter : public RequestRouterInterface { // must be fetched again with an ExpandOne(Edges.dst) // create requests - InitializeExecutionState(state, std::move(request)); + std::vector> unsent_requests = RequestsForExpandOne(request); // begin all requests in parallel - for (auto &request : state.requests) { + RunningRequests running_requests = {}; + running_requests.reserve(unsent_requests.size()); + for (size_t i = 0; i < unsent_requests.size(); i++) { + auto &request = unsent_requests[i]; + io::ReadinessToken readiness_token{i}; auto &storage_client = GetStorageClientForShard(request.shard); msgs::ReadRequests req = request.request; - request.async_request_token = storage_client.SendAsyncReadRequest(req); + storage_client.SendAsyncReadRequest(req, notifier_, readiness_token); + running_requests.emplace(readiness_token.GetId(), request); } // drive requests to completion - std::vector responses; - responses.reserve(state.requests.size()); - do { - DriveReadResponses(state, responses); - } while (!state.requests.empty()); + auto responses = DriveReadResponses(running_requests); // post-process responses std::vector result_rows; @@ -380,10 +358,8 @@ class RequestRouter : public RequestRouterInterface { } private: - void InitializeExecutionState(ExecutionState &state, - std::vector new_vertices) { - state.transaction_id = transaction_id_; - + std::vector> RequestsForCreateVertices( + const std::vector &new_vertices) { std::map per_shard_request_table; for (auto &new_vertex : new_vertices) { @@ -397,20 +373,21 @@ class RequestRouter : public RequestRouterInterface { per_shard_request_table[shard].new_vertices.push_back(std::move(new_vertex)); } + std::vector> requests = {}; + for (auto &[shard, request] : per_shard_request_table) { ShardRequestState shard_request_state{ .shard = shard, .request = request, - .async_request_token = std::nullopt, }; - state.requests.emplace_back(std::move(shard_request_state)); + requests.emplace_back(std::move(shard_request_state)); } + + return requests; } - void InitializeExecutionState(ExecutionState &state, - std::vector new_expands) { - state.transaction_id = transaction_id_; - + std::vector> RequestsForCreateExpand( + const std::vector &new_expands) { std::map per_shard_request_table; auto ensure_shard_exists_in_table = [&per_shard_request_table, transaction_id = transaction_id_](const Shard &shard) { @@ -435,27 +412,33 @@ class RequestRouter : public RequestRouterInterface { per_shard_request_table[shard_src_vertex].new_expands.push_back(std::move(new_expand)); } + std::vector> requests = {}; + for (auto &[shard, request] : per_shard_request_table) { ShardRequestState shard_request_state{ .shard = shard, .request = request, - .async_request_token = std::nullopt, }; - state.requests.emplace_back(std::move(shard_request_state)); + requests.emplace_back(std::move(shard_request_state)); } + + return requests; } - void InitializeExecutionState(ExecutionState &state) { + std::vector> RequestsForScanVertices( + const std::optional &label) { std::vector multi_shards; - state.transaction_id = transaction_id_; - if (!state.label) { - multi_shards = shards_map_.GetAllShards(); - } else { - const auto label_id = shards_map_.GetLabelId(*state.label); + if (label) { + const auto label_id = shards_map_.GetLabelId(*label); MG_ASSERT(label_id); MG_ASSERT(IsPrimaryLabel(*label_id)); - multi_shards = {shards_map_.GetShardsForLabel(*state.label)}; + multi_shards = {shards_map_.GetShardsForLabel(*label)}; + } else { + multi_shards = shards_map_.GetAllShards(); } + + std::vector> requests = {}; + for (auto &shards : multi_shards) { for (auto &[key, shard] : shards) { MG_ASSERT(!shard.empty()); @@ -467,22 +450,21 @@ class RequestRouter : public RequestRouterInterface { ShardRequestState shard_request_state{ .shard = shard, .request = std::move(request), - .async_request_token = std::nullopt, }; - state.requests.emplace_back(std::move(shard_request_state)); + requests.emplace_back(std::move(shard_request_state)); } } + + return requests; } - void InitializeExecutionState(ExecutionState &state, msgs::ExpandOneRequest request) { - state.transaction_id = transaction_id_; - + std::vector> RequestsForExpandOne(const msgs::ExpandOneRequest &request) { std::map per_shard_request_table; - auto top_level_rqst_template = request; + msgs::ExpandOneRequest top_level_rqst_template = request; top_level_rqst_template.transaction_id = transaction_id_; top_level_rqst_template.src_vertices.clear(); - state.requests.clear(); + for (auto &vertex : request.src_vertices) { auto shard = shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second)); @@ -492,15 +474,18 @@ class RequestRouter : public RequestRouterInterface { per_shard_request_table[shard].src_vertices.push_back(vertex); } + std::vector> requests = {}; + for (auto &[shard, request] : per_shard_request_table) { ShardRequestState shard_request_state{ .shard = shard, .request = request, - .async_request_token = std::nullopt, }; - state.requests.emplace_back(std::move(shard_request_state)); + requests.emplace_back(std::move(shard_request_state)); } + + return requests; } StorageClient &GetStorageClientForShard(Shard shard) { @@ -528,14 +513,18 @@ class RequestRouter : public RequestRouterInterface { } template - void DriveReadResponses(ExecutionState &state, std::vector &responses) { - for (auto &request : state.requests) { + std::vector DriveReadResponses(RunningRequests &running_requests) { + // Store responses in a map based on the corresponding request + // offset, so that they can be reassembled in the correct order + // even if they came back in randomized orders. + std::map response_map; + + while (response_map.size() < running_requests.size()) { + auto ready = notifier_.Await(); + auto &request = running_requests.at(ready.GetId()); auto &storage_client = GetStorageClientForShard(request.shard); - auto poll_result = storage_client.AwaitAsyncReadRequest(request.async_request_token.value()); - while (!poll_result) { - poll_result = storage_client.AwaitAsyncReadRequest(request.async_request_token.value()); - } + auto poll_result = storage_client.PollAsyncReadRequest(ready); if (poll_result->HasError()) { throw std::runtime_error("RequestRouter Read request timed out"); @@ -547,20 +536,36 @@ class RequestRouter : public RequestRouterInterface { throw std::runtime_error("RequestRouter Read request did not succeed"); } - responses.push_back(std::move(response)); + // the readiness token has an ID based on the request vector offset + response_map.emplace(ready.GetId(), std::move(response)); } - state.requests.clear(); + + std::vector responses; + responses.reserve(running_requests.size()); + + int last = -1; + for (auto &&[offset, response] : response_map) { + MG_ASSERT(last + 1 == offset); + responses.emplace_back(std::forward(response)); + last = offset; + } + + return responses; } template - void DriveWriteResponses(ExecutionState &state, std::vector &responses) { - for (auto &request : state.requests) { + std::vector DriveWriteResponses(RunningRequests &running_requests) { + // Store responses in a map based on the corresponding request + // offset, so that they can be reassembled in the correct order + // even if they came back in randomized orders. + std::map response_map; + + while (response_map.size() < running_requests.size()) { + auto ready = notifier_.Await(); + auto &request = running_requests.at(ready.GetId()); auto &storage_client = GetStorageClientForShard(request.shard); - auto poll_result = storage_client.AwaitAsyncWriteRequest(request.async_request_token.value()); - while (!poll_result) { - poll_result = storage_client.AwaitAsyncWriteRequest(request.async_request_token.value()); - } + auto poll_result = storage_client.PollAsyncWriteRequest(ready); if (poll_result->HasError()) { throw std::runtime_error("RequestRouter Write request timed out"); @@ -572,9 +577,21 @@ class RequestRouter : public RequestRouterInterface { throw std::runtime_error("RequestRouter Write request did not succeed"); } - responses.push_back(std::move(response)); + // the readiness token has an ID based on the request vector offset + response_map.emplace(ready.GetId(), std::move(response)); } - state.requests.clear(); + + std::vector responses; + responses.reserve(running_requests.size()); + + int last = -1; + for (auto &&[offset, response] : response_map) { + MG_ASSERT(last + 1 == offset); + responses.emplace_back(std::forward(response)); + last = offset; + } + + return responses; } void SetUpNameIdMappers() { @@ -603,6 +620,7 @@ class RequestRouter : public RequestRouterInterface { RsmStorageClientManager storage_cli_manager_; io::Io io_; coordinator::Hlc transaction_id_; + io::Notifier notifier_ = {}; // TODO(kostasrim) Add batch prefetching }; } // namespace memgraph::query::v2 diff --git a/tests/unit/future.cpp b/tests/unit/future.cpp index 490e19bbc..866a74fce 100644 --- a/tests/unit/future.cpp +++ b/tests/unit/future.cpp @@ -28,13 +28,19 @@ void Wait(Future future_1, Promise promise_2) { TEST(Future, BasicLifecycle) { std::atomic_bool waiting = false; + std::atomic_bool filled = false; - std::function notifier = [&] { + std::function wait_notifier = [&] { waiting.store(true, std::memory_order_seq_cst); return false; }; - auto [future_1, promise_1] = FuturePromisePairWithNotifier(notifier); + std::function fill_notifier = [&] { + filled.store(true, std::memory_order_seq_cst); + return false; + }; + + auto [future_1, promise_1] = FuturePromisePairWithNotifications(wait_notifier, fill_notifier); auto [future_2, promise_2] = FuturePromisePair(); std::jthread t1(Wait, std::move(future_1), std::move(promise_2)); @@ -50,6 +56,8 @@ TEST(Future, BasicLifecycle) { t1.join(); t2.join(); + EXPECT_TRUE(filled.load(std::memory_order_acquire)); + std::string result_2 = std::move(future_2).Wait(); EXPECT_TRUE(result_2 == "it worked"); } diff --git a/tests/unit/high_density_shard_create_scan.cpp b/tests/unit/high_density_shard_create_scan.cpp index 2be48fc77..cefa238ed 100644 --- a/tests/unit/high_density_shard_create_scan.cpp +++ b/tests/unit/high_density_shard_create_scan.cpp @@ -194,7 +194,8 @@ void ExecuteOp(query::v2::RequestRouter &request_router, std::se ScanAll scan_all) { auto results = request_router.ScanVertices("test_label"); - MG_ASSERT(results.size() == correctness_model.size()); + spdlog::error("got {} results, model size is {}", results.size(), correctness_model.size()); + EXPECT_EQ(results.size(), correctness_model.size()); for (const auto &vertex_accessor : results) { const auto properties = vertex_accessor.Properties(); diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp index 5f77ed4e7..50f578bb2 100644 --- a/tests/unit/query_v2_expression_evaluator.cpp +++ b/tests/unit/query_v2_expression_evaluator.cpp @@ -84,13 +84,14 @@ class MockedRequestRouter : public RequestRouterInterface { void Commit() override {} std::vector ScanVertices(std::optional /* label */) override { return {}; } - std::vector CreateVertices(std::vector new_vertices) override { + std::vector CreateVertices( + std::vector /* new_vertices */) override { return {}; } - std::vector ExpandOne(ExpandOneRequest request) override { return {}; } + std::vector ExpandOne(ExpandOneRequest /* request */) override { return {}; } - std::vector CreateExpand(std::vector new_edges) override { return {}; } + std::vector CreateExpand(std::vector /* new_edges */) override { return {}; } const std::string &PropertyToName(memgraph::storage::v3::PropertyId id) const override { return properties_.IdToName(id.AsUint()); From d0e1d86df37220af3d6a2114dcd0792e19d700c4 Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 16:57:09 +0100 Subject: [PATCH 66/93] Remove unused param --- jba.txt | 0 src/query/v2/multiframe.hpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 jba.txt diff --git a/jba.txt b/jba.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index c1bf9cdf1..b92eebf8d 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -107,7 +107,7 @@ class ValidFramesReader { using pointer = value_type *; using reference = const Frame &; - explicit Iterator(FrameWithValidity *ptr, ValidFramesReader &iterator_wrapper) : ptr_(ptr) {} + explicit Iterator(FrameWithValidity *ptr) : ptr_(ptr) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } From be3797e0a1c451597495970d53468ca5d28145de Mon Sep 17 00:00:00 2001 From: jeremy Date: Thu, 1 Dec 2022 17:01:08 +0100 Subject: [PATCH 67/93] Remove unused param --- src/query/v2/multiframe.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index c007baf7c..26cbb7f10 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -89,8 +89,8 @@ ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multi after_last_valid_frame_ = multiframe_.frames_.data() + std::distance(multiframe.frames_.begin(), it); } -ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator{&multiframe_.frames_[0], *this}; } -ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator{after_last_valid_frame_, *this}; } +ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator{&multiframe_.frames_[0]}; } +ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator{after_last_valid_frame_}; } ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {} From 0ad702175fc6217d76e3229f19722811c1f3020a Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Thu, 1 Dec 2022 18:24:51 +0200 Subject: [PATCH 68/93] Fix expression evaluator mocked request router --- tests/unit/query_v2_expression_evaluator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp index 5f77ed4e7..f819ee16f 100644 --- a/tests/unit/query_v2_expression_evaluator.cpp +++ b/tests/unit/query_v2_expression_evaluator.cpp @@ -51,6 +51,8 @@ using memgraph::msgs::CreateVerticesResponse; using memgraph::msgs::ExpandOneRequest; using memgraph::msgs::ExpandOneResponse; using memgraph::msgs::ExpandOneResultRow; +using memgraph::msgs::GetPropertiesRequest; +using memgraph::msgs::GetPropertiesResultRow; using memgraph::msgs::NewExpand; using memgraph::msgs::NewVertex; using memgraph::msgs::ScanVerticesRequest; @@ -92,6 +94,8 @@ class MockedRequestRouter : public RequestRouterInterface { std::vector CreateExpand(std::vector new_edges) override { return {}; } + std::vector GetProperties(GetPropertiesRequest rqst) override { return {}; } + const std::string &PropertyToName(memgraph::storage::v3::PropertyId id) const override { return properties_.IdToName(id.AsUint()); } From 438b51970308d00b76fabf26927a62a694be537e Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Thu, 1 Dec 2022 16:26:41 +0000 Subject: [PATCH 69/93] Apply clang-tidy feedback --- src/io/notifier.hpp | 9 ++++++--- src/io/transport.hpp | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/io/notifier.hpp b/src/io/notifier.hpp index e6b073046..81d6507bb 100644 --- a/src/io/notifier.hpp +++ b/src/io/notifier.hpp @@ -11,6 +11,10 @@ #pragma once +#include +#include +#include + namespace memgraph::io { class ReadinessToken { @@ -30,7 +34,6 @@ class Inner { void Notify(ReadinessToken readiness_token) { { std::unique_lock lock(mu_); - spdlog::trace("Notifier notifying token {}", readiness_token.GetId()); ready_.emplace_back(readiness_token); } // mutex dropped @@ -61,9 +64,9 @@ class Notifier { Notifier &operator=(Notifier &&old) = default; ~Notifier() = default; - void Notify(ReadinessToken readiness_token) { inner_->Notify(readiness_token); } + void Notify(ReadinessToken readiness_token) const { inner_->Notify(readiness_token); } - ReadinessToken Await() { return inner_->Await(); } + ReadinessToken Await() const { return inner_->Await(); } }; } // namespace memgraph::io diff --git a/src/io/transport.hpp b/src/io/transport.hpp index 2c2e79060..5dd7a9a39 100644 --- a/src/io/transport.hpp +++ b/src/io/transport.hpp @@ -107,7 +107,7 @@ class Io { ReadinessToken readiness_token) { const Duration timeout = default_timeout_; const Address from_address = address_; - std::function fill_notifier = std::bind(&Notifier::Notify, notifier, readiness_token); + std::function fill_notifier = [notifier, readiness_token]() { notifier.Notify(readiness_token); }; return implementation_.template Request(to_address, from_address, std::move(request), fill_notifier, timeout); } @@ -117,7 +117,7 @@ class Io { ResponseFuture RequestWithNotificationAndTimeout(Address to_address, RequestT request, Notifier notifier, ReadinessToken readiness_token, Duration timeout) { const Address from_address = address_; - std::function fill_notifier = std::bind(&Notifier::Notify, notifier, readiness_token); + std::function fill_notifier = [notifier, readiness_token]() { notifier.Notify(readiness_token); }; return implementation_.template Request(to_address, from_address, std::move(request), fill_notifier, timeout); } From 6b8a5fd41dc1c9d511df4e87d80643b976b889cd Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Fri, 2 Dec 2022 15:11:51 +0200 Subject: [PATCH 70/93] Make all variants of multiframe iterators model ForwardIterator concept properly --- src/query/v2/multiframe.cpp | 13 ++--- src/query/v2/multiframe.hpp | 96 ++++++++++++++++++++++++++----------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 26cbb7f10..4829addb2 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -19,15 +19,10 @@ namespace memgraph::query::v2 { -// #NoCommit uncomment https://github.com/memgraph/memgraph/pull/676#discussion_r1035704661 -// static_assert(std::forward_iterator && -// std::equality_comparable); -// static_assert(std::forward_iterator && -// std::equality_comparable); -// static_assert(std::forward_iterator && -// std::equality_comparable); -// static_assert(std::forward_iterator && -// std::equality_comparable); +static_assert(std::forward_iterator); +static_assert(std::forward_iterator); +static_assert(std::forward_iterator); +static_assert(std::forward_iterator); MultiFrame::MultiFrame(int64_t size_of_frame, size_t number_of_frames, utils::MemoryResource *execution_memory) : frames_(utils::pmr::vector( diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index b92eebf8d..a73ca1f41 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -107,22 +107,31 @@ class ValidFramesReader { using pointer = value_type *; using reference = const Frame &; + Iterator() {} explicit Iterator(FrameWithValidity *ptr) : ptr_(ptr) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } - // Prefix increment Iterator &operator++() { ptr_++; return *this; } - friend bool operator==(const Iterator &a, const Iterator &b) { return a.ptr_ == b.ptr_; }; - friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; + // clang-tidy warning is wrong here, because we & qualify the function, meaning that you can't post increment + // temporaries, e.g, (it++)++ + // NOLINTNEXTLINE (cert-dcl21-cpp) + Iterator operator++(int) & { + auto old = *this; + ptr_++; + return old; + } + + friend bool operator==(const Iterator &lhs, const Iterator &rhs) { return lhs.ptr_ == rhs.ptr_; }; + friend bool operator!=(const Iterator &lhs, const Iterator &rhs) { return lhs.ptr_ != rhs.ptr_; }; private: - FrameWithValidity *ptr_; + FrameWithValidity *ptr_{nullptr}; }; Iterator begin(); @@ -138,10 +147,10 @@ class ValidFramesModifier { explicit ValidFramesModifier(MultiFrame &multiframe); ~ValidFramesModifier() = default; - ValidFramesModifier(const ValidFramesModifier &other) = delete; // copy constructor - ValidFramesModifier(ValidFramesModifier &&other) noexcept = delete; // move constructor - ValidFramesModifier &operator=(const ValidFramesModifier &other) = delete; // copy assignment - ValidFramesModifier &operator=(ValidFramesModifier &&other) noexcept = delete; // move assignment + ValidFramesModifier(const ValidFramesModifier &other) = delete; + ValidFramesModifier(ValidFramesModifier &&other) noexcept = delete; + ValidFramesModifier &operator=(const ValidFramesModifier &other) = delete; + ValidFramesModifier &operator=(ValidFramesModifier &&other) noexcept = delete; struct Iterator { using iterator_category = std::forward_iterator_tag; @@ -149,6 +158,7 @@ class ValidFramesModifier { using value_type = Frame; using pointer = value_type *; using reference = Frame &; + Iterator() {} Iterator(FrameWithValidity *ptr, ValidFramesModifier &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(&iterator_wrapper) {} @@ -165,12 +175,21 @@ class ValidFramesModifier { return *this; } - friend bool operator==(const Iterator &a, const Iterator &b) { return a.ptr_ == b.ptr_; }; - friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; + // clang-tidy warning is wrong here, because we & qualify the function, meaning that you can't post increment + // temporaries, e.g, (it++)++ + // NOLINTNEXTLINE (cert-dcl21-cpp) + Iterator operator++(int) & { + auto old = *this; + ++*this; + return old; + } + + friend bool operator==(const Iterator &lhs, const Iterator &rhs) { return lhs.ptr_ == rhs.ptr_; }; + friend bool operator!=(const Iterator &lhs, const Iterator &rhs) { return lhs.ptr_ != rhs.ptr_; }; private: - FrameWithValidity *ptr_; - ValidFramesModifier *iterator_wrapper_; + FrameWithValidity *ptr_{nullptr}; + ValidFramesModifier *iterator_wrapper_{nullptr}; }; Iterator begin(); @@ -185,10 +204,10 @@ class ValidFramesConsumer { explicit ValidFramesConsumer(MultiFrame &multiframe); ~ValidFramesConsumer() noexcept; - ValidFramesConsumer(const ValidFramesConsumer &other) = delete; // copy constructor - ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = delete; // move constructor - ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; // copy assignment - ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = delete; // move assignment + ValidFramesConsumer(const ValidFramesConsumer &other) = delete; + ValidFramesConsumer(ValidFramesConsumer &&other) noexcept = delete; + ValidFramesConsumer &operator=(const ValidFramesConsumer &other) = delete; + ValidFramesConsumer &operator=(ValidFramesConsumer &&other) noexcept = delete; struct Iterator { using iterator_category = std::forward_iterator_tag; @@ -197,13 +216,14 @@ class ValidFramesConsumer { using pointer = value_type *; using reference = FrameWithValidity &; + Iterator() {} + Iterator(FrameWithValidity *ptr, ValidFramesConsumer &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(&iterator_wrapper) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } - // Prefix increment Iterator &operator++() { do { ptr_++; @@ -212,12 +232,21 @@ class ValidFramesConsumer { return *this; } - friend bool operator==(const Iterator &a, const Iterator &b) { return a.ptr_ == b.ptr_; }; - friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; + // clang-tidy warning is wrong here, because we & qualify the function, meaning that you can't post increment + // temporaries, e.g, (it++)++ + // NOLINTNEXTLINE (cert-dcl21-cpp) + Iterator operator++(int) & { + auto old = *this; + ++*this; + return old; + } + + friend bool operator==(const Iterator &lhs, const Iterator &rhs) { return lhs.ptr_ == rhs.ptr_; }; + friend bool operator!=(const Iterator &lhs, const Iterator &rhs) { return lhs.ptr_ != rhs.ptr_; }; private: - FrameWithValidity *ptr_; - ValidFramesConsumer *iterator_wrapper_; + FrameWithValidity *ptr_{nullptr}; + ValidFramesConsumer *iterator_wrapper_{nullptr}; }; Iterator begin(); @@ -232,10 +261,10 @@ class InvalidFramesPopulator { explicit InvalidFramesPopulator(MultiFrame &multiframe); ~InvalidFramesPopulator() = default; - InvalidFramesPopulator(const InvalidFramesPopulator &other) = delete; // copy constructor - InvalidFramesPopulator(InvalidFramesPopulator &&other) noexcept = delete; // move constructor - InvalidFramesPopulator &operator=(const InvalidFramesPopulator &other) = delete; // copy assignment - InvalidFramesPopulator &operator=(InvalidFramesPopulator &&other) noexcept = delete; // move assignment + InvalidFramesPopulator(const InvalidFramesPopulator &other) = delete; + InvalidFramesPopulator(InvalidFramesPopulator &&other) noexcept = delete; + InvalidFramesPopulator &operator=(const InvalidFramesPopulator &other) = delete; + InvalidFramesPopulator &operator=(InvalidFramesPopulator &&other) noexcept = delete; struct Iterator { using iterator_category = std::forward_iterator_tag; @@ -244,23 +273,32 @@ class InvalidFramesPopulator { using pointer = value_type *; using reference = FrameWithValidity &; + Iterator() {} explicit Iterator(FrameWithValidity *ptr) : ptr_(ptr) {} reference operator*() const { return *ptr_; } pointer operator->() { return ptr_; } - // Prefix increment Iterator &operator++() { ptr_->MakeValid(); ptr_++; return *this; } - friend bool operator==(const Iterator &a, const Iterator &b) { return a.ptr_ == b.ptr_; }; - friend bool operator!=(const Iterator &a, const Iterator &b) { return a.ptr_ != b.ptr_; }; + // clang-tidy warning is wrong here, because we & qualify the function, meaning that you can't post increment + // temporaries, e.g, (it++)++ + // NOLINTNEXTLINE (cert-dcl21-cpp) + Iterator operator++(int) & { + auto old = *this; + ++ptr_; + return old; + } + + friend bool operator==(const Iterator &lhs, const Iterator &rhs) { return lhs.ptr_ == rhs.ptr_; }; + friend bool operator!=(const Iterator &lhs, const Iterator &rhs) { return lhs.ptr_ != rhs.ptr_; }; private: - FrameWithValidity *ptr_; + FrameWithValidity *ptr_{nullptr}; }; Iterator begin(); From 9a62503803f5e1667bd70a02454bdc5419eeb3ef Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Fri, 2 Dec 2022 18:04:38 +0000 Subject: [PATCH 71/93] Tick the simulator forward from Notify::Await in a similar way that Future::Wait does --- src/io/notifier.hpp | 22 ++++++++++- src/io/rsm/raft.hpp | 32 +++++++++++----- src/io/rsm/rsm_client.hpp | 1 - src/io/simulator/simulator.hpp | 6 +++ src/io/simulator/simulator_handle.hpp | 49 +++++++++++++----------- src/io/simulator/simulator_transport.hpp | 8 ++-- src/query/v2/request_router.hpp | 18 +++++++-- tests/simulation/request_router.cpp | 2 + tests/simulation/test_cluster.hpp | 2 + 9 files changed, 99 insertions(+), 41 deletions(-) diff --git a/src/io/notifier.hpp b/src/io/notifier.hpp index 81d6507bb..9e0dec7df 100644 --- a/src/io/notifier.hpp +++ b/src/io/notifier.hpp @@ -12,6 +12,7 @@ #pragma once #include +#include #include #include @@ -29,6 +30,7 @@ class Inner { std::condition_variable cv_; std::mutex mu_; std::vector ready_; + std::optional> tick_simulator_; public: void Notify(ReadinessToken readiness_token) { @@ -44,13 +46,29 @@ class Inner { std::unique_lock lock(mu_); while (ready_.empty()) { - cv_.wait(lock); + if (tick_simulator_) [[unlikely]] { + // This avoids a deadlock in a similar way that + // Future::Wait will release its mutex while + // interacting with the simulator, due to + // the fact that the simulator may cause + // notifications that we are interested in. + lock.unlock(); + std::invoke(tick_simulator_.value()); + lock.lock(); + } else { + cv_.wait(lock); + } } ReadinessToken ret = ready_.back(); ready_.pop_back(); return ret; } + + void InstallSimulatorTicker(std::function tick_simulator) { + std::unique_lock lock(mu_); + tick_simulator_ = tick_simulator; + } }; class Notifier { @@ -67,6 +85,8 @@ class Notifier { void Notify(ReadinessToken readiness_token) const { inner_->Notify(readiness_token); } ReadinessToken Await() const { return inner_->Await(); } + + void InstallSimulatorTicker(std::function tick_simulator) { inner_->InstallSimulatorTicker(tick_simulator); } }; } // namespace memgraph::io diff --git a/src/io/rsm/raft.hpp b/src/io/rsm/raft.hpp index 4ec22ff87..07a51288d 100644 --- a/src/io/rsm/raft.hpp +++ b/src/io/rsm/raft.hpp @@ -91,33 +91,43 @@ struct ReadResponse { }; template -utils::TypeInfoRef TypeInfoFor(const ReadResponse> &read_response) { - return TypeInfoForVariant(read_response.read_return); +utils::TypeInfoRef TypeInfoFor(const ReadResponse> &response) { + return TypeInfoForVariant(response.read_return); } template -utils::TypeInfoRef TypeInfoFor(const ReadResponse & /* read_response */) { +utils::TypeInfoRef TypeInfoFor(const ReadResponse & /* response */) { return typeid(ReadReturn); } +template +utils::TypeInfoRef TypeInfoFor(const ReadRequest & /* request */) { + return typeid(ReadOperation); +} + +template +utils::TypeInfoRef TypeInfoFor(const ReadRequest> &request) { + return TypeInfoForVariant(request.operation); +} + template -utils::TypeInfoRef TypeInfoFor(const WriteResponse> &write_response) { - return TypeInfoForVariant(write_response.write_return); +utils::TypeInfoRef TypeInfoFor(const WriteResponse> &response) { + return TypeInfoForVariant(response.write_return); } template -utils::TypeInfoRef TypeInfoFor(const WriteResponse & /* write_response */) { +utils::TypeInfoRef TypeInfoFor(const WriteResponse & /* response */) { return typeid(WriteReturn); } template -utils::TypeInfoRef TypeInfoFor(const WriteRequest & /* write_request */) { +utils::TypeInfoRef TypeInfoFor(const WriteRequest & /* request */) { return typeid(WriteOperation); } template -utils::TypeInfoRef TypeInfoFor(const WriteRequest> &write_request) { - return TypeInfoForVariant(write_request.operation); +utils::TypeInfoRef TypeInfoFor(const WriteRequest> &request) { + return TypeInfoForVariant(request.operation); } /// AppendRequest is a raft-level message that the Leader @@ -846,7 +856,9 @@ class Raft { // Leaders are able to immediately respond to the requester (with a ReadResponseValue) applied to the ReplicatedState std::optional Handle(Leader & /* variable */, ReadRequest &&req, RequestId request_id, Address from_address) { - Log("handling ReadOperation"); + auto type_info = TypeInfoFor(req); + std::string demangled_name = boost::core::demangle(type_info.get().name()); + Log("handling ReadOperation<" + demangled_name + ">"); ReadOperation read_operation = req.operation; ReadResponseValue read_return = replicated_state_.Read(read_operation); diff --git a/src/io/rsm/rsm_client.hpp b/src/io/rsm/rsm_client.hpp index 1283ec3dc..b863e79a4 100644 --- a/src/io/rsm/rsm_client.hpp +++ b/src/io/rsm/rsm_client.hpp @@ -73,7 +73,6 @@ class RsmClient { if (response.retry_leader) { MG_ASSERT(!response.success, "retry_leader should never be set for successful responses"); leader_ = response.retry_leader.value(); - spdlog::error("client redirected to leader server {}", leader_.ToString()); spdlog::debug("client redirected to leader server {}", leader_.ToString()); } if (!response.success) { diff --git a/src/io/simulator/simulator.hpp b/src/io/simulator/simulator.hpp index 622c264b4..667570276 100644 --- a/src/io/simulator/simulator.hpp +++ b/src/io/simulator/simulator.hpp @@ -49,5 +49,11 @@ class Simulator { } SimulatorStats Stats() { return simulator_handle_->Stats(); } + + std::function GetSimulatorTickClosure() { + std::shared_ptr handle_copy = simulator_handle_; + std::function tick_closure = [handle_copy] { return handle_copy->MaybeTickSimulator(); }; + return tick_closure; + } }; }; // namespace memgraph::io::simulator diff --git a/src/io/simulator/simulator_handle.hpp b/src/io/simulator/simulator_handle.hpp index 0cd6b77b0..3fd9b4965 100644 --- a/src/io/simulator/simulator_handle.hpp +++ b/src/io/simulator/simulator_handle.hpp @@ -22,6 +22,8 @@ #include #include +#include + #include "io/address.hpp" #include "io/errors.hpp" #include "io/message_conversion.hpp" @@ -107,8 +109,9 @@ class SimulatorHandle { ResponseFuture SubmitRequest(Address to_address, Address from_address, Request &&request, Duration timeout, std::function &&maybe_tick_simulator, std::function &&fill_notifier) { - spdlog::trace("submitting request to {}", to_address.last_known_port); auto type_info = TypeInfoFor(request); + std::string demangled_name = boost::core::demangle(type_info.get().name()); + spdlog::trace("simulator sending request {} to {}", demangled_name, to_address); auto [future, promise] = memgraph::io::FuturePromisePairWithNotifications>( // set notifier for when the Future::Wait is called @@ -116,34 +119,36 @@ class SimulatorHandle { // set notifier for when Promise::Fill is called std::forward>(fill_notifier)); - std::unique_lock lock(mu_); + { + std::unique_lock lock(mu_); - RequestId request_id = ++request_id_counter_; + RequestId request_id = ++request_id_counter_; - const Time deadline = cluster_wide_time_microseconds_ + timeout; + const Time deadline = cluster_wide_time_microseconds_ + timeout; - std::any message(request); - OpaqueMessage om{.to_address = to_address, - .from_address = from_address, - .request_id = request_id, - .message = std::move(message), - .type_info = type_info}; - in_flight_.emplace_back(std::make_pair(to_address, std::move(om))); + std::any message(request); + OpaqueMessage om{.to_address = to_address, + .from_address = from_address, + .request_id = request_id, + .message = std::move(message), + .type_info = type_info}; + in_flight_.emplace_back(std::make_pair(to_address, std::move(om))); - PromiseKey promise_key{.requester_address = from_address, .request_id = request_id}; - OpaquePromise opaque_promise(std::move(promise).ToUnique()); - DeadlineAndOpaquePromise dop{ - .requested_at = cluster_wide_time_microseconds_, - .deadline = deadline, - .promise = std::move(opaque_promise), - }; + PromiseKey promise_key{.requester_address = from_address, .request_id = request_id}; + OpaquePromise opaque_promise(std::move(promise).ToUnique()); + DeadlineAndOpaquePromise dop{ + .requested_at = cluster_wide_time_microseconds_, + .deadline = deadline, + .promise = std::move(opaque_promise), + }; - MG_ASSERT(!promises_.contains(promise_key)); + MG_ASSERT(!promises_.contains(promise_key)); - promises_.emplace(std::move(promise_key), std::move(dop)); + promises_.emplace(std::move(promise_key), std::move(dop)); - stats_.total_messages++; - stats_.total_requests++; + stats_.total_messages++; + stats_.total_requests++; + } // lock dropped here cv_.notify_all(); diff --git a/src/io/simulator/simulator_transport.hpp b/src/io/simulator/simulator_transport.hpp index 2107c34ca..758bc43b7 100644 --- a/src/io/simulator/simulator_transport.hpp +++ b/src/io/simulator/simulator_transport.hpp @@ -36,11 +36,11 @@ class SimulatorTransport { template ResponseFuture Request(Address to_address, Address from_address, RequestT request, std::function notification, Duration timeout) { - std::function maybe_tick_simulator = [this] { return simulator_handle_->MaybeTickSimulator(); }; + std::shared_ptr handle_copy = simulator_handle_; + std::function tick_simulator = [handle_copy] { return handle_copy->MaybeTickSimulator(); }; - return simulator_handle_->template SubmitRequest(to_address, from_address, std::move(request), - timeout, std::move(maybe_tick_simulator), - std::move(notification)); + return simulator_handle_->template SubmitRequest( + to_address, from_address, std::move(request), timeout, std::move(tick_simulator), std::move(notification)); } template diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 2d563ade0..1336addae 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -135,10 +135,16 @@ class RequestRouter : public RequestRouterInterface { ~RequestRouter() override {} + void InstallSimulatorTicker(std::function tick_simulator) { + notifier_.InstallSimulatorTicker(tick_simulator); + } + void StartTransaction() override { coordinator::HlcRequest req{.last_shard_map_version = shards_map_.GetHlc()}; CoordinatorWriteRequests write_req = req; + spdlog::trace("sending hlc request to start transaction"); auto write_res = coord_cli_.SendWriteRequest(write_req); + spdlog::trace("received hlc response to start transaction"); if (write_res.HasError()) { throw std::runtime_error("HLC request failed"); } @@ -157,7 +163,9 @@ class RequestRouter : public RequestRouterInterface { void Commit() override { coordinator::HlcRequest req{.last_shard_map_version = shards_map_.GetHlc()}; CoordinatorWriteRequests write_req = req; + spdlog::trace("sending hlc request before committing transaction"); auto write_res = coord_cli_.SendWriteRequest(write_req); + spdlog::trace("received hlc response before committing transaction"); if (write_res.HasError()) { throw std::runtime_error("HLC request for commit failed"); } @@ -227,7 +235,7 @@ class RequestRouter : public RequestRouterInterface { std::vector ScanVertices(std::optional label) override { // create requests std::vector> unsent_requests = RequestsForScanVertices(label); - spdlog::error("created {} ScanVertices requests", unsent_requests.size()); + spdlog::trace("created {} ScanVertices requests", unsent_requests.size()); // begin all requests in parallel RunningRequests running_requests = {}; @@ -239,11 +247,11 @@ class RequestRouter : public RequestRouterInterface { storage_client.SendAsyncReadRequest(request.request, notifier_, readiness_token); running_requests.emplace(readiness_token.GetId(), request); } - spdlog::error("sent {} ScanVertices requests in parallel", running_requests.size()); + spdlog::trace("sent {} ScanVertices requests in parallel", running_requests.size()); // drive requests to completion auto responses = DriveReadResponses(running_requests); - spdlog::error("got back {} ScanVertices responses after driving to completion", responses.size()); + spdlog::trace("got back {} ScanVertices responses after driving to completion", responses.size()); // convert responses into VertexAccessor objects to return std::vector accessors; @@ -263,6 +271,7 @@ class RequestRouter : public RequestRouterInterface { // create requests std::vector> unsent_requests = RequestsForCreateVertices(new_vertices); + spdlog::trace("created {} CreateVertices requests", unsent_requests.size()); // begin all requests in parallel RunningRequests running_requests = {}; @@ -277,6 +286,7 @@ class RequestRouter : public RequestRouterInterface { storage_client.SendAsyncWriteRequest(request.request, notifier_, readiness_token); running_requests.emplace(readiness_token.GetId(), request); } + spdlog::trace("sent {} CreateVertices requests in parallel", running_requests.size()); // drive requests to completion return DriveWriteResponses(running_requests); @@ -519,8 +529,10 @@ class RequestRouter : public RequestRouterInterface { // even if they came back in randomized orders. std::map response_map; + spdlog::trace("waiting on readiness for token"); while (response_map.size() < running_requests.size()) { auto ready = notifier_.Await(); + spdlog::trace("got readiness for token {}", ready.GetId()); auto &request = running_requests.at(ready.GetId()); auto &storage_client = GetStorageClientForShard(request.shard); diff --git a/tests/simulation/request_router.cpp b/tests/simulation/request_router.cpp index 8187f138b..af8ec62ef 100644 --- a/tests/simulation/request_router.cpp +++ b/tests/simulation/request_router.cpp @@ -338,6 +338,8 @@ void DoTest() { CoordinatorClient coordinator_client(cli_io, c_addrs[0], c_addrs); query::v2::RequestRouter request_router(std::move(coordinator_client), std::move(cli_io)); + std::function tick_simulator = simulator.GetSimulatorTickClosure(); + request_router.InstallSimulatorTicker(tick_simulator); request_router.StartTransaction(); TestScanVertices(request_router); diff --git a/tests/simulation/test_cluster.hpp b/tests/simulation/test_cluster.hpp index 1392a0632..3e14545a9 100644 --- a/tests/simulation/test_cluster.hpp +++ b/tests/simulation/test_cluster.hpp @@ -244,6 +244,8 @@ std::pair RunClusterSimulation(const WaitForShardsToInitialize(coordinator_client); query::v2::RequestRouter request_router(std::move(coordinator_client), std::move(cli_io)); + std::function tick_simulator = simulator.GetSimulatorTickClosure(); + request_router.InstallSimulatorTicker(tick_simulator); request_router.StartTransaction(); From 68ae729b07ba4c8818b47278f876eed2db043af4 Mon Sep 17 00:00:00 2001 From: Jeremy B <97525434+42jeremy@users.noreply.github.com> Date: Mon, 5 Dec 2022 10:34:58 +0100 Subject: [PATCH 72/93] Update src/query/v2/multiframe.hpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: János Benjamin Antal --- src/query/v2/multiframe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index a73ca1f41..304a71cdb 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -107,7 +107,7 @@ class ValidFramesReader { using pointer = value_type *; using reference = const Frame &; - Iterator() {} + Iterator() = default; explicit Iterator(FrameWithValidity *ptr) : ptr_(ptr) {} reference operator*() const { return *ptr_; } From c7c0234889cf807bd17c69cf4c28f4c8e7ffce0a Mon Sep 17 00:00:00 2001 From: jeremy Date: Mon, 5 Dec 2022 10:38:01 +0100 Subject: [PATCH 73/93] Add default constructor to iterators --- src/query/v2/multiframe.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index a73ca1f41..2c57c8853 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -107,7 +107,7 @@ class ValidFramesReader { using pointer = value_type *; using reference = const Frame &; - Iterator() {} + Iterator() = default; explicit Iterator(FrameWithValidity *ptr) : ptr_(ptr) {} reference operator*() const { return *ptr_; } @@ -158,8 +158,8 @@ class ValidFramesModifier { using value_type = Frame; using pointer = value_type *; using reference = Frame &; - Iterator() {} + Iterator() = default; Iterator(FrameWithValidity *ptr, ValidFramesModifier &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(&iterator_wrapper) {} @@ -216,8 +216,7 @@ class ValidFramesConsumer { using pointer = value_type *; using reference = FrameWithValidity &; - Iterator() {} - + Iterator() = default; Iterator(FrameWithValidity *ptr, ValidFramesConsumer &iterator_wrapper) : ptr_(ptr), iterator_wrapper_(&iterator_wrapper) {} @@ -273,7 +272,7 @@ class InvalidFramesPopulator { using pointer = value_type *; using reference = FrameWithValidity &; - Iterator() {} + Iterator() = default; explicit Iterator(FrameWithValidity *ptr) : ptr_(ptr) {} reference operator*() const { return *ptr_; } From f4428af210306d7edf32eb8bd2c728e09c619fb3 Mon Sep 17 00:00:00 2001 From: jeremy Date: Mon, 5 Dec 2022 11:05:47 +0100 Subject: [PATCH 74/93] Remove reference function on operator++() & --- src/query/v2/multiframe.hpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 2c57c8853..0b6896422 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -118,10 +118,8 @@ class ValidFramesReader { return *this; } - // clang-tidy warning is wrong here, because we & qualify the function, meaning that you can't post increment - // temporaries, e.g, (it++)++ - // NOLINTNEXTLINE (cert-dcl21-cpp) - Iterator operator++(int) & { + // NOLINTNEXTLINE(cert-dcl21-cpp) + Iterator operator++(int) { auto old = *this; ptr_++; return old; @@ -175,10 +173,8 @@ class ValidFramesModifier { return *this; } - // clang-tidy warning is wrong here, because we & qualify the function, meaning that you can't post increment - // temporaries, e.g, (it++)++ - // NOLINTNEXTLINE (cert-dcl21-cpp) - Iterator operator++(int) & { + // NOLINTNEXTLINE(cert-dcl21-cpp) + Iterator operator++(int) { auto old = *this; ++*this; return old; @@ -231,10 +227,8 @@ class ValidFramesConsumer { return *this; } - // clang-tidy warning is wrong here, because we & qualify the function, meaning that you can't post increment - // temporaries, e.g, (it++)++ - // NOLINTNEXTLINE (cert-dcl21-cpp) - Iterator operator++(int) & { + // NOLINTNEXTLINE(cert-dcl21-cpp) + Iterator operator++(int) { auto old = *this; ++*this; return old; @@ -284,10 +278,8 @@ class InvalidFramesPopulator { return *this; } - // clang-tidy warning is wrong here, because we & qualify the function, meaning that you can't post increment - // temporaries, e.g, (it++)++ - // NOLINTNEXTLINE (cert-dcl21-cpp) - Iterator operator++(int) & { + // NOLINTNEXTLINE(cert-dcl21-cpp) + Iterator operator++(int) { auto old = *this; ++ptr_; return old; From 7f9eceadb333476d28801580296a92411219b960 Mon Sep 17 00:00:00 2001 From: jeremy Date: Mon, 5 Dec 2022 11:19:47 +0100 Subject: [PATCH 75/93] Remove un-needed frame modification in Once This is not needed and would be incorrect with the optional --- src/query/v2/plan/operator.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 9a000face..eeb5cd6b4 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -269,11 +269,7 @@ void Once::OnceCursor::PullMultiple(MultiFrame &multi_frame, ExecutionContext &c if (!did_pull_) { auto &first_frame = multi_frame.GetFirstFrame(); - auto *memory_resource = first_frame.GetMemoryResource(); first_frame.MakeValid(); - for (auto &value : first_frame.elems()) { - value = TypedValue{memory_resource}; - } did_pull_ = true; } } From ec73ee666c02c32a7853f7c60d284b18917921ed Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis Date: Mon, 5 Dec 2022 13:02:33 +0200 Subject: [PATCH 76/93] Remove unused jba.txt --- jba.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 jba.txt diff --git a/jba.txt b/jba.txt deleted file mode 100644 index e69de29bb..000000000 From 6d3f9ab695235cebb33f31cf91bb0043f289ff08 Mon Sep 17 00:00:00 2001 From: jeremy Date: Mon, 5 Dec 2022 14:07:53 +0100 Subject: [PATCH 77/93] Removing template from class Frame --- src/expr/interpret/eval.hpp | 6 +++--- src/expr/interpret/frame.hpp | 8 +++----- src/query/v2/bindings/frame.hpp | 4 ++-- src/query/v2/interpreter.cpp | 12 ++++++------ src/storage/v3/bindings/frame.hpp | 2 +- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/expr/interpret/eval.hpp b/src/expr/interpret/eval.hpp index 8fb300a83..b97ecc9d1 100644 --- a/src/expr/interpret/eval.hpp +++ b/src/expr/interpret/eval.hpp @@ -36,8 +36,8 @@ template class ExpressionEvaluator : public ExpressionVisitor { public: - ExpressionEvaluator(Frame *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx, - DbAccessor *dba, StorageView view) + ExpressionEvaluator(Frame *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx, DbAccessor *dba, + StorageView view) : frame_(frame), symbol_table_(&symbol_table), ctx_(&ctx), dba_(dba), view_(view) {} using ExpressionVisitor::Visit; @@ -782,7 +782,7 @@ class ExpressionEvaluator : public ExpressionVisitor { LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; } - Frame *frame_; + Frame *frame_; const SymbolTable *symbol_table_; const EvaluationContext *ctx_; DbAccessor *dba_; diff --git a/src/expr/interpret/frame.hpp b/src/expr/interpret/frame.hpp index 1bc5bba83..1cd6a99ce 100644 --- a/src/expr/interpret/frame.hpp +++ b/src/expr/interpret/frame.hpp @@ -20,7 +20,6 @@ namespace memgraph::expr { -template class Frame { public: /// Create a Frame of given size backed by a utils::NewDeleteResource() @@ -42,12 +41,11 @@ class Frame { utils::pmr::vector elems_; }; -template -class FrameWithValidity final : public Frame { +class FrameWithValidity final : public Frame { public: - explicit FrameWithValidity(int64_t size) : Frame(size), is_valid_(false) {} + explicit FrameWithValidity(int64_t size) : Frame(size), is_valid_(false) {} - FrameWithValidity(int64_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {} + FrameWithValidity(int64_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {} bool IsValid() const noexcept { return is_valid_; } void MakeValid() noexcept { is_valid_ = true; } diff --git a/src/query/v2/bindings/frame.hpp b/src/query/v2/bindings/frame.hpp index 688f14121..898b03512 100644 --- a/src/query/v2/bindings/frame.hpp +++ b/src/query/v2/bindings/frame.hpp @@ -17,6 +17,6 @@ #include "query/v2/bindings/typed_value.hpp" namespace memgraph::query::v2 { -using Frame = memgraph::expr::Frame; -using FrameWithValidity = memgraph::expr::FrameWithValidity; +using Frame = memgraph::expr::Frame; +using FrameWithValidity = memgraph::expr::FrameWithValidity; } // namespace memgraph::query::v2 diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index b5f916e9f..fde11ac00 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -148,7 +148,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa // Empty frame for evaluation of password expression. This is OK since // password should be either null or string literal and it's evaluation // should not depend on frame. - expr::Frame frame(0); + expr::Frame frame(0); SymbolTable symbol_table; EvaluationContext evaluation_context; // TODO: MemoryResource for EvaluationContext, it should probably be passed as @@ -315,7 +315,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters ¶meters, InterpreterContext *interpreter_context, RequestRouterInterface *request_router, std::vector *notifications) { - expr::Frame frame(0); + expr::Frame frame(0); SymbolTable symbol_table; EvaluationContext evaluation_context; // TODO: MemoryResource for EvaluationContext, it should probably be passed as @@ -450,7 +450,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters ¶meters, RequestRouterInterface *request_router) { - expr::Frame frame(0); + expr::Frame frame(0); SymbolTable symbol_table; EvaluationContext evaluation_context; // TODO: MemoryResource for EvaluationContext, it should probably be passed as @@ -663,7 +663,7 @@ struct PullPlan { private: std::shared_ptr plan_ = nullptr; plan::UniqueCursorPtr cursor_ = nullptr; - expr::FrameWithValidity frame_; + expr::FrameWithValidity frame_; MultiFrame multi_frame_; ExecutionContext ctx_; std::optional memory_limit_; @@ -998,7 +998,7 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map(parsed_query.query); - expr::Frame frame(0); + expr::Frame frame(0); SymbolTable symbol_table; EvaluationContext evaluation_context; evaluation_context.timestamp = QueryTimestamp(); @@ -1140,7 +1140,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra auto *cypher_query = utils::Downcast(parsed_inner_query.query); MG_ASSERT(cypher_query, "Cypher grammar should not allow other queries in PROFILE"); - expr::Frame frame(0); + expr::Frame frame(0); SymbolTable symbol_table; EvaluationContext evaluation_context; evaluation_context.timestamp = QueryTimestamp(); diff --git a/src/storage/v3/bindings/frame.hpp b/src/storage/v3/bindings/frame.hpp index 6e695816c..04a944ca4 100644 --- a/src/storage/v3/bindings/frame.hpp +++ b/src/storage/v3/bindings/frame.hpp @@ -17,5 +17,5 @@ #include "storage/v3/bindings/typed_value.hpp" namespace memgraph::storage::v3 { -using Frame = memgraph::expr::Frame; +using Frame = memgraph::expr::Frame; } // namespace memgraph::storage::v3 From 6efe074313572b6ab1c0dd83b0970ff891ee33f4 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Mon, 5 Dec 2022 13:15:12 +0000 Subject: [PATCH 78/93] Update GetProperties to use the correct style of request driving in the RequestRouter --- src/query/v2/request_router.hpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 5f5d103cc..110aa5d96 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -358,25 +358,23 @@ class RequestRouter : public RequestRouterInterface { } std::vector GetProperties(msgs::GetPropertiesRequest requests) override { - ExecutionState state = {}; - InitializeExecutionState(state, std::move(requests)); - for (auto &request : state.requests) { + // create requests + std::vector unsent_requests = RequestsForGetProperties(requests); + + // begin all requests in parallel + RunningRequests running_requests = {}; + running_requests.reserve(unsent_requests.size()); + for (size_t i = 0; i < unsent_requests.size(); i++) { + auto &request = unsent_requests[i]; + io::ReadinessToken readiness_token{i}; auto &storage_client = GetStorageClientForShard(request.shard); msgs::ReadRequests req = request.request; - request.async_request_token = storage_client.SendAsyncReadRequest(req); + storage_client.SendAsyncReadRequest(req, notifier_, readiness_token); + running_requests.emplace(readiness_token.GetId(), request); } - std::vector responses; - do { - DriveReadResponses(state, responses); - } while (!state.requests.empty()); - - std::vector result; - for (auto &res : responses) { - std::move(res.result_row.begin(), res.result_row.end(), std::back_inserter(result)); - } - - return result; + // drive requests to completion + return DriveReadResponses(running_requests); } std::optional MaybeNameToProperty(const std::string &name) const override { @@ -522,7 +520,7 @@ class RequestRouter : public RequestRouterInterface { return requests; } - void InitializeExecutionState(ExecutionState &state, msgs::GetPropertiesRequest request) { + std::vector RequestsForGetProperties(const msgs::GetPropertiesRequest &request) { std::map per_shard_request_table; auto top_level_rqst_template = request; top_level_rqst_template.transaction_id = transaction_id_; From 1b458ebc410f93d85a7c9aa0c6c1d5a32af0f032 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Mon, 5 Dec 2022 13:26:44 +0000 Subject: [PATCH 79/93] Complete migration of GetProperties to new request style --- src/query/v2/request_router.hpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 110aa5d96..0d4bf27e1 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -359,7 +359,7 @@ class RequestRouter : public RequestRouterInterface { std::vector GetProperties(msgs::GetPropertiesRequest requests) override { // create requests - std::vector unsent_requests = RequestsForGetProperties(requests); + std::vector> unsent_requests = RequestsForGetProperties(requests); // begin all requests in parallel RunningRequests running_requests = {}; @@ -374,7 +374,16 @@ class RequestRouter : public RequestRouterInterface { } // drive requests to completion - return DriveReadResponses(running_requests); + auto responses = DriveReadResponses(running_requests); + + // post-process responses + std::vector result_rows; + + for (auto &&response : responses) { + std::move(response.result_row.begin(), response.result_row.end(), std::back_inserter(result_rows)); + } + + return result_rows; } std::optional MaybeNameToProperty(const std::string &name) const override { @@ -520,15 +529,14 @@ class RequestRouter : public RequestRouterInterface { return requests; } - std::vector RequestsForGetProperties(const msgs::GetPropertiesRequest &request) { + std::vector> RequestsForGetProperties( + const msgs::GetPropertiesRequest &request) { std::map per_shard_request_table; auto top_level_rqst_template = request; top_level_rqst_template.transaction_id = transaction_id_; top_level_rqst_template.vertex_ids.clear(); top_level_rqst_template.vertices_and_edges.clear(); - state.transaction_id = transaction_id_; - for (auto &vertex : request.vertex_ids) { auto shard = shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second)); @@ -547,15 +555,18 @@ class RequestRouter : public RequestRouterInterface { per_shard_request_table[shard].vertices_and_edges.emplace_back(std::move(vertex), maybe_edge); } + std::vector> requests; + for (auto &[shard, rqst] : per_shard_request_table) { ShardRequestState shard_request_state{ .shard = shard, .request = std::move(rqst), - .async_request_token = std::nullopt, }; - state.requests.emplace_back(std::move(shard_request_state)); + requests.emplace_back(std::move(shard_request_state)); } + + return requests; } StorageClient &GetStorageClientForShard(Shard shard) { From ca3f748325403771b4dbd02cbbda6551e88180f7 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Mon, 5 Dec 2022 13:43:20 +0000 Subject: [PATCH 80/93] Apply clang-tidy feedback --- src/io/notifier.hpp | 1 + src/query/v2/request_router.hpp | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/io/notifier.hpp b/src/io/notifier.hpp index 9e0dec7df..e2b69e4ac 100644 --- a/src/io/notifier.hpp +++ b/src/io/notifier.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace memgraph::io { diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 0d4bf27e1..b38d4d005 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -359,7 +359,8 @@ class RequestRouter : public RequestRouterInterface { std::vector GetProperties(msgs::GetPropertiesRequest requests) override { // create requests - std::vector> unsent_requests = RequestsForGetProperties(requests); + std::vector> unsent_requests = + RequestsForGetProperties(std::move(requests)); // begin all requests in parallel RunningRequests running_requests = {}; @@ -530,14 +531,14 @@ class RequestRouter : public RequestRouterInterface { } std::vector> RequestsForGetProperties( - const msgs::GetPropertiesRequest &request) { + msgs::GetPropertiesRequest &&request) { std::map per_shard_request_table; auto top_level_rqst_template = request; top_level_rqst_template.transaction_id = transaction_id_; top_level_rqst_template.vertex_ids.clear(); top_level_rqst_template.vertices_and_edges.clear(); - for (auto &vertex : request.vertex_ids) { + for (auto &&vertex : request.vertex_ids) { auto shard = shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second)); if (!per_shard_request_table.contains(shard)) { From 747b8a21cdd87d5006319711ccf6e3e881b6136e Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Mon, 5 Dec 2022 14:20:06 +0000 Subject: [PATCH 81/93] Fix bug with polling redirected requests --- src/query/v2/request_router.hpp | 15 +++++++++++++-- src/storage/v3/shard_rsm.cpp | 2 +- tests/simulation/request_router.cpp | 7 ++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index b38d4d005..7e9831a70 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -45,6 +45,7 @@ #include "utils/result.hpp" namespace memgraph::query::v2 { + template class RsmStorageClientManager { public: @@ -608,7 +609,12 @@ class RequestRouter : public RequestRouterInterface { auto &request = running_requests.at(ready.GetId()); auto &storage_client = GetStorageClientForShard(request.shard); - auto poll_result = storage_client.PollAsyncReadRequest(ready); + std::optional> poll_result = + storage_client.PollAsyncReadRequest(ready); + + if (!poll_result.has_value()) { + continue; + } if (poll_result->HasError()) { throw std::runtime_error("RequestRouter Read request timed out"); @@ -649,7 +655,12 @@ class RequestRouter : public RequestRouterInterface { auto &request = running_requests.at(ready.GetId()); auto &storage_client = GetStorageClientForShard(request.shard); - auto poll_result = storage_client.PollAsyncWriteRequest(ready); + std::optional> poll_result = + storage_client.PollAsyncWriteRequest(ready); + + if (!poll_result.has_value()) { + continue; + } if (poll_result->HasError()) { throw std::runtime_error("RequestRouter Write request timed out"); diff --git a/src/storage/v3/shard_rsm.cpp b/src/storage/v3/shard_rsm.cpp index 639ffd6d8..b919d217c 100644 --- a/src/storage/v3/shard_rsm.cpp +++ b/src/storage/v3/shard_rsm.cpp @@ -611,7 +611,7 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest &&req) { return limit; }; - auto collect_response = [get_limit, &req](auto &elements, auto create_result_row) { + auto collect_response = [get_limit, &req](auto &elements, auto create_result_row) -> msgs::ReadResponses { msgs::GetPropertiesResponse response; const auto limit = get_limit(elements); for (size_t index = 0; index != limit; ++index) { diff --git a/tests/simulation/request_router.cpp b/tests/simulation/request_router.cpp index bc5168483..0f712793f 100644 --- a/tests/simulation/request_router.cpp +++ b/tests/simulation/request_router.cpp @@ -18,6 +18,8 @@ #include #include +#include + #include "common.hpp" #include "common/types.hpp" #include "coordinator/coordinator_client.hpp" @@ -370,4 +372,7 @@ void DoTest() { } } // namespace memgraph::query::v2::tests -int main() { memgraph::query::v2::tests::DoTest(); } +int main() { + spdlog::cfg::load_env_levels(); + memgraph::query::v2::tests::DoTest(); +} From 2a81ce5640f31d8c025d88584a2de7f14c8ae100 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Mon, 5 Dec 2022 15:26:18 +0100 Subject: [PATCH 82/93] Update src/io/simulator/simulator.hpp Co-authored-by: gvolfing <107616712+gvolfing@users.noreply.github.com> --- src/io/simulator/simulator.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/io/simulator/simulator.hpp b/src/io/simulator/simulator.hpp index 667570276..8afc073af 100644 --- a/src/io/simulator/simulator.hpp +++ b/src/io/simulator/simulator.hpp @@ -51,8 +51,7 @@ class Simulator { SimulatorStats Stats() { return simulator_handle_->Stats(); } std::function GetSimulatorTickClosure() { - std::shared_ptr handle_copy = simulator_handle_; - std::function tick_closure = [handle_copy] { return handle_copy->MaybeTickSimulator(); }; + std::function tick_closure = [handle_copy = simulator_handle_] { return handle_copy->MaybeTickSimulator(); }; return tick_closure; } }; From 25713405df9ad6df9320c46fc850579aba3f2537 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Mon, 5 Dec 2022 15:26:29 +0100 Subject: [PATCH 83/93] Update src/io/simulator/simulator_transport.hpp Co-authored-by: gvolfing <107616712+gvolfing@users.noreply.github.com> --- src/io/simulator/simulator_transport.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/io/simulator/simulator_transport.hpp b/src/io/simulator/simulator_transport.hpp index 758bc43b7..038cfeb03 100644 --- a/src/io/simulator/simulator_transport.hpp +++ b/src/io/simulator/simulator_transport.hpp @@ -36,8 +36,7 @@ class SimulatorTransport { template ResponseFuture Request(Address to_address, Address from_address, RequestT request, std::function notification, Duration timeout) { - std::shared_ptr handle_copy = simulator_handle_; - std::function tick_simulator = [handle_copy] { return handle_copy->MaybeTickSimulator(); }; + std::function tick_simulator = [handle_copy = simulator_handle_] { return handle_copy->MaybeTickSimulator(); }; return simulator_handle_->template SubmitRequest( to_address, from_address, std::move(request), timeout, std::move(tick_simulator), std::move(notification)); From 9bab26fb105752005745f0beacd36b3e5a2f6053 Mon Sep 17 00:00:00 2001 From: Jure Bajic Date: Mon, 5 Dec 2022 15:50:40 +0100 Subject: [PATCH 84/93] Apply suggestions from code review Co-authored-by: Kostas Kyrimis --- tests/benchmark/data_structures_remove.cpp | 2 +- tests/benchmark/skip_list_common.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index 0d7baaccf..641ad9453 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -41,7 +41,7 @@ static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); - // So we can also have elements that does don't exist + // So we can also have elements that don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); int64_t removed_elems{0}; diff --git a/tests/benchmark/skip_list_common.hpp b/tests/benchmark/skip_list_common.hpp index 8dd4c705b..54e62ed7e 100644 --- a/tests/benchmark/skip_list_common.hpp +++ b/tests/benchmark/skip_list_common.hpp @@ -99,7 +99,7 @@ inline void RunTest(std::function &, Stats &)> test std::cout << " Successful find: " << stats.succ[3] << std::endl; std::cout << std::endl; - const auto tot = stats.succ[0] + stats.succ[1] + stats.succ[2] + stats.succ[3]; + const auto tot = std::accumulate(stats.succ.begin(), + stats.succ.begin() + 3, 0); const auto tops = stats.total; std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; From e0b7d7abebb25a406bb430815b941143f2dd44f8 Mon Sep 17 00:00:00 2001 From: jbajic Date: Mon, 5 Dec 2022 16:00:17 +0100 Subject: [PATCH 85/93] Address review comments --- tests/benchmark/data_structures_common.hpp | 9 +++------ tests/benchmark/data_structures_insert.cpp | 11 ++--------- tests/benchmark/skip_list_common.hpp | 7 +++++-- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp index d6426dcfe..56c4d60cd 100644 --- a/tests/benchmark/data_structures_common.hpp +++ b/tests/benchmark/data_structures_common.hpp @@ -27,8 +27,7 @@ namespace memgraph::benchmark { template inline void PrepareData(utils::SkipList &skip_list, const int64_t num_elements) { coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; for (auto i{0}; i < num_elements; ++i) { auto acc = skip_list.access(); acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); @@ -38,8 +37,7 @@ inline void PrepareData(utils::SkipList &skip_list, const int64_t num_element template inline void PrepareData(std::map &std_map, const int64_t num_elements) { coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, @@ -51,8 +49,7 @@ inline void PrepareData(std::map &std_map, const int64_t num_eleme template inline void PrepareData(std::set &std_set, const int64_t num_elements) { coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; for (auto i{0}; i < num_elements; ++i) { std_set.insert(std::vector{storage::v3::PropertyValue{i}}); } diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 6700eaa93..6469c8ea3 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -39,8 +39,7 @@ namespace memgraph::benchmark { static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { utils::SkipList skip_list; coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto _ : state) { @@ -54,8 +53,7 @@ static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { std::map std_map; coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto _ : state) { @@ -69,11 +67,6 @@ static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { std::set std_set; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { std_set.insert(storage::v3::PrimaryKey{std::vector{storage::v3::PropertyValue{i}}}); diff --git a/tests/benchmark/skip_list_common.hpp b/tests/benchmark/skip_list_common.hpp index 54e62ed7e..4b27281c8 100644 --- a/tests/benchmark/skip_list_common.hpp +++ b/tests/benchmark/skip_list_common.hpp @@ -11,11 +11,14 @@ #pragma once +#include #include #include +#include #include #include #include +#include #include #include @@ -26,7 +29,7 @@ DEFINE_int32(duration, 10, "Duration of test (in seconds)"); struct Stats { uint64_t total{0}; - uint64_t succ[4] = {0, 0, 0, 0}; + std::array succ = {0, 0, 0, 0}; }; const int OP_INSERT = 0; @@ -99,7 +102,7 @@ inline void RunTest(std::function &, Stats &)> test std::cout << " Successful find: " << stats.succ[3] << std::endl; std::cout << std::endl; - const auto tot = std::accumulate(stats.succ.begin(), + stats.succ.begin() + 3, 0); + const auto tot = std::accumulate(stats.succ.begin(), +stats.succ.begin() + 3, 0); const auto tops = stats.total; std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; From b288f06cb7e51b69ddaaccf690e9816ed4f5017a Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Tue, 6 Dec 2022 11:31:40 +0100 Subject: [PATCH 86/93] Update src/io/rsm/rsm_client.hpp Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> --- src/io/rsm/rsm_client.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/rsm/rsm_client.hpp b/src/io/rsm/rsm_client.hpp index b863e79a4..cf56b1151 100644 --- a/src/io/rsm/rsm_client.hpp +++ b/src/io/rsm/rsm_client.hpp @@ -94,7 +94,7 @@ class RsmClient { BasicResult SendWriteRequest(WriteRequestT req) { Notifier notifier; - ReadinessToken readiness_token{0}; + const ReadinessToken readiness_token{0}; SendAsyncWriteRequest(req, notifier, readiness_token); auto poll_result = AwaitAsyncWriteRequest(readiness_token); while (!poll_result) { From 675c2fe24acb5c3fc1f1ffbfa9215c3819c6268e Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Tue, 6 Dec 2022 11:31:46 +0100 Subject: [PATCH 87/93] Update src/io/rsm/rsm_client.hpp Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> --- src/io/rsm/rsm_client.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/rsm/rsm_client.hpp b/src/io/rsm/rsm_client.hpp index cf56b1151..2b76b6399 100644 --- a/src/io/rsm/rsm_client.hpp +++ b/src/io/rsm/rsm_client.hpp @@ -105,7 +105,7 @@ class RsmClient { BasicResult SendReadRequest(ReadRequestT req) { Notifier notifier; - ReadinessToken readiness_token{0}; + const ReadinessToken readiness_token{0}; SendAsyncReadRequest(req, notifier, readiness_token); auto poll_result = AwaitAsyncReadRequest(readiness_token); while (!poll_result) { From 5d3d67cbd09faa5a13783910660cac7400c2a460 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Tue, 6 Dec 2022 10:32:57 +0000 Subject: [PATCH 88/93] Rename unsent_requests to requests_to_be_sent in RequestRouter --- src/query/v2/request_router.hpp | 44 ++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 7e9831a70..116e884c2 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -237,14 +237,14 @@ class RequestRouter : public RequestRouterInterface { // TODO(kostasrim) Simplify return result std::vector ScanVertices(std::optional label) override { // create requests - std::vector> unsent_requests = RequestsForScanVertices(label); - spdlog::trace("created {} ScanVertices requests", unsent_requests.size()); + std::vector> requests_to_be_sent = RequestsForScanVertices(label); + spdlog::trace("created {} ScanVertices requests", requests_to_be_sent.size()); // begin all requests in parallel RunningRequests running_requests = {}; - running_requests.reserve(unsent_requests.size()); - for (size_t i = 0; i < unsent_requests.size(); i++) { - auto &request = unsent_requests[i]; + running_requests.reserve(requests_to_be_sent.size()); + for (size_t i = 0; i < requests_to_be_sent.size(); i++) { + auto &request = requests_to_be_sent[i]; io::ReadinessToken readiness_token{i}; auto &storage_client = GetStorageClientForShard(request.shard); storage_client.SendAsyncReadRequest(request.request, notifier_, readiness_token); @@ -272,15 +272,15 @@ class RequestRouter : public RequestRouterInterface { MG_ASSERT(!new_vertices.empty()); // create requests - std::vector> unsent_requests = + std::vector> requests_to_be_sent = RequestsForCreateVertices(new_vertices); - spdlog::trace("created {} CreateVertices requests", unsent_requests.size()); + spdlog::trace("created {} CreateVertices requests", requests_to_be_sent.size()); // begin all requests in parallel RunningRequests running_requests = {}; - running_requests.reserve(unsent_requests.size()); - for (size_t i = 0; i < unsent_requests.size(); i++) { - auto &request = unsent_requests[i]; + running_requests.reserve(requests_to_be_sent.size()); + for (size_t i = 0; i < requests_to_be_sent.size(); i++) { + auto &request = requests_to_be_sent[i]; io::ReadinessToken readiness_token{i}; for (auto &new_vertex : request.request.new_vertices) { new_vertex.label_ids.erase(new_vertex.label_ids.begin()); @@ -299,13 +299,13 @@ class RequestRouter : public RequestRouterInterface { MG_ASSERT(!new_edges.empty()); // create requests - std::vector> unsent_requests = RequestsForCreateExpand(new_edges); + std::vector> requests_to_be_sent = RequestsForCreateExpand(new_edges); // begin all requests in parallel RunningRequests running_requests = {}; - running_requests.reserve(unsent_requests.size()); - for (size_t i = 0; i < unsent_requests.size(); i++) { - auto &request = unsent_requests[i]; + running_requests.reserve(requests_to_be_sent.size()); + for (size_t i = 0; i < requests_to_be_sent.size(); i++) { + auto &request = requests_to_be_sent[i]; io::ReadinessToken readiness_token{i}; auto &storage_client = GetStorageClientForShard(request.shard); msgs::WriteRequests req = request.request; @@ -325,13 +325,13 @@ class RequestRouter : public RequestRouterInterface { // must be fetched again with an ExpandOne(Edges.dst) // create requests - std::vector> unsent_requests = RequestsForExpandOne(request); + std::vector> requests_to_be_sent = RequestsForExpandOne(request); // begin all requests in parallel RunningRequests running_requests = {}; - running_requests.reserve(unsent_requests.size()); - for (size_t i = 0; i < unsent_requests.size(); i++) { - auto &request = unsent_requests[i]; + running_requests.reserve(requests_to_be_sent.size()); + for (size_t i = 0; i < requests_to_be_sent.size(); i++) { + auto &request = requests_to_be_sent[i]; io::ReadinessToken readiness_token{i}; auto &storage_client = GetStorageClientForShard(request.shard); msgs::ReadRequests req = request.request; @@ -360,14 +360,14 @@ class RequestRouter : public RequestRouterInterface { std::vector GetProperties(msgs::GetPropertiesRequest requests) override { // create requests - std::vector> unsent_requests = + std::vector> requests_to_be_sent = RequestsForGetProperties(std::move(requests)); // begin all requests in parallel RunningRequests running_requests = {}; - running_requests.reserve(unsent_requests.size()); - for (size_t i = 0; i < unsent_requests.size(); i++) { - auto &request = unsent_requests[i]; + running_requests.reserve(requests_to_be_sent.size()); + for (size_t i = 0; i < requests_to_be_sent.size(); i++) { + auto &request = requests_to_be_sent[i]; io::ReadinessToken readiness_token{i}; auto &storage_client = GetStorageClientForShard(request.shard); msgs::ReadRequests req = request.request; From b4d6ca22336d58a93d57a863cdb91f1c1fd2b527 Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 6 Dec 2022 11:47:48 +0100 Subject: [PATCH 89/93] Use bool as data --- tests/benchmark/data_structures_common.hpp | 6 +++--- tests/benchmark/data_structures_insert.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp index 56c4d60cd..23fe394ee 100644 --- a/tests/benchmark/data_structures_common.hpp +++ b/tests/benchmark/data_structures_common.hpp @@ -30,7 +30,7 @@ inline void PrepareData(utils::SkipList &skip_list, const int64_t num_element storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; for (auto i{0}; i < num_elements; ++i) { auto acc = skip_list.access(); - acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); + acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{true}}}); } } @@ -42,7 +42,7 @@ inline void PrepareData(std::map &std_map, const int64_t num_eleme for (auto i{0}; i < num_elements; ++i) { std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); + delta, std::vector{storage::v3::PropertyValue{true}}}}}); } } @@ -51,7 +51,7 @@ inline void PrepareData(std::set &std_set, const int64_t num_elements) { coordinator::Hlc start_timestamp; storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; for (auto i{0}; i < num_elements; ++i) { - std_set.insert(std::vector{storage::v3::PropertyValue{i}}); + std_set.insert(std::vector{storage::v3::PropertyValue{true}}); } } diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 6469c8ea3..d83cbfe23 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -40,12 +40,11 @@ static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { utils::SkipList skip_list; coordinator::Hlc start_timestamp; storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { auto acc = skip_list.access(); - acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); + acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{true}}}); } } } @@ -60,7 +59,7 @@ static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { for (auto i{0}; i < state.range(0); ++i) { std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); + delta, std::vector{storage::v3::PropertyValue{true}}}}}); } } } @@ -69,7 +68,8 @@ static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { std::set std_set; for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { - std_set.insert(storage::v3::PrimaryKey{std::vector{storage::v3::PropertyValue{i}}}); + std_set.insert( + storage::v3::PrimaryKey{std::vector{storage::v3::PropertyValue{true}}}); } } } From 0353262cc273fc8fe1f8e0767812d9ff84fce5ba Mon Sep 17 00:00:00 2001 From: jeremy Date: Fri, 9 Dec 2022 12:10:48 +0100 Subject: [PATCH 90/93] Correct impl of begin iterators --- src/query/v2/multiframe.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 4829addb2..27f9607d5 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -84,12 +84,24 @@ ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multi after_last_valid_frame_ = multiframe_.frames_.data() + std::distance(multiframe.frames_.begin(), it); } -ValidFramesReader::Iterator ValidFramesReader::begin() { return Iterator{&multiframe_.frames_[0]}; } +ValidFramesReader::Iterator ValidFramesReader::begin() { + if (multiframe_.frames_[0].IsValid()) { + return Iterator{&multiframe_.frames_[0]}; + } + return end(); +} + ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator{after_last_valid_frame_}; } ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {} -ValidFramesModifier::Iterator ValidFramesModifier::begin() { return Iterator{&multiframe_.frames_[0], *this}; } +ValidFramesModifier::Iterator ValidFramesModifier::begin() { + if (multiframe_.frames_[0].IsValid()) { + return Iterator{&multiframe_.frames_[0], *this}; + } + return end(); +} + ValidFramesModifier::Iterator ValidFramesModifier::end() { return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this}; } @@ -103,7 +115,12 @@ ValidFramesConsumer::~ValidFramesConsumer() noexcept { multiframe_.DefragmentValidFrames(); } -ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { return Iterator{&multiframe_.frames_[0], *this}; } +ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { + if (multiframe_.frames_[0].IsValid()) { + return Iterator{&multiframe_.frames_[0], *this}; + } + return end(); +} ValidFramesConsumer::Iterator ValidFramesConsumer::end() { return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this}; From 50f76b926bac82ad0c0d3287a2cfc8d6e5435ada Mon Sep 17 00:00:00 2001 From: jeremy Date: Fri, 9 Dec 2022 12:20:01 +0100 Subject: [PATCH 91/93] Make MultiFrame pointer instead of ref inside impl of iterators --- src/query/v2/multiframe.cpp | 32 ++++++++++++++++---------------- src/query/v2/multiframe.hpp | 8 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/query/v2/multiframe.cpp b/src/query/v2/multiframe.cpp index 27f9607d5..02d396fda 100644 --- a/src/query/v2/multiframe.cpp +++ b/src/query/v2/multiframe.cpp @@ -69,7 +69,7 @@ ValidFramesConsumer MultiFrame::GetValidFramesConsumer() { return ValidFramesCon InvalidFramesPopulator MultiFrame::GetInvalidFramesPopulator() { return InvalidFramesPopulator{*this}; } -ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multiframe) { +ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(&multiframe) { /* From: https://en.cppreference.com/w/cpp/algorithm/find Returns an iterator to the first element in the range [first, last) that satisfies specific criteria: @@ -81,55 +81,55 @@ ValidFramesReader::ValidFramesReader(MultiFrame &multiframe) : multiframe_(multi */ auto it = std::find_if(multiframe.frames_.begin(), multiframe.frames_.end(), [](const auto &frame) { return !frame.IsValid(); }); - after_last_valid_frame_ = multiframe_.frames_.data() + std::distance(multiframe.frames_.begin(), it); + after_last_valid_frame_ = multiframe_->frames_.data() + std::distance(multiframe.frames_.begin(), it); } ValidFramesReader::Iterator ValidFramesReader::begin() { - if (multiframe_.frames_[0].IsValid()) { - return Iterator{&multiframe_.frames_[0]}; + if (multiframe_->frames_[0].IsValid()) { + return Iterator{&multiframe_->frames_[0]}; } return end(); } ValidFramesReader::Iterator ValidFramesReader::end() { return Iterator{after_last_valid_frame_}; } -ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(multiframe) {} +ValidFramesModifier::ValidFramesModifier(MultiFrame &multiframe) : multiframe_(&multiframe) {} ValidFramesModifier::Iterator ValidFramesModifier::begin() { - if (multiframe_.frames_[0].IsValid()) { - return Iterator{&multiframe_.frames_[0], *this}; + if (multiframe_->frames_[0].IsValid()) { + return Iterator{&multiframe_->frames_[0], *this}; } return end(); } ValidFramesModifier::Iterator ValidFramesModifier::end() { - return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this}; + return Iterator{multiframe_->frames_.data() + multiframe_->frames_.size(), *this}; } -ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(multiframe) {} +ValidFramesConsumer::ValidFramesConsumer(MultiFrame &multiframe) : multiframe_(&multiframe) {} // NOLINTNEXTLINE (bugprone-exception-escape) ValidFramesConsumer::~ValidFramesConsumer() noexcept { // TODO Possible optimisation: only DefragmentValidFrames if one frame has been invalidated? Only if does not // cost too much to store it - multiframe_.DefragmentValidFrames(); + multiframe_->DefragmentValidFrames(); } ValidFramesConsumer::Iterator ValidFramesConsumer::begin() { - if (multiframe_.frames_[0].IsValid()) { - return Iterator{&multiframe_.frames_[0], *this}; + if (multiframe_->frames_[0].IsValid()) { + return Iterator{&multiframe_->frames_[0], *this}; } return end(); } ValidFramesConsumer::Iterator ValidFramesConsumer::end() { - return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size(), *this}; + return Iterator{multiframe_->frames_.data() + multiframe_->frames_.size(), *this}; } -InvalidFramesPopulator::InvalidFramesPopulator(MultiFrame &multiframe) : multiframe_(multiframe) {} +InvalidFramesPopulator::InvalidFramesPopulator(MultiFrame &multiframe) : multiframe_(&multiframe) {} InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() { - for (auto &frame : multiframe_.frames_) { + for (auto &frame : multiframe_->frames_) { if (!frame.IsValid()) { return Iterator{&frame}; } @@ -138,7 +138,7 @@ InvalidFramesPopulator::Iterator InvalidFramesPopulator::begin() { } InvalidFramesPopulator::Iterator InvalidFramesPopulator::end() { - return Iterator{multiframe_.frames_.data() + multiframe_.frames_.size()}; + return Iterator{multiframe_->frames_.data() + multiframe_->frames_.size()}; } } // namespace memgraph::query::v2 diff --git a/src/query/v2/multiframe.hpp b/src/query/v2/multiframe.hpp index 0b6896422..8588993a7 100644 --- a/src/query/v2/multiframe.hpp +++ b/src/query/v2/multiframe.hpp @@ -137,7 +137,7 @@ class ValidFramesReader { private: FrameWithValidity *after_last_valid_frame_; - MultiFrame &multiframe_; + MultiFrame *multiframe_; }; class ValidFramesModifier { @@ -192,7 +192,7 @@ class ValidFramesModifier { Iterator end(); private: - MultiFrame &multiframe_; + MultiFrame *multiframe_; }; class ValidFramesConsumer { @@ -246,7 +246,7 @@ class ValidFramesConsumer { Iterator end(); private: - MultiFrame &multiframe_; + MultiFrame *multiframe_; }; class InvalidFramesPopulator { @@ -296,7 +296,7 @@ class InvalidFramesPopulator { Iterator end(); private: - MultiFrame &multiframe_; + MultiFrame *multiframe_; }; } // namespace memgraph::query::v2 From d6f150558250dc260b977e69d38aae3cb6c4e3e4 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Mon, 12 Dec 2022 15:14:48 +0000 Subject: [PATCH 92/93] Make Shard into a proper struct that can contain additional metadata --- src/coordinator/shard_map.cpp | 10 +++++----- src/coordinator/shard_map.hpp | 29 ++++++++++++++++++++++++++++- src/query/v2/request_router.hpp | 10 +++++----- tests/simulation/request_router.cpp | 4 ++-- tests/simulation/sharded_map.cpp | 6 +++--- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/coordinator/shard_map.cpp b/src/coordinator/shard_map.cpp index ea167db87..7334fd0f9 100644 --- a/src/coordinator/shard_map.cpp +++ b/src/coordinator/shard_map.cpp @@ -283,7 +283,7 @@ std::vector ShardMap::AssignShards(Address storage_manager, // TODO(tyler) avoid these triple-nested loops by having the heartbeat include better info bool machine_contains_shard = false; - for (auto &aas : shard) { + for (auto &aas : shard.peers) { if (initialized.contains(aas.address.unique_id)) { machine_contains_shard = true; if (aas.status != Status::CONSENSUS_PARTICIPANT) { @@ -311,7 +311,7 @@ std::vector ShardMap::AssignShards(Address storage_manager, } } - if (!machine_contains_shard && shard.size() < label_space.replication_factor) { + if (!machine_contains_shard && shard.peers.size() < label_space.replication_factor) { // increment version for each new uuid for deterministic creation IncrementShardMapVersion(); @@ -337,7 +337,7 @@ std::vector ShardMap::AssignShards(Address storage_manager, .status = Status::INITIALIZING, }; - shard.emplace_back(aas); + shard.peers.emplace_back(aas); } } } @@ -556,12 +556,12 @@ EdgeTypeIdMap ShardMap::AllocateEdgeTypeIds(const std::vector &new bool ShardMap::ClusterInitialized() const { for (const auto &[label_id, label_space] : label_spaces) { for (const auto &[low_key, shard] : label_space.shards) { - if (shard.size() < label_space.replication_factor) { + if (shard.peers.size() < label_space.replication_factor) { spdlog::info("label_space below desired replication factor"); return false; } - for (const auto &aas : shard) { + for (const auto &aas : shard.peers) { if (aas.status != Status::CONSENSUS_PARTICIPANT) { spdlog::info("shard member not yet a CONSENSUS_PARTICIPANT"); return false; diff --git a/src/coordinator/shard_map.hpp b/src/coordinator/shard_map.hpp index 80c32eeba..6bb7a0624 100644 --- a/src/coordinator/shard_map.hpp +++ b/src/coordinator/shard_map.hpp @@ -76,7 +76,34 @@ struct AddressAndStatus { }; using PrimaryKey = std::vector; -using Shard = std::vector; + +struct Shard { + std::vector peers; + uint64_t version; + + friend std::ostream &operator<<(std::ostream &in, const Shard &shard) { + using utils::print_helpers::operator<<; + + in << "Shard { peers: "; + in << shard.peers; + in << " version: "; + in << shard.version; + in << " }"; + + return in; + } + + friend bool operator==(const Shard &lhs, const Shard &rhs) = default; + + friend bool operator<(const Shard &lhs, const Shard &rhs) { + if (lhs.peers != rhs.peers) { + return lhs.peers < rhs.peers; + } + + return lhs.version < rhs.version; + } +}; + using Shards = std::map; using LabelName = std::string; using PropertyName = std::string; diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 116e884c2..05918e8a3 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -484,7 +484,7 @@ class RequestRouter : public RequestRouterInterface { for (auto &shards : multi_shards) { for (auto &[key, shard] : shards) { - MG_ASSERT(!shard.empty()); + MG_ASSERT(!shard.peers.empty()); msgs::ScanVerticesRequest request; request.transaction_id = transaction_id_; @@ -584,11 +584,11 @@ class RequestRouter : public RequestRouterInterface { } void AddStorageClientToManager(Shard target_shard) { - MG_ASSERT(!target_shard.empty()); - auto leader_addr = target_shard.front(); + MG_ASSERT(!target_shard.peers.empty()); + auto leader_addr = target_shard.peers.front(); std::vector
addresses; - addresses.reserve(target_shard.size()); - for (auto &address : target_shard) { + addresses.reserve(target_shard.peers.size()); + for (auto &address : target_shard.peers) { addresses.push_back(std::move(address.address)); } auto cli = StorageClient(io_, std::move(leader_addr.address), std::move(addresses)); diff --git a/tests/simulation/request_router.cpp b/tests/simulation/request_router.cpp index 0f712793f..dc60bb8e5 100644 --- a/tests/simulation/request_router.cpp +++ b/tests/simulation/request_router.cpp @@ -113,7 +113,7 @@ ShardMap CreateDummyShardmap(coordinator::Address a_io_1, coordinator::Address a AddressAndStatus aas1_2{.address = a_io_2, .status = Status::CONSENSUS_PARTICIPANT}; AddressAndStatus aas1_3{.address = a_io_3, .status = Status::CONSENSUS_PARTICIPANT}; - Shard shard1 = {aas1_1, aas1_2, aas1_3}; + Shard shard1 = Shard{.peers = {aas1_1, aas1_2, aas1_3}, .version = 1}; auto key1 = storage::v3::PropertyValue(0); auto key2 = storage::v3::PropertyValue(0); @@ -125,7 +125,7 @@ ShardMap CreateDummyShardmap(coordinator::Address a_io_1, coordinator::Address a AddressAndStatus aas2_2{.address = b_io_2, .status = Status::CONSENSUS_PARTICIPANT}; AddressAndStatus aas2_3{.address = b_io_3, .status = Status::CONSENSUS_PARTICIPANT}; - Shard shard2 = {aas2_1, aas2_2, aas2_3}; + Shard shard2 = Shard{.peers = {aas2_1, aas2_2, aas2_3}, .version = 1}; auto key3 = storage::v3::PropertyValue(12); auto key4 = storage::v3::PropertyValue(13); diff --git a/tests/simulation/sharded_map.cpp b/tests/simulation/sharded_map.cpp index 91661fc66..cdc2d69b6 100644 --- a/tests/simulation/sharded_map.cpp +++ b/tests/simulation/sharded_map.cpp @@ -109,7 +109,7 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add AddressAndStatus aas1_2{.address = a_io_2, .status = Status::CONSENSUS_PARTICIPANT}; AddressAndStatus aas1_3{.address = a_io_3, .status = Status::CONSENSUS_PARTICIPANT}; - Shard shard1 = {aas1_1, aas1_2, aas1_3}; + Shard shard1 = Shard{.peers = {aas1_1, aas1_2, aas1_3}, .version = 1}; const auto key1 = PropertyValue(0); const auto key2 = PropertyValue(0); @@ -121,7 +121,7 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add AddressAndStatus aas2_2{.address = b_io_2, .status = Status::CONSENSUS_PARTICIPANT}; AddressAndStatus aas2_3{.address = b_io_3, .status = Status::CONSENSUS_PARTICIPANT}; - Shard shard2 = {aas2_1, aas2_2, aas2_3}; + Shard shard2 = Shard{.peers = {aas2_1, aas2_2, aas2_3}, .version = 1}; auto key3 = PropertyValue(12); auto key4 = PropertyValue(13); @@ -134,7 +134,7 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add std::optional DetermineShardLocation(const Shard &target_shard, const std::vector
&a_addrs, ShardClient &a_client, const std::vector
&b_addrs, ShardClient &b_client) { - for (const auto &addr : target_shard) { + for (const auto &addr : target_shard.peers) { if (addr.address == b_addrs[0]) { return &b_client; } From 1170e6762f0909055b1ae0e7411e1d093bb6eefc Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Mon, 12 Dec 2022 15:22:17 +0000 Subject: [PATCH 93/93] Rename coordinator::Shard to coordinator::ShardMetadata to avoid conflation with storage::v3::Shard --- src/coordinator/shard_map.cpp | 10 +++---- src/coordinator/shard_map.hpp | 18 ++++++------ src/query/v2/request_router.hpp | 28 +++++++++---------- tests/simulation/request_router.cpp | 6 ++-- tests/simulation/sharded_map.cpp | 14 +++++----- tests/simulation/test_cluster.hpp | 2 +- tests/unit/high_density_shard_create_scan.cpp | 2 +- tests/unit/machine_manager.cpp | 2 +- 8 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/coordinator/shard_map.cpp b/src/coordinator/shard_map.cpp index 7334fd0f9..f38e6f823 100644 --- a/src/coordinator/shard_map.cpp +++ b/src/coordinator/shard_map.cpp @@ -360,9 +360,9 @@ bool ShardMap::SplitShard(Hlc previous_shard_map_version, LabelId label_id, cons MG_ASSERT(!shards_in_map.contains(key)); MG_ASSERT(label_spaces.contains(label_id)); - // Finding the Shard that the new PrimaryKey should map to. + // Finding the ShardMetadata that the new PrimaryKey should map to. auto prev = std::prev(shards_in_map.upper_bound(key)); - Shard duplicated_shard = prev->second; + ShardMetadata duplicated_shard = prev->second; // Apply the split shards_in_map[key] = duplicated_shard; @@ -383,7 +383,7 @@ std::optional ShardMap::InitializeNewLabel(std::string label_name, std: labels.emplace(std::move(label_name), label_id); PrimaryKey initial_key = SchemaToMinKey(schema); - Shard empty_shard = {}; + ShardMetadata empty_shard = {}; Shards shards = { {initial_key, empty_shard}, @@ -479,7 +479,7 @@ Shards ShardMap::GetShardsForRange(const LabelName &label_name, const PrimaryKey return shards; } -Shard ShardMap::GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const { +ShardMetadata ShardMap::GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const { MG_ASSERT(labels.contains(label_name)); LabelId label_id = labels.at(label_name); @@ -492,7 +492,7 @@ Shard ShardMap::GetShardForKey(const LabelName &label_name, const PrimaryKey &ke return std::prev(label_space.shards.upper_bound(key))->second; } -Shard ShardMap::GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const { +ShardMetadata ShardMap::GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const { MG_ASSERT(label_spaces.contains(label_id)); const auto &label_space = label_spaces.at(label_id); diff --git a/src/coordinator/shard_map.hpp b/src/coordinator/shard_map.hpp index 6bb7a0624..fc408e965 100644 --- a/src/coordinator/shard_map.hpp +++ b/src/coordinator/shard_map.hpp @@ -77,14 +77,14 @@ struct AddressAndStatus { using PrimaryKey = std::vector; -struct Shard { +struct ShardMetadata { std::vector peers; uint64_t version; - friend std::ostream &operator<<(std::ostream &in, const Shard &shard) { + friend std::ostream &operator<<(std::ostream &in, const ShardMetadata &shard) { using utils::print_helpers::operator<<; - in << "Shard { peers: "; + in << "ShardMetadata { peers: "; in << shard.peers; in << " version: "; in << shard.version; @@ -93,9 +93,9 @@ struct Shard { return in; } - friend bool operator==(const Shard &lhs, const Shard &rhs) = default; + friend bool operator==(const ShardMetadata &lhs, const ShardMetadata &rhs) = default; - friend bool operator<(const Shard &lhs, const Shard &rhs) { + friend bool operator<(const ShardMetadata &lhs, const ShardMetadata &rhs) { if (lhs.peers != rhs.peers) { return lhs.peers < rhs.peers; } @@ -104,7 +104,7 @@ struct Shard { } }; -using Shards = std::map; +using Shards = std::map; using LabelName = std::string; using PropertyName = std::string; using EdgeTypeName = std::string; @@ -126,7 +126,7 @@ PrimaryKey SchemaToMinKey(const std::vector &schema); struct LabelSpace { std::vector schema; // Maps between the smallest primary key stored in the shard and the shard - std::map shards; + std::map shards; size_t replication_factor; friend std::ostream &operator<<(std::ostream &in, const LabelSpace &label_space) { @@ -187,9 +187,9 @@ struct ShardMap { Shards GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key, const PrimaryKey &end_key) const; - Shard GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const; + ShardMetadata GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const; - Shard GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const; + ShardMetadata GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const; PropertyMap AllocatePropertyIds(const std::vector &new_properties); diff --git a/src/query/v2/request_router.hpp b/src/query/v2/request_router.hpp index 05918e8a3..3dd2f164b 100644 --- a/src/query/v2/request_router.hpp +++ b/src/query/v2/request_router.hpp @@ -50,7 +50,7 @@ template class RsmStorageClientManager { public: using CompoundKey = io::rsm::ShardRsmKey; - using Shard = coordinator::Shard; + using ShardMetadata = coordinator::ShardMetadata; RsmStorageClientManager() = default; RsmStorageClientManager(const RsmStorageClientManager &) = delete; RsmStorageClientManager(RsmStorageClientManager &&) = delete; @@ -58,25 +58,25 @@ class RsmStorageClientManager { RsmStorageClientManager &operator=(RsmStorageClientManager &&) = delete; ~RsmStorageClientManager() = default; - void AddClient(Shard key, TStorageClient client) { cli_cache_.emplace(std::move(key), std::move(client)); } + void AddClient(ShardMetadata key, TStorageClient client) { cli_cache_.emplace(std::move(key), std::move(client)); } - bool Exists(const Shard &key) { return cli_cache_.contains(key); } + bool Exists(const ShardMetadata &key) { return cli_cache_.contains(key); } void PurgeCache() { cli_cache_.clear(); } - TStorageClient &GetClient(const Shard &key) { + TStorageClient &GetClient(const ShardMetadata &key) { auto it = cli_cache_.find(key); MG_ASSERT(it != cli_cache_.end(), "Non-existing shard client"); return it->second; } private: - std::map cli_cache_; + std::map cli_cache_; }; template struct ShardRequestState { - memgraph::coordinator::Shard shard; + memgraph::coordinator::ShardMetadata shard; TRequest request; }; @@ -125,7 +125,7 @@ class RequestRouter : public RequestRouterInterface { using CoordinatorWriteRequests = coordinator::CoordinatorWriteRequests; using CoordinatorClient = coordinator::CoordinatorClient; using Address = io::Address; - using Shard = coordinator::Shard; + using ShardMetadata = coordinator::ShardMetadata; using ShardMap = coordinator::ShardMap; using CompoundKey = coordinator::PrimaryKey; using VertexAccessor = query::v2::accessors::VertexAccessor; @@ -403,7 +403,7 @@ class RequestRouter : public RequestRouterInterface { private: std::vector> RequestsForCreateVertices( const std::vector &new_vertices) { - std::map per_shard_request_table; + std::map per_shard_request_table; for (auto &new_vertex : new_vertices) { MG_ASSERT(!new_vertex.label_ids.empty(), "No label_ids provided for new vertex in RequestRouter::CreateVertices"); @@ -431,9 +431,9 @@ class RequestRouter : public RequestRouterInterface { std::vector> RequestsForCreateExpand( const std::vector &new_expands) { - std::map per_shard_request_table; + std::map per_shard_request_table; auto ensure_shard_exists_in_table = [&per_shard_request_table, - transaction_id = transaction_id_](const Shard &shard) { + transaction_id = transaction_id_](const ShardMetadata &shard) { if (!per_shard_request_table.contains(shard)) { msgs::CreateExpandRequest create_expand_request{.transaction_id = transaction_id}; per_shard_request_table.insert({shard, std::move(create_expand_request)}); @@ -503,7 +503,7 @@ class RequestRouter : public RequestRouterInterface { } std::vector> RequestsForExpandOne(const msgs::ExpandOneRequest &request) { - std::map per_shard_request_table; + std::map per_shard_request_table; msgs::ExpandOneRequest top_level_rqst_template = request; top_level_rqst_template.transaction_id = transaction_id_; top_level_rqst_template.src_vertices.clear(); @@ -533,7 +533,7 @@ class RequestRouter : public RequestRouterInterface { std::vector> RequestsForGetProperties( msgs::GetPropertiesRequest &&request) { - std::map per_shard_request_table; + std::map per_shard_request_table; auto top_level_rqst_template = request; top_level_rqst_template.transaction_id = transaction_id_; top_level_rqst_template.vertex_ids.clear(); @@ -571,7 +571,7 @@ class RequestRouter : public RequestRouterInterface { return requests; } - StorageClient &GetStorageClientForShard(Shard shard) { + StorageClient &GetStorageClientForShard(ShardMetadata shard) { if (!storage_cli_manager_.Exists(shard)) { AddStorageClientToManager(shard); } @@ -583,7 +583,7 @@ class RequestRouter : public RequestRouterInterface { return GetStorageClientForShard(std::move(shard)); } - void AddStorageClientToManager(Shard target_shard) { + void AddStorageClientToManager(ShardMetadata target_shard) { MG_ASSERT(!target_shard.peers.empty()); auto leader_addr = target_shard.peers.front(); std::vector
addresses; diff --git a/tests/simulation/request_router.cpp b/tests/simulation/request_router.cpp index dc60bb8e5..4248e7876 100644 --- a/tests/simulation/request_router.cpp +++ b/tests/simulation/request_router.cpp @@ -46,8 +46,8 @@ using coordinator::CoordinatorClient; using coordinator::CoordinatorRsm; using coordinator::HlcRequest; using coordinator::HlcResponse; -using coordinator::Shard; using coordinator::ShardMap; +using coordinator::ShardMetadata; using coordinator::Shards; using coordinator::Status; using io::Address; @@ -113,7 +113,7 @@ ShardMap CreateDummyShardmap(coordinator::Address a_io_1, coordinator::Address a AddressAndStatus aas1_2{.address = a_io_2, .status = Status::CONSENSUS_PARTICIPANT}; AddressAndStatus aas1_3{.address = a_io_3, .status = Status::CONSENSUS_PARTICIPANT}; - Shard shard1 = Shard{.peers = {aas1_1, aas1_2, aas1_3}, .version = 1}; + ShardMetadata shard1 = ShardMetadata{.peers = {aas1_1, aas1_2, aas1_3}, .version = 1}; auto key1 = storage::v3::PropertyValue(0); auto key2 = storage::v3::PropertyValue(0); @@ -125,7 +125,7 @@ ShardMap CreateDummyShardmap(coordinator::Address a_io_1, coordinator::Address a AddressAndStatus aas2_2{.address = b_io_2, .status = Status::CONSENSUS_PARTICIPANT}; AddressAndStatus aas2_3{.address = b_io_3, .status = Status::CONSENSUS_PARTICIPANT}; - Shard shard2 = Shard{.peers = {aas2_1, aas2_2, aas2_3}, .version = 1}; + ShardMetadata shard2 = ShardMetadata{.peers = {aas2_1, aas2_2, aas2_3}, .version = 1}; auto key3 = storage::v3::PropertyValue(12); auto key4 = storage::v3::PropertyValue(13); diff --git a/tests/simulation/sharded_map.cpp b/tests/simulation/sharded_map.cpp index cdc2d69b6..d27858abc 100644 --- a/tests/simulation/sharded_map.cpp +++ b/tests/simulation/sharded_map.cpp @@ -40,8 +40,8 @@ using memgraph::coordinator::CoordinatorRsm; using memgraph::coordinator::HlcRequest; using memgraph::coordinator::HlcResponse; using memgraph::coordinator::PrimaryKey; -using memgraph::coordinator::Shard; using memgraph::coordinator::ShardMap; +using memgraph::coordinator::ShardMetadata; using memgraph::coordinator::Shards; using memgraph::coordinator::Status; using memgraph::io::Address; @@ -109,7 +109,7 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add AddressAndStatus aas1_2{.address = a_io_2, .status = Status::CONSENSUS_PARTICIPANT}; AddressAndStatus aas1_3{.address = a_io_3, .status = Status::CONSENSUS_PARTICIPANT}; - Shard shard1 = Shard{.peers = {aas1_1, aas1_2, aas1_3}, .version = 1}; + ShardMetadata shard1 = ShardMetadata{.peers = {aas1_1, aas1_2, aas1_3}, .version = 1}; const auto key1 = PropertyValue(0); const auto key2 = PropertyValue(0); @@ -121,7 +121,7 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add AddressAndStatus aas2_2{.address = b_io_2, .status = Status::CONSENSUS_PARTICIPANT}; AddressAndStatus aas2_3{.address = b_io_3, .status = Status::CONSENSUS_PARTICIPANT}; - Shard shard2 = Shard{.peers = {aas2_1, aas2_2, aas2_3}, .version = 1}; + ShardMetadata shard2 = ShardMetadata{.peers = {aas2_1, aas2_2, aas2_3}, .version = 1}; auto key3 = PropertyValue(12); auto key4 = PropertyValue(13); @@ -131,9 +131,9 @@ ShardMap CreateDummyShardmap(Address a_io_1, Address a_io_2, Address a_io_3, Add return sm; } -std::optional DetermineShardLocation(const Shard &target_shard, const std::vector
&a_addrs, - ShardClient &a_client, const std::vector
&b_addrs, - ShardClient &b_client) { +std::optional DetermineShardLocation(const ShardMetadata &target_shard, + const std::vector
&a_addrs, ShardClient &a_client, + const std::vector
&b_addrs, ShardClient &b_client) { for (const auto &addr : target_shard.peers) { if (addr.address == b_addrs[0]) { return &b_client; @@ -275,7 +275,7 @@ int main() { const PrimaryKey compound_key = {cm_key_1, cm_key_2}; - // Look for Shard + // Look for ShardMetadata BasicResult read_res = coordinator_client.SendWriteRequest(req); diff --git a/tests/simulation/test_cluster.hpp b/tests/simulation/test_cluster.hpp index 3e14545a9..2e8bdf92f 100644 --- a/tests/simulation/test_cluster.hpp +++ b/tests/simulation/test_cluster.hpp @@ -47,8 +47,8 @@ using coordinator::GetShardMapRequest; using coordinator::GetShardMapResponse; using coordinator::Hlc; using coordinator::HlcResponse; -using coordinator::Shard; using coordinator::ShardMap; +using coordinator::ShardMetadata; using io::Address; using io::Io; using io::rsm::RsmClient; diff --git a/tests/unit/high_density_shard_create_scan.cpp b/tests/unit/high_density_shard_create_scan.cpp index cefa238ed..9fabf6ccc 100644 --- a/tests/unit/high_density_shard_create_scan.cpp +++ b/tests/unit/high_density_shard_create_scan.cpp @@ -44,8 +44,8 @@ using coordinator::GetShardMapRequest; using coordinator::GetShardMapResponse; using coordinator::Hlc; using coordinator::HlcResponse; -using coordinator::Shard; using coordinator::ShardMap; +using coordinator::ShardMetadata; using io::Address; using io::Io; using io::local_transport::LocalSystem; diff --git a/tests/unit/machine_manager.cpp b/tests/unit/machine_manager.cpp index 748233737..74b7d3863 100644 --- a/tests/unit/machine_manager.cpp +++ b/tests/unit/machine_manager.cpp @@ -45,8 +45,8 @@ using memgraph::coordinator::CoordinatorWriteRequests; using memgraph::coordinator::CoordinatorWriteResponses; using memgraph::coordinator::Hlc; using memgraph::coordinator::HlcResponse; -using memgraph::coordinator::Shard; using memgraph::coordinator::ShardMap; +using memgraph::coordinator::ShardMetadata; using memgraph::io::Io; using memgraph::io::local_transport::LocalSystem; using memgraph::io::local_transport::LocalTransport;