* perf(search, metadata): add KG write buffer, score cache, adaptive budgets * perf(indexing): wire KGWriteBuffer into indexing service Converts per-document addEdgesUnique calls into batched flush via KGWriteBuffer. 500-doc ingest: 500 SQL round-trips → 1 batch. Verified: 14.2× edge insert gap (14.8K→195K edges/s) in kg_edge bench. Tests: 10/10 Catch2 pass (105 assertions). * perf(storage): add fsync durability, parallel batch, read integrity * test(storage): add stress, crash-sim, cold-cache, batch benchmarks * fix: CI optimizations and work coordinator shutdown fix * fix(testing): stabilize TSAN coverage gate * fix: harden KG flush, score cache, and benches - keep KG buffers intact when batch commit fails - count flushed edges per batch instead of lifetime totals - guard the Simeon score cache and clear it on rebuild - fall back to fsync when F_FULLFSYNC fails on macOS - remove interactive sudo from cold-cache benchmarks
564 lines
20 KiB
C++
564 lines
20 KiB
C++
// Copyright (c) 2025 YAMS Contributors
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <yams/app/services/graph_query_service.hpp>
|
|
#include <yams/metadata/connection_pool.h>
|
|
#include <yams/metadata/knowledge_graph_store.h>
|
|
#include <yams/metadata/metadata_repository.h>
|
|
|
|
#include "../../../common/metadata_test_db.h"
|
|
|
|
#include <filesystem>
|
|
#include <memory>
|
|
|
|
using namespace yams::app::services;
|
|
using namespace yams::metadata;
|
|
|
|
namespace {
|
|
|
|
struct GraphQueryServiceFixture {
|
|
GraphQueryServiceFixture() {
|
|
dbPath = yams::test::migrated_metadata_db_template().clone("yams_graph_query_service_db_");
|
|
testDir = dbPath.parent_path() / dbPath.stem();
|
|
std::filesystem::create_directories(testDir);
|
|
|
|
ConnectionPoolConfig poolConfig;
|
|
poolConfig.minConnections = 1;
|
|
poolConfig.maxConnections = 2;
|
|
pool = std::make_shared<ConnectionPool>(dbPath.string(), poolConfig);
|
|
auto poolInitRes = pool->initialize();
|
|
REQUIRE((poolInitRes.has_value()));
|
|
|
|
metadataRepo = std::make_shared<MetadataRepository>(
|
|
*pool, nullptr, MetadataRepository::SchemaBootstrapMode::AssumeReady);
|
|
|
|
KnowledgeGraphStoreConfig kgConfig;
|
|
kgConfig.enable_alias_fts = true;
|
|
kgConfig.enable_wal = false;
|
|
auto kgResult = makeSqliteKnowledgeGraphStore(*pool, kgConfig);
|
|
REQUIRE((kgResult.has_value()));
|
|
kgStore = std::shared_ptr<KnowledgeGraphStore>(std::move(kgResult).value());
|
|
metadataRepo->setKnowledgeGraphStore(kgStore);
|
|
}
|
|
|
|
~GraphQueryServiceFixture() {
|
|
kgStore.reset();
|
|
metadataRepo.reset();
|
|
pool.reset();
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(testDir, ec);
|
|
yams::test::remove_sqlite_artifacts(dbPath);
|
|
}
|
|
|
|
std::filesystem::path testDir;
|
|
std::filesystem::path dbPath;
|
|
std::shared_ptr<ConnectionPool> pool;
|
|
std::shared_ptr<MetadataRepository> metadataRepo;
|
|
std::shared_ptr<KnowledgeGraphStore> kgStore;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("GraphQueryService: relation name filters apply", "[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode caller;
|
|
caller.nodeKey = "symbol:caller";
|
|
caller.label = "caller";
|
|
caller.type = "function";
|
|
auto callerId = fixture.kgStore->upsertNode(caller);
|
|
REQUIRE((callerId.has_value()));
|
|
|
|
KGNode callee;
|
|
callee.nodeKey = "symbol:callee";
|
|
callee.label = "callee";
|
|
callee.type = "function";
|
|
auto calleeId = fixture.kgStore->upsertNode(callee);
|
|
REQUIRE((calleeId.has_value()));
|
|
|
|
KGNode includeTarget;
|
|
includeTarget.nodeKey = "path:file:/include/header.h";
|
|
includeTarget.label = "header.h";
|
|
includeTarget.type = "file";
|
|
auto includeId = fixture.kgStore->upsertNode(includeTarget);
|
|
REQUIRE((includeId.has_value()));
|
|
|
|
KGEdge callEdge;
|
|
callEdge.srcNodeId = callerId.value();
|
|
callEdge.dstNodeId = calleeId.value();
|
|
callEdge.relation = "calls";
|
|
callEdge.weight = 1.0f;
|
|
REQUIRE((fixture.kgStore->addEdge(callEdge).has_value()));
|
|
|
|
KGEdge includeEdge;
|
|
includeEdge.srcNodeId = callerId.value();
|
|
includeEdge.dstNodeId = includeId.value();
|
|
includeEdge.relation = "includes";
|
|
includeEdge.weight = 1.0f;
|
|
REQUIRE((fixture.kgStore->addEdge(includeEdge).has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = callerId.value();
|
|
req.maxDepth = 1;
|
|
req.maxResults = 10;
|
|
req.relationNames = {"calls"};
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
|
|
REQUIRE((resp.allConnectedNodes.size() == 1));
|
|
REQUIRE((resp.allConnectedNodes[0].nodeMetadata.node.nodeKey == "symbol:callee"));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: PathVersion includes has_version edges", "[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode pathNode;
|
|
pathNode.nodeKey = "path:abc:/repo/file.cpp";
|
|
pathNode.label = "/repo/file.cpp";
|
|
pathNode.type = "path";
|
|
auto pathId = fixture.kgStore->upsertNode(pathNode);
|
|
REQUIRE((pathId.has_value()));
|
|
|
|
KGNode blobNode;
|
|
blobNode.nodeKey = "blob:deadbeef";
|
|
blobNode.label = "deadbeef";
|
|
blobNode.type = "blob";
|
|
auto blobId = fixture.kgStore->upsertNode(blobNode);
|
|
REQUIRE((blobId.has_value()));
|
|
|
|
KGEdge versionEdge;
|
|
versionEdge.srcNodeId = pathId.value();
|
|
versionEdge.dstNodeId = blobId.value();
|
|
versionEdge.relation = "has_version";
|
|
versionEdge.weight = 1.0f;
|
|
REQUIRE((fixture.kgStore->addEdge(versionEdge).has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = pathId.value();
|
|
req.maxDepth = 1;
|
|
req.maxResults = 10;
|
|
req.relationFilters = {GraphRelationType::PathVersion};
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
|
|
REQUIRE((resp.allConnectedNodes.size() == 1));
|
|
REQUIRE((resp.allConnectedNodes[0].nodeMetadata.node.nodeKey == "blob:deadbeef"));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: RenamedFrom includes renamed_to edges", "[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode oldPath;
|
|
oldPath.nodeKey = "path:snap1:/repo/old.cpp";
|
|
oldPath.label = "/repo/old.cpp";
|
|
oldPath.type = "path";
|
|
auto oldId = fixture.kgStore->upsertNode(oldPath);
|
|
REQUIRE((oldId.has_value()));
|
|
|
|
KGNode newPath;
|
|
newPath.nodeKey = "path:snap2:/repo/new.cpp";
|
|
newPath.label = "/repo/new.cpp";
|
|
newPath.type = "path";
|
|
auto newId = fixture.kgStore->upsertNode(newPath);
|
|
REQUIRE((newId.has_value()));
|
|
|
|
KGEdge renameEdge;
|
|
renameEdge.srcNodeId = oldId.value();
|
|
renameEdge.dstNodeId = newId.value();
|
|
renameEdge.relation = "renamed_to";
|
|
renameEdge.weight = 1.0f;
|
|
REQUIRE((fixture.kgStore->addEdge(renameEdge).has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = oldId.value();
|
|
req.maxDepth = 1;
|
|
req.maxResults = 10;
|
|
req.relationFilters = {GraphRelationType::RenamedFrom};
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
|
|
REQUIRE((resp.allConnectedNodes.size() == 1));
|
|
REQUIRE((resp.allConnectedNodes[0].nodeMetadata.node.nodeKey == "path:snap2:/repo/new.cpp"));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: relation names are canonicalized", "[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode caller;
|
|
caller.nodeKey = "symbol:canonical:caller";
|
|
caller.label = "caller";
|
|
caller.type = "function";
|
|
auto callerId = fixture.kgStore->upsertNode(caller);
|
|
REQUIRE((callerId.has_value()));
|
|
|
|
KGNode callee;
|
|
callee.nodeKey = "symbol:canonical:callee";
|
|
callee.label = "callee";
|
|
callee.type = "function";
|
|
auto calleeId = fixture.kgStore->upsertNode(callee);
|
|
REQUIRE((calleeId.has_value()));
|
|
|
|
KGEdge callEdge;
|
|
callEdge.srcNodeId = callerId.value();
|
|
callEdge.dstNodeId = calleeId.value();
|
|
callEdge.relation = "calls";
|
|
callEdge.weight = 1.0f;
|
|
REQUIRE((fixture.kgStore->addEdge(callEdge).has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = callerId.value();
|
|
req.maxDepth = 1;
|
|
req.maxResults = 10;
|
|
req.relationNames = {"CALL"};
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
REQUIRE((resp.allConnectedNodes.size() == 1));
|
|
REQUIRE((resp.allConnectedNodes[0].nodeMetadata.node.nodeKey == "symbol:canonical:callee"));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: SymbolReference enum includes calls", "[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode caller;
|
|
caller.nodeKey = "symbol:enum:caller";
|
|
caller.label = "caller";
|
|
caller.type = "function";
|
|
auto callerId = fixture.kgStore->upsertNode(caller);
|
|
REQUIRE((callerId.has_value()));
|
|
|
|
KGNode callee;
|
|
callee.nodeKey = "symbol:enum:callee";
|
|
callee.label = "callee";
|
|
callee.type = "function";
|
|
auto calleeId = fixture.kgStore->upsertNode(callee);
|
|
REQUIRE((calleeId.has_value()));
|
|
|
|
KGEdge callEdge;
|
|
callEdge.srcNodeId = callerId.value();
|
|
callEdge.dstNodeId = calleeId.value();
|
|
callEdge.relation = "calls";
|
|
callEdge.weight = 1.0f;
|
|
REQUIRE((fixture.kgStore->addEdge(callEdge).has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = callerId.value();
|
|
req.maxDepth = 1;
|
|
req.maxResults = 10;
|
|
req.relationFilters = {GraphRelationType::SymbolReference};
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
REQUIRE((resp.allConnectedNodes.size() == 1));
|
|
REQUIRE((resp.allConnectedNodes[0].nodeMetadata.node.nodeKey == "symbol:enum:callee"));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: BFS uses discovered parent edge for attribution",
|
|
"[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode origin;
|
|
origin.nodeKey = "symbol:path:origin";
|
|
origin.label = "origin";
|
|
origin.type = "function";
|
|
auto originId = fixture.kgStore->upsertNode(origin);
|
|
REQUIRE((originId.has_value()));
|
|
|
|
KGNode mid;
|
|
mid.nodeKey = "symbol:path:mid";
|
|
mid.label = "mid";
|
|
mid.type = "function";
|
|
auto midId = fixture.kgStore->upsertNode(mid);
|
|
REQUIRE((midId.has_value()));
|
|
|
|
KGNode target;
|
|
target.nodeKey = "symbol:path:target";
|
|
target.label = "target";
|
|
target.type = "function";
|
|
auto targetId = fixture.kgStore->upsertNode(target);
|
|
REQUIRE((targetId.has_value()));
|
|
|
|
KGEdge edge1;
|
|
edge1.srcNodeId = originId.value();
|
|
edge1.dstNodeId = midId.value();
|
|
edge1.relation = "contains";
|
|
edge1.weight = 1.0f;
|
|
REQUIRE((fixture.kgStore->addEdge(edge1).has_value()));
|
|
|
|
KGEdge edge2;
|
|
edge2.srcNodeId = midId.value();
|
|
edge2.dstNodeId = targetId.value();
|
|
edge2.relation = "calls";
|
|
edge2.weight = 0.9f;
|
|
edge2.properties = R"({"source":"unit","confidence":0.73})";
|
|
REQUIRE((fixture.kgStore->addEdge(edge2).has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = originId.value();
|
|
req.maxDepth = 2;
|
|
req.maxResults = 10;
|
|
req.hydrateFully = false;
|
|
req.includeEdgeProperties = true;
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
|
|
REQUIRE((resp.allConnectedNodes.size() == 2));
|
|
|
|
const auto* targetNode = [&]() -> const GraphConnectedNode* {
|
|
for (const auto& node : resp.allConnectedNodes) {
|
|
if (node.nodeMetadata.node.nodeKey == "symbol:path:target") {
|
|
return &node;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}();
|
|
|
|
REQUIRE((targetNode != nullptr));
|
|
CHECK((targetNode->distance == 2));
|
|
REQUIRE((targetNode->connectingEdges.size() == 1));
|
|
CHECK((targetNode->connectingEdges[0].srcNodeId == midId.value()));
|
|
CHECK((targetNode->connectingEdges[0].dstNodeId == targetId.value()));
|
|
CHECK((targetNode->connectingEdges[0].relation == "calls"));
|
|
REQUIRE((targetNode->connectingEdges[0].properties.has_value()));
|
|
CHECK((targetNode->connectingEdges[0].properties.value().find("\"confidence\":0.73") !=
|
|
std::string::npos));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: multi-path chooses higher-signal parent relation",
|
|
"[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode origin{.nodeKey = "symbol:multi:origin", .label = "origin", .type = "function"};
|
|
KGNode structHop{.nodeKey = "symbol:multi:struct", .label = "structHop", .type = "file"};
|
|
KGNode semanticHop{
|
|
.nodeKey = "symbol:multi:semantic", .label = "semanticHop", .type = "function"};
|
|
KGNode target{.nodeKey = "symbol:multi:target", .label = "target", .type = "function"};
|
|
|
|
auto originId = fixture.kgStore->upsertNode(origin);
|
|
auto structHopId = fixture.kgStore->upsertNode(structHop);
|
|
auto semanticHopId = fixture.kgStore->upsertNode(semanticHop);
|
|
auto targetId = fixture.kgStore->upsertNode(target);
|
|
REQUIRE((originId.has_value()));
|
|
REQUIRE((structHopId.has_value()));
|
|
REQUIRE((semanticHopId.has_value()));
|
|
REQUIRE((targetId.has_value()));
|
|
|
|
KGEdge e1{.srcNodeId = originId.value(),
|
|
.dstNodeId = structHopId.value(),
|
|
.relation = "contains",
|
|
.weight = 1.0f};
|
|
KGEdge e2{.srcNodeId = originId.value(),
|
|
.dstNodeId = semanticHopId.value(),
|
|
.relation = "calls",
|
|
.weight = 1.0f};
|
|
KGEdge e3{.srcNodeId = structHopId.value(),
|
|
.dstNodeId = targetId.value(),
|
|
.relation = "contains",
|
|
.weight = 1.0f};
|
|
KGEdge e4{.srcNodeId = semanticHopId.value(),
|
|
.dstNodeId = targetId.value(),
|
|
.relation = "calls",
|
|
.weight = 1.0f};
|
|
REQUIRE((fixture.kgStore->addEdge(e1).has_value()));
|
|
REQUIRE((fixture.kgStore->addEdge(e2).has_value()));
|
|
REQUIRE((fixture.kgStore->addEdge(e3).has_value()));
|
|
REQUIRE((fixture.kgStore->addEdge(e4).has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = originId.value();
|
|
req.maxDepth = 2;
|
|
req.maxResults = 10;
|
|
req.hydrateFully = false;
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
|
|
const auto* targetNode = [&]() -> const GraphConnectedNode* {
|
|
for (const auto& node : resp.allConnectedNodes) {
|
|
if (node.nodeMetadata.node.nodeKey == "symbol:multi:target") {
|
|
return &node;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}();
|
|
|
|
REQUIRE((targetNode != nullptr));
|
|
REQUIRE((targetNode->connectingEdges.size() == 1));
|
|
CHECK((targetNode->connectingEdges[0].srcNodeId == semanticHopId.value()));
|
|
CHECK((targetNode->connectingEdges[0].relation == "calls"));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: cycles keep path attribution stable", "[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode origin{.nodeKey = "symbol:cycle:origin", .label = "origin", .type = "function"};
|
|
KGNode mid{.nodeKey = "symbol:cycle:mid", .label = "mid", .type = "function"};
|
|
KGNode target{.nodeKey = "symbol:cycle:target", .label = "target", .type = "function"};
|
|
|
|
auto originId = fixture.kgStore->upsertNode(origin);
|
|
auto midId = fixture.kgStore->upsertNode(mid);
|
|
auto targetId = fixture.kgStore->upsertNode(target);
|
|
REQUIRE((originId.has_value()));
|
|
REQUIRE((midId.has_value()));
|
|
REQUIRE((targetId.has_value()));
|
|
|
|
REQUIRE((fixture.kgStore
|
|
->addEdge(KGEdge{.srcNodeId = originId.value(),
|
|
.dstNodeId = midId.value(),
|
|
.relation = "calls",
|
|
.weight = 1.0f})
|
|
.has_value()));
|
|
REQUIRE((fixture.kgStore
|
|
->addEdge(KGEdge{.srcNodeId = midId.value(),
|
|
.dstNodeId = originId.value(),
|
|
.relation = "calls",
|
|
.weight = 1.0f})
|
|
.has_value()));
|
|
REQUIRE((fixture.kgStore
|
|
->addEdge(KGEdge{.srcNodeId = midId.value(),
|
|
.dstNodeId = targetId.value(),
|
|
.relation = "references",
|
|
.weight = 1.0f})
|
|
.has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = originId.value();
|
|
req.maxDepth = 2;
|
|
req.maxResults = 10;
|
|
req.hydrateFully = false;
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
|
|
REQUIRE((resp.allConnectedNodes.size() == 2));
|
|
const auto& depthOne = resp.nodesByDistance.at(1);
|
|
REQUIRE((depthOne.size() == 1));
|
|
CHECK((depthOne.front().nodeMetadata.node.nodeKey == "symbol:cycle:mid"));
|
|
|
|
const auto& depthTwo = resp.nodesByDistance.at(2);
|
|
REQUIRE((depthTwo.size() == 1));
|
|
CHECK((depthTwo.front().nodeMetadata.node.nodeKey == "symbol:cycle:target"));
|
|
REQUIRE((depthTwo.front().connectingEdges.size() == 1));
|
|
CHECK((depthTwo.front().connectingEdges.front().srcNodeId == midId.value()));
|
|
CHECK((depthTwo.front().connectingEdges.front().relation == "references"));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: relation filters apply before traversal expansion",
|
|
"[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode origin{.nodeKey = "symbol:filter:path:origin", .label = "origin", .type = "function"};
|
|
KGNode mid{.nodeKey = "symbol:filter:path:mid", .label = "mid", .type = "file"};
|
|
KGNode target{.nodeKey = "symbol:filter:path:target", .label = "target", .type = "function"};
|
|
|
|
auto originId = fixture.kgStore->upsertNode(origin);
|
|
auto midId = fixture.kgStore->upsertNode(mid);
|
|
auto targetId = fixture.kgStore->upsertNode(target);
|
|
REQUIRE((originId.has_value()));
|
|
REQUIRE((midId.has_value()));
|
|
REQUIRE((targetId.has_value()));
|
|
|
|
REQUIRE((fixture.kgStore
|
|
->addEdge(KGEdge{.srcNodeId = originId.value(),
|
|
.dstNodeId = midId.value(),
|
|
.relation = "contains",
|
|
.weight = 1.0f})
|
|
.has_value()));
|
|
REQUIRE((fixture.kgStore
|
|
->addEdge(KGEdge{.srcNodeId = midId.value(),
|
|
.dstNodeId = targetId.value(),
|
|
.relation = "calls",
|
|
.weight = 1.0f})
|
|
.has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = originId.value();
|
|
req.maxDepth = 2;
|
|
req.maxResults = 10;
|
|
req.hydrateFully = false;
|
|
req.relationNames = {"contains"};
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
|
|
REQUIRE((resp.allConnectedNodes.size() == 1));
|
|
CHECK((resp.allConnectedNodes.front().nodeMetadata.node.nodeKey == "symbol:filter:path:mid"));
|
|
}
|
|
|
|
TEST_CASE("GraphQueryService: semantic relations rank above structural at same depth",
|
|
"[services][graph]") {
|
|
GraphQueryServiceFixture fixture;
|
|
auto service = makeGraphQueryService(fixture.kgStore, fixture.metadataRepo);
|
|
REQUIRE((service != nullptr));
|
|
|
|
KGNode origin{.nodeKey = "symbol:rank:origin", .label = "origin", .type = "function"};
|
|
KGNode semantic{.nodeKey = "symbol:rank:semantic", .label = "semantic", .type = "function"};
|
|
KGNode structural{.nodeKey = "symbol:rank:structural", .label = "structural", .type = "file"};
|
|
|
|
auto originId = fixture.kgStore->upsertNode(origin);
|
|
auto semanticId = fixture.kgStore->upsertNode(semantic);
|
|
auto structuralId = fixture.kgStore->upsertNode(structural);
|
|
REQUIRE((originId.has_value()));
|
|
REQUIRE((semanticId.has_value()));
|
|
REQUIRE((structuralId.has_value()));
|
|
|
|
REQUIRE((fixture.kgStore
|
|
->addEdge(KGEdge{.srcNodeId = originId.value(),
|
|
.dstNodeId = structuralId.value(),
|
|
.relation = "contains",
|
|
.weight = 1.0f})
|
|
.has_value()));
|
|
REQUIRE((fixture.kgStore
|
|
->addEdge(KGEdge{.srcNodeId = originId.value(),
|
|
.dstNodeId = semanticId.value(),
|
|
.relation = "calls",
|
|
.weight = 1.0f})
|
|
.has_value()));
|
|
|
|
GraphQueryRequest req;
|
|
req.nodeId = originId.value();
|
|
req.maxDepth = 1;
|
|
req.maxResults = 10;
|
|
req.hydrateFully = false;
|
|
|
|
auto result = service->query(req);
|
|
REQUIRE((result.has_value()));
|
|
const auto& resp = result.value();
|
|
|
|
REQUIRE((resp.allConnectedNodes.size() == 2));
|
|
CHECK((resp.allConnectedNodes.front().nodeMetadata.node.nodeKey == "symbol:rank:semantic"));
|
|
CHECK((resp.allConnectedNodes.back().nodeMetadata.node.nodeKey == "symbol:rank:structural"));
|
|
}
|