Repository: scylladb/scylla-rust-driver
Pull request #361 (2021-11-26 14:56:43)

Support more collection types
This PR adds full support for using `HashSet`, `BTreeSet` and `BTreeMap` with the driver.
`HashSet` already had a `FromCqlVal` implementation, but it didn't have `Value`.

I rewrote `cql_collections_test` a bit to cover all these added types.

Fixes: #344

## Pre-review checklist

<!--
    Make sure you took care of the issues on the list.
    Put 'x' into those boxes which apply.
    You can also create the PR now and click on all relevant checkboxes.
    See CONTRIBUTING.md for more details.
-->

- [x] I have split my patch into logically separate commits.
- [x] All commit messages clearly explain what they change and why.
- [x] I added relevant tests for new features and bug fixes.
- [x] All commits compile, pass static checks and pass test.
- [x] PR description sums up the changes and reasons why they should be introduced.
- [x] I added appropriate `Fixes:` annotations to PR description.



Patch:
diff --git a/docs/source/data-types/collections.md b/docs/source/data-types/collections.md
index 1557ca68..43301d31 100644
--- a/docs/source/data-types/collections.md
+++ b/docs/source/data-types/collections.md
@@ -11,9 +11,9 @@
 use scylla::IntoTypedRows;
 
 // Insert a list of ints into the table
-let to_insert: Vec<i32> = vec![1, 2, 3, 4, 5];
+let my_list: Vec<i32> = vec![1, 2, 3, 4, 5];
 session
-    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&to_insert,))
+    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&my_list,))
     .await?;
 
 // Read a list of ints from the table
@@ -27,7 +27,7 @@ if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.ro
 ```
 
 ## Set
-`Set` is represented as `Vec<T>`
+`Set` is represented as `Vec<T>`, `HashSet<T>` or `BTreeSet<T>`:
 
 ```rust
 # extern crate scylla;
@@ -37,9 +37,9 @@ if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.ro
 use scylla::IntoTypedRows;
 
 // Insert a set of ints into the table
-let to_insert: Vec<i32> = vec![1, 2, 3, 4, 5];
+let my_set: Vec<i32> = vec![1, 2, 3, 4, 5];
 session
-    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&to_insert,))
+    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&my_set,))
     .await?;
 
 // Read a set of ints from the table
@@ -52,8 +52,56 @@ if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.ro
 # }
 ```
 
+```rust
+# extern crate scylla;
+# use scylla::Session;
+# use std::error::Error;
+# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
+use scylla::IntoTypedRows;
+use std::collections::HashSet;
+
+// Insert a set of ints into the table
+let my_set: HashSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();
+session
+    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&my_set,))
+    .await?;
+
+// Read a set of ints from the table
+if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
+    for row in rows.into_typed::<(HashSet<i32>,)>() {
+        let (set_value,): (HashSet<i32>,) = row?;
+    }
+}
+# Ok(())
+# }
+```
+
+```rust
+# extern crate scylla;
+# use scylla::Session;
+# use std::error::Error;
+# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
+use scylla::IntoTypedRows;
+use std::collections::BTreeSet;
+
+// Insert a set of ints into the table
+let my_set: BTreeSet<i32> = vec![1, 2, 3, 4, 5].into_iter().collect();
+session
+    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&my_set,))
+    .await?;
+
+// Read a set of ints from the table
+if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
+    for row in rows.into_typed::<(BTreeSet<i32>,)>() {
+        let (set_value,): (BTreeSet<i32>,) = row?;
+    }
+}
+# Ok(())
+# }
+```
+
 ## Map
-`Map` is represented as `std::collections::HashMap<K, V>`
+`Map` is represented as `HashMap<K, V>` or `BTreeMap<K, V>`
 
 ```rust
 # extern crate scylla;
@@ -64,11 +112,11 @@ use scylla::IntoTypedRows;
 use std::collections::HashMap;
 
 // Insert a map of text and int into the table
-let mut to_insert: HashMap<String, i32> = HashMap::new();
-to_insert.insert("abcd".to_string(), 16);
+let mut my_map: HashMap<String, i32> = HashMap::new();
+my_map.insert("abcd".to_string(), 16);
 
 session
-    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&to_insert,))
+    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&my_map,))
     .await?;
 
 // Read a map from the table
@@ -79,4 +127,30 @@ if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.ro
 }
 # Ok(())
 # }
