agentic_forge_core/query/
delta.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6pub enum ChangeType {
7 Created,
8 Updated,
9 Deleted,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Change<T> {
14 pub id: String,
15 pub change_type: ChangeType,
16 pub timestamp: i64,
17 pub value: Option<T>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct DeltaResult<T> {
22 pub changes: Vec<Change<T>>,
23 pub from_version: u64,
24 pub to_version: u64,
25 pub has_more: bool,
26}
27
28impl<T> DeltaResult<T> {
29 pub fn empty(version: u64) -> Self {
30 Self {
31 changes: Vec::new(),
32 from_version: version,
33 to_version: version,
34 has_more: false,
35 }
36 }
37
38 pub fn is_empty(&self) -> bool {
39 self.changes.is_empty()
40 }
41
42 pub fn len(&self) -> usize {
43 self.changes.len()
44 }
45}
46
47#[derive(Debug, Clone)]
48pub struct VersionedState {
49 version: u64,
50 timestamps: Vec<(String, i64, ChangeType)>,
51}
52
53impl VersionedState {
54 pub fn new() -> Self {
55 Self {
56 version: 0,
57 timestamps: Vec::new(),
58 }
59 }
60
61 pub fn record_change(&mut self, id: impl Into<String>, change_type: ChangeType) {
62 self.version += 1;
63 let ts = chrono::Utc::now().timestamp_micros();
64 self.timestamps.push((id.into(), ts, change_type));
65 }
66
67 pub fn version(&self) -> u64 {
68 self.version
69 }
70
71 pub fn changes_since(&self, since_ts: i64) -> Vec<&(String, i64, ChangeType)> {
72 self.timestamps
73 .iter()
74 .filter(|(_, ts, _)| *ts > since_ts)
75 .collect()
76 }
77
78 pub fn changes_since_version(&self, since_version: u64) -> Vec<&(String, i64, ChangeType)> {
79 if since_version as usize >= self.timestamps.len() {
80 return Vec::new();
81 }
82 self.timestamps[since_version as usize..].iter().collect()
83 }
84
85 pub fn last_change_timestamp(&self) -> i64 {
86 self.timestamps.last().map(|(_, ts, _)| *ts).unwrap_or(0)
87 }
88
89 pub fn is_unchanged_since(&self, since_version: u64) -> bool {
90 self.version <= since_version
91 }
92}
93
94impl Default for VersionedState {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_versioned_state_new() {
106 let state = VersionedState::new();
107 assert_eq!(state.version(), 0);
108 }
109
110 #[test]
111 fn test_versioned_state_record() {
112 let mut state = VersionedState::new();
113 state.record_change("entity_1", ChangeType::Created);
114 assert_eq!(state.version(), 1);
115 state.record_change("entity_2", ChangeType::Created);
116 assert_eq!(state.version(), 2);
117 }
118
119 #[test]
120 fn test_versioned_state_changes_since_version() {
121 let mut state = VersionedState::new();
122 state.record_change("a", ChangeType::Created);
123 state.record_change("b", ChangeType::Created);
124 state.record_change("c", ChangeType::Updated);
125
126 let changes = state.changes_since_version(1);
127 assert_eq!(changes.len(), 2); }
129
130 #[test]
131 fn test_versioned_state_unchanged() {
132 let mut state = VersionedState::new();
133 state.record_change("a", ChangeType::Created);
134 assert!(state.is_unchanged_since(1));
135 assert!(!state.is_unchanged_since(0));
136 }
137
138 #[test]
139 fn test_delta_result_empty() {
140 let result: DeltaResult<String> = DeltaResult::empty(5);
141 assert!(result.is_empty());
142 assert_eq!(result.from_version, 5);
143 }
144
145 #[test]
146 fn test_delta_proportional_cost() {
147 let mut state = VersionedState::new();
148 for i in 0..100 {
149 state.record_change(format!("item_{}", i), ChangeType::Created);
150 }
151 let v_before = state.version();
152
153 state.record_change("item_100", ChangeType::Created);
155
156 let all_changes = state.changes_since_version(0);
157 let delta_changes = state.changes_since_version(v_before);
158
159 assert_eq!(all_changes.len(), 101);
160 assert_eq!(delta_changes.len(), 1);
161 assert!(delta_changes.len() < all_changes.len() / 50);
163 }
164
165 #[test]
166 fn test_unchanged_state_free() {
167 let mut state = VersionedState::new();
168 state.record_change("a", ChangeType::Created);
169 let v = state.version();
170 let changes = state.changes_since_version(v);
172 assert!(changes.is_empty());
173 assert!(state.is_unchanged_since(v));
174 }
175}