Everett
Loading...
Searching...
No Matches
pins.h
Go to the documentation of this file.
1
13#pragma once
14
15#include "everett/fingerprint.h"
16
17#include <algorithm>
18#include <memory>
19#include <optional>
20#include <span>
21#include <stdexcept>
22#include <string>
23#include <string_view>
24#include <utility>
25#include <vector>
26
27namespace everett {
28 // Persistent owner of an exact collection of objects and their additive
29 // contributions. A contribution is not necessarily the hash of the object's
30 // own native records: an update that deletes a key contributes its negative
31 // old binding even when its sole native record is a zero-hash tombstone.
32 template <class T, class A = wrapping_fingerprint_algebra> struct pin_set {
33 using element = typename A::element;
34 using pin = std::shared_ptr<T const>;
35
36 struct entry {
37 // An exact object identity, assigned by the object store, never inferred
38 // from a collision-prone logical signature. Different objects may have
39 // equal contributions and equal own-record signatures.
40 std::string object_id;
42 element contribution = A::zero();
43 std::optional<element> native_signature;
44 };
45
46 pin_set() : state_(std::make_shared<state>()) {}
47
48 std::size_t size() const noexcept { return state_->entries.size(); }
49 bool empty() const noexcept { return state_->entries.empty(); }
50 std::span<entry const> entries() const noexcept { return state_->entries; }
51 std::span<pin const> pins() const noexcept { return state_->pins; }
52 element signature() const { return state_->signature; }
53
54 entry const * find(std::string_view object_id) const noexcept {
55 auto found = std::find_if(state_->entries.begin(), state_->entries.end(),
56 [&](entry const & candidate) { return candidate.object_id == object_id; });
57 return found == state_->entries.end() ? nullptr : &*found;
58 }
59
61 auto result = A::zero();
62 for (auto const & item : state_->entries) result = A::add(result, item.contribution);
63 return result;
64 }
65
66 pin_set add(entry item) const {
67 validate(item);
68 if (find(item.object_id)) throw std::invalid_argument("duplicate pinned object identity");
69 auto next = std::make_shared<state>(*state_);
70 next->signature = A::add(next->signature, item.contribution);
71 next->pins.push_back(item.object);
72 next->entries.push_back(std::move(item));
73 return pin_set(std::move(next));
74 }
75
76 pin_set remove(std::string_view object_id) const {
77 auto old = find(object_id);
78 if (!old) throw std::invalid_argument("removing an unpinned object identity");
79 auto next = std::make_shared<state>();
80 next->signature = A::subtract(state_->signature, old->contribution);
81 for (auto const & item : state_->entries) {
82 if (item.object_id == object_id) continue;
83 next->entries.push_back(item);
84 next->pins.push_back(item.object);
85 }
86 return pin_set(std::move(next));
87 }
88
89 // Replace a physical composition while preserving its logical contribution.
90 // All expected identities must still exist, exactly once. A new immutable
91 // owner is returned only after validating the complete replacement; old
92 // readers retain the original objects and metadata without synchronization.
93 pin_set replace(std::span<std::string const> expected_inputs,
94 std::vector<entry> replacements) const {
95 std::vector<std::string_view> inputs;
96 inputs.reserve(expected_inputs.size());
97 auto removed = A::zero();
98 for (auto const & object_id : expected_inputs) {
99 if (std::find(inputs.begin(), inputs.end(), object_id) != inputs.end())
100 throw std::invalid_argument("duplicate expected pinned input");
101 auto old = find(object_id);
102 if (!old) throw std::invalid_argument("expected pinned input is absent");
103 inputs.push_back(object_id);
104 removed = A::add(removed, old->contribution);
105 }
106
107 auto is_input = [&](std::string_view object_id) {
108 return std::find(inputs.begin(), inputs.end(), object_id) != inputs.end();
109 };
110 auto added = A::zero();
111 for (std::size_t i = 0; i < replacements.size(); ++i) {
112 auto const & item = replacements[i];
113 validate(item);
114 if (find(item.object_id))
115 throw std::invalid_argument("replacement must name a new immutable object");
116 for (std::size_t j = 0; j < i; ++j)
117 if (replacements[j].object_id == item.object_id)
118 throw std::invalid_argument("duplicate replacement object identity");
119 added = A::add(added, item.contribution);
120 }
121 if (added != removed)
122 throw std::invalid_argument("replacement changes the pinned contribution sum");
123
124 auto next = std::make_shared<state>();
125 next->signature = A::add(A::subtract(state_->signature, removed), added);
126 // Keep unrelated entries in their original order. The owner's sum is
127 // order independent; semantic recency/order belongs to its consumer.
128 for (auto const & item : state_->entries) {
129 if (is_input(item.object_id)) continue;
130 next->entries.push_back(item);
131 next->pins.push_back(item.object);
132 }
133 for (auto & item : replacements) {
134 next->pins.push_back(item.object);
135 next->entries.push_back(std::move(item));
136 }
137 return pin_set(std::move(next));
138 }
139
140 private:
141 struct state {
142 std::vector<entry> entries;
143 // Cached projection used by snapshot callers that only need the pins.
144 std::vector<pin> pins;
145 element signature = A::zero();
146 };
147
148 static void validate(entry const & item) {
149 if (item.object_id.empty()) throw std::invalid_argument("empty pinned object identity");
150 if (!item.object) throw std::invalid_argument("null pinned object");
151 }
152
153 explicit pin_set(std::shared_ptr<state const> value) : state_(std::move(value)) {}
154 std::shared_ptr<state const> state_;
155 };
156}
Declares Everett's fingerprint support.
Definition active_engine.h:18
Definition object_path.h:38
object_id(std::string hex)
Definition object_path.h:39
Definition pins.h:36
std::string object_id
Definition pins.h:40
std::optional< element > native_signature
Definition pins.h:43
element contribution
Definition pins.h:42
pin object
Definition pins.h:41
Definition pins.h:141
element signature
Definition pins.h:145
std::vector< pin > pins
Definition pins.h:144
std::vector< entry > entries
Definition pins.h:142
Definition pins.h:32
std::shared_ptr< T const > pin
Definition pins.h:34
pin_set()
Definition pins.h:46
bool empty() const noexcept
Definition pins.h:49
pin_set remove(std::string_view object_id) const
Definition pins.h:76
std::size_t size() const noexcept
Definition pins.h:48
std::shared_ptr< state const > state_
Definition pins.h:154
element signature() const
Definition pins.h:52
typename A::element element
Definition pins.h:33
pin_set add(entry item) const
Definition pins.h:66
pin_set(std::shared_ptr< state const > value)
Definition pins.h:153
std::span< entry const > entries() const noexcept
Definition pins.h:50
static void validate(entry const &item)
Definition pins.h:148
entry const * find(std::string_view object_id) const noexcept
Definition pins.h:54
pin_set replace(std::span< std::string const > expected_inputs, std::vector< entry > replacements) const
Definition pins.h:93
element recompute_signature() const
Definition pins.h:60
std::span< pin const > pins() const noexcept
Definition pins.h:51