Skip to main content

kdb_connection/queries/
kdql.rs

1//! `kdQL` kDB Document Query Language
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::prelude::*;
8
9#[derive(Deserialize, Serialize)]
10#[serde(untagged)]
11pub enum Value
12{
13    Integer(i64),
14    Float(f64),
15    String(String),
16    Array(Vec<Value>),
17    Map(ValueMap),
18}
19
20macro_rules! value_into {
21    ($name:tt, $type:ty) => {
22        impl From<$type> for Value
23        {
24            fn from(value: $type) -> Value
25            {
26                Value::$name(value.into())
27            }
28        }
29    };
30}
31
32value_into!(Integer, i64);
33value_into!(Float, f64);
34value_into!(String, String);
35value_into!(String, &str);
36value_into!(Array, Vec<Value>);
37value_into!(Map, ValueMap);
38
39pub type ValueMap = HashMap<String, Value>;
40
41/// Convenient macro for creating ValueMap.
42///
43/// Example:
44///
45/// ```rust
46/// # use kdb_connection::{krql_value_map, queries::kdql::ValueMap};
47/// let value_map: ValueMap = krql_value_map!("hello" => 12);
48/// ```
49#[macro_export]
50macro_rules! krql_value_map {
51  // map-like
52  ($($k:expr => $v:expr),* $(,)?) => {
53    {
54      let value_map: $crate::queries::kdql::ValueMap = core::convert::From::from([$(($k.to_string(), $v.into()),)*]);
55      value_map
56    }
57  };
58}
59
60#[derive(Deserialize, Serialize)]
61#[serde(untagged)]
62pub enum What
63{
64    #[serde(serialize_with = "serialize_all", deserialize_with = "deserialize_all")]
65    All,
66    Identifiers(Vec<String>),
67}
68
69fn serialize_all<S>(serializer: S) -> Result<S::Ok, S::Error>
70where
71    S: serde::ser::Serializer,
72{
73    serializer.serialize_str("*")
74}
75
76fn deserialize_all<'de, D>(deserializer: D) -> Result<(), D::Error>
77where
78    D: serde::de::Deserializer<'de>,
79{
80    use serde::de::Error;
81    let s = String::deserialize(deserializer)?;
82    if s == "*"
83    {
84        Ok(())
85    }
86    else
87    {
88        Err(D::Error::custom("Expected '*'"))
89    }
90}
91
92impl From<Vec<&str>> for What
93{
94    fn from(value: Vec<&str>) -> Self
95    {
96        Self::Identifiers(value.into_iter().map(|x| x.to_owned()).collect())
97    }
98}
99
100/// Define a kdQL query, according to https://auksys.org/documentation/5/query_languages/kdql/
101#[derive(Deserialize, Serialize)]
102#[serde(rename_all = "lowercase")]
103pub enum KDQLQuery
104{
105    /// Create new documents
106    Create
107    {
108        /// Store the documents in this collection
109        into: String,
110        /// List of documents to store
111        documents: Vec<HashMap<String, Value>>,
112    },
113    /// Drop collections, as named in the vector
114    Drop(Vec<String>),
115    /// Retrieve content of a collection
116    Retrieve
117    {
118        /// Content to return
119        what: What,
120        /// The collection where to retrieve documents from
121        from: String,
122        /// The condition that the documents should matches
123        matches: HashMap<String, Value>,
124    },
125}
126
127impl TryFrom<&KDQLQuery> for dbc::Query
128{
129    type Error = Error;
130    fn try_from(value: &KDQLQuery) -> std::result::Result<Self, Self::Error>
131    {
132        // serde_saphyr incorrectly generate some queries, so serialize to json for now, https://gitlab.com/auksys/kdb/-/issues/38
133        let kdql_query = serde_json::to_string(value)?;
134        Ok(dbc::Query::new(
135            kdql_query,
136            Default::default(),
137            dbc::QueryType::KDQL,
138        ))
139    }
140}
141
142impl TryFrom<KDQLQuery> for dbc::Query
143{
144    type Error = Error;
145    fn try_from(value: KDQLQuery) -> std::result::Result<Self, Self::Error>
146    {
147        (&value).try_into()
148    }
149}
150
151#[cfg(test)]
152mod test
153{
154    use super::{KDQLQuery, What};
155    #[test]
156    fn test_kdql_create()
157    {
158        let query = KDQLQuery::Create {
159            into: "test".into(),
160            documents: vec![
161                krql_value_map!("name" => "a"),
162                krql_value_map!("name" => "b"),
163            ],
164        };
165        assert_eq!(
166            serde_saphyr::to_string(&query).unwrap(),
167            r#"create:
168  into: test
169  documents:
170    - name: a
171    - name: b
172"#
173        );
174    }
175    #[test]
176    fn test_kdql_retrieve_all()
177    {
178        let query = KDQLQuery::Retrieve {
179            what: What::All,
180            from: "test".into(),
181            matches: krql_value_map!("name" => "a"),
182        };
183        assert_eq!(
184            serde_saphyr::to_string(&query).unwrap(),
185            r#"retrieve:
186  what: "*"
187  from: test
188  matches:
189    name: a
190"#
191        );
192    }
193    // #[test]
194    // TODO This test fails due to a bug in serde_saphyr, https://gitlab.com/auksys/kdb/-/issues/38
195    #[allow(dead_code)]
196    fn test_kdql_retrieve_age()
197    {
198        let query = KDQLQuery::Retrieve {
199            what: vec!["age"].into(),
200            from: "test".into(),
201            matches: krql_value_map!("name" => "a"),
202        };
203        assert_eq!(
204            serde_saphyr::to_string(&query).unwrap(),
205            r#"retrieve:
206  what:
207    - age
208  from: test
209  matches:
210    name: a
211"#
212        );
213    }
214}