//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2019 ArangoDB GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Markus Pfeiffer //////////////////////////////////////////////////////////////////////////////// #ifndef TESTS_GRAPH_TOOLS_H #define TESTS_GRAPH_TOOLS_H #include "gtest/gtest.h" #include "IResearch/common.h" #include "Mocks/LogLevels.h" #include "Mocks/Servers.h" #include "Mocks/StorageEngineMock.h" #include "Aql/AqlFunctionFeature.h" #include "Aql/AqlItemBlockSerializationFormat.h" #include "Aql/Ast.h" #include "Aql/ExecutionPlan.h" #include "Aql/OptimizerRulesFeature.h" #include "Aql/Query.h" #include "ClusterEngine/ClusterEngine.h" #include "Graph/ConstantWeightShortestPathFinder.h" #include "Graph/ShortestPathOptions.h" #include "Graph/ShortestPathResult.h" #include "Random/RandomGenerator.h" #include "RestServer/AqlFeature.h" #include "RestServer/DatabaseFeature.h" #include "RestServer/DatabasePathFeature.h" #include "RestServer/QueryRegistryFeature.h" #include "RestServer/SystemDatabaseFeature.h" #include "RestServer/TraverserEngineRegistryFeature.h" #include "StorageEngine/EngineSelectorFeature.h" #include "Transaction/Methods.h" #include "Transaction/StandaloneContext.h" #include "Utils/SingleCollectionTransaction.h" #include "VocBase/LogicalCollection.h" #include using namespace arangodb; using namespace arangodb::aql; using namespace arangodb::graph; using namespace arangodb::velocypack; namespace arangodb { namespace tests { namespace graph { struct GraphTestSetup : public arangodb::tests::LogSuppressor { StorageEngineMock engine; arangodb::application_features::ApplicationServer server; std::unique_ptr system; std::vector> features; GraphTestSetup() : engine(server), server(nullptr, nullptr) { arangodb::EngineSelectorFeature::ENGINE = &engine; arangodb::transaction::Methods::clearDataSourceRegistrationCallbacks(); arangodb::ClusterEngine::Mocking = true; arangodb::RandomGenerator::initialize(arangodb::RandomGenerator::RandomType::MERSENNE); // setup required application features features.emplace_back(server.addFeature(), false); features.emplace_back(server.addFeature(), false); features.emplace_back(server.addFeature(), false); // must be first system = std::make_unique(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, systemDBInfo(server)); features.emplace_back(server.addFeature( system.get()), false); // required for IResearchAnalyzerFeature features.emplace_back(server.addFeature(), false); // must be before AqlFeature features.emplace_back(server.addFeature(), true); features.emplace_back(server.addFeature(), true); features.emplace_back(server.addFeature(), true); // required for IResearchAnalyzerFeature for (auto& f : features) { f.first.prepare(); } for (auto& f : features) { if (f.second) { f.first.start(); } } auto& dbPathFeature = server.getFeature(); arangodb::tests::setDatabasePath(dbPathFeature); // ensure test data is stored in a unique directory } ~GraphTestSetup() { system.reset(); // destroy before reseting the 'ENGINE' arangodb::AqlFeature(server).stop(); // unset singleton instance arangodb::EngineSelectorFeature::ENGINE = nullptr; // destroy application features for (auto& f : features) { if (f.second) { f.first.stop(); } } for (auto& f : features) { f.first.unprepare(); } } }; // Setup struct MockGraphDatabase { TRI_vocbase_t vocbase; std::vector queries; std::vector spos; MockGraphDatabase(application_features::ApplicationServer& server, std::string name) : vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, createInfo(server, name, 1)) {} ~MockGraphDatabase() { for (auto& q : queries) { if (q->trx() != nullptr) { q->trx()->abort(); } delete q; } for (auto& o : spos) { delete o; } } // Create a collection with name and vertices // with ids 0..n-1 void addVertexCollection(std::string name, size_t n) { std::shared_ptr vertices; auto createJson = velocypack::Parser::fromJson("{ \"name\": \"" + name + "\", \"type\": 2 }"); vertices = vocbase.createCollection(createJson->slice()); ASSERT_TRUE((nullptr != vertices)); std::vector insertedDocs; std::vector> docs; for (size_t i = 0; i < n; i++) { docs.emplace_back(arangodb::velocypack::Parser::fromJson( "{ \"_key\": \"" + std::to_string(i) + "\"}")); }; arangodb::OperationOptions options; options.returnNew = true; arangodb::SingleCollectionTransaction trx(arangodb::transaction::StandaloneContext::Create(vocbase), *vertices, arangodb::AccessMode::Type::WRITE); EXPECT_TRUE((trx.begin().ok())); for (auto& entry : docs) { auto res = trx.insert(vertices->name(), entry->slice(), options); EXPECT_TRUE((res.ok())); insertedDocs.emplace_back(res.slice().get("new")); } EXPECT_TRUE((trx.commit().ok())); EXPECT_TRUE(insertedDocs.size() == n); EXPECT_TRUE(vertices->type()); } struct EdgeDef { EdgeDef(size_t from, size_t to) : _from(from), _to(to){}; EdgeDef(size_t from, size_t to, double weight) : _from(from), _to(to), _weight(weight){}; size_t _from; size_t _to; std::optional _weight; }; // Create a collection with name of edges given by void addEdgeCollection(std::string name, std::string vertexCollection, std::vector edgedef) { std::shared_ptr edges; auto createJson = velocypack::Parser::fromJson("{ \"name\": \"" + name + "\", \"type\": 3 }"); edges = vocbase.createCollection(createJson->slice()); ASSERT_TRUE((nullptr != edges)); auto indexJson = velocypack::Parser::fromJson("{ \"type\": \"edge\" }"); bool created = false; auto index = edges->createIndex(indexJson->slice(), created); EXPECT_TRUE(index); EXPECT_TRUE(created); std::vector insertedDocs; std::vector> docs; for (auto& p : edgedef) { // std::cout << "edge: " << vertexCollection << " " << p.first << " -> " // << p.second << std::endl; // This is moderately horrible auto docJson = p._weight.has_value() ? velocypack::Parser::fromJson( "{ \"_from\": \"" + vertexCollection + "/" + std::to_string(p._from) + "\"" + ", \"_to\": \"" + vertexCollection + "/" + std::to_string(p._to) + "\", \"cost\": " + std::to_string(p._weight.value()) + "}") : velocypack::Parser::fromJson( "{ \"_from\": \"" + vertexCollection + "/" + std::to_string(p._from) + "\"" + ", \"_to\": \"" + vertexCollection + "/" + std::to_string(p._to) + "\" }"); docs.emplace_back(docJson); } arangodb::OperationOptions options; options.returnNew = true; arangodb::SingleCollectionTransaction trx(arangodb::transaction::StandaloneContext::Create(vocbase), *edges, arangodb::AccessMode::Type::WRITE); EXPECT_TRUE((trx.begin().ok())); for (auto& entry : docs) { auto res = trx.insert(edges->name(), entry->slice(), options); EXPECT_TRUE((res.ok())); insertedDocs.emplace_back(res.slice().get("new")); } EXPECT_TRUE((trx.commit().ok())); EXPECT_TRUE(insertedDocs.size() == edgedef.size()); } arangodb::aql::Query* getQuery(std::string qry) { auto queryString = arangodb::aql::QueryString(qry); arangodb::aql::Query* query = new arangodb::aql::Query(false, vocbase, queryString, nullptr, arangodb::velocypack::Parser::fromJson("{}"), arangodb::aql::PART_MAIN); // TODO Local fix of #10304. Remove this after #10304 is merged back. // query->parse(); query->prepare(arangodb::QueryRegistryFeature::registry(), SerializationFormat::SHADOWROWS); queries.emplace_back(query); return query; } arangodb::graph::ShortestPathOptions* getShortestPathOptions(arangodb::aql::Query* query) { arangodb::graph::ShortestPathOptions* spo; auto plan = query->plan(); auto ast = plan->getAst(); auto _toCondition = ast->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND); auto _fromCondition = ast->createNodeNaryOperator(NODE_TYPE_OPERATOR_NARY_AND); auto tmpVar = plan->getAst()->variables()->createTemporaryVariable(); AstNode* tmpId1 = plan->getAst()->createNodeReference(tmpVar); AstNode* tmpId2 = plan->getAst()->createNodeValueString("", 0); { auto const* access = ast->createNodeAttributeAccess(tmpId1, StaticStrings::ToString.c_str(), StaticStrings::ToString.length()); auto const* cond = ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, access, tmpId2); _toCondition->addMember(cond); } { auto const* access = ast->createNodeAttributeAccess(tmpId1, StaticStrings::FromString.c_str(), StaticStrings::FromString.length()); auto const* cond = ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_EQ, access, tmpId2); _fromCondition->addMember(cond); } spo = new ShortestPathOptions(query); spo->setVariable(tmpVar); spo->addLookupInfo(plan, "e", StaticStrings::FromString, _fromCondition->clone(ast)); spo->addReverseLookupInfo(plan, "e", StaticStrings::ToString, _toCondition->clone(ast)); spos.emplace_back(spo); return spo; } }; // namespace graph bool checkPath(ShortestPathOptions* spo, ShortestPathResult result, std::vector vertices, std::vector> edges, std::string& msgs); } // namespace graph } // namespace tests } // namespace arangodb #endif