+```
+
+```rust
+# extern crate scylla;
+# use scylla::Session;
+# use std::error::Error;
+# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
+use scylla::IntoTypedRows;
+use std::collections::BTreeMap;
+
+// Insert a map of text and int into the table
+let mut my_map: BTreeMap<String, i32> = BTreeMap::new();
+my_map.insert("abcd".to_string(), 16);
+
+session
+    .query("INSERT INTO keyspace.table (a) VALUES(?)", (&my_map,))
+    .await?;
+
+// Read a map from the table
+if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
+    for row in rows.into_typed::<(BTreeMap<String, i32>,)>() {
+        let (map_value,): (BTreeMap<String, i32>,) = row?;
+    }
+}
+# Ok(())
+# }
 ```
\ No newline at end of file
diff --git a/scylla/src/frame/response/cql_to_rust.rs b/scylla/src/frame/response/cql_to_rust.rs
index 4ce3f2ee..f8a3efaa 100644
--- a/scylla/src/frame/response/cql_to_rust.rs
+++ b/scylla/src/frame/response/cql_to_rust.rs
@@ -3,8 +3,7 @@ use crate::frame::value::Counter;
 use bigdecimal::BigDecimal;
 use chrono::{Duration, NaiveDate};
 use num_bigint::BigInt;
-use std::collections::HashMap;
-use std::collections::HashSet;
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
 use std::hash::Hash;
 use std::net::IpAddr;
 use thiserror::Error;
@@ -44,6 +43,13 @@ pub trait FromRow: Sized {
     fn from_row(row: Row) -> Result<Self, FromRowError>;
 }
 
+// CqlValue can be converted to CqlValue
+impl FromCqlVal<CqlValue> for CqlValue {
+    fn from_cql(cql_val: CqlValue) -> Result<CqlValue, FromCqlValError> {
+        Ok(cql_val)
+    }
+}
+
 // Implement from_cql<Option<CqlValue>> for every type that has from_cql<CqlValue>
 // This tries to unwrap the option or fails with an error
 impl<T: FromCqlVal<CqlValue>> FromCqlVal<Option<CqlValue>> for T {
@@ -127,6 +133,30 @@ impl<T: FromCqlVal<CqlValue> + Eq + Hash> FromCqlVal<CqlValue> for HashSet<T> {
     }
 }
 
+impl<T: FromCqlVal<CqlValue> + Ord> FromCqlVal<CqlValue> for BTreeSet<T> {
+    fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
+        cql_val
+            .into_vec()
+            .ok_or(FromCqlValError::BadCqlType)?
+            .into_iter()
+            .map(T::from_cql)
+            .collect::<Result<BTreeSet<T>, FromCqlValError>>()
+    }
+}
+
+impl<K: FromCqlVal<CqlValue> + Ord, V: FromCqlVal<CqlValue>> FromCqlVal<CqlValue>
+    for BTreeMap<K, V>
+{
+    fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
+        let vec = cql_val.into_pair_vec().ok_or(FromCqlValError::BadCqlType)?;
+        let mut res = BTreeMap::new();
+        for (key, value) in vec {
+            res.insert(K::from_cql(key)?, V::from_cql(value)?);
+        }
+        Ok(res)
+    }
+}
+
 macro_rules! replace_expr {
     ($_t:tt $sub:expr) => {
         $sub
diff --git a/scylla/src/frame/value.rs b/scylla/src/frame/value.rs
index 787ada24..e25ff216 100644
--- a/scylla/src/frame/value.rs
+++ b/scylla/src/frame/value.rs
@@ -4,7 +4,7 @@ use chrono::prelude::*;
 use chrono::Duration;
 use num_bigint::BigInt;
 use std::borrow::Cow;
-use std::collections::HashMap;
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
 use std::convert::TryInto;
 use std::net::IpAddr;
 use thiserror::Error;
@@ -447,27 +447,53 @@ fn serialize_map<K: Value, V: Value>(
     Ok(())
 }
 
+fn serialize_list_or_set<'a, V: 'a + Value>(
+    elements_iter: impl Iterator<Item = &'a V>,
+    element_count: usize,
+    buf: &mut Vec<u8>,
+) -> Result<(), ValueTooBig> {
+    let bytes_num_pos: usize = buf.len();
+    buf.put_i32(0);
+
+    buf.put_i32(element_count.try_into().map_err(|_| ValueTooBig)?);
+    for value in elements_iter {
+        <V as Value>::serialize(value, buf)?;
+    }
+
+    let written_bytes: usize = buf.len() - bytes_num_pos - 4;
+    let written_bytes_i32: i32 = written_bytes.try_into().map_err(|_| ValueTooBig)?;
+    buf[bytes_num_pos..(bytes_num_pos + 4)].copy_from_slice(&written_bytes_i32.to_be_bytes());
+
+    Ok(())
+}
+
+impl<V: Value> Value for HashSet<V> {
+    fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
+        serialize_list_or_set(self.iter(), self.len(), buf)
+    }
+}
+
 impl<K: Value, V: Value> Value for HashMap<K, V> {
     fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
         serialize_map(self.iter(), self.len(), buf)
     }
 }
 
-impl<T: Value> Value for Vec<T> {
+impl<V: Value> Value for BTreeSet<V> {
     fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
-        let bytes_num_pos: usize = buf.len();
-        buf.put_i32(0);
-
-        buf.put_i32(self.len().try_into().map_err(|_| ValueTooBig)?);
-        for value in self {
-            <T as Value>::serialize(value, buf)?;
-        }
+        serialize_list_or_set(self.iter(), self.len(), buf)
+    }
+}
 
-        let written_bytes: usize = buf.len() - bytes_num_pos - 4;
-        let written_bytes_i32: i32 = written_bytes.try_into().map_err(|_| ValueTooBig)?;
-        buf[bytes_num_pos..(bytes_num_pos + 4)].copy_from_slice(&written_bytes_i32.to_be_bytes());
+impl<K: Value, V: Value> Value for BTreeMap<K, V> {
+    fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
+        serialize_map(self.iter(), self.len(), buf)
+    }
+}
 
-        Ok(())
+impl<T: Value> Value for Vec<T> {
+    fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
+        serialize_list_or_set(self.iter(), self.len(), buf)
     }
 }
 


Test patch:
diff --git a/scylla/src/frame/cql_collections_test.rs b/scylla/src/frame/cql_collections_test.rs
index 0968bda9..42ba857c 100644
--- a/scylla/src/frame/cql_collections_test.rs
+++ b/scylla/src/frame/cql_collections_test.rs
@@ -1,211 +1,227 @@
-use crate::{frame::response::result::CqlValue, IntoTypedRows, SessionBuilder};
-use std::{collections::HashMap, env};
-
-#[tokio::test]
-async fn test_cql_collections() {
-    // Create connection
+use crate::cql_to_rust::FromCqlVal;
+use crate::frame::value::Value;
+use crate::{frame::response::result::CqlValue, IntoTypedRows, Session, SessionBuilder};
+use std::{
+    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
+    env,
+};
+
+async fn connect() -> Session {
     let uri = env::var("SCYLLA_URI").unwrap_or_else(|_| "127.0.0.1:9042".to_string());
     let session = SessionBuilder::new().known_node(uri).build().await.unwrap();
-
     session.query("CREATE KEYSPACE IF NOT EXISTS ks WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1}", &[]).await.unwrap();
 
-    // HashMap test
-
-    session
-        .query(
-            "CREATE TABLE IF NOT EXISTS ks.teams (id int, team map<int, text>, primary key (id))",
-            &[],
-        )
-        .await
-        .unwrap();
-
-    let team1: HashMap<i32, String> = vec![(1, "Ginny".to_owned()), (2, "Cho".to_owned())]
-        .into_iter()
-        .collect();
-    // Test CqlValue serialization too
-    let team2: CqlValue = CqlValue::Map(vec![
-        (CqlValue::Int(1), CqlValue::Text("John".into())),
-        (CqlValue::Int(2), CqlValue::Text("Jessica".into())),
-    ]);
-
-    session
-        .query("INSERT INTO ks.teams (id, team) VALUES (1, ?)", (team1,))
-        .await
-        .unwrap();
-    session
-        .query("INSERT INTO ks.teams (id, team) VALUES (2, ?)", (team2,))
-        .await
-        .unwrap();
-
-    let mut received_kv_pairs: Vec<Vec<(i32, String)>> = session
-        .query("SELECT team FROM ks.teams", &[])
-        .await
-        .unwrap()
-        .rows
-        .unwrap()
-        .into_typed::<(HashMap<i32, String>,)>()
-        .map(Result::unwrap)
-        .map(|(hashmap,)| {
-            let mut v: Vec<_> = hashmap.into_iter().collect();
-            v.sort_unstable();
-            v
-        })
-        .collect();
-    received_kv_pairs.sort_unstable();
-
-    assert_eq!(
-        received_kv_pairs,
-        vec![
-            vec![(1, "Ginny".into()), (2, "Cho".into())],
-            vec![(1, "John".into()), (2, "Jessica".into())]
-        ]
-    );
-
-    // Set test
-
     session
-        .query(
-            "CREATE TABLE IF NOT EXISTS ks.images (name text PRIMARY KEY, owner text, tags set<text>)",
-            &[],
-        )
-        .await.unwrap();
-
-    let tags1: Vec<String> = vec!["pet".to_owned(), "cute".to_owned()];
-    let tags2: CqlValue = CqlValue::Set(vec![
-        CqlValue::Text("kitten".into()),
-        CqlValue::Text("cat".into()),
-        CqlValue::Text("lol".into()),
-    ]);
+}
 
+async fn create_table(session: &Session, table_name: &str, value_type: &str) {
     session
         .query(
-            "INSERT INTO ks.images (name, owner, tags) VALUES ('cat.jpg', 'jsmith', ?)",
-            (tags1,),
+            format!(
+                "CREATE TABLE IF NOT EXISTS ks.{} (p int PRIMARY KEY, val {})",
+                table_name, value_type
+            ),
+            (),
         )
         .await
         .unwrap();
+}
+
+async fn insert_and_select<InsertT, SelectT>(
+    session: &Session,
+    table_name: &str,
+    to_insert: &InsertT,
+    expected: &SelectT,
+) where
+    InsertT: Value,
+    SelectT: FromCqlVal<Option<CqlValue>> + PartialEq + std::fmt::Debug,
+{
     session
         .query(
-            "UPDATE ks.images SET tags = ? WHERE name = 'cat.jpg';",
-            (tags2,),
+            format!("INSERT INTO ks.{} (p, val) VALUES (0, ?)", table_name),
+            (&to_insert,),
         )
         .await
         .unwrap();
 
-    let mut received_elements: Vec<String> = session
-        .query("SELECT tags FROM ks.images", &[])
+    let selected_value: SelectT = session
+        .query(format!("SELECT val FROM ks.{} WHERE p = 0", table_name), ())
         .await
         .unwrap()
         .rows
         .unwrap()
-        .into_typed::<(Vec<String>,)>()
-        .map(Result::unwrap)
-        .map(|(v,)| v.into_iter())
-        .flatten()
-        .collect();
-    received_elements.sort_unstable();
-
-    assert_eq!(
-        received_elements,
-        vec!["cat".to_string(), "kitten".to_string(), "lol".to_string()]
-    );
-
-    // List test
-
-    session
-        .query(
-            "CREATE TABLE IF NOT EXISTS ks.plays ( id text PRIMARY KEY, game text, players int, scores list<int>)",
-            &[],
-        )
-        .await.unwrap();
-
-    let scores1: Vec<i32> = vec![17, 4, 2];
-    let scores2: CqlValue =
-        CqlValue::List(vec![CqlValue::Int(3), CqlValue::Int(9), CqlValue::Int(4)]);
-
-    session
-        .query(
-            "INSERT INTO ks.plays (id, game, players, scores) VALUES ('123-afde', 'quake', 3, ?)",
-            (&scores1,),
-        )
-        .await
-        .unwrap();
-    session
-        .query(
-            "INSERT INTO ks.plays (id, game, players, scores) VALUES ('1234-qwer', 'cs', 10, ?)",
-            (&scores2,),
-        )
-        .await
-        .unwrap();
-
-    let mut received_elements: Vec<Vec<i32>> = session
-        .query("SELECT scores FROM ks.plays", &[])
-        .await
+        .into_typed::<(SelectT,)>()
+        .next()
         .unwrap()
-        .rows
         .unwrap()
-        .into_typed::<(Vec<i32>,)>()
-        .map(Result::unwrap)
-        .map(|(v,)| v.into_iter().collect())
-        .collect();
-    received_elements.sort_unstable();
+        .0;
 
-    assert_eq!(received_elements, vec![vec![3, 9, 4], vec![17, 4, 2]]);
+    assert_eq!(&selected_value, expected);
+}
 
-    // Tuple test
+#[tokio::test]
+async fn test_cql_list() {
+    let session: Session = connect().await;
+
+    let table_name: &str = "test_cql_list_tab";
+    create_table(&session, table_name, "list<int>").await;
+
+    // Vec
+    let list1: Vec<i32> = vec![-1, 0, 1, 1, 2];
+    let list2: Vec<i32> = vec![100, 212, 2323];
+    let list_empty: Vec<i32> = vec![];
+    let list_empty_selected: Option<Vec<i32>> = None;
+    insert_and_select(&session, table_name, &list1, &list1).await;
+    insert_and_select(&session, table_name, &list2, &list2).await;
+    insert_and_select(&session, table_name, &list_empty, &list_empty_selected).await;
+
+    // CqlValue
+    let list_cql_value: CqlValue =
+        CqlValue::List(vec![CqlValue::Int(-1), CqlValue::Int(1), CqlValue::Int(0)]);
+    let list_cql_value_empty: CqlValue = CqlValue::List(Vec::new());
+    let list_cql_value_empty_selected: Option<CqlValue> = None;
+
+    insert_and_select(&session, table_name, &list_cql_value, &list_cql_value).await;
+    insert_and_select(
+        &session,
+        table_name,
+        &list_cql_value_empty,
+        &list_cql_value_empty_selected,
+    )
+    .await;
+}
 
-    session
-        .query(
-            "CREATE TABLE IF NOT EXISTS ks.durations (event text, duration tuple<int, text>, primary key (event))",
-            &[],
-        )
-        .await.unwrap();
+#[tokio::test]
+async fn test_cql_set() {
+    let session: Session = connect().await;
+
+    let table_name: &str = "test_cql_set_tab";
+    create_table(&session, table_name, "set<int>").await;
+
+    // Vec
+    let set_vec_sorted: Vec<i32> = vec![-1, 0, 1, 2];
+    let set_vec_unordered: Vec<i32> = vec![1, -1, 1, 2, 0];
+    let set_vec_empty: Vec<i32> = vec![];
+    let set_vec_empty_selected: Option<Vec<i32>> = None;
+    insert_and_select(&session, table_name, &set_vec_sorted, &set_vec_sorted).await;
+    insert_and_select(&session, table_name, &set_vec_unordered, &set_vec_sorted).await;
+    insert_and_select(
+        &session,
+        table_name,
+        &set_vec_empty,
+        &set_vec_empty_selected,
+    )
+    .await;
+
+    // HashSet
+    let set_hashset: HashSet<i32> = vec![-1, 1, -2].into_iter().collect();
+    let set_hashset_empty: HashSet<i32> = HashSet::new();
+    let set_hashset_empty_selected: Option<HashSet<i32>> = None;
+    insert_and_select(&session, table_name, &set_hashset, &set_hashset).await;
+    insert_and_select(
+        &session,
+        table_name,
+        &set_hashset_empty,
+        &set_hashset_empty_selected,
+    )
+    .await;
+
+    // BTreeSet
+    let set_btreeset: BTreeSet<i32> = vec![0, -2, -1].into_iter().collect();
+    let set_btreeset_empty: BTreeSet<i32> = BTreeSet::new();
+    let set_btreeset_empty_selected: Option<BTreeSet<i32>> = None;
+    insert_and_select(&session, table_name, &set_btreeset, &set_btreeset).await;
+    insert_and_select(
+        &session,
+        table_name,
+        &set_btreeset_empty,
+        &set_btreeset_empty_selected,
+    )
+    .await;
+
+    // CqlValue
+    let set_cql_value: CqlValue =
+        CqlValue::Set(vec![CqlValue::Int(-1), CqlValue::Int(1), CqlValue::Int(2)]);
+    let set_cql_value_empty: CqlValue = CqlValue::Set(Vec::new());
+    let set_cql_value_empty_selected: Option<CqlValue> = None;
+    insert_and_select(&session, table_name, &set_cql_value, &set_cql_value).await;
+    insert_and_select(
+        &session,
+        table_name,
+        &set_cql_value_empty,
+        &set_cql_value_empty_selected,
+    )
+    .await;
+}
 
-    let duration1 = (3, "hours");
-    let duration2 = CqlValue::Tuple(vec![
-        Some(CqlValue::Int(2)),
-        Some(CqlValue::Text("minutes".into())),
+#[tokio::test]
+async fn test_cql_map() {
+    let session: Session = connect().await;
+
+    let table_name: &str = "test_cql_map_tab";
+    create_table(&session, table_name, "map<int, int>").await;
+
+    // HashMap
+    let map_hashmap: HashMap<i32, i32> = vec![(-1, 0), (0, 1), (2, 1)].into_iter().collect();
+    let map_hashmap_empty: HashMap<i32, i32> = HashMap::new();
+    let map_hashmap_empty_selected: Option<HashMap<i32, i32>> = None;
+    insert_and_select(&session, table_name, &map_hashmap, &map_hashmap).await;
+    insert_and_select(
+        &session,
+        table_name,
+        &map_hashmap_empty,
+        &map_hashmap_empty_selected,
+    )
+    .await;
+
+    // BTreeMap
+    let map_btreemap: BTreeMap<i32, i32> = vec![(10, 20), (30, 10), (4, 5)].into_iter().collect();
+    let map_btreemap_empty: BTreeMap<i32, i32> = BTreeMap::new();
+    let map_btreemap_empty_selected: Option<BTreeMap<i32, i32>> = None;
+    insert_and_select(&session, table_name, &map_btreemap, &map_btreemap).await;
+    insert_and_select(
+        &session,
+        table_name,
+        &map_btreemap_empty,
+        &map_btreemap_empty_selected,
+    )
+    .await;
+
+    // CqlValue
+    let map_cql_value: CqlValue = CqlValue::Map(vec![
+        (CqlValue::Int(2), CqlValue::Int(4)),
+        (CqlValue::Int(8), CqlValue::Int(16)),
     ]);
-    session
-        .query(
-            "INSERT INTO ks.durations (event, duration) VALUES ('ev1', ?)",
-            (duration1,),
-        )
-        .await
-        .unwrap();
-    session
-        .query(
-            "INSERT INTO ks.durations (event, duration) VALUES ('ev2', ?)",
-            (duration2,),
-        )
-        .await
-        .unwrap();
-    session
-        .query(
-            "INSERT INTO ks.durations (event, duration) VALUES ('ev3', (4, null))",
-            &[],
-        )
-        .await
-        .unwrap();
+    let map_cql_value_empty: CqlValue = CqlValue::Map(Vec::new());
+    let map_cql_value_empty_selected: Option<CqlValue> = None;
+    insert_and_select(&session, table_name, &map_cql_value, &map_cql_value).await;
+    insert_and_select(
+        &session,
+        table_name,
+        &map_cql_value_empty,
+        &map_cql_value_empty_selected,
+    )
+    .await;
+}
 
-    let mut received_elements: Vec<(i32, Option<String>)> = session
-        .query("SELECT duration FROM ks.durations", &[])
-        .await
-        .unwrap()
-        .rows
-        .unwrap()
-        .into_typed::<((i32, Option<String>),)>()
-        .map(Result::unwrap)
-        .map(|(x,)| x)
-        .collect();
-    received_elements.sort_unstable();
-
-    assert_eq!(
-        received_elements,
-        vec![
-            (2, Some("minutes".into())),
-            (3, Some("hours".into())),
-            (4, None)
-        ]
-    );
+#[tokio::test]
+async fn test_cql_tuple() {
+    let session: Session = connect().await;
+
+    let table_name: &str = "test_cql_tuple_tab";
+    create_table(&session, table_name, "tuple<int, int, text>").await;
+
+    // Rust tuple ()
+    let tuple1: (i32, i32, String) = (1, 2, "three".to_string());
+    let tuple2: (Option<i32>, Option<i32>, String) = (Some(4), None, "sixteen".to_string());
+    insert_and_select(&session, table_name, &tuple1, &tuple1).await;
+    insert_and_select(&session, table_name, &tuple2, &tuple2).await;
+
+    // CqlValue
+    let tuple_cql_value: CqlValue = CqlValue::Tuple(vec![
+        None,
+        Some(CqlValue::Int(1024)),
+        Some(CqlValue::Text("cql_value_text".to_string())),
+    ]);
+    insert_and_select(&session, table_name, &tuple_cql_value, &tuple_cql_value).await;
 }