Atomic compare-and-swap. If current value equals `expected`, sets to `new_value` and returns success. If current value differs, returns the actual current value. If key doesn't exist and `expected` is empty, creates the key (create-if-not-exists).
(
&mut self,
tenant_id: u64,
collection: &str,
key: &[u8],
expected: &[u8],
new_value: &[u8],
now_ms: u64,
)
| 150 | /// If current value differs, returns the actual current value. |
| 151 | /// If key doesn't exist and `expected` is empty, creates the key (create-if-not-exists). |
| 152 | pub fn cas( |
| 153 | &mut self, |
| 154 | tenant_id: u64, |
| 155 | collection: &str, |
| 156 | key: &[u8], |
| 157 | expected: &[u8], |
| 158 | new_value: &[u8], |
| 159 | now_ms: u64, |
| 160 | ) -> CasResult { |
| 161 | let tkey = table_key(tenant_id, collection); |
| 162 | let table = self.ensure_table(tkey, tenant_id, collection); |
| 163 | |
| 164 | let current = table.get(key, now_ms).map(|v| v.to_vec()); |
| 165 | |
| 166 | // For typed KV entries (maps), compare against the first non-key string field. |
| 167 | let matches = match ¤t { |
| 168 | None => expected.is_empty(), |
| 169 | Some(v) => { |
| 170 | if v.as_slice() == expected { |
| 171 | true |
| 172 | } else if let Ok(nodedb_types::Value::Object(map)) = |
| 173 | nodedb_types::value_from_msgpack(v) |
| 174 | { |
| 175 | // Compare expected string against the first non-key string field. |
| 176 | let expected_str = String::from_utf8_lossy(expected); |
| 177 | map.iter().any(|(k, val)| { |
| 178 | k != "key" |
| 179 | && matches!(val, nodedb_types::Value::String(s) if s == expected_str.as_ref()) |
| 180 | }) |
| 181 | } else { |
| 182 | false |
| 183 | } |
| 184 | } |
| 185 | }; |
| 186 | |
| 187 | if matches { |
| 188 | // For typed KV: update the field value within the map. |
| 189 | let write_bytes = if let Some(ref cur) = current |
| 190 | && let Ok(nodedb_types::Value::Object(mut map)) = |
| 191 | nodedb_types::value_from_msgpack(cur) |
| 192 | && map.len() > 1 |
| 193 | { |
| 194 | let new_str = String::from_utf8_lossy(new_value).to_string(); |
| 195 | let mut updated = false; |
| 196 | for (k, v) in map.iter_mut() { |
| 197 | if k == "key" { |
| 198 | continue; |
| 199 | } |
| 200 | if matches!(v, nodedb_types::Value::String(_)) { |
| 201 | *v = nodedb_types::Value::String(new_str.clone()); |
| 202 | updated = true; |
| 203 | break; |
| 204 | } |
| 205 | } |
| 206 | if updated { |
| 207 | nodedb_types::value_to_msgpack(&nodedb_types::Value::Object(map)) |
| 208 | .unwrap_or_else(|_| new_value.to_vec()) |
| 209 | } else { |