Add AST cache

Reviewers: buda, teon.banek, florijan

Reviewed By: buda

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D468
This commit is contained in:
Mislav Bradac 2017-06-14 18:53:02 +02:00
parent b6da65b9e7
commit 67b859cf13
33 changed files with 399 additions and 243 deletions

View File

@ -319,44 +319,45 @@ target_link_libraries(antlr_opencypher_parser_lib antlr4_static)
# all memgraph src files
set(memgraph_src_files
${src_dir}/data_structures/concurrent/skiplist_gc.cpp
${src_dir}/database/graph_db.cpp
${src_dir}/database/graph_db_accessor.cpp
${src_dir}/dbms/dbms.cpp
${src_dir}/utils/numerics/saturate.cpp
${src_dir}/durability/recovery.cpp
${src_dir}/durability/snapshooter.cpp
${src_dir}/io/network/addrinfo.cpp
${src_dir}/io/network/network_endpoint.cpp
${src_dir}/io/network/socket.cpp
${src_dir}/threading/thread.cpp
${src_dir}/durability/snapshooter.cpp
${src_dir}/durability/recovery.cpp
${src_dir}/storage/property_value.cpp
${src_dir}/storage/locking/record_lock.cpp
${src_dir}/storage/record_accessor.cpp
${src_dir}/storage/vertex_accessor.cpp
${src_dir}/storage/edge_accessor.cpp
${src_dir}/transactions/transaction.cpp
${src_dir}/template_engine/engine.cpp
${src_dir}/logging/streams/stdout.cpp
${src_dir}/logging/streams/stderr.cpp
${src_dir}/logging/levels.cpp
${src_dir}/logging/logs/sync_log.cpp
${src_dir}/logging/logs/async_log.cpp
${src_dir}/logging/default.cpp
${src_dir}/logging/levels.cpp
${src_dir}/logging/log.cpp
${src_dir}/database/graph_db.cpp
${src_dir}/database/graph_db_accessor.cpp
${src_dir}/data_structures/concurrent/skiplist_gc.cpp
${src_dir}/query/engine.cpp
${src_dir}/query/frontend/stripped.cpp
${src_dir}/logging/logs/async_log.cpp
${src_dir}/logging/logs/sync_log.cpp
${src_dir}/logging/streams/stderr.cpp
${src_dir}/logging/streams/stdout.cpp
${src_dir}/query/common.cpp
${src_dir}/query/console.cpp
${src_dir}/query/engine.cpp
${src_dir}/query/frontend/ast/ast.cpp
${src_dir}/query/frontend/ast/cypher_main_visitor.cpp
${src_dir}/query/typed_value.cpp
${src_dir}/query/frontend/semantic/symbol_generator.cpp
${src_dir}/query/frontend/stripped.cpp
${src_dir}/query/interpret/awesome_memgraph_functions.cpp
${src_dir}/query/interpreter.cpp
${src_dir}/query/plan/cost_estimator.cpp
${src_dir}/query/plan/operator.cpp
${src_dir}/query/plan/rule_based_planner.cpp
${src_dir}/query/plan/variable_start_planner.cpp
${src_dir}/query/plan/cost_estimator.cpp
${src_dir}/query/frontend/semantic/symbol_generator.cpp
${src_dir}/query/typed_value.cpp
${src_dir}/storage/edge_accessor.cpp
${src_dir}/storage/locking/record_lock.cpp
${src_dir}/storage/property_value.cpp
${src_dir}/storage/record_accessor.cpp
${src_dir}/storage/vertex_accessor.cpp
${src_dir}/template_engine/engine.cpp
${src_dir}/threading/thread.cpp
${src_dir}/transactions/transaction.cpp
${src_dir}/utils/numerics/saturate.cpp
)
# -----------------------------------------------------------------------------

View File

