kdb-connection 0.1.0

kDB connection.
Documentation
//! `kdQL` kDB Document Query Language

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::prelude::*;

#[derive(Deserialize, Serialize)]
#[serde(untagged)]
pub enum Value
{
    Integer(i64),
    Float(f64),
    String(String),
    Array(Vec<Value>),
    Map(ValueMap),
}

macro_rules! value_into {
    ($name:tt, $type:ty) => {
        impl From<$type> for Value
        {
            fn from(value: $type) -> Value
            {
                Value::$name(value.into())
            }
        }
    };
}

value_into!(Integer, i64);
value_into!(Float, f64);
value_into!(String, String);
value_into!(String, &str);
value_into!(Array, Vec<Value>);
value_into!(Map, ValueMap);

pub type ValueMap = HashMap<String, Value>;

/// Convenient macro for creating ValueMap.
///
/// Example:
///
/// ```rust
/// # use kdb_connection::{krql_value_map, queries::kdql::ValueMap};
/// let value_map: ValueMap = krql_value_map!("hello" => 12);
/// ```
#[macro_export]
macro_rules! krql_value_map {
  // map-like
  ($($k:expr => $v:expr),* $(,)?) => {
    {
      let value_map: $crate::queries::kdql::ValueMap = core::convert::From::from([$(($k.to_string(), $v.into()),)*]);
      value_map
    }
  };
}

#[derive(Deserialize, Serialize)]
#[serde(untagged)]
pub enum What
{
    #[serde(serialize_with = "serialize_all", deserialize_with = "deserialize_all")]
    All,
    Identifiers(Vec<String>),
}

fn serialize_all<S>(serializer: S) -> Result<S::Ok, S::Error>
where
    S: serde::ser::Serializer,
{
    serializer.serialize_str("*")
}

fn deserialize_all<'de, D>(deserializer: D) -> Result<(), D::Error>
where
    D: serde::de::Deserializer<'de>,
{
    use serde::de::Error;
    let s = String::deserialize(deserializer)?;
    if s == "*"
    {
        Ok(())
    }
    else
    {
        Err(D::Error::custom("Expected '*'"))
    }
}

impl From<Vec<&str>> for What
{
    fn from(value: Vec<&str>) -> Self
    {
        Self::Identifiers(value.into_iter().map(|x| x.to_owned()).collect())
    }
}

/// Define a kdQL query, according to https://auksys.org/documentation/5/query_languages/kdql/
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum KDQLQuery
{
    /// Create new documents
    Create
    {
        /// Store the documents in this collection
        into: String,
        /// List of documents to store
        documents: Vec<HashMap<String, Value>>,
    },
    /// Drop collections, as named in the vector
    Drop(Vec<String>),
    /// Retrieve content of a collection
    Retrieve
    {
        /// Content to return
        what: What,
        /// The collection where to retrieve documents from
        from: String,
        /// The condition that the documents should matches
        matches: HashMap<String, Value>,
    },
}

impl TryFrom<&KDQLQuery> for dbc::Query
{
    type Error = Error;
    fn try_from(value: &KDQLQuery) -> std::result::Result<Self, Self::Error>
    {
        // serde_saphyr incorrectly generate some queries, so serialize to json for now, https://gitlab.com/auksys/kdb/-/issues/38
        let kdql_query = serde_json::to_string(value)?;
        Ok(dbc::Query::new(
            kdql_query,
            Default::default(),
            dbc::QueryType::KDQL,
        ))
    }
}

impl TryFrom<KDQLQuery> for dbc::Query
{
    type Error = Error;
    fn try_from(value: KDQLQuery) -> std::result::Result<Self, Self::Error>
    {
        (&value).try_into()
    }
}

#[cfg(test)]
mod test
{
    use super::{KDQLQuery, What};
    #[test]
    fn test_kdql_create()
    {
        let query = KDQLQuery::Create {
            into: "test".into(),
            documents: vec![
                krql_value_map!("name" => "a"),
                krql_value_map!("name" => "b"),
            ],
        };
        assert_eq!(
            serde_saphyr::to_string(&query).unwrap(),
            r#"create:
  into: test
  documents:
    - name: a
    - name: b
"#
        );
    }
    #[test]
    fn test_kdql_retrieve_all()
    {
        let query = KDQLQuery::Retrieve {
            what: What::All,
            from: "test".into(),
            matches: krql_value_map!("name" => "a"),
        };
        assert_eq!(
            serde_saphyr::to_string(&query).unwrap(),
            r#"retrieve:
  what: "*"
  from: test
  matches:
    name: a
"#
        );
    }
    // #[test]
    // TODO This test fails due to a bug in serde_saphyr, https://gitlab.com/auksys/kdb/-/issues/38
    #[allow(dead_code)]
    fn test_kdql_retrieve_age()
    {
        let query = KDQLQuery::Retrieve {
            what: vec!["age"].into(),
            from: "test".into(),
            matches: krql_value_map!("name" => "a"),
        };
        assert_eq!(
            serde_saphyr::to_string(&query).unwrap(),
            r#"retrieve:
  what:
    - age
  from: test
  matches:
    name: a
"#
        );
    }
}