use crate::error::CatalogError;
use crate::{Catalog, IndexKind, IndexSpec};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Partitioning {
#[default]
Local,
Global {
splits: Vec<Vec<u8>>,
},
}
static LOCAL: Partitioning = Partitioning::Local;
impl Partitioning {
pub fn is_global(&self) -> bool {
matches!(self, Partitioning::Global { .. })
}
pub fn partitions(&self) -> usize {
match self {
Partitioning::Local => 1,
Partitioning::Global { splits } => splits.len() + 1,
}
}
pub fn partition_of(&self, enc: &[u8]) -> usize {
match self {
Partitioning::Local => 0,
Partitioning::Global { splits } => splits.partition_point(|s| s.as_slice() <= enc),
}
}
pub fn split_values(&self, ty: crate::ValType) -> Vec<Vec<u8>> {
let Partitioning::Global { splits } = self else { return Vec::new() };
splits.iter().map(|s| decode_order_key(ty, s)).collect()
}
}
pub fn splits_from_weighted(mut points: Vec<(Vec<u8>, u64)>, parts: usize) -> Vec<Vec<u8>> {
points.sort_unstable_by(|a, b| a.0.cmp(&b.0));
let mut distinct: Vec<(Vec<u8>, u64)> = Vec::with_capacity(points.len());
for (v, w) in points {
match distinct.last_mut() {
Some((last, lw)) if *last == v => *lw += w,
_ => distinct.push((v, w)),
}
}
let mut before = Vec::with_capacity(distinct.len() + 1);
let mut acc = 0u128;
for (_, w) in &distinct {
before.push(acc);
acc += u128::from(*w);
}
before.push(acc);
let parts = parts.max(1) as u128;
let mut out: Vec<Vec<u8>> = Vec::with_capacity(parts as usize - 1);
let mut j = 1;
for k in 1..parts {
let target = acc * k / parts;
while j + 1 < distinct.len() && before[j + 1] <= target {
j += 1;
}
let pick = if j + 1 < distinct.len()
&& before[j + 1] - target < target.saturating_sub(before[j])
{
j + 1
} else {
j
};
let Some((v, _)) = distinct.get(pick) else { break };
if out.last() < Some(v) {
out.push(v.clone());
}
}
out
}
pub(crate) fn parse_split_point(spec: &IndexSpec, raw: &[u8]) -> Option<Vec<u8>> {
if spec.composite.is_none() {
return crate::order_key(spec.ty, raw);
}
let hex = raw.strip_prefix(b"0x")?;
if hex.len() % 2 != 0 {
return None;
}
let digit = |c: u8| (c as char).to_digit(16).map(|d| d as u8);
hex.chunks(2).map(|p| Some(digit(p[0])? << 4 | digit(p[1])?)).collect()
}
pub(crate) fn split_point_text(spec: &IndexSpec, enc: &[u8]) -> Vec<u8> {
if spec.composite.is_none() {
return decode_order_key(spec.ty, enc);
}
let mut out = b"0x".to_vec();
for b in enc {
out.extend_from_slice(format!("{b:02x}").as_bytes());
}
out
}
fn decode_order_key(ty: crate::ValType, enc: &[u8]) -> Vec<u8> {
let word = || u64::from_be_bytes(enc.try_into().unwrap_or([0; 8]));
match ty {
crate::ValType::I64 => (((word() ^ (1 << 63)) as i64).to_string()).into_bytes(),
crate::ValType::F64 => {
let m = word();
let bits = if m >> 63 == 1 { m & !(1 << 63) } else { !m };
f64::from_bits(bits).to_string().into_bytes()
}
_ => enc.to_vec(),
}
}
pub fn partition_owner(name: &[u8], p: usize, n: usize) -> usize {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for &b in name {
h = (h ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3);
}
(p + (h % n as u64) as usize) % n
}
fn global_guard(spec: &IndexSpec, splits: &[Vec<u8>]) -> Result<(), CatalogError> {
if !matches!(spec.kind, IndexKind::Range | IndexKind::Unique) {
return Err(CatalogError::GlobalNeedsOrder);
}
if splits.windows(2).any(|w| w[0] >= w[1]) {
return Err(CatalogError::SplitsOutOfOrder);
}
Ok(())
}
impl Catalog {
pub fn create_with(
&mut self,
spec: IndexSpec,
partitioning: Partitioning,
) -> Result<(), CatalogError> {
if let Partitioning::Global { splits } = &partitioning {
global_guard(&spec, splits)?;
}
let name = spec.name.clone();
self.create(spec)?;
if partitioning.is_global() {
self.parts.push((name, partitioning));
}
Ok(())
}
pub fn partitioning(&self, name: &[u8]) -> &Partitioning {
self.parts.iter().find(|(n, _)| n == name).map_or(&LOCAL, |(_, p)| p)
}
pub fn set_splits(&mut self, name: &[u8], splits: Vec<Vec<u8>>) -> bool {
if splits.windows(2).any(|w| w[0] >= w[1]) {
return false;
}
match self.parts.iter_mut().find(|(n, _)| n == name) {
Some((_, p)) => {
*p = Partitioning::Global { splits };
true
}
None => false,
}
}
}
impl IndexSpec {
pub fn parse_split_point(&self, raw: &[u8]) -> Option<Vec<u8>> {
parse_split_point(self, raw)
}
pub fn split_point_text(&self, enc: &[u8]) -> Vec<u8> {
split_point_text(self, enc)
}
}