use serde::{Deserialize, Serialize};
use crate::{
ClientContext, ClientProtocolError, ClientRequest, Namespace, ReadConsistency, StructuredKey,
WriteConsistency,
};
pub const HIBERNATE_SUPPORTED_MAJOR: u8 = 6;
pub const HIBERNATE_SUPPORTED_RANGE: &str = "Hibernate ORM 6.x";
pub const HIBERNATE_CONTRACT_VERSION: u16 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum L2AccessMode {
ReadOnly,
NonStrictReadWrite,
ReadWrite,
Transactional,
}
impl L2AccessMode {
pub const fn consistency_mapping(self) -> L2ConsistencyMapping {
match self {
Self::ReadOnly => L2ConsistencyMapping {
label: L2ConsistencyLabel::StrongImmutable,
read: ReadConsistency::Strong,
write: None,
immutable: true,
invalidates_on_write: false,
invalidates_on_commit: false,
joins_jvm_transaction: false,
},
Self::NonStrictReadWrite => L2ConsistencyMapping {
label: L2ConsistencyLabel::BestEffortInvalidate,
read: ReadConsistency::Eventual,
write: Some(WriteConsistency::Local),
immutable: false,
invalidates_on_write: true,
invalidates_on_commit: false,
joins_jvm_transaction: false,
},
Self::ReadWrite | Self::Transactional => L2ConsistencyMapping {
label: L2ConsistencyLabel::InvalidateOnCommit,
read: ReadConsistency::Session,
write: Some(WriteConsistency::Quorum),
immutable: false,
invalidates_on_write: false,
invalidates_on_commit: true,
joins_jvm_transaction: false,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum L2ConsistencyLabel {
StrongImmutable,
BestEffortInvalidate,
InvalidateOnCommit,
}
impl L2ConsistencyLabel {
pub const fn as_str(self) -> &'static str {
match self {
Self::StrongImmutable => "strong-immutable",
Self::BestEffortInvalidate => "best-effort-invalidate",
Self::InvalidateOnCommit => "invalidate-on-commit",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct L2ConsistencyMapping {
pub label: L2ConsistencyLabel,
pub read: ReadConsistency,
pub write: Option<WriteConsistency>,
pub immutable: bool,
pub invalidates_on_write: bool,
pub invalidates_on_commit: bool,
pub joins_jvm_transaction: bool,
}
impl L2ConsistencyMapping {
pub fn client_context(self) -> ClientContext {
ClientContext {
session_token: None,
read: Some(self.read),
write: self.write,
preferred_region: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum HibernateRegionKind {
Entity,
Collection,
NaturalId,
Query,
Timestamps,
}
impl HibernateRegionKind {
pub const fn key_segment(self) -> &'static str {
match self {
Self::Entity => "entity",
Self::Collection => "collection",
Self::NaturalId => "natural-id",
Self::Query => "query",
Self::Timestamps => "timestamps",
}
}
pub const fn query_cache_behavior(self) -> QueryCacheBehavior {
match self {
Self::Query | Self::Timestamps => QueryCacheBehavior::TimestampBulkInvalidation,
Self::Entity | Self::Collection | Self::NaturalId => QueryCacheBehavior::NotQueryCache,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum QueryCacheBehavior {
TimestampBulkInvalidation,
NotQueryCache,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RegionMapping {
pub region: String,
pub ns: Namespace,
pub kind: HibernateRegionKind,
pub mode: L2AccessMode,
}
impl RegionMapping {
pub fn new(
region: impl Into<String>,
ns: Namespace,
kind: HibernateRegionKind,
mode: L2AccessMode,
) -> Result<Self, ClientProtocolError> {
let region = region.into();
if region.trim().is_empty() {
return Err(ClientProtocolError::InvalidField("hibernate_region"));
}
Ok(Self {
region,
ns,
kind,
mode,
})
}
pub fn from_region(
region: impl Into<String>,
kind: HibernateRegionKind,
mode: L2AccessMode,
) -> Result<Self, ClientProtocolError> {
let region = region.into();
let ns = Namespace::new(format!("hibernate:{}", region.trim()))?;
Self::new(region, ns, kind, mode)
}
pub const fn consistency_mapping(&self) -> L2ConsistencyMapping {
self.mode.consistency_mapping()
}
pub fn client_context(&self) -> ClientContext {
self.consistency_mapping().client_context()
}
pub fn key<I, S>(&self, segments: I) -> Result<StructuredKey, ClientProtocolError>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut key_segments = vec![self.kind.key_segment().to_owned()];
key_segments.extend(segments.into_iter().map(Into::into));
StructuredKey::new(key_segments)
}
pub fn get(&self, key: StructuredKey) -> ClientRequest {
ClientRequest::Get {
ns: self.ns.clone(),
key,
}
}
pub fn put(&self, key: StructuredKey, value: Vec<u8>, ttl_ms: Option<u64>) -> ClientRequest {
ClientRequest::Put {
ns: self.ns.clone(),
key,
value,
ttl_ms,
dimensions: vec![
"hibernate".to_owned(),
self.kind.key_segment().to_owned(),
self.region.clone(),
],
}
}
pub fn invalidate(&self, key: StructuredKey) -> ClientRequest {
ClientRequest::Invalidate {
ns: self.ns.clone(),
key,
}
}
pub fn evict_region(&self) -> ClientRequest {
ClientRequest::EvictRegion {
ns: self.ns.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct QueryCacheMapping {
pub query_region: RegionMapping,
pub timestamps_region: RegionMapping,
}
impl QueryCacheMapping {
pub fn new(
query_region: RegionMapping,
timestamps_region: RegionMapping,
) -> Result<Self, ClientProtocolError> {
if query_region.kind != HibernateRegionKind::Query {
return Err(ClientProtocolError::InvalidField("query_region_kind"));
}
if timestamps_region.kind != HibernateRegionKind::Timestamps {
return Err(ClientProtocolError::InvalidField("timestamps_region_kind"));
}
Ok(Self {
query_region,
timestamps_region,
})
}
pub fn bulk_update_evictions(&self) -> [ClientRequest; 2] {
[
self.query_region.evict_region(),
self.timestamps_region.evict_region(),
]
}
}