@ -74,7 +74,7 @@ class QueryEngine : public Loggable {
clock_t end_parsing_time = clock();
auto plan = LoadCypher(stripped);
clock_t end_planning_time = clock();
auto result = plan->run(db_accessor, stripped.parameters(), stream);
auto result = plan->run(db_accessor, stripped.literals(), stream);
clock_t end_execution_time = clock();
if (UNLIKELY(!result)) {
// info because it might be something like deadlock in which

View File

@ -7,6 +7,7 @@
#include "database/graph_db.hpp"
#include "database/graph_db_datatypes.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/parameters.hpp"
#include "query/typed_value.hpp"
#include "utils/assert.hpp"
@ -37,6 +38,8 @@ class AstTreeStorage {
AstTreeStorage();
AstTreeStorage(const AstTreeStorage &) = delete;
AstTreeStorage &operator=(const AstTreeStorage &) = delete;
AstTreeStorage(AstTreeStorage &&) = default;
AstTreeStorage &operator=(AstTreeStorage &&) = default;
template <typename T, typename... Args>
T *Create(Args &&... args) {
@ -1364,6 +1367,51 @@ class Unwind : public Clause {
}
};
/// CachedAst is used for storing high level asts.
///
/// After query is stripped, parsed and converted to high level ast it can be
/// stored in this class and new trees can be created by plugging different
/// literals.
class CachedAst {
public:
CachedAst(AstTreeStorage storage) : storage_(std::move(storage)) {}
/// Create new storage by plugging literals on its positions.
AstTreeStorage Plug(const Parameters &literals) {
AstTreeStorage new_ast;
storage_.query()->Clone(new_ast);
LiteralsPlugger plugger(literals);
new_ast.query()->Accept(plugger);
return new_ast;
}
private:
class LiteralsPlugger : public HierarchicalTreeVisitor {
public:
using HierarchicalTreeVisitor::PreVisit;
using typename HierarchicalTreeVisitor::ReturnType;
using HierarchicalTreeVisitor::Visit;
using HierarchicalTreeVisitor::PostVisit;
LiteralsPlugger(const Parameters &parameters) : parameters_(parameters) {}
bool Visit(PrimitiveLiteral &literal) override {
permanent_assert(
literal.token_position_ != -1,
"Use AstPlugLiteralsVisitor only on ast created by parsing queries");
literal.value_ = parameters_.AtTokenPosition(literal.token_position_);
return true;
}
bool Visit(Identifier &) override { return true; }
private:
const Parameters &parameters_;
};
AstTreeStorage storage_;
};
#undef CLONE_BINARY_EXPRESSION
#undef CLONE_UNARY_EXPRESSION
}

View File

@ -32,17 +32,19 @@ StrippedQuery::StrippedQuery(const std::string &query)
std::vector<std::string> token_strings;
token_strings.reserve(tokens.size());
// A helper function that generates a new param name for the stripped
// literal, appends is to the the stripped_query and adds the passed
// value to stripped args.
auto replace_stripped = [this, &token_strings](const TypedValue &value) {
// const std::string &new_value) {
const auto &stripped_name = parameters_.Add(value);
token_strings.push_back("$" + stripped_name);
// A helper function that stores literal and its token position in a
// literals_. In stripped query text literal is replaced with a new_value.
// new_value can be any value that is lexed as a literal.
auto replace_stripped = [this, &token_strings](
int position, const TypedValue &value, const std::string &new_value) {
literals_.Add(position, value);
token_strings.push_back(new_value);
};
// Convert tokens to strings, perform lowercasing and filtering.
for (const auto *token : tokens) {
int position = token->getTokenIndex();
switch (token->getType()) {
case CypherLexer::UNION:
case CypherLexer::ALL:
@ -78,6 +80,8 @@ StrippedQuery::StrippedQuery(const std::string &query)
case CypherLexer::ENDS:
case CypherLexer::CONTAINS:
case CypherLexer::IS:
// We don't strip NULL, since it can appear in special expressions like IS
// NULL and IS NOT NULL.
case CypherLexer::CYPHERNULL:
case CypherLexer::COUNT:
case CypherLexer::FILTER:
@ -95,22 +99,25 @@ StrippedQuery::StrippedQuery(const std::string &query)
case CypherLexer::DecimalInteger:
case CypherLexer::HexInteger:
case CypherLexer::OctalInteger:
replace_stripped(ParseIntegerLiteral(token->getText()));
replace_stripped(position, ParseIntegerLiteral(token->getText()),
kStrippedIntToken);
break;
case CypherLexer::StringLiteral:
replace_stripped(ParseStringLiteral(token->getText()));
replace_stripped(position, ParseStringLiteral(token->getText()),
kStrippedStringToken);
break;
case CypherLexer::RegularDecimalReal:
case CypherLexer::ExponentDecimalReal:
replace_stripped(ParseDoubleLiteral(token->getText()));
replace_stripped(position, ParseDoubleLiteral(token->getText()),
kStrippedDoubleToken);
break;
case CypherLexer::TRUE:
replace_stripped(true);
replace_stripped(position, true, kStrippedBooleanToken);
break;
case CypherLexer::FALSE:
replace_stripped(false);
replace_stripped(position, false, kStrippedBooleanToken);
break;
default:

View File

@ -1,28 +1,33 @@
#pragma once
#include <map>
#include "logging/loggable.hpp"
#include "query/parameters.hpp"
#include "storage/property_value_store.hpp"
#include "query/typed_value.hpp"
#include "utils/assert.hpp"
#include "utils/hashing/fnv.hpp"
namespace query {
/*
* StrippedQuery contains:
* * stripped query
* * plan arguments stripped from query
* * hash of stripped query
*/
// Strings used to replace original tokens. Different types are replaced with
// different token.
const std::string kStrippedIntToken = "0";
const std::string kStrippedDoubleToken = "0.0";
const std::string kStrippedStringToken = "\"a\"";
const std::string kStrippedBooleanToken = "true";
/**
* StrippedQuery contains:
* * stripped query
* * literals stripped from query
* * hash of stripped query
*/
class StrippedQuery : Loggable {
public:
/**
* Strips the input query and stores stripped query, stripped arguments and
* stripped query hash.
*
* @param query input query
* @param query Input query.
*/
explicit StrippedQuery(const std::string &query);
@ -41,17 +46,17 @@ class StrippedQuery : Loggable {
StrippedQuery &operator=(StrippedQuery &&other) = default;
const std::string &query() const { return query_; }
const Parameters &parameters() const { return parameters_; }
auto &literals() const { return literals_; }
HashType hash() const { return hash_; }
private:
// stripped query
// Stripped query.
std::string query_;
// striped arguments
Parameters parameters_;
// Token positions of stripped out literals mapped to their values.
Parameters literals_;
// hash based on the stripped query
// Hash based on the stripped query.
HashType hash_;
};
}

View File

@ -0,0 +1,6 @@
#include "query/interpreter.hpp"
// TODO: Remove this flag. Ast caching can be disabled by setting this flag to
// false, this is useful for recerating antlr crashes in highly concurrent test.
// Once antlr bugs are fixed, or real test is written this flag can be removed.
DEFINE_bool(ast_cache, true, "Use ast caching.");

View File

@ -4,14 +4,19 @@
#include <limits>
#include "database/graph_db_accessor.hpp"
#include "gflags/gflags.h"
#include "query/context.hpp"
#include "query/frontend/ast/cypher_main_visitor.hpp"
#include "query/frontend/opencypher/parser.hpp"
#include "query/frontend/semantic/symbol_generator.hpp"
#include "query/frontend/stripped.hpp"
#include "query/interpret/frame.hpp"
#include "query/plan/cost_estimator.hpp"
#include "query/plan/planner.hpp"
// TODO: Remove this flag and add flag that limits cache size.
DECLARE_bool(ast_cache);
namespace query {
class Interpreter : public Loggable {
@ -26,22 +31,48 @@ class Interpreter : public Loggable {
Context ctx(config, db_accessor);
std::map<std::string, TypedValue> summary;
// query -> AST
frontend::opencypher::Parser parser(query);
// query -> stripped query
StrippedQuery stripped(query);
auto low_level_tree = parser.tree();
// stripped query -> high level tree
AstTreeStorage ast_storage = [&]() {
if (!FLAGS_ast_cache) {
// stripped query -> AST
frontend::opencypher::Parser parser(query);
auto low_level_tree = parser.tree();
clock_t antlr_end_time = clock();
// AST -> high level tree
frontend::CypherMainVisitor visitor(ctx);
visitor.visit(low_level_tree);
return std::move(visitor.storage());
}
// AST -> high level tree
frontend::CypherMainVisitor visitor(ctx);
visitor.visit(low_level_tree);
auto high_level_tree = visitor.query();
auto ast_cache_accessor = ast_cache_.access();
auto it = ast_cache_accessor.find(query::StrippedQuery(query).hash());
if (it == ast_cache_accessor.end()) {
// stripped query -> AST
frontend::opencypher::Parser parser(stripped.query());
auto low_level_tree = parser.tree();
// AST -> high level tree
frontend::CypherMainVisitor visitor(ctx);
visitor.visit(low_level_tree);
// Cache it.
it = ast_cache_accessor
.insert(stripped.hash(),
CachedAst(std::move(visitor.storage())))
.first;
}
return it->second.Plug(stripped.literals());
}();
clock_t frontend_end_time = clock();
// symbol table fill
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
high_level_tree->Accept(symbol_generator);
ast_storage.query()->Accept(symbol_generator);
// high level tree -> logical plan
std::unique_ptr<plan::LogicalOperator> logical_plan;
@ -50,7 +81,7 @@ class Interpreter : public Loggable {
bool FLAGS_query_cost_planner = true;
if (FLAGS_query_cost_planner) {
auto plans = plan::MakeLogicalPlan<plan::VariableStartPlanner>(
visitor.storage(), symbol_table, &db_accessor);
ast_storage, symbol_table, &db_accessor);
double min_cost = std::numeric_limits<double>::max();
for (auto &plan : plans) {
plan::CostEstimator estimator(db_accessor);
@ -66,7 +97,7 @@ class Interpreter : public Loggable {
query_plan_cost_estimation = min_cost;
} else {
logical_plan = plan::MakeLogicalPlan<plan::RuleBasedPlanner>(
visitor.storage(), symbol_table, &db_accessor);
ast_storage, symbol_table, &db_accessor);
plan::CostEstimator cost_estimator(db_accessor);
logical_plan->Accept(cost_estimator);
query_plan_cost_estimation = cost_estimator.cost();
@ -119,9 +150,9 @@ class Interpreter : public Loggable {
return TypedValue(double(end - start) / CLOCKS_PER_SEC);
};
summary["query_parsing_time"] = time_second(start_time, antlr_end_time);
summary["query_parsing_time"] = time_second(start_time, frontend_end_time);
summary["query_planning_time"] =
time_second(antlr_end_time, planning_end_time);
time_second(frontend_end_time, planning_end_time);
summary["query_plan_execution_time"] =
time_second(planning_end_time, execution_end_time);
summary["query_cost_estimate"] = query_plan_cost_estimation;
@ -137,7 +168,7 @@ class Interpreter : public Loggable {
}
private:
// ConcurrentMap<HashType, CachedAst> ast_cache_;
ConcurrentMap<HashType, CachedAst> ast_cache_;
};
} // namespace query

View File

@ -6,37 +6,41 @@
#ifndef MEMGRAPH_PARAMETERS_HPP
#define MEMGRAPH_PARAMETERS_HPP
#include <algorithm>
#include <utility>
#include <vector>
#include "query/typed_value.hpp"
/**
* Encapsulates user provided parameters (and stripped literals)
* and provides ways of obtaining them by name or position.
* and provides ways of obtaining them by position.
*/
struct Parameters {
public:
/**
* Adds a value to the stripped arguments under a sequentially
* generated name and returns a reference to that name.
* Adds a value to the stripped arguments under a token position.
*
* @param position Token position in query of value.
* @param value
* @return
*/
const std::string &Add(const query::TypedValue &value) {
return storage_.emplace(NextName(), value).first->first;
void Add(int position, const query::TypedValue &value) {
storage_.emplace_back(position, value);
}
/**
* Returns the value found for the given name.
* The name MUST be present in this container
* (this is asserted).
* Returns the value found for the given token position.
*
* @param name Param name.
* @return Value for the given param.
* @param position Token position in query of value.
* @return Value for the given token position.
*/
const query::TypedValue &At(const std::string &name) const {
auto found = storage_.find(name);
const query::TypedValue &AtTokenPosition(int position) const {
auto found = std::find_if(storage_.begin(), storage_.end(),
[&](const std::pair<int, query::TypedValue> a) {
return a.first == position;
});
permanent_assert(found != storage_.end(),
"Name must be present in stripped arg container");
"Token position must be present in container");
return found->second;
}
@ -44,31 +48,20 @@ struct Parameters {
* Returns the position-th stripped value. Asserts that this
* container has at least (position + 1) elements.
*
* This is future proofing for when both query params and
* stripping will be supported and naming collisions will have to
* be avoided.
*
* @param position Which stripped param is sought.
* @return Stripped param.
* @return Token position and value for sought param.
*/
const query::TypedValue &At(const size_t position) const {
permanent_assert(position < storage_.size(), "Invalid position");
return storage_.find(NameForPosition(position))->second;
const std::pair<int, query::TypedValue> &At(int position) const {
permanent_assert(position < static_cast<int>(storage_.size()),
"Invalid position");
return storage_[position];
}
/** Returns the number of arguments in this container */
size_t Size() const { return storage_.size(); }
int size() const { return storage_.size(); }
private:
std::map<std::string, query::TypedValue> storage_;
/** Generates and returns a new name */
std::string NextName() const { return NameForPosition(storage_.size()); }
/** Returns a name for positon */
std::string NameForPosition(unsigned long position) const {
return "stripped_arg_" + std::to_string(position);
}
std::vector<std::pair<int, query::TypedValue>> storage_;
};
#endif // MEMGRAPH_PARAMETERS_HPP

View File

@ -54,12 +54,12 @@ bool run_general_query(GraphDbAccessor &db_accessor, const Parameters &args,
vertices_indexed.push_back(&vertices[i]);
if (query_type == CliqueQuery::SCORE_AND_LIMIT &&
vertices[i].has_label(db_accessor.label("profile"))) {
auto has_prop =
vertices[i].PropsAt(db_accessor.property("profile_id")) == args.At(0);
auto has_prop = vertices[i].PropsAt(db_accessor.property("profile_id")) ==
args.At(0).second;
if (has_prop.type() == query::TypedValue::Type::Null) continue;
if (has_prop.Value<bool>() == false) continue;
has_prop =
vertices[i].PropsAt(db_accessor.property("partner_id")) == args.At(1);
has_prop = vertices[i].PropsAt(db_accessor.property("partner_id")) ==
args.At(1).second;
if (has_prop.type() == query::TypedValue::Type::Null) continue;
if (has_prop.Value<bool>() == false) continue;
profile_index = i;
@ -119,8 +119,9 @@ bool run_general_query(GraphDbAccessor &db_accessor, const Parameters &args,
std::vector<std::vector<int>> results;
for (int i = 0; i < n; ++i) {
const VertexAccessor v = *vertices_indexed[i];
auto cmp_res = v.PropsAt(db_accessor.property("garment_id")) ==
args.At(query_type == CliqueQuery::SCORE_AND_LIMIT ? 8 : 0);
auto cmp_res =
v.PropsAt(db_accessor.property("garment_id")) ==
args.At(query_type == CliqueQuery::SCORE_AND_LIMIT ? 8 : 0).second;
if (cmp_res.type() != query::TypedValue::Type::Bool) continue;
if (cmp_res.Value<bool>() != true) continue;
auto neigh = connected[i].Ones();
@ -202,7 +203,7 @@ bool run_general_query(GraphDbAccessor &db_accessor, const Parameters &args,
reverse(results.begin(), results.end());
}
const int limit = query_type == CliqueQuery::SCORE_AND_LIMIT
? args.At((int)args.Size() - 1).Value<int64_t>()
? args.At((int)args.size() - 1).second.Value<int64_t>()
: (int)results.size();
for (int i = 0; i < std::min(limit, (int)results.size()); ++i) {
std::vector<query::TypedValue> result;

View File

@ -20,9 +20,9 @@ class CPUPlan : public PlanInterface<Stream> {
bool run(GraphDbAccessor &db_accessor, const Parameters &args,
Stream &stream) {
auto v = db_accessor.insert_vertex();
v.PropsSet(db_accessor.property("profile_id"), args.At(0));
v.PropsSet(db_accessor.property("partner_id"), args.At(1));
v.PropsSet(db_accessor.property("conceals"), args.At(2));
v.PropsSet(db_accessor.property("profile_id"), args.At(0).second);
v.PropsSet(db_accessor.property("partner_id"), args.At(1).second);
v.PropsSet(db_accessor.property("conceals"), args.At(2).second);
v.add_label(db_accessor.label("profile"));
std::vector<std::string> headers{std::string("p")};
stream.Header(headers);

View File

@ -19,8 +19,8 @@ class CPUPlan : public PlanInterface<Stream> {
bool run(GraphDbAccessor &db_accessor, const Parameters &args,
Stream &stream) {
auto v = db_accessor.insert_vertex();
v.PropsSet(db_accessor.property("profile_id"), args.At(0));
v.PropsSet(db_accessor.property("partner_id"), args.At(1));
v.PropsSet(db_accessor.property("profile_id"), args.At(0).second);
v.PropsSet(db_accessor.property("partner_id"), args.At(1).second);
v.add_label(db_accessor.label("profile"));
std::vector<std::string> headers{std::string("p")};
stream.Header(headers);

View File

@ -20,9 +20,9 @@ class CPUPlan : public PlanInterface<Stream> {
bool run(GraphDbAccessor &db_accessor, const Parameters &args,
Stream &stream) {
auto v = db_accessor.insert_vertex();
v.PropsSet(db_accessor.property("profile_id"), args.At(0));
v.PropsSet(db_accessor.property("partner_id"), args.At(1));
v.PropsSet(db_accessor.property("reveals"), args.At(2));
v.PropsSet(db_accessor.property("profile_id"), args.At(0).second);
v.PropsSet(db_accessor.property("partner_id"), args.At(1).second);
v.PropsSet(db_accessor.property("reveals"), args.At(2).second);
v.add_label(db_accessor.label("profile"));
std::vector<std::string> headers{std::string("p")};
stream.Header(headers);

View File

@ -20,8 +20,8 @@ class CPUPlan : public PlanInterface<Stream> {
Stream &stream) {
auto v = db_accessor.insert_vertex();
v.add_label(db_accessor.label("garment"));
v.PropsSet(db_accessor.property("garment_id"), args.At(0));
v.PropsSet(db_accessor.property("garment_category_id"), args.At(1));
v.PropsSet(db_accessor.property("garment_id"), args.At(0).second);
v.PropsSet(db_accessor.property("garment_category_id"), args.At(1).second);
std::vector<std::string> headers{std::string("g")};
stream.Header(headers);
std::vector<TypedValue> result{TypedValue(v)};

View File

@ -21,9 +21,9 @@ class CPUPlan : public PlanInterface<Stream> {
Stream &stream) {
auto v = db_accessor.insert_vertex();
v.add_label(db_accessor.label("garment"));
v.PropsSet(db_accessor.property("garment_id"), args.At(0));
v.PropsSet(db_accessor.property("garment_category_id"), args.At(1));
v.PropsSet(db_accessor.property("conceals"), args.At(2));
v.PropsSet(db_accessor.property("garment_id"), args.At(0).second);
v.PropsSet(db_accessor.property("garment_category_id"), args.At(1).second);
v.PropsSet(db_accessor.property("conceals"), args.At(2).second);
std::vector<std::string> headers{std::string("g")};
stream.Header(headers);
std::vector<TypedValue> result{TypedValue(v)};

View File

@ -21,9 +21,9 @@ class CPUPlan : public PlanInterface<Stream> {
Stream &stream) {
auto v = db_accessor.insert_vertex();
v.add_label(db_accessor.label("garment"));
v.PropsSet(db_accessor.property("garment_id"), args.At(0));
v.PropsSet(db_accessor.property("garment_category_id"), args.At(1));
v.PropsSet(db_accessor.property("reveals"), args.At(2));
v.PropsSet(db_accessor.property("garment_id"), args.At(0).second);
v.PropsSet(db_accessor.property("garment_category_id"), args.At(1).second);
v.PropsSet(db_accessor.property("reveals"), args.At(2).second);
std::vector<std::string> headers{std::string("g")};
stream.Header(headers);
std::vector<TypedValue> result{TypedValue(v)};

View File

@ -24,7 +24,7 @@ class CPUPlan : public PlanInterface<Stream> {
if (vertex.has_label(db_accessor.label("garment"))) {
TypedValue prop = vertex.PropsAt(db_accessor.property("garment_id"));
if (prop.type() == TypedValue::Type::Null) continue;
auto cmp = prop == args.At(0);
auto cmp = prop == args.At(0).second;
if (cmp.type() != TypedValue::Type::Bool) continue;
if (cmp.Value<bool>() != true) continue;
std::vector<TypedValue> result{TypedValue(vertex)};

View File

@ -26,7 +26,7 @@ class CPUPlan : public PlanInterface<Stream> {
if (g1.has_label(db_accessor.label("garment"))) {
TypedValue prop = g1.PropsAt(db_accessor.property("garment_id"));
if (prop.type() == TypedValue::Type::Null) continue;
auto cmp = prop == args.At(0);
auto cmp = prop == args.At(0).second;
if (cmp.type() != TypedValue::Type::Bool) continue;
if (cmp.Value<bool>() != true) continue;
g1_set.push_back(g1);
@ -36,7 +36,7 @@ class CPUPlan : public PlanInterface<Stream> {
if (g2.has_label(db_accessor.label("garment"))) {
auto prop = g2.PropsAt(db_accessor.property("garment_id"));
if (prop.type() == PropertyValue::Type::Null) continue;
auto cmp = prop == args.At(1);
auto cmp = prop == args.At(1).second;
if (cmp.type() != TypedValue::Type::Bool) continue;
if (cmp.Value<bool>() != true) continue;
g2_set.push_back(g2);

View File

@ -2,9 +2,9 @@
#include <string>
#include "match_garment_set_label_general_return.hpp"
#include "query/parameters.hpp"
#include "query/plan_interface.hpp"
#include "using.hpp"
#include "query/parameters.hpp"
using std::cout;
using std::endl;

View File

@ -2,9 +2,9 @@
#include <string>
#include "match_garment_set_label_general_return.hpp"
#include "query/parameters.hpp"
#include "query/plan_interface.hpp"
#include "using.hpp"
#include "query/parameters.hpp"
using std::cout;
using std::endl;

View File

@ -2,9 +2,9 @@
#include <string>
#include "match_garment_set_label_general_return.hpp"
#include "query/parameters.hpp"
#include "query/plan_interface.hpp"
#include "using.hpp"
#include "query/parameters.hpp"
using std::cout;
using std::endl;

View File

@ -2,9 +2,9 @@
#include <string>
#include "match_garment_set_label_general_return.hpp"
#include "query/parameters.hpp"
#include "query/plan_interface.hpp"
#include "using.hpp"
#include "query/parameters.hpp"
using std::cout;
using std::endl;

View File

@ -2,9 +2,9 @@
#include <string>
#include "match_garment_set_label_general_return.hpp"
#include "query/parameters.hpp"
#include "query/plan_interface.hpp"
#include "using.hpp"
#include "query/parameters.hpp"
using std::cout;
using std::endl;

View File

@ -23,7 +23,7 @@ bool run_general_query(GraphDbAccessor &db_accessor, const Parameters &args,
query::TypedValue prop =
vertex.PropsAt(db_accessor.property("garment_id"));
if (prop.type() == query::TypedValue::Type::Null) continue;
query::TypedValue cmp = prop == args.At(0);
query::TypedValue cmp = prop == args.At(0).second;
if (cmp.type() != query::TypedValue::Type::Bool) continue;
if (cmp.Value<bool>() != true) continue;
vertex.add_label(db_accessor.label(general_label));

View File

@ -24,13 +24,13 @@ class CPUPlan : public PlanInterface<Stream> {
if (vertex.has_label(db_accessor.label("profile"))) {
TypedValue prop = vertex.PropsAt(db_accessor.property("profile_id"));
if (prop.type() == TypedValue::Type::Null) continue;
auto cmp = prop == args.At(0);
auto cmp = prop == args.At(0).second;
if (cmp.type() != TypedValue::Type::Bool) continue;
if (cmp.Value<bool>() != true) continue;
TypedValue prop2 = vertex.PropsAt(db_accessor.property("partner_id"));
if (prop2.type() == TypedValue::Type::Null) continue;
auto cmp2 = prop2 == args.At(1);
auto cmp2 = prop2 == args.At(1).second;
if (cmp2.type() != TypedValue::Type::Bool) continue;
if (cmp2.Value<bool>() != true) continue;
std::vector<TypedValue> result{TypedValue(vertex)};

View File

@ -1,9 +1,9 @@
#include <iostream>
#include <string>
#include "query/frontend/stripped.hpp"
#include "query/parameters.hpp"
#include "query/plan_interface.hpp"
#include "query/frontend/stripped.hpp"
#include "query/typed_value.hpp"
#include "storage/edge_accessor.hpp"
#include "storage/vertex_accessor.hpp"
@ -26,20 +26,20 @@ class CPUPlan : public PlanInterface<Stream> {
auto profile = [&db_accessor, &args](const VertexAccessor &v) -> bool {
TypedValue prop = v.PropsAt(db_accessor.property("profile_id"));
if (prop.type() == TypedValue::Type::Null) return false;
auto cmp = prop == args.At(0);
auto cmp = prop == args.At(0).second;
if (cmp.type() != TypedValue::Type::Bool) return false;
if (cmp.Value<bool>() != true) return false;
TypedValue prop2 = v.PropsAt(db_accessor.property("partner_id"));
if (prop2.type() == TypedValue::Type::Null) return false;
auto cmp2 = prop2 == args.At(1);
auto cmp2 = prop2 == args.At(1).second;
if (cmp2.type() != TypedValue::Type::Bool) return false;
return cmp2.Value<bool>();
};
auto garment = [&db_accessor, &args](const VertexAccessor &v) -> bool {
TypedValue prop = v.PropsAt(db_accessor.property("garment_id"));
if (prop.type() == TypedValue::Type::Null) return false;
auto cmp = prop == args.At(2);
auto cmp = prop == args.At(2).second;
if (cmp.type() != TypedValue::Type::Bool) return false;
return cmp.Value<bool>();
};

View File

@ -26,13 +26,13 @@ class CPUPlan : public PlanInterface<Stream> {
if (g1.has_label(db_accessor.label("profile"))) {
auto prop = TypedValue(g1.PropsAt(db_accessor.property("profile_id")));
if (prop.type() == TypedValue::Type::Null) continue;
auto cmp = prop == args.At(0);
auto cmp = prop == args.At(0).second;
if (cmp.type() != TypedValue::Type::Bool) continue;
if (cmp.Value<bool>() != true) continue;
auto prop2 = TypedValue(g1.PropsAt(db_accessor.property("partner_id")));
if (prop2.type() == TypedValue::Type::Null) continue;
auto cmp2 = prop2 == args.At(1);
auto cmp2 = prop2 == args.At(1).second;
if (cmp2.type() != TypedValue::Type::Bool) continue;
if (cmp2.Value<bool>() != true) continue;
g1_set.push_back(g1);
@ -42,7 +42,7 @@ class CPUPlan : public PlanInterface<Stream> {
if (g2.has_label(db_accessor.label("garment"))) {
auto prop = TypedValue(g2.PropsAt(db_accessor.property("garment_id")));
if (prop.type() == TypedValue::Type::Null) continue;
auto cmp = prop == args.At(2);
auto cmp = prop == args.At(2).second;
if (cmp.type() != TypedValue::Type::Bool) continue;
if (cmp.Value<bool>() != true) continue;
g2_set.push_back(g2);
@ -52,7 +52,7 @@ class CPUPlan : public PlanInterface<Stream> {
for (auto g2 : g2_set) {
EdgeAccessor e =
db_accessor.insert_edge(g1, g2, db_accessor.edge_type("score"));
e.PropsSet(db_accessor.property("score"), args.At(3));
e.PropsSet(db_accessor.property("score"), args.At(3).second);
std::vector<TypedValue> result{TypedValue(e)};
stream.Result(result);
}

View File

@ -25,20 +25,20 @@ class CPUPlan : public PlanInterface<Stream> {
auto profile = [&db_accessor, &args](const VertexAccessor &v) -> bool {
TypedValue prop = v.PropsAt(db_accessor.property("profile_id"));
if (prop.type() == TypedValue::Type::Null) return false;
auto cmp = prop == args.At(0);
auto cmp = prop == args.At(0).second;
if (cmp.type() != TypedValue::Type::Bool) return false;
if (cmp.Value<bool>() != true) return false;
TypedValue prop2 = v.PropsAt(db_accessor.property("partner_id"));
if (prop2.type() == TypedValue::Type::Null) return false;
auto cmp2 = prop2 == args.At(1);
auto cmp2 = prop2 == args.At(1).second;
if (cmp2.type() != TypedValue::Type::Bool) return false;
return cmp2.Value<bool>();
};
auto garment = [&db_accessor, &args](const VertexAccessor &v) -> bool {
TypedValue prop = v.PropsAt(db_accessor.property("garment_id"));
if (prop.type() == TypedValue::Type::Null) return false;
auto cmp = prop == args.At(2);
auto cmp = prop == args.At(2).second;
if (cmp.type() != TypedValue::Type::Bool) return false;
return cmp.Value<bool>();
};
@ -47,7 +47,7 @@ class CPUPlan : public PlanInterface<Stream> {
auto to = edge.to();
if (edge.edge_type() != db_accessor.edge_type("score")) continue;
if ((profile(from) && garment(to)) || (profile(to) && garment(from))) {
edge.PropsSet(db_accessor.property("score"), args.At(3));
edge.PropsSet(db_accessor.property("score"), args.At(3).second);
edge.SwitchNew();
std::vector<TypedValue> result{TypedValue(edge)};
stream.Result(result);

View File

@ -31,8 +31,8 @@ int main(int argc, char **argv) {
std::cout << fmt::format("Stripped query: {}\n", preprocessed.query());
std::cout << fmt::format("Query hash: {}\n", preprocessed.hash());
std::cout << fmt::format("Property values:\n");
for (int i = 0; i < static_cast<int>(preprocessed.parameters().Size()); ++i) {
fmt::format(" {}", preprocessed.parameters().At(i));
for (int i = 0; i < preprocessed.literals().size(); ++i) {
fmt::format(" {}", preprocessed.literals().At(i).second);
}
std::cout << std::endl;

View File

@ -0,0 +1,63 @@
#include "communication/result_stream_faker.hpp"
#include "database/graph_db_accessor.hpp"
#include "dbms/dbms.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "query/engine.hpp"
// TODO: This is not a unit test, but tests/integration dir is chaotic at the
// moment. After tests refactoring is done, move/rename this.
namespace {
// Run query with different ast twice to see if query executes correctly when
// ast is read from cache.
TEST(QueryEngine, AstCache) {
QueryEngine<ResultStreamFaker> engine;
Dbms dbms;
{
ResultStreamFaker stream;
auto dba = dbms.active();
engine.Run("RETURN 2 + 3", *dba, stream);
ASSERT_EQ(stream.GetResults().size(), 1U);
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
ASSERT_EQ(stream.GetResults()[0][0].Value<int64_t>(), 5);
}
{
// Cached ast, different literals.
ResultStreamFaker stream;
auto dba = dbms.active();
engine.Run("RETURN 5 + 4", *dba, stream);
ASSERT_EQ(stream.GetResults().size(), 1U);
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
ASSERT_EQ(stream.GetResults()[0][0].Value<int64_t>(), 9);
}
{
// Different ast (because of different types).
ResultStreamFaker stream;
auto dba = dbms.active();
engine.Run("RETURN 5.5 + 4", *dba, stream);
ASSERT_EQ(stream.GetResults().size(), 1U);
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
ASSERT_EQ(stream.GetResults()[0][0].Value<double>(), 9.5);
}
{
// Cached ast, same literals.
ResultStreamFaker stream;
auto dba = dbms.active();
engine.Run("RETURN 2 + 3", *dba, stream);
ASSERT_EQ(stream.GetResults().size(), 1U);
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
ASSERT_EQ(stream.GetResults()[0][0].Value<int64_t>(), 5);
}
{
// Cached ast, different literals.
ResultStreamFaker stream;
auto dba = dbms.active();
engine.Run("RETURN 10.5 + 1", *dba, stream);
ASSERT_EQ(stream.GetResults().size(), 1U);
ASSERT_EQ(stream.GetResults()[0].size(), 1U);
ASSERT_EQ(stream.GetResults()[0][0].Value<double>(), 11.5);
}
}
}

View File

@ -1,97 +0,0 @@
//
// Copyright 2017 Memgraph
// Created by Florijan Stamenkovic on 07.03.17.
//
#include "gtest/gtest.h"
#include "query/frontend/stripped.hpp"
#include "query/typed_value.hpp"
using query::TypedValue;
using query::StrippedQuery;
void EXPECT_PROP_TRUE(const TypedValue& a) {
EXPECT_TRUE(a.type() == TypedValue::Type::Bool && a.Value<bool>());
}
void EXPECT_PROP_EQ(const TypedValue& a, const TypedValue& b) {
EXPECT_PROP_TRUE(a == b);
}
TEST(QueryStripper, NoLiterals) {
StrippedQuery stripped("CREATE (n)");
EXPECT_EQ(stripped.parameters().Size(), 0);
EXPECT_EQ(stripped.query(), "create ( n )");
}
TEST(QueryStripper, DecimalInteger) {
StrippedQuery stripped("RETURN 42");
EXPECT_EQ(stripped.parameters().Size(), 1);
EXPECT_EQ(stripped.parameters().At(0).Value<int64_t>(), 42);
EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
}
TEST(QueryStripper, OctalInteger) {
StrippedQuery stripped("RETURN 010");
EXPECT_EQ(stripped.parameters().Size(), 1);
EXPECT_EQ(stripped.parameters().At(0).Value<int64_t>(), 8);
EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
}
TEST(QueryStripper, HexInteger) {
StrippedQuery stripped("RETURN 0xa");
EXPECT_EQ(stripped.parameters().Size(), 1);
EXPECT_EQ(stripped.parameters().At(0).Value<int64_t>(), 10);
EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
}
TEST(QueryStripper, RegularDecimal) {
StrippedQuery stripped("RETURN 42.3");
EXPECT_EQ(stripped.parameters().Size(), 1);
EXPECT_FLOAT_EQ(stripped.parameters().At(0).Value<double>(), 42.3);
EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
}
TEST(QueryStripper, ExponentDecimal) {
StrippedQuery stripped("RETURN 4e2");
EXPECT_EQ(stripped.parameters().Size(), 1);
EXPECT_FLOAT_EQ(stripped.parameters().At(0).Value<double>(), 4e2);
EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
}
TEST(QueryStripper, StringLiteral) {
StrippedQuery stripped("RETURN 'something'");
EXPECT_EQ(stripped.parameters().Size(), 1);
EXPECT_EQ(stripped.parameters().At(0).Value<std::string>(), "something");
EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
}
TEST(QueryStripper, BoolLiteral) {
StrippedQuery stripped("RETURN true");
EXPECT_EQ(stripped.parameters().Size(), 1);
EXPECT_PROP_EQ(stripped.parameters().At(0), TypedValue(true));
EXPECT_EQ(stripped.query(), "return $stripped_arg_0");
}
TEST(QueryStripper, ListLiteral) {
StrippedQuery stripped("MATCH (n) RETURN [n, n.prop]");
EXPECT_EQ(stripped.parameters().Size(), 0);
EXPECT_EQ(stripped.query(), "match ( n ) return [ n , n . prop ]");
}
TEST(QueryStripper, MapLiteral) {
StrippedQuery stripped("MATCH (n) RETURN {val: n}");
EXPECT_EQ(stripped.parameters().Size(), 0);
EXPECT_EQ(stripped.query(), "match ( n ) return { val : n }");
}
TEST(QueryStripper, RangeLiteral) {
StrippedQuery stripped("MATCH (n)-[*2..3]-() RETURN n");
EXPECT_EQ(stripped.parameters().Size(), 2);
EXPECT_EQ(stripped.parameters().At(0).Value<int64_t>(), 2);
EXPECT_EQ(stripped.parameters().At(1).Value<int64_t>(), 3);
EXPECT_EQ(
stripped.query(),
"match ( n ) - [ * $stripped_arg_0 .. $stripped_arg_1 ] - ( ) return n");
}

98
tests/unit/stripped.cpp Normal file
View File

@ -0,0 +1,98 @@
//
// Copyright 2017 Memgraph
// Created by Florijan Stamenkovic on 07.03.17.
//
#include "gtest/gtest.h"
#include "query/frontend/stripped.hpp"
#include "query/typed_value.hpp"
using namespace query;
void EXPECT_PROP_TRUE(const TypedValue& a) {
EXPECT_TRUE(a.type() == TypedValue::Type::Bool && a.Value<bool>());
}
void EXPECT_PROP_EQ(const TypedValue& a, const TypedValue& b) {
EXPECT_PROP_TRUE(a == b);
}
TEST(QueryStripper, NoLiterals) {
StrippedQuery stripped("CREATE (n)");
EXPECT_EQ(stripped.literals().size(), 0);
EXPECT_EQ(stripped.query(), "create ( n )");
}
TEST(QueryStripper, DecimalInteger) {
StrippedQuery stripped("RETURN 42");
EXPECT_EQ(stripped.literals().size(), 1);
EXPECT_EQ(stripped.literals().At(0).first, 2);
EXPECT_EQ(stripped.literals().At(0).second.Value<int64_t>(), 42);
EXPECT_EQ(stripped.literals().AtTokenPosition(2).Value<int64_t>(), 42);
EXPECT_EQ(stripped.query(), "return " + kStrippedIntToken);
}
TEST(QueryStripper, OctalInteger) {
StrippedQuery stripped("RETURN 010");
EXPECT_EQ(stripped.literals().size(), 1);
EXPECT_EQ(stripped.literals().At(0).second.Value<int64_t>(), 8);
EXPECT_EQ(stripped.query(), "return " + kStrippedIntToken);
}
TEST(QueryStripper, HexInteger) {
StrippedQuery stripped("RETURN 0xa");
EXPECT_EQ(stripped.literals().size(), 1);
EXPECT_EQ(stripped.literals().At(0).second.Value<int64_t>(), 10);
EXPECT_EQ(stripped.query(), "return " + kStrippedIntToken);
}
TEST(QueryStripper, RegularDecimal) {
StrippedQuery stripped("RETURN 42.3");
EXPECT_EQ(stripped.literals().size(), 1);
EXPECT_FLOAT_EQ(stripped.literals().At(0).second.Value<double>(), 42.3);
EXPECT_EQ(stripped.query(), "return " + kStrippedDoubleToken);
}
TEST(QueryStripper, ExponentDecimal) {
StrippedQuery stripped("RETURN 4e2");
EXPECT_EQ(stripped.literals().size(), 1);
EXPECT_FLOAT_EQ(stripped.literals().At(0).second.Value<double>(), 4e2);
EXPECT_EQ(stripped.query(), "return " + kStrippedDoubleToken);
}
TEST(QueryStripper, StringLiteral) {
StrippedQuery stripped("RETURN 'something'");
EXPECT_EQ(stripped.literals().size(), 1);
EXPECT_EQ(stripped.literals().At(0).second.Value<std::string>(), "something");
EXPECT_EQ(stripped.query(), "return " + kStrippedStringToken);
}
TEST(QueryStripper, BoolLiteral) {
StrippedQuery stripped("RETURN true");
EXPECT_EQ(stripped.literals().size(), 1);
EXPECT_PROP_EQ(stripped.literals().At(0).second, TypedValue(true));
EXPECT_EQ(stripped.query(), "return " + kStrippedBooleanToken);
}
TEST(QueryStripper, ListLiteral) {
StrippedQuery stripped("MATCH (n) RETURN [n, n.prop]");
EXPECT_EQ(stripped.literals().size(), 0);
EXPECT_EQ(stripped.query(), "match ( n ) return [ n , n . prop ]");
}
TEST(QueryStripper, MapLiteral) {
StrippedQuery stripped("MATCH (n) RETURN {val: n}");
EXPECT_EQ(stripped.literals().size(), 0);
EXPECT_EQ(stripped.query(), "match ( n ) return { val : n }");
}
TEST(QueryStripper, RangeLiteral) {
StrippedQuery stripped("MATCH (n)-[*2..3]-() RETURN n");
EXPECT_EQ(stripped.literals().size(), 2);
EXPECT_EQ(stripped.literals().At(0).second.Value<int64_t>(), 2);
EXPECT_EQ(stripped.literals().At(1).second.Value<int64_t>(), 3);
EXPECT_EQ(stripped.query(), "match ( n ) - [ * " + kStrippedIntToken +
" .. " + kStrippedIntToken +
" ] - ( ) return n");
}