use crate::{Dialect, Error, ExecOutcome, Executor, Result, Row, ShardCodec, ShardRoute};
use sea_query::Value;
pub type ShardId = u32;
pub const SHARD_NAME_WIDTH: usize = 4;
pub fn shard_db_name(family: &str, shard: ShardId) -> String {
format!("{family}_shard_{shard:0w$}", w = SHARD_NAME_WIDTH)
}
pub fn directory_db_name(family: &str) -> String {
format!("{family}_directory")
}
pub trait ShardLocator {
fn locate(&self, key: u64) -> ShardId;
}
impl ShardLocator for ShardCodec {
fn locate(&self, key: u64) -> ShardId {
self.shard_of(key)
}
}
#[derive(Clone, Copy, Debug)]
pub struct HashLocator {
pub shards: u32,
}
impl HashLocator {
pub const fn new(shards: u32) -> Self {
Self { shards }
}
}
impl ShardLocator for HashLocator {
fn locate(&self, key: u64) -> ShardId {
(mix64(key) % self.shards.max(1) as u64) as u32
}
}
fn mix64(key: u64) -> u64 {
let mut x = key;
x = (x ^ (x >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
x = (x ^ (x >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
x ^ (x >> 31)
}
#[derive(Clone, Debug)]
pub struct SlotLocator {
slots: u32,
map: Vec<ShardId>,
}
impl SlotLocator {
pub fn new(map: Vec<ShardId>) -> Self {
Self { slots: map.len().max(1) as u32, map }
}
pub fn single(slots: u32, shard: ShardId) -> Self {
Self::new(vec![shard; slots.max(1) as usize])
}
pub fn slot_of(&self, key: u64) -> u32 {
(mix64(key) % self.slots as u64) as u32
}
pub fn map_mut(&mut self) -> &mut Vec<ShardId> {
&mut self.map
}
}
impl ShardLocator for SlotLocator {
fn locate(&self, key: u64) -> ShardId {
self.map[self.slot_of(key) as usize]
}
}
pub trait ShardCatalog {
type Exec: Executor;
fn global(&self) -> &Self::Exec;
fn shard(&self, id: ShardId) -> Option<&Self::Exec>;
fn shard_ids(&self) -> Vec<ShardId>;
}
#[derive(Clone)]
pub struct ShardedExecutor<C, L = ShardCodec> {
catalog: C,
locator: L,
dialect: Dialect,
family: String,
}
impl<C: ShardCatalog, L: ShardLocator> ShardedExecutor<C, L> {
pub fn new(catalog: C, locator: L, family: impl Into<String>) -> Self {
let dialect = catalog.global().dialect();
Self { catalog, locator, dialect, family: family.into() }
}
pub fn catalog(&self) -> &C {
&self.catalog
}
pub fn locator(&self) -> &L {
&self.locator
}
pub fn shard_name(&self, shard: ShardId) -> String {
shard_db_name(&self.family, shard)
}
fn pick(&self, route: ShardRoute) -> Result<&C::Exec> {
match route {
ShardRoute::Global => Ok(self.catalog.global()),
ShardRoute::Key(id) => {
let s = self.locator.locate(id);
self.catalog
.shard(s)
.ok_or_else(|| Error::msg(format!("no executor for shard {s} (id {id})")))
}
}
}
}
impl<C: ShardCatalog, L: ShardLocator> Executor for ShardedExecutor<C, L> {
fn dialect(&self) -> Dialect {
self.dialect
}
fn shard_family(&self) -> Option<&str> {
Some(&self.family)
}
async fn fetch_all(&self, sql: &str, params: Vec<Value>) -> Result<Vec<Box<dyn Row>>> {
self.catalog.global().fetch_all(sql, params).await
}
async fn execute(&self, sql: &str, params: Vec<Value>) -> Result<ExecOutcome> {
self.catalog.global().execute(sql, params).await
}
async fn fetch_all_routed(
&self,
route: ShardRoute,
sql: &str,
params: Vec<Value>,
) -> Result<Vec<Box<dyn Row>>> {
self.pick(route)?.fetch_all(sql, params).await
}
async fn execute_routed(
&self,
route: ShardRoute,
sql: &str,
params: Vec<Value>,
) -> Result<ExecOutcome> {
self.pick(route)?.execute(sql, params).await
}
}
#[derive(Clone)]
pub struct MapCatalog<X> {
global: X,
shards: Vec<(ShardId, X)>,
}
impl<X: Executor> MapCatalog<X> {
pub fn new(global: X) -> Self {
Self { global, shards: Vec::new() }
}
pub fn with_shard(mut self, id: ShardId, exec: X) -> Self {
self.shards.push((id, exec));
self
}
}
impl<X: Executor> ShardCatalog for MapCatalog<X> {
type Exec = X;
fn global(&self) -> &X {
&self.global
}
fn shard(&self, id: ShardId) -> Option<&X> {
self.shards.iter().find(|(i, _)| *i == id).map(|(_, e)| e)
}
fn shard_ids(&self) -> Vec<ShardId> {
self.shards.iter().map(|(i, _)| *i).collect()
}
}