35 lines
1.3 KiB
Rust
35 lines
1.3 KiB
Rust
//! Generic read/write for [`crate::models::game_ext`] opaque state.
|
|
//!
|
|
//! Core never interprets the payload. Writes happen INSIDE the owning entity's
|
|
//! transaction (see `squad::replace_squad_with_extension`) so the canonical
|
|
//! entity and its opaque extension commit atomically — there is deliberately no
|
|
//! standalone "write extension" entry point that could desync the two.
|
|
|
|
use crate::db::Pool;
|
|
use crate::error::AppResult;
|
|
use crate::models::game_ext::GameEntityExt;
|
|
|
|
/// Fetch the stored opaque extension for a scoped entity, or `None`. The caller
|
|
/// compares `canonical_fingerprint` against the entity's *current* fingerprint to
|
|
/// decide freshness — this layer does not know how to fingerprint any entity.
|
|
pub async fn get_ext(
|
|
pool: &Pool,
|
|
game_id: &str,
|
|
entity_kind: &str,
|
|
entity_id: &str,
|
|
namespace: &str,
|
|
) -> AppResult<Option<GameEntityExt>> {
|
|
let row = sqlx::query_as::<_, GameEntityExt>(
|
|
"SELECT game_id, entity_kind, entity_id, namespace, schema_version, \
|
|
canonical_fingerprint, payload, updated_at FROM game_entity_ext \
|
|
WHERE game_id = ? AND entity_kind = ? AND entity_id = ? AND namespace = ?",
|
|
)
|
|
.bind(game_id)
|
|
.bind(entity_kind)
|
|
.bind(entity_id)
|
|
.bind(namespace)
|
|
.fetch_optional(pool)
|
|
.await?;
|
|
Ok(row)
|
|
}
|