feat(kv): set_if compare-and-swap for KV (per-app + shared + SDK) (Track A M5)
No atomic CAS existed, so concurrent writers to the same KV key raced. Add set_if(key, expected, new): writes only when the current value equals expected, or (expected absent) only when the key is missing — the primitive for lock-free counters / optimistic concurrency. - KvService + GroupKvService traits gain set_if with a defaulted "unsupported" impl (Noop + other impls untouched); the real services override it. - kv_repo/group_kv_repo: atomic set_if — a single UPDATE ... WHERE value = $expected (JSONB semantic equality) or INSERT ... ON CONFLICT DO NOTHING. Atomic without a transaction; returns whether the write happened. - Services enforce the same value-size cap + (group) writes-authed + row/byte quotas as set; the event fires only on a successful swap. - Executor Rhai SDK: kv::collection(c).set_if(key, expected, new) and the shared_collection variant; Rhai () for expected means "only if absent". Pinned by kv_service/group_kv_service set_if unit tests (CAS + fail-closed) + sdk_kv::kv_set_if_compare_and_swap (through a real script). No migration. docs/sdk-shape.md documents the primitive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,25 @@ pub trait KvService: Send + Sync {
|
||||
value: serde_json::Value,
|
||||
) -> Result<(), KvError>;
|
||||
|
||||
/// Compare-and-swap. Atomically writes `new` only if the current value
|
||||
/// equals `expected` — or, when `expected` is `None`, only if the key is
|
||||
/// ABSENT (insert-if-absent). Returns `true` if the swap happened, `false`
|
||||
/// if the precondition failed (value differed / key already present).
|
||||
/// Default: unsupported, so only storage-backed impls opt in.
|
||||
async fn set_if(
|
||||
&self,
|
||||
cx: &SdkCallCx,
|
||||
collection: &str,
|
||||
key: &str,
|
||||
expected: Option<serde_json::Value>,
|
||||
new: serde_json::Value,
|
||||
) -> Result<bool, KvError> {
|
||||
let _ = (cx, collection, key, expected, new);
|
||||
Err(KvError::Backend(
|
||||
"set_if is not supported by this kv implementation".into(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn delete(&self, cx: &SdkCallCx, collection: &str, key: &str) -> Result<bool, KvError>;
|
||||
|
||||
async fn has(&self, cx: &SdkCallCx, collection: &str, key: &str) -> Result<bool, KvError>;
|
||||
|
||||
Reference in New Issue
Block a user