|
@@ -195,6 +195,8 @@ bool DatabaseService::initialize() {
|
|
|
// migration-created documents are stamped with the correct precision.
|
|
// migration-created documents are stamped with the correct precision.
|
|
|
config_manager_->loadFromStore();
|
|
config_manager_->loadFromStore();
|
|
|
policy_manager_->loadFromStore();
|
|
policy_manager_->loadFromStore();
|
|
|
|
|
+ relation_manager_->loadFromStore();
|
|
|
|
|
+ applyRelationDeclarations();
|
|
|
|
|
|
|
|
// v2.9.0 — re-apply persisted index declarations to each project's LMDB
|
|
// v2.9.0 — re-apply persisted index declarations to each project's LMDB
|
|
|
// store. This is load-bearing, not bookkeeping: the declaration is what
|
|
// store. This is load-bearing, not bookkeeping: the declaration is what
|
|
@@ -379,6 +381,55 @@ void DatabaseService::applyIndexDeclarations() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void DatabaseService::applyRelationDeclarations() {
|
|
|
|
|
+ // Group by (project, bare child collection) — set_relations() is a
|
|
|
|
|
+ // per-collection call on that project's LmdbDocumentStore and replaces
|
|
|
|
|
+ // whatever was declared before, so every relation sharing a child
|
|
|
|
|
+ // collection must land in one call.
|
|
|
|
|
+ std::unordered_map<std::string,
|
|
|
|
|
+ std::vector<smartbotic::db::storage::RelationRef>> byChild;
|
|
|
|
|
+ // Track which project each grouping key belongs to alongside the bare
|
|
|
|
|
+ // collection name, since the map key alone doesn't carry it.
|
|
|
|
|
+ std::unordered_map<std::string, std::pair<std::string, std::string>> keyToProjectCollection;
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& r : relation_manager_->listRelations()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const auto rn = resolveCollection(r.name);
|
|
|
|
|
+ const auto rc = resolveCollection(r.child);
|
|
|
|
|
+ const std::string key = rc.project + ":" + rc.collection;
|
|
|
|
|
+ byChild[key].push_back(
|
|
|
|
|
+ smartbotic::db::storage::RelationRef{rn.collection, r.childField});
|
|
|
|
|
+ keyToProjectCollection[key] = {rc.project, rc.collection};
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ // Advisory per relation: one unparseable declaration must not
|
|
|
|
|
+ // stop the rest from being armed. Loud, because a skipped
|
|
|
|
|
+ // relation silently maintains no reverse index for its child.
|
|
|
|
|
+ spdlog::error("v2.11 relations: could not apply declaration '{}': {}",
|
|
|
|
|
+ r.name, e.what());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ size_t applied = 0;
|
|
|
|
|
+ for (const auto& [key, refs] : byChild) {
|
|
|
|
|
+ const auto& [project, collection] = keyToProjectCollection[key];
|
|
|
|
|
+ try {
|
|
|
|
|
+ auto* ds = docStore(project);
|
|
|
|
|
+ auto* lmdb = dynamic_cast<smartbotic::db::storage::LmdbDocumentStore*>(ds);
|
|
|
|
|
+ if (lmdb == nullptr) continue;
|
|
|
|
|
+ lmdb->set_relations(collection, refs);
|
|
|
|
|
+ ++applied;
|
|
|
|
|
+ spdlog::info("v2.11 relations: {} relation(s) active with child '{}:{}'",
|
|
|
|
|
+ refs.size(), project, collection);
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("v2.11 relations: could not apply declarations for '{}:{}': {}",
|
|
|
|
|
+ project, collection, e.what());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (applied > 0) {
|
|
|
|
|
+ spdlog::info("v2.11 relations: applied declarations for {} child collection(s)", applied);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
void DatabaseService::auditSubdbPlacement() {
|
|
void DatabaseService::auditSubdbPlacement() {
|
|
|
if (!projects_) return;
|
|
if (!projects_) return;
|
|
|
|
|
|
|
@@ -999,6 +1050,9 @@ void DatabaseService::setupComponents() {
|
|
|
// v2.8.0 — access policy. Constructed here; its cache is loaded after
|
|
// v2.8.0 — access policy. Constructed here; its cache is loaded after
|
|
|
// recovery alongside the view and collection-config caches.
|
|
// recovery alongside the view and collection-config caches.
|
|
|
policy_manager_ = std::make_unique<PolicyManager>(*store_);
|
|
policy_manager_ = std::make_unique<PolicyManager>(*store_);
|
|
|
|
|
+ // v2.11.0 T6a — relation declarations. Constructed here; its cache is
|
|
|
|
|
+ // loaded after recovery alongside the view/config/policy caches.
|
|
|
|
|
+ relation_manager_ = std::make_unique<RelationManager>(*store_);
|
|
|
store_->setConfigManager(config_manager_.get());
|
|
store_->setConfigManager(config_manager_.get());
|
|
|
|
|
|
|
|
// v1.9.0 — disk-resident version history. Replaces the in-heap
|
|
// v1.9.0 — disk-resident version history. Replaces the in-heap
|
|
@@ -1177,7 +1231,7 @@ void DatabaseService::setupComponents() {
|
|
|
// Create gRPC implementations
|
|
// Create gRPC implementations
|
|
|
storageImpl_ = std::make_unique<DatabaseGrpcImpl>(
|
|
storageImpl_ = std::make_unique<DatabaseGrpcImpl>(
|
|
|
*this, *store_, *persistence_, *events_, *files_, *encryption_, *view_manager_, *config_manager_,
|
|
*this, *store_, *persistence_, *events_, *files_, *encryption_, *view_manager_, *config_manager_,
|
|
|
- *policy_manager_
|
|
|
|
|
|
|
+ *policy_manager_, *relation_manager_
|
|
|
);
|
|
);
|
|
|
// v2.7.0 — hand the impl the union of every listener's keys so it can
|
|
// v2.7.0 — hand the impl the union of every listener's keys so it can
|
|
|
// resolve a principal per call. A token is only accepted if some listener's
|
|
// resolve a principal per call. A token is only accepted if some listener's
|