use crate::models::effective_partition_key::EffectivePartitionKey;
use crate::models::range::EpkRange;
use crate::models::ETag;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PartitionKeyRange {
#[serde(rename = "id")]
pub id: String,
#[serde(rename = "_rid", skip_serializing_if = "Option::is_none")]
pub resource_id: Option<String>,
#[serde(rename = "_self", skip_serializing_if = "Option::is_none")]
pub self_link: Option<String>,
#[serde(rename = "_etag", skip_serializing_if = "Option::is_none")]
pub etag: Option<ETag>,
#[serde(rename = "_ts", skip_serializing_if = "Option::is_none")]
pub timestamp: Option<i64>,
#[serde(rename = "minInclusive")]
pub min_inclusive: EffectivePartitionKey,
#[serde(rename = "maxExclusive")]
pub max_exclusive: EffectivePartitionKey,
#[serde(rename = "ridPrefix", skip_serializing_if = "Option::is_none")]
pub rid_prefix: Option<i32>,
#[serde(rename = "throughputFraction", default)]
pub throughput_fraction: f64,
#[serde(rename = "targetThroughput", skip_serializing_if = "Option::is_none")]
pub target_throughput: Option<f64>,
#[serde(rename = "status", default)]
pub(crate) status: PartitionKeyRangeStatus,
#[serde(rename = "_lsn", default)]
pub lsn: i64,
#[serde(rename = "parents", skip_serializing_if = "Option::is_none")]
pub parents: Option<Vec<String>>,
#[serde(
rename = "ownedArchivalPKRangeIds",
skip_serializing_if = "Option::is_none"
)]
pub owned_archival_pk_range_ids: Option<Vec<String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub(crate) enum PartitionKeyRangeStatus {
#[default]
Online,
Splitting,
Offline,
Split,
}
impl PartitionKeyRange {
pub fn new(
id: String,
min_inclusive: impl Into<EffectivePartitionKey>,
max_exclusive: impl Into<EffectivePartitionKey>,
) -> Self {
Self {
id,
resource_id: None,
self_link: None,
etag: None,
timestamp: None,
min_inclusive: min_inclusive.into(),
max_exclusive: max_exclusive.into(),
rid_prefix: None,
throughput_fraction: 0.0,
target_throughput: None,
status: PartitionKeyRangeStatus::default(),
lsn: 0,
parents: None,
owned_archival_pk_range_ids: None,
}
}
pub(crate) fn as_range(&self) -> EpkRange<&EffectivePartitionKey> {
EpkRange {
min: &self.min_inclusive,
max: &self.max_exclusive,
is_min_inclusive: true,
is_max_inclusive: false,
}
}
pub fn get_parent_ids(&self) -> HashSet<String> {
self.parents
.as_ref()
.map(|parents| parents.iter().cloned().collect())
.unwrap_or_default()
}
}
impl PartialEq for PartitionKeyRange {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.resource_id == other.resource_id
&& self.min_inclusive == other.min_inclusive
&& self.max_exclusive == other.max_exclusive
}
}
impl Eq for PartitionKeyRange {}
impl PartialOrd for PartitionKeyRange {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PartitionKeyRange {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.min_inclusive.cmp(&other.min_inclusive)
}
}
impl Hash for PartitionKeyRange {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
self.resource_id.hash(state);
self.min_inclusive.hash(state);
self.max_exclusive.hash(state);
}
}
#[derive(Debug, Deserialize)]
pub(crate) struct PkRangesResponse {
#[serde(rename = "PartitionKeyRanges")]
pub partition_key_ranges: Vec<PartitionKeyRange>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn partition_key_range_creation() {
let pkr = PartitionKeyRange::new("1".to_string(), "", "FF");
assert_eq!(pkr.id, "1");
assert_eq!(pkr.min_inclusive.as_str(), "");
assert_eq!(pkr.max_exclusive.as_str(), "FF");
}
#[test]
fn as_range() {
let pkr = PartitionKeyRange::new("1".to_string(), "00", "FF");
let range = pkr.as_range();
assert_eq!(range.min.as_str(), "00");
assert_eq!(range.max.as_str(), "FF");
assert!(range.is_min_inclusive);
assert!(!range.is_max_inclusive);
}
#[test]
fn equality_check() {
let pkr1 = PartitionKeyRange::new("1".to_string(), "00", "FF");
let mut pkr2 = PartitionKeyRange::new("1".to_string(), "00", "FF");
assert_eq!(pkr1, pkr2);
pkr2.id = "2".to_string();
assert_ne!(pkr1, pkr2);
}
#[test]
fn serialization() {
let pkr = PartitionKeyRange {
id: "1".to_string(),
resource_id: Some("rid123".to_string()),
self_link: None,
etag: None,
timestamp: Some(1234567890),
min_inclusive: EffectivePartitionKey::from(""),
max_exclusive: EffectivePartitionKey::from("FF"),
rid_prefix: Some(42),
throughput_fraction: 0.5,
target_throughput: Some(1000.0),
status: PartitionKeyRangeStatus::Online,
lsn: 100,
parents: Some(vec!["0".to_string()]),
owned_archival_pk_range_ids: None,
};
let json = serde_json::to_string(&pkr).unwrap();
let deserialized: PartitionKeyRange = serde_json::from_str(&json).unwrap();
assert_eq!(pkr, deserialized);
}
#[test]
fn range_overlap() {
let range1: EpkRange<String> =
EpkRange::new("00".to_string(), "50".to_string(), true, false);
let range2 = EpkRange::new("40".to_string(), "80".to_string(), true, false);
let range3 = EpkRange::new("60".to_string(), "90".to_string(), true, false);
assert!(EpkRange::check_overlapping(&range1, &range2));
assert!(!EpkRange::check_overlapping(&range1, &range3));
}
}