use crate::error::{errorstring, SkyhashError};
use crate::types::{Array, FlatElement};
use crate::Element;
use crate::IntoSkyhashBytes;
use crate::Query;
use crate::RespCode;
use crate::SkyResult;
cfg_async! {
use crate::AsyncResult;
use crate::actions::AsyncSocket;
}
cfg_sync! {
use crate::actions::SyncSocket;
}
#[non_exhaustive]
#[derive(Debug, PartialEq)]
pub enum KeymapType {
Str,
Binstr,
Other(String),
}
pub enum WhereAmI {
Keyspace(String),
Table(String, String),
}
impl WhereAmI {
pub fn into_entity_repr(self) -> String {
match self {
Self::Keyspace(ks) => ks,
Self::Table(ks, tbl) => {
format!("{ks}:{tbl}", ks = ks, tbl = tbl)
}
}
}
}
impl KeymapType {
fn priv_to_string(&self) -> String {
match self {
Self::Str => "str".to_owned(),
Self::Binstr => "binstr".to_owned(),
Self::Other(oth) => oth.clone(),
}
}
}
#[derive(Debug, PartialEq)]
pub struct Keymap {
entity: String,
ktype: Option<KeymapType>,
vtype: Option<KeymapType>,
volatile: bool,
}
impl Keymap {
pub fn new(entity: impl AsRef<str>) -> Self {
Self {
entity: entity.as_ref().to_owned(),
ktype: None,
vtype: None,
volatile: false,
}
}
pub fn set_ktype(mut self, ktype: KeymapType) -> Self {
self.ktype = Some(ktype);
self
}
pub fn set_vtype(mut self, vtype: KeymapType) -> Self {
self.vtype = Some(vtype);
self
}
pub fn set_volatile(mut self) -> Self {
self.volatile = true;
self
}
}
pub trait CreateTableIntoQuery: Send + Sync {
fn into_query(self) -> Query;
}
impl CreateTableIntoQuery for Keymap {
fn into_query(self) -> Query {
let arg = format!(
"keymap({ktype},{vtype})",
ktype = self
.ktype
.as_ref()
.unwrap_or(&KeymapType::Binstr)
.priv_to_string(),
vtype = self
.vtype
.as_ref()
.unwrap_or(&KeymapType::Binstr)
.priv_to_string(),
);
let q = Query::from("CREATE").arg("TABLE").arg(self.entity).arg(arg);
if self.volatile {
q.arg("volatile")
} else {
q
}
}
}
macro_rules! implement_ddl {
(
$(
$(#[$attr:meta])+
fn $name:ident$(<$($tyargs:ident : $ty:ident $(+$tye:lifetime)*),*>)?(
$($argname:ident: $argty:ty),*) -> $ret:ty {
$($block:block)?
$($($mtch:pat)|+ => $expect:expr),+
}
)*
) => {
#[cfg(feature = "sync")]
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
pub trait Ddl: SyncSocket {
$(
$(#[$attr])*
#[inline]
fn $name<'s, $($($tyargs: $ty $(+$tye)*, )*)?>(&'s mut self $(, $argname: $argty)*) -> SkyResult<$ret> {
gen_match!(self.run($($block)?), $($($mtch)+, $expect),*)
}
)*
}
#[cfg(feature = "aio")]
#[cfg_attr(docsrs, doc(cfg(feature = "aio")))]
pub trait AsyncDdl: AsyncSocket {
$(
$(#[$attr])*
#[inline]
fn $name<'s, $($($tyargs: $ty $(+$tye)*, )*)?>(&'s mut self $(, $argname: $argty)*) -> AsyncResult<SkyResult<$ret>> {
Box::pin(async move {gen_match!(self.run($($block)?).await, $($($mtch)+, $expect),*)})
}
)*
}
};
}
cfg_async! {
impl<T> AsyncDdl for T where T: AsyncSocket {}
}
cfg_sync! {
impl<T> Ddl for T where T: SyncSocket {}
}
implement_ddl! {
fn switch<T: IntoSkyhashBytes + 's>(entity: T) -> () {
{ Query::from("use").arg(entity) }
Element::RespCode(RespCode::Okay) => ()
}
fn create_keyspace(ks: impl IntoSkyhashBytes + 's) -> bool {
{ Query::from("CREATE").arg("KEYSPACE").arg(ks) }
Element::RespCode(RespCode::Okay) => true,
Element::RespCode(RespCode::ErrorString(estr)) => match_estr! {
estr,
errorstring::ERR_ALREADY_EXISTS => false
}
}
fn create_table(table: impl CreateTableIntoQuery + 's) -> () {
{ table.into_query() }
Element::RespCode(RespCode::Okay) => ()
}
fn drop_table(table: impl IntoSkyhashBytes + 's) -> bool {
{ Query::from("DROP").arg("TABLE").arg(table) }
Element::RespCode(RespCode::Okay) => true,
Element::RespCode(RespCode::ErrorString(estr)) => match_estr! {
estr,
errorstring::CONTAINER_NOT_FOUND => false
}
}
fn drop_keyspace(keyspace: impl IntoSkyhashBytes + 's, force: bool) -> () {
{
let q = Query::from("DROP").arg("KEYSPACE").arg(keyspace);
if force {
q.arg("force")
} else {
q
}
}
Element::RespCode(RespCode::Okay) => {}
}
fn whereami() -> WhereAmI {
{
Query::from("whereami")
}
Element::Array(
Array::Flat(mut frr)
) => {
if frr.iter().all(|v| matches!(v, FlatElement::String(_))) {
return Err(SkyhashError::InvalidResponse.into());
}
match frr.len() {
1 => WhereAmI::Keyspace(match frr.swap_remove(0) {
FlatElement::String(st) => st,
_ => unsafe {
core::hint::unreachable_unchecked()
}
}),
2 => {
let (ks, tbl) = match (frr.swap_remove(0), frr.swap_remove(1)) {
(FlatElement::String(ks),FlatElement::String(tbl)) => (ks, tbl),
_ => unsafe {
core::hint::unreachable_unchecked()
}
};
WhereAmI::Table(ks, tbl)
}
_ => return Err(SkyhashError::InvalidResponse.into()),
}
}
}
}
#[cfg(test)]
mod tests {
use super::{CreateTableIntoQuery, Keymap, KeymapType};
#[test]
fn test_query_generation_from_keymap() {
let qgen = Keymap::new("mytbl")
.set_ktype(KeymapType::Str)
.set_vtype(KeymapType::Binstr)
.into_query();
let qman = crate::query!("CREATE", "TABLE", "mytbl", "keymap(str,binstr)");
assert_eq!(qgen, qman);
}
}