From f6529cd4c35c729d9033954879870fea1a3a1052 Mon Sep 17 00:00:00 2001 From: Dominik Gleich <dominik.gleich@memgraph.io> Date: Tue, 28 Feb 2017 12:14:49 +0100 Subject: [PATCH] Clique #1 and #2 works. Summary: Merge branch 'dev' into clique_1 Started implementing clique. Merge branch 'dev' into clique_1 Add multilne query reload. Clique 1# implementation. Merge branch 'dev' into clique_1 Clique #1 and #2 implemented. Add delete all query. Remove unique from clique. Reviewers: florijan, mislav.bradac, buda Reviewed By: florijan, mislav.bradac, buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D73 --- src/query/plan_compiler.hpp | 2 + tests/data/queries/core/mg_basic_002.txt | 46 +-- tests/integration/hardcoded_query/clique.cpp | 33 ++ tests/integration/hardcoded_query/clique.hpp | 299 ++++++++++++++++++ .../hardcoded_query/clique_scored.cpp | 40 +++ .../hardcoded_query/delete_all.cpp | 26 ++ .../hardcoded_query/match_garment.cpp | 38 +++ .../hardcoded_query/match_profile.cpp | 44 +++ .../match_profile_garment_score.cpp | 59 ++++ .../match_profile_garment_update_score.cpp | 60 ++++ tests/integration/hardcoded_query/using.hpp | 2 +- tests/integration/query_engine_common.hpp | 4 +- .../stream/print_record_stream.hpp | 2 +- tests/manual/query_engine.cpp | 18 +- 14 files changed, 647 insertions(+), 26 deletions(-) create mode 100644 tests/integration/hardcoded_query/clique.cpp create mode 100644 tests/integration/hardcoded_query/clique.hpp create mode 100644 tests/integration/hardcoded_query/clique_scored.cpp create mode 100644 tests/integration/hardcoded_query/delete_all.cpp create mode 100644 tests/integration/hardcoded_query/match_garment.cpp create mode 100644 tests/integration/hardcoded_query/match_profile.cpp create mode 100644 tests/integration/hardcoded_query/match_profile_garment_score.cpp create mode 100644 tests/integration/hardcoded_query/match_profile_garment_update_score.cpp diff --git a/src/query/plan_compiler.hpp b/src/query/plan_compiler.hpp index c4cef29d6..42460d6fa 100644 --- a/src/query/plan_compiler.hpp +++ b/src/query/plan_compiler.hpp @@ -53,7 +53,9 @@ class PlanCompiler : public Loggable { // generate compile command auto compile_command = utils::prints( "clang++" + flags, +#ifdef HARDCODED_OUTPUT_STREAM "-DHARDCODED_OUTPUT_STREAM", +#endif // "-std=c++1y -O2 -DNDEBUG", "-std=c++1y", // compile flags in_file, // input file diff --git a/tests/data/queries/core/mg_basic_002.txt b/tests/data/queries/core/mg_basic_002.txt index 222e4bdd0..e3e7dc8f4 100644 --- a/tests/data/queries/core/mg_basic_002.txt +++ b/tests/data/queries/core/mg_basic_002.txt @@ -1,25 +1,32 @@ +MATCH (n) DETACH DELETE n CREATE (g:garment {garment_id: 1234, garment_category_id: 1, conceals: 30}) RETURN g MATCH(g:garment {garment_id: 1234}) SET g:AA RETURN g MATCH(g:garment {garment_id: 1234}) SET g:BB RETURN g MATCH(g:garment {garment_id: 1234}) SET g:EE RETURN g + CREATE (g:garment {garment_id: 2345, garment_category_id: 6, reveals: 10}) RETURN g MATCH(g:garment {garment_id: 2345}) SET g:CC RETURN g MATCH(g:garment {garment_id: 2345}) SET g:DD RETURN g + CREATE (g:garment {garment_id: 3456, garment_category_id: 8}) RETURN g MATCH(g:garment {garment_id: 3456}) SET g:CC RETURN g MATCH(g:garment {garment_id: 3456}) SET g:DD RETURN g + CREATE (g:garment {garment_id: 4567, garment_category_id: 15}) RETURN g MATCH(g:garment {garment_id: 4567}) SET g:AA RETURN g MATCH(g:garment {garment_id: 4567}) SET g:BB RETURN g MATCH(g:garment {garment_id: 4567}) SET g:DD RETURN g + CREATE (g:garment {garment_id: 5678, garment_category_id: 19}) RETURN g MATCH(g:garment {garment_id: 5678}) SET g:BB RETURN g MATCH(g:garment {garment_id: 5678}) SET g:CC RETURN g MATCH(g:garment {garment_id: 5678}) SET g:EE RETURN g + CREATE (g:garment {garment_id: 6789, garment_category_id: 3}) RETURN g MATCH(g:garment {garment_id: 6789}) SET g:AA RETURN g MATCH(g:garment {garment_id: 6789}) SET g:DD RETURN g MATCH(g:garment {garment_id: 6789}) SET g:EE RETURN g + CREATE (g:garment {garment_id: 7890, garment_category_id: 25}) RETURN g MATCH(g:garment {garment_id: 7890}) SET g:AA RETURN g MATCH(g:garment {garment_id: 7890}) SET g:BB RETURN g @@ -29,40 +36,28 @@ MATCH(g:garment {garment_id: 7890}) SET g:EE RETURN g MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 1234}),(g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 6789}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 5678}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 3456}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r -MATCH (g1:garment {garment_id: 1234}),(g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r +MATCH (g1:garment {garment_id: 1234}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r + MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 4567}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r + MATCH (g1:garment {garment_id: 6789}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r + MATCH (g1:garment {garment_id: 5678}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r + MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 3456}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 2345}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r + MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 5678}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 6789}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 7890}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r MATCH (g1:garment {garment_id: 3456}), (g2:garment {garment_id: 4567}) CREATE (g1)-[r:default_outfit]->(g2) RETURN r + CREATE (p:profile {profile_id: 111, partner_id: 55, reveals: 30}) RETURN p CREATE (p:profile {profile_id: 112, partner_id: 55}) RETURN p CREATE (p:profile {profile_id: 112, partner_id: 77, conceals: 10}) RETURN p @@ -73,14 +68,27 @@ MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 34 MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 4567}) CREATE (p)-[s:score]->(g) SET s.score=1000 RETURN s MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1500 RETURN s MATCH (p:profile {profile_id: 111, partner_id: 55}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1800 RETURN s + MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 1234}) CREATE (p)-[s:score]->(g) SET s.score=2000 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 4567}) CREATE (p)-[s:score]->(g) SET s.score=1500 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 5678}) CREATE (p)-[s:score]->(g) SET s.score=1000 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1600 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 55}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1900 RETURN s + MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 1234}) CREATE (p)-[s:score]->(g) SET s.score=1500 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 2345}) CREATE (p)-[s:score]->(g) SET s.score=1300 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 3456}) CREATE (p)-[s:score]->(g) SET s.score=1300 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 5678}) CREATE (p)-[s:score]->(g) SET s.score=1200 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 6789}) CREATE (p)-[s:score]->(g) SET s.score=1700 RETURN s MATCH (p:profile {profile_id: 112, partner_id: 77}), (g:garment {garment_id: 7890}) CREATE (p)-[s:score]->(g) SET s.score=1900 RETURN s + + +MATCH (g:garment {garment_id: 1234}) RETURN g +MATCH (p:profile {profile_id: 112, partner_id: 77}) RETURN p +MATCH (p:profile {profile_id: 112, partner_id: 77})-[s:score]-(g:garment {garment_id: 1234}) RETURN s +MATCH (p:profile {profile_id: 112, partner_id: 77})-[s:score]-(g:garment {garment_id: 1234}) SET s.score = 3137 RETURN s +MATCH (p:profile {profile_id: 112, partner_id: 77})-[s:score]-(g:garment {garment_id: 1234}) SET s.score = 1500 RETURN s + +MATCH (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), (b:garment)-[:default_outfit]-(d:garment) WHERE a.garment_id=1234 RETURN a.garment_id,b.garment_id,c.garment_id,d.garment_id +MATCH (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), (b:garment)-[:default_outfit]-(d:garment), (e:profile {profile_id: 112, partner_id: 55})-[s1:score]-(a:garment),(e:profile {profile_id: 112, partner_id: 55})-[s2:score]-(b:garment), (e:profile {profile_id: 112, partner_id: 55})-[s3:score]-(c:garment), (e:profile {profile_id: 112, partner_id: 55})-[s4:score]-(d:garment) WHERE a.garment_id=1234 RETURN a.garment_id,b.garment_id,c.garment_id,d.garment_id, s1.score+s2.score+s3.score+s4.score ORDER BY s1.score+s2.score+s3.score+s4.score DESC LIMIT 10 + diff --git a/tests/integration/hardcoded_query/clique.cpp b/tests/integration/hardcoded_query/clique.cpp new file mode 100644 index 000000000..39b20f740 --- /dev/null +++ b/tests/integration/hardcoded_query/clique.cpp @@ -0,0 +1,33 @@ +#include <algorithm> +#include <bitset> +#include <iostream> +#include <string> + +#include "clique.hpp" +#include "query/plan_interface.hpp" +#include "storage/edge_accessor.hpp" +#include "storage/vertex_accessor.hpp" +#include "using.hpp" +#include "utils/assert.hpp" + +using std::cout; +using std::endl; + +// Query: MATCH +// (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), +// (b:garment)-[:default_outfit]-(d:garment) WHERE a.garment_id=1234 RETURN +// a.garment_id,b.garment_id,c.garment_id,d.garment_id + +class CPUPlan : public PlanInterface<Stream> { + public: + bool run(GraphDbAccessor &db_accessor, const TypedValueStore<> &args, + Stream &stream) { + return run_general_query(db_accessor, args, stream, CliqueQuery::FIND_ALL); + } + + ~CPUPlan() {} +}; + +extern "C" PlanInterface<Stream> *produce() { return new CPUPlan(); } + +extern "C" void destruct(PlanInterface<Stream> *p) { delete p; } diff --git a/tests/integration/hardcoded_query/clique.hpp b/tests/integration/hardcoded_query/clique.hpp new file mode 100644 index 000000000..6dec7455d --- /dev/null +++ b/tests/integration/hardcoded_query/clique.hpp @@ -0,0 +1,299 @@ +#include <algorithm> +#include <bitset> +#include <iostream> +#include <string> + +#include "query/plan_interface.hpp" +#include "storage/edge_accessor.hpp" +#include "storage/vertex_accessor.hpp" +#include "using.hpp" +#include "utils/assert.hpp" + +using std::cout; +using std::endl; + +// General Query: MATCH +// (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), +// (b:garment)-[:default_outfit]-(d:garment), (e:profile {profile_id: 112, +// partner_id: 55})-[s1:score]-(a:garment),(e:profile {profile_id: 112, +// partner_id: 55})-[s2:score]-(b:garment), (e:profile {profile_id: 112, +// partner_id: 55})-[s3:score]-(c:garment), (e:profile {profile_id: 112, +// partner_id: 55})-[s4:score]-(d:garment) WHERE a.garment_id=1234 RETURN +// a.garment_id,b.garment_id,c.garment_id,d.garment_id, +// s1.score+s2.score+s3.score+s4.score ORDER BY +// s1.score+s2.score+s3.score+s4.score DESC LIMIT 10 + +template <typename TStore> +/** + * Bitset data structure with a number of bits provided in constructor. + * @tparam type of underlying bit storage: int32, int64, char, etc. + */ +class Bitset { + public: + /** + * Create bitset. + * @param sz size of bitset + */ + Bitset(size_t sz) : block_size_(8 * sizeof(TStore)) { + if (sz % block_size_ != 0) sz += block_size_; + blocks_.resize(sz / block_size_); + } + /** + * Set bit to one. + * @param idx position of bit. + */ + void Set(int idx) { + debug_assert(idx < static_cast<int64_t>(blocks_.size()) * block_size_, + "Invalid bit location."); + int bucket = idx / block_size_; + blocks_[bucket] |= TStore(1) << idx % block_size_; + } + /** + * Return bit at position. + * @param idx position of bit. + * @return 1/0. + */ + bool At(int idx) const { + debug_assert(idx < static_cast<int64_t>(blocks_.size()) * block_size_, + "Invalid bit location."); + int bucket = idx / block_size_; + return (blocks_[bucket] >> (idx % block_size_)) & 1; + } + /** + * Intersect two bitsets + * @param other bitset. + * @return intersection. + */ + Bitset<TStore> Intersect(const Bitset<TStore> &other) { + debug_assert(this->blocks_.size() == other.size(), + "Bitsets are not of equal size."); + Bitset<TStore> ret(this->blocks_.size() * this->block_size_); + for (int i = 0; i < (int)this->blocks_.size(); ++i) { + ret.blocks_[i] = this->blocks_[i] & other.blocks_[i]; + continue; + } + return ret; + } + /** + * Positions of bits set to 1. + * @return positions of bits set to 1. + */ + std::vector<int> Ones() const { + std::vector<int> ret; + int ret_idx = 0; + for (auto x : blocks_) { + while (x) { + auto pos = CountTrailingZeroes(x & -x); + x -= x & -x; + ret.push_back(ret_idx + pos); + } + ret_idx += block_size_; + } + return ret; + } + + private: + /** + * Calculate number of trailing zeroes in a binary number, usually a power + * of two. + * @return number of trailing zeroes. + */ + size_t CountTrailingZeroes(TStore v) const { + size_t ret = 0; + while (v >> 32) ret += 32, v >>= 32; + if (v >> 16) ret += 16, v >>= 16; + if (v >> 8) ret += 8, v >>= 8; + if (v >> 4) ret += 4, v >>= 4; + if (v >> 2) ret += 2, v >>= 2; + if (v >> 1) ret += 1, v >>= 1; + return ret; + } + std::vector<TStore> blocks_; + const size_t block_size_; +}; + +enum CliqueQuery { SCORE_AND_LIMIT, FIND_ALL }; + +bool run_general_query(GraphDbAccessor &db_accessor, + const TypedValueStore<> &args, Stream &stream, + enum CliqueQuery query_type) { + stream.write_fields( + {"a.garment_id", "b.garment_id", "c.garment_id", "d.garment_id"}); + std::vector<VertexAccessor> vertices = db_accessor.vertices(); + std::vector<EdgeAccessor> edges = db_accessor.edges(); + + std::vector<VertexAccessor *> vertices_indexed; + std::vector<EdgeAccessor *> edges_indexed; + + int profile_index = -1; + for (int i = 0; i < (int)vertices.size(); ++i) { + if (vertices[i].has_label(db_accessor.label("garment"))) + 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); + if (has_prop.type_ == TypedValue::Type::Null) continue; + if (has_prop.Value<bool>() == false) continue; + has_prop = + vertices[i].PropsAt(db_accessor.property("partner_id")) == args.at(1); + if (has_prop.type_ == TypedValue::Type::Null) continue; + if (has_prop.Value<bool>() == false) continue; + profile_index = i; + } + } + for (int i = 0; i < (int)edges.size(); ++i) { + if (edges[i].edge_type() == db_accessor.edge_type("default_outfit") || + edges[i].edge_type() == db_accessor.edge_type("score")) + edges_indexed.push_back(&edges[i]); + } + const int n = vertices_indexed.size(); + auto cmp_vertex = [](const VertexAccessor *a, + const VertexAccessor *b) -> bool { return *a < *b; }; + auto cmp_edge = [](const EdgeAccessor *a, const EdgeAccessor *b) -> bool { + if (a->from() != b->from()) return a->from() < b->from(); + return a->to() < b->to(); + }; + /** + * Index of vertex in a list of vertices_indexed. + * @param v VertexAccessor to a vertex. + * @return position of vertex or -1 if it doesn't exist. + */ + auto query = [&vertices_indexed, + &cmp_vertex](const VertexAccessor &v) -> int { + int pos = lower_bound(vertices_indexed.begin(), vertices_indexed.end(), &v, + cmp_vertex) - + vertices_indexed.begin(); + if (pos == (int)vertices_indexed.size() || *vertices_indexed[pos] != v) + return -1; + return pos; + }; + /** + * Update bitset of neighbours. Set bit to 1 for index of every vertex + * endpoint of edges with type default_outfit. + * @param bitset bitset to update. + * @param edges edges from which to update bitset. + */ + auto update = [&db_accessor, &query](Bitset<int64_t> &bitset, + const std::vector<EdgeAccessor> &edges) { + for (auto e : edges) { + if (e.edge_type() != db_accessor.edge_type("default_outfit")) continue; + const int from = query(e.from()); + const int to = query(e.to()); + if (from == -1 || to == -1) continue; + bitset.Set(from); + bitset.Set(to); + } + }; + std::sort(vertices_indexed.begin(), vertices_indexed.end(), cmp_vertex); + std::sort(edges_indexed.begin(), edges_indexed.end(), cmp_edge); + std::vector<Bitset<int64_t>> connected; + for (int i = 0; i < n; ++i) { + Bitset<int64_t> connected_to(n); + update(connected_to, vertices_indexed[i]->in()); + update(connected_to, vertices_indexed[i]->out()); + connected.push_back(connected_to); + } + 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); + if (cmp_res.type_ != TypedValue::Type::Bool) continue; + if (cmp_res.Value<bool>() != true) continue; + auto neigh = connected[i].Ones(); + for (int j : neigh) { + if (j == i) continue; + for (int k : connected[j].Intersect(connected[i]).Ones()) { + if (k == i) continue; + if (k == j) continue; + Bitset<int64_t> intersection = connected[j].Intersect(connected[k]); + for (int l : intersection.Ones()) { + if (l == i) continue; + if (l == j) continue; + if (l == k) continue; + if (connected[l].At(i) == false) continue; + results.push_back({i, j, k, l}); + } + } + } + } + /** + * Get edge index of edge associated with two vertices. + * @param first one vertex endpoint + * @param second other vertex endpoint + * @return EdgeAccessor* if it exists, nullptr otherwise. + */ + auto get_edge = [&edges_indexed]( + const VertexAccessor &first, + const VertexAccessor &second) -> EdgeAccessor * { + auto cmp_edge_to_pair = []( + const EdgeAccessor *edge, + const pair<const VertexAccessor *, const VertexAccessor *> &e) -> bool { + if (edge->from() != *e.first) return edge->from() < *e.first; + if (edge->to() != *e.second) return edge->to() < *e.second; + return false; + }; + auto pos = lower_bound(edges_indexed.begin(), edges_indexed.end(), + std::make_pair(&first, &second), cmp_edge_to_pair) - + edges_indexed.begin(); + if (pos != (int)edges_indexed.size() && + edges_indexed[pos]->from() == first && + edges_indexed[pos]->to() == second) + return edges_indexed[pos]; + pos = lower_bound(edges_indexed.begin(), edges_indexed.end(), + std::make_pair(&second, &first), cmp_edge_to_pair) - + edges_indexed.begin(); + if (pos != (int)edges_indexed.size() && + edges_indexed[pos]->from() == second && + edges_indexed[pos]->to() == first) + return edges_indexed[pos]; + debug_assert(false, "Edge doesn't exist."); + return nullptr; + }; + + /** + * Calculate score of clique by profile_index if it exists. + * @param V index of clique vertices in vertices_indexed. + * @return score if profile_index exists, else 0. + */ + auto calc_score = [&db_accessor, &vertices, &profile_index, &vertices_indexed, + &get_edge](const std::vector<int> &V) -> int { + int res = 0; + if (profile_index == -1) return 0; + for (auto x : V) { + auto edge = get_edge(vertices[profile_index], *vertices_indexed[x]); + if (edge == nullptr) continue; + auto prop = edge->PropsAt(db_accessor.property("score")); + if (prop.type_ == TypedValue::Type::Int) res += prop.Value<int>(); + } + return res; + }; + if (query_type == CliqueQuery::SCORE_AND_LIMIT) { + auto cmp_results = [&calc_score](const std::vector<int> &first, + const std::vector<int> &second) { + return calc_score(first) < calc_score(second); + }; + sort(results.begin(), results.end(), cmp_results); + reverse(results.begin(), results.end()); + } + const int limit = query_type == CliqueQuery::SCORE_AND_LIMIT + ? args.at((int)args.size() - 1).Value<int>() + : (int)results.size(); + for (int i = 0; i < std::min(limit, (int)results.size()); ++i) { + stream.write_record(); + stream.write_list_header(query_type == CliqueQuery::SCORE_AND_LIMIT ? 5 + : 4); + for (auto x : results[i]) { + stream.write(vertices_indexed[x] + ->PropsAt(db_accessor.property("garment_id")) + .Value<int>()); + } + if (query_type == CliqueQuery::SCORE_AND_LIMIT) + stream.write(calc_score(results[i])); + } + stream.write_empty_fields(); + stream.write_meta("r"); + db_accessor.transaction_.commit(); + return true; +} diff --git a/tests/integration/hardcoded_query/clique_scored.cpp b/tests/integration/hardcoded_query/clique_scored.cpp new file mode 100644 index 000000000..c091f7a01 --- /dev/null +++ b/tests/integration/hardcoded_query/clique_scored.cpp @@ -0,0 +1,40 @@ +#include <algorithm> +#include <bitset> +#include <iostream> +#include <string> + +#include "clique.hpp" +#include "query/plan_interface.hpp" +#include "storage/edge_accessor.hpp" +#include "storage/vertex_accessor.hpp" +#include "using.hpp" +#include "utils/assert.hpp" + +using std::cout; +using std::endl; + +// Query: MATCH +// (a:garment)-[:default_outfit]-(b:garment)-[:default_outfit]-(c:garment)-[:default_outfit]-(d:garment)-[:default_outfit]-(a:garment)-[:default_outfit]-(c:garment), +// (b:garment)-[:default_outfit]-(d:garment), (e:profile {profile_id: 112, +// partner_id: 55})-[s1:score]-(a:garment),(e:profile {profile_id: 112, +// partner_id: 55})-[s2:score]-(b:garment), (e:profile {profile_id: 112, +// partner_id: 55})-[s3:score]-(c:garment), (e:profile {profile_id: 112, +// partner_id: 55})-[s4:score]-(d:garment) WHERE a.garment_id=1234 RETURN +// a.garment_id,b.garment_id,c.garment_id,d.garment_id, +// s1.score+s2.score+s3.score+s4.score ORDER BY +// s1.score+s2.score+s3.score+s4.score DESC LIMIT 10 + +class CPUPlan : public PlanInterface<Stream> { + public: + bool run(GraphDbAccessor &db_accessor, const TypedValueStore<> &args, + Stream &stream) { + return run_general_query(db_accessor, args, stream, + CliqueQuery::SCORE_AND_LIMIT); + } + + ~CPUPlan() {} +}; + +extern "C" PlanInterface<Stream> *produce() { return new CPUPlan(); } + +extern "C" void destruct(PlanInterface<Stream> *p) { delete p; } diff --git a/tests/integration/hardcoded_query/delete_all.cpp b/tests/integration/hardcoded_query/delete_all.cpp new file mode 100644 index 000000000..f0303654f --- /dev/null +++ b/tests/integration/hardcoded_query/delete_all.cpp @@ -0,0 +1,26 @@ +#include <iostream> +#include <string> + +#include "query/plan_interface.hpp" +#include "using.hpp" + +using std::cout; +using std::endl; + +// Query: MATCH (n) DETACH DELETE n + +class CPUPlan : public PlanInterface<Stream> { + public: + bool run(GraphDbAccessor &db_accessor, const TypedValueStore<> &args, + Stream &stream) { + for (auto v : db_accessor.vertices()) db_accessor.detach_remove_vertex(v); + db_accessor.transaction_.commit(); + return true; + } + + ~CPUPlan() {} +}; + +extern "C" PlanInterface<Stream> *produce() { return new CPUPlan(); } + +extern "C" void destruct(PlanInterface<Stream> *p) { delete p; } diff --git a/tests/integration/hardcoded_query/match_garment.cpp b/tests/integration/hardcoded_query/match_garment.cpp new file mode 100644 index 000000000..42b4786e2 --- /dev/null +++ b/tests/integration/hardcoded_query/match_garment.cpp @@ -0,0 +1,38 @@ +#include <iostream> +#include <string> + +#include "query/plan_interface.hpp" +#include "storage/edge_accessor.hpp" +#include "storage/vertex_accessor.hpp" +#include "using.hpp" + +using std::cout; +using std::endl; + +// Query: MATCH (g:garment {garment_id: 1234}) RETURN g + +class CPUPlan : public PlanInterface<Stream> { + public: + bool run(GraphDbAccessor &db_accessor, const TypedValueStore<> &args, + Stream &stream) { + stream.write_field("g"); + for (auto vertex : db_accessor.vertices()) { + if (vertex.has_label(db_accessor.label("garment"))) { + auto prop = vertex.PropsAt(db_accessor.property("garment_id")); + if (prop.type_ == TypedValue::Type::Null) continue; + auto cmp = prop == args.at(0); + if (cmp.type_ != TypedValue::Type::Bool) continue; + if (cmp.Value<bool>() != true) continue; + stream.write_vertex_record(vertex); + } + } + stream.write_meta("r"); + db_accessor.transaction_.commit(); + return true; + } + ~CPUPlan() {} +}; + +extern "C" PlanInterface<Stream> *produce() { return new CPUPlan(); } + +extern "C" void destruct(PlanInterface<Stream> *p) { delete p; } diff --git a/tests/integration/hardcoded_query/match_profile.cpp b/tests/integration/hardcoded_query/match_profile.cpp new file mode 100644 index 000000000..0cb626bb4 --- /dev/null +++ b/tests/integration/hardcoded_query/match_profile.cpp @@ -0,0 +1,44 @@ +#include <iostream> +#include <string> + +#include "query/plan_interface.hpp" +#include "storage/edge_accessor.hpp" +#include "storage/vertex_accessor.hpp" +#include "using.hpp" + +using std::cout; +using std::endl; + +// Query: MATCH (p:profile {profile_id: 112, partner_id: 77}) RETURN p + +class CPUPlan : public PlanInterface<Stream> { + public: + bool run(GraphDbAccessor &db_accessor, const TypedValueStore<> &args, + Stream &stream) { + stream.write_field("p"); + for (auto vertex : db_accessor.vertices()) { + if (vertex.has_label(db_accessor.label("profile"))) { + auto prop = vertex.PropsAt(db_accessor.property("profile_id")); + if (prop.type_ == TypedValue::Type::Null) continue; + auto cmp = prop == args.at(0); + if (cmp.type_ != TypedValue::Type::Bool) continue; + if (cmp.Value<bool>() != true) continue; + + auto prop2 = vertex.PropsAt(db_accessor.property("partner_id")); + if (prop2.type_ == TypedValue::Type::Null) continue; + auto cmp2 = prop2 == args.at(1); + if (cmp2.type_ != TypedValue::Type::Bool) continue; + if (cmp2.Value<bool>() != true) continue; + stream.write_vertex_record(vertex); + } + } + stream.write_meta("r"); + db_accessor.transaction_.commit(); + return true; + } + ~CPUPlan() {} +}; + +extern "C" PlanInterface<Stream> *produce() { return new CPUPlan(); } + +extern "C" void destruct(PlanInterface<Stream> *p) { delete p; } diff --git a/tests/integration/hardcoded_query/match_profile_garment_score.cpp b/tests/integration/hardcoded_query/match_profile_garment_score.cpp new file mode 100644 index 000000000..95845bef8 --- /dev/null +++ b/tests/integration/hardcoded_query/match_profile_garment_score.cpp @@ -0,0 +1,59 @@ +#include <iostream> +#include <string> + +#include "query/plan_interface.hpp" +#include "storage/edge_accessor.hpp" +#include "storage/vertex_accessor.hpp" +#include "using.hpp" + +using std::cout; +using std::endl; + +// Query: MATCH (p:profile {profile_id: 111, partner_id: +// 55})-[s:score]-(g:garment +// {garment_id: 1234}) RETURN s + +class CPUPlan : public PlanInterface<Stream> { + public: + bool run(GraphDbAccessor &db_accessor, const TypedValueStore<> &args, + Stream &stream) { + stream.write_field("s"); + auto profile = [&db_accessor, &args](const VertexAccessor &v) -> bool { + auto prop = v.PropsAt(db_accessor.property("profile_id")); + if (prop.type_ == TypedValue::Type::Null) return false; + auto cmp = prop == args.at(0); + if (cmp.type_ != TypedValue::Type::Bool) return false; + if (cmp.Value<bool>() != true) return false; + + auto prop2 = v.PropsAt(db_accessor.property("partner_id")); + if (prop2.type_ == TypedValue::Type::Null) return false; + auto cmp2 = prop2 == args.at(1); + if (cmp2.type_ != TypedValue::Type::Bool) return false; + return cmp2.Value<bool>(); + }; + auto garment = [&db_accessor, &args](const VertexAccessor &v) -> bool { + auto prop = v.PropsAt(db_accessor.property("garment_id")); + if (prop.type_ == TypedValue::Type::Null) return false; + auto cmp = prop == args.at(2); + if (cmp.type_ != TypedValue::Type::Bool) return false; + return cmp.Value<bool>(); + }; + for (auto edge : db_accessor.edges()) { + auto from = edge.from(); + auto to = edge.to(); + if (edge.edge_type() != db_accessor.edge_type("score")) continue; + if ((profile(from) && garment(to)) || (profile(to) && garment(from))) { + stream.write_edge_record(edge); + } + } + stream.write_meta("r"); + db_accessor.transaction_.commit(); + return true; + } + + ~CPUPlan() {} +}; + +extern "C" PlanInterface<Stream> *produce() { return new CPUPlan(); } + +extern "C" void destruct(PlanInterface<Stream> *p) { delete p; } diff --git a/tests/integration/hardcoded_query/match_profile_garment_update_score.cpp b/tests/integration/hardcoded_query/match_profile_garment_update_score.cpp new file mode 100644 index 000000000..180923d9c --- /dev/null +++ b/tests/integration/hardcoded_query/match_profile_garment_update_score.cpp @@ -0,0 +1,60 @@ +#include <iostream> +#include <string> + +#include "query/plan_interface.hpp" +#include "storage/edge_accessor.hpp" +#include "storage/vertex_accessor.hpp" +#include "using.hpp" + +using std::cout; +using std::endl; + +// Query: MATCH (p:profile {profile_id: 111, partner_id: +// 55})-[s:score]-(g:garment +// {garment_id: 1234}) SET s.score = 3137 RETURN s + +class CPUPlan : public PlanInterface<Stream> { + public: + bool run(GraphDbAccessor &db_accessor, const TypedValueStore<> &args, + Stream &stream) { + stream.write_field("s"); + auto profile = [&db_accessor, &args](const VertexAccessor &v) -> bool { + auto prop = v.PropsAt(db_accessor.property("profile_id")); + if (prop.type_ == TypedValue::Type::Null) return false; + auto cmp = prop == args.at(0); + if (cmp.type_ != TypedValue::Type::Bool) return false; + if (cmp.Value<bool>() != true) return false; + + auto prop2 = v.PropsAt(db_accessor.property("partner_id")); + if (prop2.type_ == TypedValue::Type::Null) return false; + auto cmp2 = prop2 == args.at(1); + if (cmp2.type_ != TypedValue::Type::Bool) return false; + return cmp2.Value<bool>(); + }; + auto garment = [&db_accessor, &args](const VertexAccessor &v) -> bool { + auto prop = v.PropsAt(db_accessor.property("garment_id")); + if (prop.type_ == TypedValue::Type::Null) return false; + auto cmp = prop == args.at(2); + if (cmp.type_ != TypedValue::Type::Bool) return false; + return cmp.Value<bool>(); + }; + for (auto edge : db_accessor.edges()) { + auto from = edge.from(); + 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)); + stream.write_edge_record(edge); + } + } + stream.write_meta("rw"); + db_accessor.transaction_.commit(); + return true; + } + + ~CPUPlan() {} +}; + +extern "C" PlanInterface<Stream> *produce() { return new CPUPlan(); } + +extern "C" void destruct(PlanInterface<Stream> *p) { delete p; } diff --git a/tests/integration/hardcoded_query/using.hpp b/tests/integration/hardcoded_query/using.hpp index 892bed8e0..35ed43da0 100644 --- a/tests/integration/hardcoded_query/using.hpp +++ b/tests/integration/hardcoded_query/using.hpp @@ -2,7 +2,7 @@ // the flag is only used in hardcoded queries compilation // see usage in plan_compiler.hpp -#ifdef HARDCODED_OUTPUT_STREAM +#ifndef HARDCODED_OUTPUT_STREAM #include "communication/bolt/communication.hpp" using Stream = communication::OutputStream; #else diff --git a/tests/integration/query_engine_common.hpp b/tests/integration/query_engine_common.hpp index 153b18fd8..2d9497411 100644 --- a/tests/integration/query_engine_common.hpp +++ b/tests/integration/query_engine_common.hpp @@ -98,8 +98,8 @@ auto LoadQueryPlans(Logger &log, QueryEngineT &engine, auto query = trim(line.substr(pos + query_mark.size())); while (i + 1 < (int)lines.size() && lines[i + 1].find(comment) != std::string::npos) { - query += trim( - lines[i + 1].substr(lines[i + 1].find(comment) + comment.length())); + query += + lines[i + 1].substr(lines[i + 1].find(comment) + comment.length()); ++i; } // load/compile implementations only for the queries which are diff --git a/tests/integration/stream/print_record_stream.hpp b/tests/integration/stream/print_record_stream.hpp index 1b1ad3a97..e05309b35 100644 --- a/tests/integration/stream/print_record_stream.hpp +++ b/tests/integration/stream/print_record_stream.hpp @@ -63,7 +63,7 @@ class PrintRecordStream { stream << "Field: " << field << '\n'; } - void write(const TypedValue &value) { stream << value; } + void write(const TypedValue &value) { stream << value << " "; } void write_list_header(size_t size) { stream << "List: " << size << '\n'; } void write_record() { stream << "Record\n"; } diff --git a/tests/manual/query_engine.cpp b/tests/manual/query_engine.cpp index c0ef4f9e0..f9d7c10f8 100644 --- a/tests/manual/query_engine.cpp +++ b/tests/manual/query_engine.cpp @@ -1,3 +1,4 @@ +#define HARDCODED_OUTPUT_STREAM #include "../integration/query_engine_common.hpp" #include "dbms/dbms.hpp" @@ -39,12 +40,23 @@ int main(int argc, char* argv[]) { // take only cpp files if (event.path.extension() != ".cpp") return; - auto query_mark = std::string("// Query: "); + auto comment = std::string("// "); + auto query_mark = comment + std::string("Query: "); auto lines = read_lines(event.path); - for (auto& line : lines) { + for (int i = 0; i < (int)lines.size(); ++i) { + // find query in the line + auto& line = lines[i]; auto pos = line.find(query_mark); + // if query doesn't exist pass if (pos == std::string::npos) continue; - auto query = line.substr(pos + query_mark.size()); + auto query = trim(line.substr(pos + query_mark.size())); + while (i + 1 < (int)lines.size() && + lines[i + 1].find(comment) != std::string::npos) { + query += lines[i + 1].substr(lines[i + 1].find(comment) + + comment.length()); + ++i; + } + log.info("Reload: {}", query); query_engine.Unload(query); try {