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:
parent
b6da65b9e7
commit
67b859cf13
@ -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
|
||||
)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ¶meters) : 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 ¶meters_;
|
||||
};
|
||||
|
||||
AstTreeStorage storage_;
|
||||
};
|
||||
|
||||
#undef CLONE_BINARY_EXPRESSION
|
||||
#undef CLONE_UNARY_EXPRESSION
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 ¶meters() 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_;
|
||||
};
|
||||
}
|
||||
|
6
src/query/interpreter.cpp
Normal file
6
src/query/interpreter.cpp
Normal 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.");
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)};
|
||||
|
@ -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)};
|
||||
|
@ -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)};
|
||||
|
@ -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)};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
@ -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)};
|
||||
|
@ -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>();
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
63
tests/unit/query_engine.cpp
Normal file
63
tests/unit/query_engine.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
98
tests/unit/stripped.cpp
Normal 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");
|
||||
}
|
Loading…
Reference in New Issue
Block a user