linkrs_api/
session_variables.rs1use parking_lot::RwLock;
5use std::collections::HashMap;
6
7use linkrs_core::Value;
8
9#[derive(Debug, Clone)]
15enum VariableOp {
16 Set {
17 name: String,
18 prev: Option<Value>,
19 value: Value,
20 },
21 Savepoint {
22 name: String,
23 },
24}
25
26#[derive(Debug, Default)]
34pub struct SessionVariables {
35 base: RwLock<HashMap<String, Value>>,
36 ops: RwLock<Vec<VariableOp>>,
37}
38
39impl SessionVariables {
40 pub fn new() -> Self {
41 Self::default()
42 }
43
44 pub fn set_variable(&self, name: String, value: Value, in_transaction: bool) {
48 if in_transaction {
49 let prev = self.variable_value(&name);
50 self.ops.write().push(VariableOp::Set { name, prev, value });
51 } else {
52 self.base.write().insert(name, value);
53 }
54 }
55
56 pub fn variable_value(&self, name: &str) -> Option<Value> {
59 let ops = self.ops.read();
60 for op in ops.iter().rev() {
61 if let VariableOp::Set {
62 name: op_name,
63 value,
64 ..
65 } = op
66 {
67 if op_name == name {
68 return Some(value.clone());
69 }
70 }
71 }
72 self.base.read().get(name).cloned()
73 }
74
75 pub fn variables_snapshot(&self) -> HashMap<String, Value> {
78 let mut merged = self.base.read().clone();
79 for op in self.ops.read().iter() {
80 if let VariableOp::Set { name, value, .. } = op {
81 merged.insert(name.clone(), value.clone());
82 }
83 }
84 merged
85 }
86
87 pub fn commit_variables(&self) {
90 let mut base = self.base.write();
91 let ops = std::mem::take(&mut *self.ops.write());
92 for op in ops {
93 if let VariableOp::Set { name, value, .. } = op {
94 base.insert(name, value);
95 }
96 }
97 }
98
99 pub fn rollback_variables(&self) {
101 self.restore_overlay_after(None);
102 }
103
104 pub fn rollback_variables_to(&self, savepoint_name: &str) -> bool {
107 self.restore_overlay_after(Some(savepoint_name))
108 }
109
110 pub fn release_variable_savepoint(&self, savepoint_name: &str) {
113 let mut ops = self.ops.write();
114 ops.retain(|op| !matches!(op, VariableOp::Savepoint { name } if name == savepoint_name));
115 }
116
117 pub fn push_variable_savepoint(&self, savepoint_name: &str) {
120 self.ops.write().push(VariableOp::Savepoint {
121 name: savepoint_name.to_string(),
122 });
123 }
124
125 fn restore_overlay_after(&self, savepoint_name: Option<&str>) -> bool {
129 let mut base = self.base.write();
130 let mut ops = self.ops.write();
131 let removed: Vec<VariableOp> = match savepoint_name {
132 Some(name) => {
133 let mut found = None;
134 for (idx, op) in ops.iter().enumerate() {
135 if matches!(op, VariableOp::Savepoint { name: n } if n == name) {
136 found = Some(idx + 1);
137 }
138 }
139 match found {
140 Some(idx) => ops.drain(idx..).collect(),
141 None => return false,
142 }
143 }
144 None => std::mem::take(&mut *ops),
145 };
146 for op in removed.iter().rev() {
147 if let VariableOp::Set { name, prev, .. } = op {
148 match prev {
149 Some(value) => {
150 base.insert(name.clone(), value.clone());
151 }
152 None => {
153 base.remove(name);
154 }
155 }
156 }
157 }
158 true
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 fn store() -> SessionVariables {
167 SessionVariables::new()
168 }
169
170 #[test]
171 fn set_outside_transaction_mutates_base() {
172 let s = store();
173 s.set_variable("x".into(), Value::Int(1), false);
174 assert_eq!(s.variable_value("x"), Some(Value::Int(1)));
175 assert_eq!(s.variables_snapshot().len(), 1);
176 }
177
178 #[test]
179 fn rollback_restores_pre_transaction_value() {
180 let s = store();
181 s.set_variable("x".into(), Value::Int(1), false);
182 s.set_variable("x".into(), Value::Int(2), true);
183 s.set_variable("y".into(), Value::string("txn"), true);
184 assert_eq!(s.variable_value("x"), Some(Value::Int(2)));
185 assert_eq!(s.variable_value("y"), Some(Value::string("txn")));
186 s.rollback_variables();
187 assert_eq!(s.variable_value("x"), Some(Value::Int(1)));
188 assert_eq!(s.variable_value("y"), None);
189 }
190
191 #[test]
192 fn commit_merges_overlay() {
193 let s = store();
194 s.set_variable("a".into(), Value::Int(1), false);
195 s.set_variable("a".into(), Value::Int(5), true);
196 s.commit_variables();
197 assert_eq!(s.variable_value("a"), Some(Value::Int(5)));
198 assert_eq!(s.variables_snapshot().len(), 1);
199 }
200
201 #[test]
202 fn rollback_to_savepoint_restores_after_marker() {
203 let s = store();
204 s.set_variable("a".into(), Value::Int(1), false);
205 s.push_variable_savepoint("sp1");
206 s.set_variable("a".into(), Value::Int(2), true);
207 s.set_variable("b".into(), Value::Int(3), true);
208 assert!(s.rollback_variables_to("sp1"));
209 assert_eq!(s.variable_value("a"), Some(Value::Int(1)));
210 assert_eq!(s.variable_value("b"), None);
211 s.set_variable("b".into(), Value::Int(4), true);
212 assert!(!s.rollback_variables_to("missing"));
213 assert_eq!(s.variable_value("b"), Some(Value::Int(4)));
214 }
215
216 #[test]
217 fn release_savepoint_drops_marker() {
218 let s = store();
219 s.push_variable_savepoint("sp2");
220 s.release_variable_savepoint("sp2");
221 assert!(!s.rollback_variables_to("sp2"));
222 }
223
224 #[test]
225 fn rollback_of_new_variable_removes_it() {
226 let s = store();
227 s.set_variable("new".into(), Value::Int(42), true);
228 s.rollback_variables();
229 assert_eq!(s.variable_value("new"), None);
230 }
231}