Handle OrderBy in ScanVertices request (#594)
- Refactor shard_rsm and move function into expr.hpp/expr.cpp and request_helper.hpp/request_helper.cpp
This commit is contained in:
parent
17090dd8ac
commit
e5437080c5
@ -345,6 +345,7 @@ struct ScanVerticesRequest {
|
||||
// expression whose result is returned for every vertex
|
||||
std::vector<std::string> vertex_expressions;
|
||||
std::optional<size_t> batch_limit;
|
||||
std::vector<OrderBy> order_bys;
|
||||
StorageView storage_view{StorageView::NEW};
|
||||
|
||||
std::optional<Label> label;
|
||||
|
@ -19,6 +19,8 @@ set(storage_v3_src_files
|
||||
storage.cpp
|
||||
shard_rsm.cpp
|
||||
bindings/typed_value.cpp
|
||||
expr.cpp
|
||||
request_helper.cpp
|
||||
storage.cpp)
|
||||
|
||||
# ######################
|
||||
|
210
src/storage/v3/expr.cpp
Normal file
210
src/storage/v3/expr.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
// 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 "storage/v3/expr.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "db_accessor.hpp"
|
||||
#include "opencypher/parser.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/bindings/ast/ast.hpp"
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
#include "storage/v3/bindings/cypher_main_visitor.hpp"
|
||||
#include "storage/v3/bindings/db_accessor.hpp"
|
||||
#include "storage/v3/bindings/eval.hpp"
|
||||
#include "storage/v3/bindings/frame.hpp"
|
||||
#include "storage/v3/bindings/symbol_generator.hpp"
|
||||
#include "storage/v3/bindings/symbol_table.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
#include "storage/v3/value_conversions.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
msgs::Value ConstructValueVertex(const VertexAccessor &acc, View view) {
|
||||
// Get the vertex id
|
||||
auto prim_label = acc.PrimaryLabel(view).GetValue();
|
||||
memgraph::msgs::Label value_label{.id = prim_label};
|
||||
|
||||
auto prim_key = conversions::ConvertValueVector(acc.PrimaryKey(view).GetValue());
|
||||
memgraph::msgs::VertexId vertex_id = std::make_pair(value_label, prim_key);
|
||||
|
||||
// Get the labels
|
||||
auto vertex_labels = acc.Labels(view).GetValue();
|
||||
std::vector<memgraph::msgs::Label> value_labels;
|
||||
value_labels.reserve(vertex_labels.size());
|
||||
|
||||
std::transform(vertex_labels.begin(), vertex_labels.end(), std::back_inserter(value_labels),
|
||||
[](const auto &label) { return msgs::Label{.id = label}; });
|
||||
|
||||
return msgs::Value({.id = vertex_id, .labels = value_labels});
|
||||
}
|
||||
|
||||
msgs::Value ConstructValueEdge(const EdgeAccessor &acc, View view) {
|
||||
msgs::EdgeType type = {.id = acc.EdgeType()};
|
||||
msgs::EdgeId gid = {.gid = acc.Gid().AsUint()};
|
||||
|
||||
msgs::Label src_prim_label = {.id = acc.FromVertex().primary_label};
|
||||
memgraph::msgs::VertexId src_vertex =
|
||||
std::make_pair(src_prim_label, conversions::ConvertValueVector(acc.FromVertex().primary_key));
|
||||
|
||||
msgs::Label dst_prim_label = {.id = acc.ToVertex().primary_label};
|
||||
msgs::VertexId dst_vertex =
|
||||
std::make_pair(dst_prim_label, conversions::ConvertValueVector(acc.ToVertex().primary_key));
|
||||
|
||||
auto properties = acc.Properties(view);
|
||||
|
||||
std::vector<std::pair<PropertyId, msgs::Value>> present_properties;
|
||||
if (properties.HasValue()) {
|
||||
auto props = properties.GetValue();
|
||||
std::vector<std::pair<PropertyId, msgs::Value>> present_properties;
|
||||
present_properties.reserve(props.size());
|
||||
|
||||
std::transform(props.begin(), props.end(), std::back_inserter(present_properties),
|
||||
[](std::pair<const PropertyId, PropertyValue> &prop) {
|
||||
return std::make_pair(prop.first, conversions::FromPropertyValueToValue(std::move(prop.second)));
|
||||
});
|
||||
}
|
||||
|
||||
return msgs::Value(msgs::Edge{.src = std::move(src_vertex),
|
||||
.dst = std::move(dst_vertex),
|
||||
.properties = std::move(present_properties),
|
||||
.id = gid,
|
||||
.type = type});
|
||||
}
|
||||
|
||||
msgs::Value FromTypedValueToValue(TypedValue &&tv) {
|
||||
switch (tv.type()) {
|
||||
case TypedValue::Type::Bool:
|
||||
return msgs::Value(tv.ValueBool());
|
||||
case TypedValue::Type::Double:
|
||||
return msgs::Value(tv.ValueDouble());
|
||||
case TypedValue::Type::Int:
|
||||
return msgs::Value(tv.ValueInt());
|
||||
case TypedValue::Type::List: {
|
||||
std::vector<msgs::Value> list;
|
||||
auto &tv_list = tv.ValueList();
|
||||
list.reserve(tv_list.size());
|
||||
std::transform(tv_list.begin(), tv_list.end(), std::back_inserter(list),
|
||||
[](auto &elem) { return FromTypedValueToValue(std::move(elem)); });
|
||||
return msgs::Value(list);
|
||||
}
|
||||
case TypedValue::Type::Map: {
|
||||
std::map<std::string, msgs::Value> map;
|
||||
for (auto &[key, val] : tv.ValueMap()) {
|
||||
map.emplace(key, FromTypedValueToValue(std::move(val)));
|
||||
}
|
||||
return msgs::Value(map);
|
||||
}
|
||||
case TypedValue::Type::Null:
|
||||
return {};
|
||||
case TypedValue::Type::String:
|
||||
return msgs::Value((std::string(tv.ValueString())));
|
||||
case TypedValue::Type::Vertex:
|
||||
return ConstructValueVertex(tv.ValueVertex(), View::OLD);
|
||||
case TypedValue::Type::Edge:
|
||||
return ConstructValueEdge(tv.ValueEdge(), View::OLD);
|
||||
|
||||
// TBD -> we need to specify temporal types, not a priority.
|
||||
case TypedValue::Type::Date:
|
||||
case TypedValue::Type::LocalTime:
|
||||
case TypedValue::Type::LocalDateTime:
|
||||
case TypedValue::Type::Duration:
|
||||
case TypedValue::Type::Path: {
|
||||
MG_ASSERT(false, "This conversion between TypedValue and Value is not implemented yet!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<msgs::Value> ConvertToValueVectorFromTypedValueVector(
|
||||
std::vector<memgraph::storage::v3::TypedValue> &&vec) {
|
||||
std::vector<msgs::Value> ret;
|
||||
ret.reserve(vec.size());
|
||||
|
||||
std::transform(vec.begin(), vec.end(), std::back_inserter(ret),
|
||||
[](auto &elem) { return FromTypedValueToValue(std::move(elem)); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PropertyId> NamesToProperties(const std::vector<std::string> &property_names, DbAccessor &dba) {
|
||||
std::vector<PropertyId> properties;
|
||||
properties.reserve(property_names.size());
|
||||
|
||||
for (const auto &name : property_names) {
|
||||
properties.push_back(dba.NameToProperty(name));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
std::vector<memgraph::storage::v3::LabelId> NamesToLabels(const std::vector<std::string> &label_names,
|
||||
DbAccessor &dba) {
|
||||
std::vector<memgraph::storage::v3::LabelId> labels;
|
||||
labels.reserve(label_names.size());
|
||||
for (const auto &name : label_names) {
|
||||
labels.push_back(dba.NameToLabel(name));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
std::any ParseExpression(const std::string &expr, memgraph::expr::AstStorage &storage) {
|
||||
memgraph::frontend::opencypher::Parser<memgraph::frontend::opencypher::ParserOpTag::EXPRESSION> parser(expr);
|
||||
ParsingContext pc;
|
||||
CypherMainVisitor visitor(pc, &storage);
|
||||
|
||||
auto *ast = parser.tree();
|
||||
return visitor.visit(ast);
|
||||
}
|
||||
|
||||
TypedValue ComputeExpression(DbAccessor &dba, const std::optional<memgraph::storage::v3::VertexAccessor> &v_acc,
|
||||
const std::optional<memgraph::storage::v3::EdgeAccessor> &e_acc,
|
||||
const std::string &expression, std::string_view node_name, std::string_view edge_name) {
|
||||
AstStorage storage;
|
||||
Frame frame{1 + 1}; // 1 for the node_identifier, 1 for the edge_identifier
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext ctx;
|
||||
|
||||
ExpressionEvaluator eval{&frame, symbol_table, ctx, &dba, View::OLD};
|
||||
auto expr = ParseExpression(expression, storage);
|
||||
|
||||
auto node_identifier = Identifier(std::string(node_name), false);
|
||||
auto edge_identifier = Identifier(std::string(edge_name), false);
|
||||
|
||||
std::vector<Identifier *> identifiers;
|
||||
identifiers.push_back(&node_identifier);
|
||||
identifiers.push_back(&edge_identifier);
|
||||
|
||||
expr::SymbolGenerator symbol_generator(&symbol_table, identifiers);
|
||||
(std::any_cast<Expression *>(expr))->Accept(symbol_generator);
|
||||
|
||||
if (node_identifier.symbol_pos_ != -1) {
|
||||
MG_ASSERT(std::find_if(symbol_table.table().begin(), symbol_table.table().end(),
|
||||
[&node_name](const std::pair<int32_t, Symbol> &position_symbol_pair) {
|
||||
return position_symbol_pair.second.name() == node_name;
|
||||
}) != symbol_table.table().end());
|
||||
|
||||
frame[symbol_table.at(node_identifier)] = *v_acc;
|
||||
}
|
||||
|
||||
if (edge_identifier.symbol_pos_ != -1) {
|
||||
MG_ASSERT(std::find_if(symbol_table.table().begin(), symbol_table.table().end(),
|
||||
[&edge_name](const std::pair<int32_t, Symbol> &position_symbol_pair) {
|
||||
return position_symbol_pair.second.name() == edge_name;
|
||||
}) != symbol_table.table().end());
|
||||
|
||||
frame[symbol_table.at(edge_identifier)] = *e_acc;
|
||||
}
|
||||
|
||||
return Eval(std::any_cast<Expression *>(expr), ctx, storage, eval, dba);
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
55
src/storage/v3/expr.hpp
Normal file
55
src/storage/v3/expr.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include "db_accessor.hpp"
|
||||
#include "opencypher/parser.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/bindings/ast/ast.hpp"
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
#include "storage/v3/bindings/cypher_main_visitor.hpp"
|
||||
#include "storage/v3/bindings/db_accessor.hpp"
|
||||
#include "storage/v3/bindings/eval.hpp"
|
||||
#include "storage/v3/bindings/frame.hpp"
|
||||
#include "storage/v3/bindings/symbol_generator.hpp"
|
||||
#include "storage/v3/bindings/symbol_table.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
memgraph::msgs::Value ConstructValueVertex(const memgraph::storage::v3::VertexAccessor &acc, View view);
|
||||
|
||||
msgs::Value ConstructValueEdge(const EdgeAccessor &acc, View view);
|
||||
|
||||
msgs::Value FromTypedValueToValue(TypedValue &&tv);
|
||||
|
||||
std::vector<msgs::Value> ConvertToValueVectorFromTypedValueVector(std::vector<TypedValue> &&vec);
|
||||
|
||||
std::vector<PropertyId> NamesToProperties(const std::vector<std::string> &property_names, DbAccessor &dba);
|
||||
|
||||
std::vector<LabelId> NamesToLabels(const std::vector<std::string> &label_names, DbAccessor &dba);
|
||||
|
||||
template <class TExpression>
|
||||
auto Eval(TExpression *expr, EvaluationContext &ctx, AstStorage &storage, ExpressionEvaluator &eval, DbAccessor &dba) {
|
||||
ctx.properties = NamesToProperties(storage.properties_, dba);
|
||||
ctx.labels = NamesToLabels(storage.labels_, dba);
|
||||
auto value = expr->Accept(eval);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::any ParseExpression(const std::string &expr, AstStorage &storage);
|
||||
|
||||
TypedValue ComputeExpression(DbAccessor &dba, const std::optional<VertexAccessor> &v_acc,
|
||||
const std::optional<EdgeAccessor> &e_acc, const std::string &expression,
|
||||
std::string_view node_name, std::string_view edge_name);
|
||||
|
||||
} // namespace memgraph::storage::v3
|
82
src/storage/v3/request_helper.cpp
Normal file
82
src/storage/v3/request_helper.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 "storage/v3/request_helper.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "pretty_print_ast_to_original_expression.hpp"
|
||||
#include "storage/v3/bindings/db_accessor.hpp"
|
||||
#include "storage/v3/expr.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
std::vector<Element> OrderByElements(Shard::Accessor &acc, DbAccessor &dba, VerticesIterable &vertices_iterable,
|
||||
std::vector<msgs::OrderBy> &order_bys) {
|
||||
std::vector<Element> ordered;
|
||||
ordered.reserve(acc.ApproximateVertexCount());
|
||||
std::vector<Ordering> ordering;
|
||||
ordering.reserve(order_bys.size());
|
||||
for (const auto &order : order_bys) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto compare_typed_values = TypedValueVectorCompare(ordering);
|
||||
for (auto it = vertices_iterable.begin(); it != vertices_iterable.end(); ++it) {
|
||||
std::vector<TypedValue> properties_order_by;
|
||||
properties_order_by.reserve(order_bys.size());
|
||||
|
||||
for (const auto &order_by : order_bys) {
|
||||
const auto val =
|
||||
ComputeExpression(dba, *it, std::nullopt, order_by.expression.expression, expr::identifier_node_symbol, "");
|
||||
properties_order_by.push_back(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) {
|
||||
return compare_typed_values(pair1.properties_order_by, pair2.properties_order_by);
|
||||
});
|
||||
return ordered;
|
||||
}
|
||||
|
||||
VerticesIterable::Iterator GetStartVertexIterator(VerticesIterable &vertex_iterable,
|
||||
const std::vector<PropertyValue> &start_ids, const View view) {
|
||||
auto it = vertex_iterable.begin();
|
||||
while (it != vertex_iterable.end()) {
|
||||
if (const auto &vertex = *it; start_ids <= vertex.PrimaryKey(view).GetValue()) {
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
std::vector<Element>::const_iterator GetStartOrderedElementsIterator(const std::vector<Element> &ordered_elements,
|
||||
const std::vector<PropertyValue> &start_ids,
|
||||
const View view) {
|
||||
for (auto it = ordered_elements.begin(); it != ordered_elements.end(); ++it) {
|
||||
if (const auto &vertex = it->vertex_acc; start_ids <= vertex.PrimaryKey(view).GetValue()) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return ordered_elements.end();
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
116
src/storage/v3/request_helper.hpp
Normal file
116
src/storage/v3/request_helper.hpp
Normal file
@ -0,0 +1,116 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include "ast/ast.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
inline bool TypedValueCompare(const TypedValue &a, const TypedValue &b) {
|
||||
// in ordering null comes after everything else
|
||||
// at the same time Null is not less that null
|
||||
// first deal with Null < Whatever case
|
||||
if (a.IsNull()) return false;
|
||||
// now deal with NotNull < Null case
|
||||
if (b.IsNull()) return true;
|
||||
|
||||
// comparisons are from this point legal only between values of
|
||||
// the same type, or int+float combinations
|
||||
if ((a.type() != b.type() && !(a.IsNumeric() && b.IsNumeric())))
|
||||
throw utils::BasicException("Can't compare value of type {} to value of type {}.", a.type(), b.type());
|
||||
|
||||
switch (a.type()) {
|
||||
case TypedValue::Type::Bool:
|
||||
return !a.ValueBool() && b.ValueBool();
|
||||
case TypedValue::Type::Int:
|
||||
if (b.type() == TypedValue::Type::Double)
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
return a.ValueInt() < b.ValueDouble();
|
||||
else
|
||||
return a.ValueInt() < b.ValueInt();
|
||||
case TypedValue::Type::Double:
|
||||
if (b.type() == TypedValue::Type::Int)
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
return a.ValueDouble() < b.ValueInt();
|
||||
else
|
||||
return a.ValueDouble() < b.ValueDouble();
|
||||
case TypedValue::Type::String:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueString() < b.ValueString();
|
||||
case TypedValue::Type::Date:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueDate() < b.ValueDate();
|
||||
case TypedValue::Type::LocalTime:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueLocalTime() < b.ValueLocalTime();
|
||||
case TypedValue::Type::LocalDateTime:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueLocalDateTime() < b.ValueLocalDateTime();
|
||||
case TypedValue::Type::Duration:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueDuration() < b.ValueDuration();
|
||||
case TypedValue::Type::List:
|
||||
case TypedValue::Type::Map:
|
||||
case TypedValue::Type::Vertex:
|
||||
case TypedValue::Type::Edge:
|
||||
case TypedValue::Type::Path:
|
||||
throw utils::BasicException("Comparison is not defined for values of type {}.", a.type());
|
||||
case TypedValue::Type::Null:
|
||||
LOG_FATAL("Invalid type");
|
||||
}
|
||||
}
|
||||
|
||||
class TypedValueVectorCompare final {
|
||||
public:
|
||||
explicit TypedValueVectorCompare(const std::vector<Ordering> &ordering) : ordering_(ordering) {}
|
||||
|
||||
bool operator()(const std::vector<TypedValue> &c1, const std::vector<TypedValue> &c2) const {
|
||||
// ordering is invalid if there are more elements in the collections
|
||||
// then there are in the ordering_ vector
|
||||
MG_ASSERT(c1.size() <= ordering_.size() && c2.size() <= ordering_.size(),
|
||||
"Collections contain more elements then there are orderings");
|
||||
|
||||
auto c1_it = c1.begin();
|
||||
auto c2_it = c2.begin();
|
||||
auto ordering_it = ordering_.begin();
|
||||
for (; c1_it != c1.end() && c2_it != c2.end(); c1_it++, c2_it++, ordering_it++) {
|
||||
if (TypedValueCompare(*c1_it, *c2_it)) return *ordering_it == Ordering::ASC;
|
||||
if (TypedValueCompare(*c2_it, *c1_it)) return *ordering_it == Ordering::DESC;
|
||||
}
|
||||
|
||||
// at least one collection is exhausted
|
||||
// c1 is less then c2 iff c1 reached the end but c2 didn't
|
||||
return (c1_it == c1.end()) && (c2_it != c2.end());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Ordering> ordering_;
|
||||
};
|
||||
|
||||
struct Element {
|
||||
std::vector<TypedValue> properties_order_by;
|
||||
VertexAccessor vertex_acc;
|
||||
};
|
||||
|
||||
std::vector<Element> OrderByElements(Shard::Accessor &acc, DbAccessor &dba, VerticesIterable &vertices_iterable,
|
||||
std::vector<msgs::OrderBy> &order_bys);
|
||||
|
||||
VerticesIterable::Iterator GetStartVertexIterator(VerticesIterable &vertex_iterable,
|
||||
const std::vector<PropertyValue> &start_ids, View view);
|
||||
|
||||
std::vector<Element>::const_iterator GetStartOrderedElementsIterator(const std::vector<Element> &ordered_elements,
|
||||
const std::vector<PropertyValue> &start_ids,
|
||||
View view);
|
||||
} // namespace memgraph::storage::v3
|
@ -26,10 +26,13 @@
|
||||
#include "storage/v3/bindings/symbol_generator.hpp"
|
||||
#include "storage/v3/bindings/symbol_table.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
#include "storage/v3/expr.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/request_helper.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/shard_rsm.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "storage/v3/value_conversions.hpp"
|
||||
@ -60,10 +63,10 @@ using SpecificEdgeProperties = std::tuple<msgs::VertexId, msgs::Gid, SpecificEdg
|
||||
using SpecificEdgePropertiesVector = std::vector<SpecificEdgeProperties>;
|
||||
using AllEdgePropertiesVector = std::vector<AllEdgeProperties>;
|
||||
|
||||
using EdgeAccessors = std::vector<memgraph::storage::v3::EdgeAccessor>;
|
||||
using EdgeAccessors = std::vector<storage::v3::EdgeAccessor>;
|
||||
|
||||
using EdgeFiller = std::function<bool(const EdgeAccessor &edge, bool is_in_edge, msgs::ExpandOneResultRow &result_row)>;
|
||||
using EdgeUniqunessFunction = std::function<EdgeAccessors(EdgeAccessors &&, memgraph::msgs::EdgeDirection)>;
|
||||
using EdgeUniqunessFunction = std::function<EdgeAccessors(EdgeAccessors &&, msgs::EdgeDirection)>;
|
||||
|
||||
struct VertexIdCmpr {
|
||||
bool operator()(const storage::v3::VertexId *lhs, const storage::v3::VertexId *rhs) const { return *lhs < *rhs; }
|
||||
@ -142,188 +145,7 @@ std::optional<std::map<PropertyId, Value>> CollectAllPropertiesFromAccessor(cons
|
||||
return ret;
|
||||
}
|
||||
|
||||
msgs::Value ConstructValueVertex(const VertexAccessor &acc, View view) {
|
||||
// Get the vertex id
|
||||
auto prim_label = acc.PrimaryLabel(view).GetValue();
|
||||
msgs::Label value_label{.id = prim_label};
|
||||
|
||||
auto prim_key = ConvertValueVector(acc.PrimaryKey(view).GetValue());
|
||||
msgs::VertexId vertex_id = std::make_pair(value_label, prim_key);
|
||||
|
||||
// Get the labels
|
||||
auto vertex_labels = acc.Labels(view).GetValue();
|
||||
std::vector<msgs::Label> value_labels;
|
||||
value_labels.reserve(vertex_labels.size());
|
||||
|
||||
std::transform(vertex_labels.begin(), vertex_labels.end(), std::back_inserter(value_labels),
|
||||
[](const auto &label) { return msgs::Label{.id = label}; });
|
||||
|
||||
return Value({.id = vertex_id, .labels = value_labels});
|
||||
}
|
||||
|
||||
Value ConstructValueEdge(const EdgeAccessor &acc, View view) {
|
||||
msgs::EdgeType type = {.id = acc.EdgeType()};
|
||||
msgs::EdgeId gid = {.gid = acc.Gid().AsUint()};
|
||||
|
||||
Label src_prim_label = {.id = acc.FromVertex().primary_label};
|
||||
msgs::VertexId src_vertex = std::make_pair(src_prim_label, ConvertValueVector(acc.FromVertex().primary_key));
|
||||
|
||||
Label dst_prim_label = {.id = acc.ToVertex().primary_label};
|
||||
msgs::VertexId dst_vertex = std::make_pair(dst_prim_label, ConvertValueVector(acc.ToVertex().primary_key));
|
||||
|
||||
auto properties = acc.Properties(view);
|
||||
|
||||
std::vector<std::pair<PropertyId, Value>> present_properties;
|
||||
if (properties.HasValue()) {
|
||||
auto &props = properties.GetValue();
|
||||
present_properties.reserve(props.size());
|
||||
|
||||
std::transform(props.begin(), props.end(), std::back_inserter(present_properties),
|
||||
[](std::pair<const PropertyId, PropertyValue> &prop) {
|
||||
return std::make_pair(prop.first, FromPropertyValueToValue(std::move(prop.second)));
|
||||
});
|
||||
}
|
||||
|
||||
return Value(msgs::Edge{.src = std::move(src_vertex),
|
||||
.dst = std::move(dst_vertex),
|
||||
.properties = std::move(present_properties),
|
||||
.id = gid,
|
||||
.type = type});
|
||||
}
|
||||
|
||||
Value FromTypedValueToValue(TypedValue &&tv) {
|
||||
switch (tv.type()) {
|
||||
case TypedValue::Type::Bool:
|
||||
return Value(tv.ValueBool());
|
||||
case TypedValue::Type::Double:
|
||||
return Value(tv.ValueDouble());
|
||||
case TypedValue::Type::Int:
|
||||
return Value(tv.ValueInt());
|
||||
case TypedValue::Type::List: {
|
||||
std::vector<Value> list;
|
||||
auto &tv_list = tv.ValueList();
|
||||
list.reserve(tv_list.size());
|
||||
std::transform(tv_list.begin(), tv_list.end(), std::back_inserter(list),
|
||||
[](auto &elem) { return FromTypedValueToValue(std::move(elem)); });
|
||||
return Value(list);
|
||||
}
|
||||
case TypedValue::Type::Map: {
|
||||
std::map<std::string, Value> map;
|
||||
for (auto &[key, val] : tv.ValueMap()) {
|
||||
map.emplace(key, FromTypedValueToValue(std::move(val)));
|
||||
}
|
||||
return Value(map);
|
||||
}
|
||||
case TypedValue::Type::Null:
|
||||
return Value{};
|
||||
case TypedValue::Type::String:
|
||||
return Value((std::string(tv.ValueString())));
|
||||
case TypedValue::Type::Vertex:
|
||||
return ConstructValueVertex(tv.ValueVertex(), View::OLD);
|
||||
case TypedValue::Type::Edge:
|
||||
return ConstructValueEdge(tv.ValueEdge(), View::OLD);
|
||||
|
||||
// TBD -> we need to specify temporal types, not a priority.
|
||||
case TypedValue::Type::Date:
|
||||
case TypedValue::Type::LocalTime:
|
||||
case TypedValue::Type::LocalDateTime:
|
||||
case TypedValue::Type::Duration:
|
||||
case TypedValue::Type::Path: {
|
||||
MG_ASSERT(false, "This conversion between TypedValue and Value is not implemented yet!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Value{};
|
||||
}
|
||||
|
||||
std::vector<Value> ConvertToValueVectorFromTypedValueVector(std::vector<TypedValue> &&vec) {
|
||||
std::vector<Value> ret;
|
||||
ret.reserve(vec.size());
|
||||
|
||||
std::transform(vec.begin(), vec.end(), std::back_inserter(ret),
|
||||
[](auto &elem) { return FromTypedValueToValue(std::move(elem)); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PropertyId> NamesToProperties(const std::vector<std::string> &property_names, DbAccessor &dba) {
|
||||
std::vector<PropertyId> properties;
|
||||
properties.reserve(property_names.size());
|
||||
|
||||
for (const auto &name : property_names) {
|
||||
properties.push_back(dba.NameToProperty(name));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
std::vector<LabelId> NamesToLabels(const std::vector<std::string> &label_names, DbAccessor &dba) {
|
||||
std::vector<LabelId> labels;
|
||||
labels.reserve(label_names.size());
|
||||
for (const auto &name : label_names) {
|
||||
labels.push_back(dba.NameToLabel(name));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
template <class TExpression>
|
||||
auto Eval(TExpression *expr, EvaluationContext &ctx, AstStorage &storage, ExpressionEvaluator &eval, DbAccessor &dba) {
|
||||
ctx.properties = NamesToProperties(storage.properties_, dba);
|
||||
ctx.labels = NamesToLabels(storage.labels_, dba);
|
||||
auto value = expr->Accept(eval);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::any ParseExpression(const std::string &expr, memgraph::expr::AstStorage &storage) {
|
||||
memgraph::frontend::opencypher::Parser<memgraph::frontend::opencypher::ParserOpTag::EXPRESSION> parser(expr);
|
||||
ParsingContext pc;
|
||||
CypherMainVisitor visitor(pc, &storage);
|
||||
|
||||
auto *ast = parser.tree();
|
||||
return visitor.visit(ast);
|
||||
}
|
||||
|
||||
TypedValue ComputeExpression(DbAccessor &dba, const std::optional<VertexAccessor> &v_acc,
|
||||
const std::optional<EdgeAccessor> &e_acc, const std::string &expression,
|
||||
std::string_view node_name, std::string_view edge_name) {
|
||||
AstStorage storage;
|
||||
Frame frame{1 + 1}; // 1 for the node_identifier, 1 for the edge_identifier
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext ctx;
|
||||
|
||||
ExpressionEvaluator eval{&frame, symbol_table, ctx, &dba, View::OLD};
|
||||
auto expr = ParseExpression(expression, storage);
|
||||
|
||||
auto node_identifier = Identifier(std::string(node_name), false);
|
||||
auto edge_identifier = Identifier(std::string(edge_name), false);
|
||||
|
||||
std::vector<Identifier *> identifiers;
|
||||
identifiers.push_back(&node_identifier);
|
||||
identifiers.push_back(&edge_identifier);
|
||||
|
||||
expr::SymbolGenerator symbol_generator(&symbol_table, identifiers);
|
||||
(std::any_cast<Expression *>(expr))->Accept(symbol_generator);
|
||||
|
||||
if (node_identifier.symbol_pos_ != -1) {
|
||||
MG_ASSERT(std::find_if(symbol_table.table().begin(), symbol_table.table().end(),
|
||||
[&node_name](const std::pair<int32_t, Symbol> &position_symbol_pair) {
|
||||
return position_symbol_pair.second.name() == node_name;
|
||||
}) != symbol_table.table().end());
|
||||
|
||||
frame[symbol_table.at(node_identifier)] = *v_acc;
|
||||
}
|
||||
|
||||
if (edge_identifier.symbol_pos_ != -1) {
|
||||
MG_ASSERT(std::find_if(symbol_table.table().begin(), symbol_table.table().end(),
|
||||
[&edge_name](const std::pair<int32_t, Symbol> &position_symbol_pair) {
|
||||
return position_symbol_pair.second.name() == edge_name;
|
||||
}) != symbol_table.table().end());
|
||||
|
||||
frame[symbol_table.at(edge_identifier)] = *e_acc;
|
||||
}
|
||||
|
||||
return Eval(std::any_cast<Expression *>(expr), ctx, storage, eval, dba);
|
||||
}
|
||||
|
||||
bool FilterOnVertex(DbAccessor &dba, const VertexAccessor &v_acc, const std::vector<std::string> &filters,
|
||||
bool FilterOnVertex(DbAccessor &dba, const storage::v3::VertexAccessor &v_acc, const std::vector<std::string> &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, "");
|
||||
@ -462,6 +284,17 @@ std::optional<std::array<std::vector<EdgeAccessor>, 2>> FillUpConnectingEdges(
|
||||
return std::array<std::vector<EdgeAccessor>, 2>{in_edges, out_edges};
|
||||
}
|
||||
|
||||
using AllEdgePropertyDataSructure = std::map<PropertyId, msgs::Value>;
|
||||
using SpecificEdgePropertyDataSructure = std::vector<msgs::Value>;
|
||||
|
||||
using AllEdgeProperties = std::tuple<msgs::VertexId, msgs::Gid, AllEdgePropertyDataSructure>;
|
||||
using SpecificEdgeProperties = std::tuple<msgs::VertexId, msgs::Gid, SpecificEdgePropertyDataSructure>;
|
||||
|
||||
using SpecificEdgePropertiesVector = std::vector<SpecificEdgeProperties>;
|
||||
using AllEdgePropertiesVector = std::vector<AllEdgeProperties>;
|
||||
|
||||
using EdgeFiller = std::function<bool(const EdgeAccessor &edge, bool is_in_edge, msgs::ExpandOneResultRow &result_row)>;
|
||||
|
||||
template <bool are_in_edges>
|
||||
bool FillEdges(const std::vector<EdgeAccessor> &edges, const msgs::ExpandOneRequest &req, msgs::ExpandOneResultRow &row,
|
||||
const EdgeFiller &edge_filler) {
|
||||
@ -525,30 +358,28 @@ EdgeUniqunessFunction InitializeEdgeUniqunessFunction(bool only_unique_neighbor_
|
||||
|
||||
if (only_unique_neighbor_rows) {
|
||||
maybe_filter_based_on_edge_uniquness = [](EdgeAccessors &&edges,
|
||||
memgraph::msgs::EdgeDirection edge_direction) -> EdgeAccessors {
|
||||
std::function<bool(std::set<const storage::v3::VertexId *, VertexIdCmpr> &,
|
||||
const memgraph::storage::v3::EdgeAccessor &)>
|
||||
msgs::EdgeDirection edge_direction) -> EdgeAccessors {
|
||||
std::function<bool(std::set<const storage::v3::VertexId *, VertexIdCmpr> &, const storage::v3::EdgeAccessor &)>
|
||||
is_edge_unique;
|
||||
switch (edge_direction) {
|
||||
case memgraph::msgs::EdgeDirection::OUT: {
|
||||
case msgs::EdgeDirection::OUT: {
|
||||
is_edge_unique = [](std::set<const storage::v3::VertexId *, VertexIdCmpr> &other_vertex_set,
|
||||
const memgraph::storage::v3::EdgeAccessor &edge_acc) {
|
||||
const storage::v3::EdgeAccessor &edge_acc) {
|
||||
auto [it, insertion_happened] = other_vertex_set.insert(&edge_acc.ToVertex());
|
||||
return insertion_happened;
|
||||
};
|
||||
break;
|
||||
}
|
||||
case memgraph::msgs::EdgeDirection::IN: {
|
||||
case msgs::EdgeDirection::IN: {
|
||||
is_edge_unique = [](std::set<const storage::v3::VertexId *, VertexIdCmpr> &other_vertex_set,
|
||||
const memgraph::storage::v3::EdgeAccessor &edge_acc) {
|
||||
const storage::v3::EdgeAccessor &edge_acc) {
|
||||
auto [it, insertion_happened] = other_vertex_set.insert(&edge_acc.FromVertex());
|
||||
return insertion_happened;
|
||||
};
|
||||
break;
|
||||
}
|
||||
case memgraph::msgs::EdgeDirection::BOTH:
|
||||
MG_ASSERT(false,
|
||||
"This is should never happen, memgraph::msgs::EdgeDirection::BOTH should not be passed here.");
|
||||
case msgs::EdgeDirection::BOTH:
|
||||
MG_ASSERT(false, "This is should never happen, msgs::EdgeDirection::BOTH should not be passed here.");
|
||||
}
|
||||
|
||||
EdgeAccessors ret;
|
||||
@ -563,10 +394,8 @@ EdgeUniqunessFunction InitializeEdgeUniqunessFunction(bool only_unique_neighbor_
|
||||
return ret;
|
||||
};
|
||||
} else {
|
||||
maybe_filter_based_on_edge_uniquness = [](EdgeAccessors &&edges,
|
||||
memgraph::msgs::EdgeDirection /*edge_direction*/) -> EdgeAccessors {
|
||||
return std::move(edges);
|
||||
};
|
||||
maybe_filter_based_on_edge_uniquness =
|
||||
[](EdgeAccessors &&edges, msgs::EdgeDirection /*edge_direction*/) -> EdgeAccessors { return std::move(edges); };
|
||||
}
|
||||
|
||||
return maybe_filter_based_on_edge_uniquness;
|
||||
@ -920,65 +749,82 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) {
|
||||
bool action_successful = true;
|
||||
|
||||
std::vector<msgs::ScanResultRow> results;
|
||||
if (req.batch_limit) {
|
||||
results.reserve(*req.batch_limit);
|
||||
}
|
||||
std::optional<msgs::VertexId> next_start_id;
|
||||
|
||||
const auto view = View(req.storage_view);
|
||||
auto vertex_iterable = acc.Vertices(view);
|
||||
bool did_reach_starting_point = false;
|
||||
uint64_t sample_counter = 0;
|
||||
|
||||
const auto start_ids = ConvertPropertyVector(std::move(req.start_id.second));
|
||||
auto dba = DbAccessor{&acc};
|
||||
|
||||
for (auto it = vertex_iterable.begin(); it != vertex_iterable.end(); ++it) {
|
||||
const auto &vertex = *it;
|
||||
|
||||
if (start_ids <= vertex.PrimaryKey(View(req.storage_view)).GetValue()) {
|
||||
did_reach_starting_point = true;
|
||||
const auto emplace_scan_result = [&](const VertexAccessor &vertex) {
|
||||
std::vector<Value> expression_results;
|
||||
// TODO(gvolfing) it should be enough to check these only once.
|
||||
if (vertex.Properties(View(req.storage_view)).HasError()) {
|
||||
action_successful = false;
|
||||
spdlog::debug("Could not retrieve properties from VertexAccessor. Transaction id: {}",
|
||||
req.transaction_id.logical_id);
|
||||
}
|
||||
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);
|
||||
if (!eval) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!req.vertex_expressions.empty()) {
|
||||
// NOTE - DbAccessor might get removed in the future.
|
||||
expression_results = ConvertToValueVectorFromTypedValueVector(
|
||||
EvaluateVertexExpressions(dba, vertex, req.vertex_expressions, expr::identifier_node_symbol));
|
||||
}
|
||||
|
||||
if (did_reach_starting_point) {
|
||||
std::vector<Value> expression_results;
|
||||
|
||||
// TODO(gvolfing) it should be enough to check these only once.
|
||||
if (vertex.Properties(View(req.storage_view)).HasError()) {
|
||||
action_successful = false;
|
||||
spdlog::debug("Could not retrive properties from VertexAccessor. Transaction id: {}",
|
||||
req.transaction_id.logical_id);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
if (!eval) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!req.vertex_expressions.empty()) {
|
||||
expression_results = ConvertToValueVectorFromTypedValueVector(
|
||||
EvaluateVertexExpressions(dba, vertex, req.vertex_expressions, expr::identifier_node_symbol));
|
||||
}
|
||||
|
||||
std::optional<std::map<PropertyId, Value>> found_props;
|
||||
std::optional<std::map<PropertyId, Value>> found_props;
|
||||
|
||||
if (req.props_to_return) {
|
||||
found_props = CollectSpecificPropertiesFromAccessor(vertex, req.props_to_return.value(), view);
|
||||
} else {
|
||||
const auto *schema = shard_->GetSchema(shard_->PrimaryLabel());
|
||||
if (req.props_to_return) {
|
||||
found_props = CollectSpecificPropertiesFromAccessor(vertex, req.props_to_return.value(), view);
|
||||
} else {
|
||||
found_props = CollectAllPropertiesFromAccessor(vertex, view, schema);
|
||||
}
|
||||
found_props = CollectAllPropertiesFromAccessor(vertex, view, schema);
|
||||
}
|
||||
|
||||
// TODO(gvolfing) -VERIFY-
|
||||
// Vertex is separated from the properties in the response.
|
||||
// Is it useful to return just a vertex without the properties?
|
||||
if (!found_props) {
|
||||
action_successful = false;
|
||||
}
|
||||
|
||||
results.emplace_back(msgs::ScanResultRow{.vertex = ConstructValueVertex(vertex, view).vertex_v,
|
||||
.props = FromMap(found_props.value()),
|
||||
.evaluated_vertex_expressions = std::move(expression_results)});
|
||||
};
|
||||
|
||||
const auto start_id = ConvertPropertyVector(std::move(req.start_id.second));
|
||||
uint64_t sample_counter{0};
|
||||
auto vertex_iterable = acc.Vertices(view);
|
||||
if (!req.order_bys.empty()) {
|
||||
const auto ordered = OrderByElements(acc, dba, vertex_iterable, req.order_bys);
|
||||
// we are traversing Elements
|
||||
auto it = GetStartOrderedElementsIterator(ordered, start_id, View(req.storage_view));
|
||||
for (; it != ordered.end(); ++it) {
|
||||
emplace_scan_result(it->vertex_acc);
|
||||
++sample_counter;
|
||||
if (req.batch_limit && sample_counter == req.batch_limit) {
|
||||
// Reached the maximum specified batch size.
|
||||
// Get the next element before exiting.
|
||||
++it;
|
||||
if (it != ordered.end()) {
|
||||
const auto &next_vertex = it->vertex_acc;
|
||||
next_start_id = ConstructValueVertex(next_vertex, view).vertex_v.id;
|
||||
}
|
||||
|
||||
// TODO(gvolfing) -VERIFY-
|
||||
// Vertex is seperated from the properties in the response.
|
||||
// Is it useful to return just a vertex without the properties?
|
||||
if (!found_props) {
|
||||
action_successful = false;
|
||||
break;
|
||||
}
|
||||
|
||||
results.emplace_back(msgs::ScanResultRow{.vertex = ConstructValueVertex(vertex, view).vertex_v,
|
||||
.props = FromMap(found_props.value()),
|
||||
.evaluated_vertex_expressions = std::move(expression_results)});
|
||||
}
|
||||
} else {
|
||||
// We are going through VerticesIterable::Iterator
|
||||
auto it = GetStartVertexIterator(vertex_iterable, start_id, View(req.storage_view));
|
||||
for (; it != vertex_iterable.end(); ++it) {
|
||||
emplace_scan_result(*it);
|
||||
|
||||
++sample_counter;
|
||||
if (req.batch_limit && sample_counter == req.batch_limit) {
|
||||
@ -1059,4 +905,4 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest && /*req*/)
|
||||
return msgs::GetPropertiesResponse{};
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
} // namespace memgraph::storage::v3
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "io/simulator/simulator_transport.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/shard_rsm.hpp"
|
||||
@ -414,7 +415,7 @@ std::tuple<size_t, std::optional<msgs::VertexId>> AttemptToScanAllWithExpression
|
||||
msgs::VertexId start_id,
|
||||
uint64_t batch_limit,
|
||||
uint64_t prop_val_to_check_against) {
|
||||
std::string filter_expr1 = "MG_SYMBOL_NODE.property = " + std::to_string(prop_val_to_check_against);
|
||||
std::string filter_expr1 = "MG_SYMBOL_NODE.prop1 = " + std::to_string(prop_val_to_check_against);
|
||||
std::vector<std::string> filter_expressions = {filter_expr1};
|
||||
|
||||
std::string regular_expr1 = "2+2";
|
||||
@ -445,6 +446,72 @@ std::tuple<size_t, std::optional<msgs::VertexId>> AttemptToScanAllWithExpression
|
||||
}
|
||||
}
|
||||
|
||||
void AttemptToScanAllWithOrderByOnPrimaryProperty(ShardClient &client, msgs::VertexId start_id, uint64_t batch_limit) {
|
||||
msgs::ScanVerticesRequest scan_req;
|
||||
scan_req.batch_limit = batch_limit;
|
||||
scan_req.order_bys = {{msgs::Expression{"MG_SYMBOL_NODE.prop1"}, msgs::OrderingDirection::DESCENDING}};
|
||||
scan_req.props_to_return = std::nullopt;
|
||||
scan_req.start_id = start_id;
|
||||
scan_req.storage_view = msgs::StorageView::NEW;
|
||||
scan_req.transaction_id.logical_id = GetTransactionId();
|
||||
|
||||
while (true) {
|
||||
auto read_res = client.SendReadRequest(scan_req);
|
||||
if (read_res.HasError()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto write_response_result = read_res.GetValue();
|
||||
auto write_response = std::get<msgs::ScanVerticesResponse>(write_response_result);
|
||||
|
||||
MG_ASSERT(write_response.success);
|
||||
MG_ASSERT(write_response.results.size() == 5, "Expecting 5 results!");
|
||||
for (int64_t i{0}; i < 5; ++i) {
|
||||
const auto expected_primary_key = std::vector{msgs::Value(1023 - i)};
|
||||
const auto actual_primary_key = write_response.results[i].vertex.id.second;
|
||||
MG_ASSERT(expected_primary_key == actual_primary_key, "The order of vertices is not correct");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AttemptToScanAllWithOrderByOnSecondaryProperty(ShardClient &client, msgs::VertexId start_id,
|
||||
uint64_t batch_limit) {
|
||||
msgs::ScanVerticesRequest scan_req;
|
||||
scan_req.batch_limit = batch_limit;
|
||||
scan_req.order_bys = {{msgs::Expression{"MG_SYMBOL_NODE.prop4"}, msgs::OrderingDirection::DESCENDING}};
|
||||
scan_req.props_to_return = std::nullopt;
|
||||
scan_req.start_id = start_id;
|
||||
scan_req.storage_view = msgs::StorageView::NEW;
|
||||
scan_req.transaction_id.logical_id = GetTransactionId();
|
||||
|
||||
while (true) {
|
||||
auto read_res = client.SendReadRequest(scan_req);
|
||||
if (read_res.HasError()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto write_response_result = read_res.GetValue();
|
||||
auto write_response = std::get<msgs::ScanVerticesResponse>(write_response_result);
|
||||
|
||||
MG_ASSERT(write_response.success);
|
||||
MG_ASSERT(write_response.results.size() == 5, "Expecting 5 results!");
|
||||
for (int64_t i{0}; i < 5; ++i) {
|
||||
const auto expected_prop4 = std::vector{msgs::Value(1023 - i)};
|
||||
const auto actual_prop4 = std::invoke([&write_response, i]() {
|
||||
const auto res = std::ranges::find_if(write_response.results[i].props, [](const auto &id_value_prop_pair) {
|
||||
return id_value_prop_pair.first.AsInt() == 4;
|
||||
});
|
||||
MG_ASSERT(res != write_response.results[i].props.end(), "Property does not exist!");
|
||||
return std::vector{res->second};
|
||||
});
|
||||
|
||||
MG_ASSERT(expected_prop4 == actual_prop4, "The order of vertices is not correct");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AttemptToExpandOneWithWrongEdgeType(ShardClient &client, uint64_t src_vertex_val, EdgeTypeId edge_type_id) {
|
||||
// Source vertex
|
||||
msgs::Label label = {.id = get_primary_label()};
|
||||
@ -733,7 +800,7 @@ void AttemptToExpandOneWithSpecifiedEdgeProperties(ShardClient &client, uint64_t
|
||||
|
||||
void AttemptToExpandOneWithFilters(ShardClient &client, uint64_t src_vertex_val, EdgeTypeId edge_type_id,
|
||||
uint64_t edge_prop_id, uint64_t prop_val_to_check_against) {
|
||||
std::string filter_expr1 = "MG_SYMBOL_NODE.property = " + std::to_string(prop_val_to_check_against);
|
||||
std::string filter_expr1 = "MG_SYMBOL_NODE.prop1 = " + std::to_string(prop_val_to_check_against);
|
||||
|
||||
// Source vertex
|
||||
msgs::Label label = {.id = get_primary_label()};
|
||||
@ -882,6 +949,9 @@ void TestScanAllOneGo(ShardClient &client) {
|
||||
auto [result_size_2, next_id_2] = AttemptToScanAllWithExpression(client, v_id, 5, unique_prop_val_2);
|
||||
MG_ASSERT(result_size_2 == 1);
|
||||
|
||||
AttemptToScanAllWithOrderByOnPrimaryProperty(client, v_id, 5);
|
||||
AttemptToScanAllWithOrderByOnSecondaryProperty(client, v_id, 5);
|
||||
|
||||
auto [result_size_with_batch, next_id_with_batch] = AttemptToScanAllWithBatchLimit(client, v_id, 5);
|
||||
auto [result_size_without_batch, next_id_without_batch] = AttemptToScanAllWithoutBatchLimit(client, v_id);
|
||||
|
||||
@ -1033,9 +1103,9 @@ int TestMessages() {
|
||||
auto shard_ptr2 = std::make_unique<Shard>(get_primary_label(), min_prim_key, max_prim_key, schema_prop);
|
||||
auto shard_ptr3 = std::make_unique<Shard>(get_primary_label(), min_prim_key, max_prim_key, schema_prop);
|
||||
|
||||
shard_ptr1->StoreMapping({{1, "label"}, {2, "property"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}});
|
||||
shard_ptr2->StoreMapping({{1, "label"}, {2, "property"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}});
|
||||
shard_ptr3->StoreMapping({{1, "label"}, {2, "property"}, {3, "label1"}, {4, "prop2"}, {5, "prop3"}, {6, "prop4"}});
|
||||
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"}});
|
||||
|
||||
std::vector<Address> address_for_1{shard_server_2_address, shard_server_3_address};
|
||||
std::vector<Address> address_for_2{shard_server_1_address, shard_server_3_address};
|
||||
|
@ -3,43 +3,187 @@ import gdb.printing
|
||||
|
||||
|
||||
def build_memgraph_pretty_printers():
|
||||
'''Instantiate and return all memgraph pretty printer classes.'''
|
||||
pp = gdb.printing.RegexpCollectionPrettyPrinter('memgraph')
|
||||
pp.add_printer('memgraph::query::TypedValue', '^memgraph::query::TypedValue$', TypedValuePrinter)
|
||||
"""Instantiate and return all memgraph pretty printer classes."""
|
||||
pp = gdb.printing.RegexpCollectionPrettyPrinter("memgraph")
|
||||
pp.add_printer("memgraph::query::TypedValue", "^memgraph::query::TypedValue$", TypedValuePrinter)
|
||||
pp.add_printer("memgraph::query::v2::TypedValue", "^memgraph::query::v2::TypedValue$", TypedValuePrinter2)
|
||||
pp.add_printer("memgraph::storage::v3::TypedValue", "^memgraph::storage::v3::TypedValue$", TypedValuePrinter3)
|
||||
pp.add_printer(
|
||||
"memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>",
|
||||
"^memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>$",
|
||||
TypedValuePrinter4,
|
||||
)
|
||||
return pp
|
||||
|
||||
|
||||
class TypedValuePrinter(gdb.printing.PrettyPrinter):
|
||||
'''Pretty printer for memgraph::query::TypedValue'''
|
||||
"""Pretty printer for memgraph::query::TypedValue"""
|
||||
|
||||
def __init__(self, val):
|
||||
super(TypedValuePrinter, self).__init__('TypedValue')
|
||||
super(TypedValuePrinter, self).__init__("TypedValue")
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
def _to_str(val):
|
||||
return '{%s %s}' % (value_type, self.val[val])
|
||||
value_type = str(self.val['type_'])
|
||||
if value_type == 'memgraph::query::TypedValue::Type::Null':
|
||||
return '{%s}' % value_type
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::Bool':
|
||||
return _to_str('bool_v')
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::Int':
|
||||
return _to_str('int_v')
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::Double':
|
||||
return _to_str('double_v')
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::String':
|
||||
return _to_str('string_v')
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::List':
|
||||
return _to_str('list_v')
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::Map':
|
||||
return _to_str('map_v')
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::Vertex':
|
||||
return _to_str('vertex_v')
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::Edge':
|
||||
return _to_str('edge_v')
|
||||
elif value_type == 'memgraph::query::TypedValue::Type::Path':
|
||||
return _to_str('path_v')
|
||||
return '{%s}' % value_type
|
||||
return "{%s %s}" % (value_type, self.val[val])
|
||||
|
||||
gdb.printing.register_pretty_printer(None, build_memgraph_pretty_printers(),
|
||||
replace=True)
|
||||
value_type = str(self.val["type_"])
|
||||
if value_type == "memgraph::query::TypedValue::Type::Null":
|
||||
return "{%s}" % value_type
|
||||
elif value_type == "memgraph::query::TypedValue::Type::Bool":
|
||||
return _to_str("bool_v")
|
||||
elif value_type == "memgraph::query::TypedValue::Type::Int":
|
||||
return _to_str("int_v")
|
||||
elif value_type == "memgraph::query::TypedValue::Type::Double":
|
||||
return _to_str("double_v")
|
||||
elif value_type == "memgraph::query::TypedValue::Type::String":
|
||||
return _to_str("string_v")
|
||||
elif value_type == "memgraph::query::TypedValue::Type::List":
|
||||
return _to_str("list_v")
|
||||
elif value_type == "memgraph::query::TypedValue::Type::Map":
|
||||
return _to_str("map_v")
|
||||
elif value_type == "memgraph::query::TypedValue::Type::Vertex":
|
||||
return _to_str("vertex_v")
|
||||
elif value_type == "memgraph::query::TypedValue::Type::Edge":
|
||||
return _to_str("edge_v")
|
||||
elif value_type == "memgraph::query::TypedValue::Type::Path":
|
||||
return _to_str("path_v")
|
||||
return "{%s}" % value_type
|
||||
|
||||
|
||||
class TypedValuePrinter2(gdb.printing.PrettyPrinter):
|
||||
"""Pretty printer for memgraph::query::TypedValue"""
|
||||
|
||||
def __init__(self, val):
|
||||
super(TypedValuePrinter2, self).__init__("TypedValue2")
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
def _to_str(val):
|
||||
return "{%s %s}" % (value_type, self.val[val])
|
||||
|
||||
value_type = str(self.val["type_"])
|
||||
if value_type == "memgraph::query::v2::TypedValue::Type::Null":
|
||||
return "{%s}" % value_type
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::Bool":
|
||||
return _to_str("bool_v")
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::Int":
|
||||
return _to_str("int_v")
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::Double":
|
||||
return _to_str("double_v")
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::String":
|
||||
return _to_str("string_v")
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::List":
|
||||
return _to_str("list_v")
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::Map":
|
||||
return _to_str("map_v")
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::Vertex":
|
||||
return _to_str("vertex_v")
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::Edge":
|
||||
return _to_str("edge_v")
|
||||
elif value_type == "memgraph::query::v2::TypedValue::Type::Path":
|
||||
return _to_str("path_v")
|
||||
return "{%s}" % value_type
|
||||
|
||||
|
||||
class TypedValuePrinter3(gdb.printing.PrettyPrinter):
|
||||
"""Pretty printer for memgraph::query::TypedValue"""
|
||||
|
||||
def __init__(self, val):
|
||||
super(TypedValuePrinter3, self).__init__("TypedValue3")
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
def _to_str(val):
|
||||
return "{%s %s}" % (value_type, self.val[val])
|
||||
|
||||
value_type = str(self.val["type_"])
|
||||
if value_type == "memgraph::storage::v3::TypedValue::Type::Null":
|
||||
return "{%s}" % value_type
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::Bool":
|
||||
return _to_str("bool_v")
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::Int":
|
||||
return _to_str("int_v")
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::Double":
|
||||
return _to_str("double_v")
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::String":
|
||||
return _to_str("string_v")
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::List":
|
||||
return _to_str("list_v")
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::Map":
|
||||
return _to_str("map_v")
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::Vertex":
|
||||
return _to_str("vertex_v")
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::Edge":
|
||||
return _to_str("edge_v")
|
||||
elif value_type == "memgraph::storage::v3::TypedValue::Type::Path":
|
||||
return _to_str("path_v")
|
||||
return "{%s}" % value_type
|
||||
|
||||
|
||||
class TypedValuePrinter4(gdb.printing.PrettyPrinter):
|
||||
"""Pretty printer for memgraph::query::TypedValue"""
|
||||
|
||||
def __init__(self, val):
|
||||
super(TypedValuePrinter4, self).__init__("TypedValue4")
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
def _to_str(val):
|
||||
return "{%s %s}" % (value_type, self.val[val])
|
||||
|
||||
value_type = str(self.val["type_"])
|
||||
if (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::Null"
|
||||
):
|
||||
return "{%s}" % value_type
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::Bool"
|
||||
):
|
||||
return _to_str("bool_v")
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::Int"
|
||||
):
|
||||
return _to_str("int_v")
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::Double"
|
||||
):
|
||||
return _to_str("double_v")
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::String"
|
||||
):
|
||||
return _to_str("string_v")
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::List"
|
||||
):
|
||||
return _to_str("list_v")
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::Map"
|
||||
):
|
||||
return _to_str("map_v")
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::Vertex"
|
||||
):
|
||||
return _to_str("vertex_v")
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::Edge"
|
||||
):
|
||||
return _to_str("edge_v")
|
||||
elif (
|
||||
value_type
|
||||
== "memgraph::expr::TypedValueT<memgraph::storage::v3::VertexAccessor, memgraph::storage::v3::EdgeAccessor, memgraph::storage::v3::Path>::Type::Path"
|
||||
):
|
||||
return _to_str("path_v")
|
||||
return "{%s}" % value_type
|
||||
|
||||
|
||||
gdb.printing.register_pretty_printer(None, build_memgraph_pretty_printers(), replace=True)
|
||||
|
Loading…
Reference in New Issue
Block a user