Repository: scylladb/scylla-rust-driver
Pull request #204 (2021-03-30 20:21:05)

Fix sending/receiving Inet and Blob in queries
## Description
There were missing implementations for `IpAddr` and `Vec<u8>` which didn't allow to send/receive them in queries properly.

Added:
* `impl Value for IpAddr`
* `impl FromCQLVal for Vec<u8>`
* Tests for `Inet` and `Blob`

## Fixed issues:
Fixes: #201

## 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/scylla/src/frame/response/cql_to_rust.rs b/scylla/src/frame/response/cql_to_rust.rs
index 2edb3fae..51990b56 100644
--- a/scylla/src/frame/response/cql_to_rust.rs
+++ b/scylla/src/frame/response/cql_to_rust.rs
@@ -83,6 +83,7 @@ impl_from_cql_val!(f32, as_float); // f32::from_cql<CqlValue>
 impl_from_cql_val!(f64, as_double); // f64::from_cql<CqlValue>
 impl_from_cql_val!(bool, as_boolean); // bool::from_cql<CqlValue>
 impl_from_cql_val!(String, into_string); // String::from_cql<CqlValue>
+impl_from_cql_val!(Vec<u8>, into_blob); // Vec<u8>::from_cql<CqlValue>
 impl_from_cql_val!(IpAddr, as_inet); // IpAddr::from_cql<CqlValue>
 impl_from_cql_val!(Uuid, as_uuid); // Uuid::from_cql<CqlValue>
 impl_from_cql_val!(BigDecimal, into_decimal); // BigDecimal::from_cql<CqlValue>
diff --git a/scylla/src/frame/response/result.rs b/scylla/src/frame/response/result.rs
index 2b3238ee..3a33d03c 100644
--- a/scylla/src/frame/response/result.rs
+++ b/scylla/src/frame/response/result.rs
@@ -230,6 +230,13 @@ impl CqlValue {
         }
     }
 
+    pub fn into_blob(self) -> Option<Vec<u8>> {
+        match self {
+            Self::Blob(b) => Some(b),
+            _ => None,
+        }
+    }
+
     pub fn as_inet(&self) -> Option<IpAddr> {
         match self {
             Self::Inet(a) => Some(*a),
diff --git a/scylla/src/frame/value.rs b/scylla/src/frame/value.rs
index 86521c34..26aaa9f8 100644
--- a/scylla/src/frame/value.rs
+++ b/scylla/src/frame/value.rs
@@ -6,6 +6,7 @@ use num_bigint::BigInt;
 use std::borrow::Cow;
 use std::collections::HashMap;
 use std::convert::TryInto;
+use std::net::IpAddr;
 use thiserror::Error;
 use uuid::Uuid;
 
@@ -350,6 +351,23 @@ impl Value for Vec<u8> {
     }
 }
 
+impl Value for IpAddr {
+    fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
+        match self {
+            IpAddr::V4(addr) => {
+                buf.put_i32(4);
+                buf.extend_from_slice(&addr.octets());
+            }
+            IpAddr::V6(addr) => {
+                buf.put_i32(16);
+                buf.extend_from_slice(&addr.octets());
+            }
+        }
+
+        Ok(())
+    }
+}
+
 impl Value for String {
     fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
         <&str as Value>::serialize(&self.as_str(), buf)


Test patch:
diff --git a/scylla/src/frame/cql_types_test.rs b/scylla/src/frame/cql_types_test.rs
index 28382857..33b25e85 100644
--- a/scylla/src/frame/cql_types_test.rs
+++ b/scylla/src/frame/cql_types_test.rs
@@ -12,6 +12,7 @@ use num_bigint::BigInt;
 use std::cmp::PartialEq;
 use std::env;
 use std::fmt::Debug;
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
 use std::str::FromStr;
 use uuid::Uuid;
 
@@ -571,3 +572,163 @@ async fn test_timeuuid() {
         assert_eq!(read_timeuuid.as_bytes(), timeuuid_bytes);
     }
 }
+
+#[tokio::test]
+async fn test_inet() {
+    let session: Session = init_test("inet_tests", "inet").await;
+
+    let tests = [
+        ("0.0.0.0", IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))),
+        ("127.0.0.1", IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
+        ("10.0.0.1", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))),
+        (
+            "255.255.255.255",
+            IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)),
+        ),
+        ("::0", IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0))),
+        ("::1", IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
+        (
+            "2001:db8::8a2e:370:7334",
+            IpAddr::V6(Ipv6Addr::new(
+                0x2001, 0x0db8, 0, 0, 0, 0x8a2e, 0x0370, 0x7334,
+            )),
+        ),
+        (
+            "2001:0db8:0000:0000:0000:8a2e:0370:7334",
+            IpAddr::V6(Ipv6Addr::new(
+                0x2001, 0x0db8, 0, 0, 0, 0x8a2e, 0x0370, 0x7334,
+            )),
+        ),
+        (
+            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+            IpAddr::V6(Ipv6Addr::new(
+                u16::MAX,
+                u16::MAX,
+                u16::MAX,
+                u16::MAX,
+                u16::MAX,
+                u16::MAX,
+                u16::MAX,
+                u16::MAX,
+            )),
+        ),
+    ];
+
+    for (inet_str, inet) in &tests {
+        // Insert inet as a string and verify that it matches
+        session
+            .query(
+                format!(
+                    "INSERT INTO ks.inet_tests (id, val) VALUES (0, '{}')",
+                    inet_str
+                ),
+                &[],
+            )
+            .await
+            .unwrap();
+
+        let (read_inet,): (IpAddr,) = session
+            .query("SELECT val from ks.inet_tests WHERE id = 0", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(IpAddr,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_inet, *inet);
+
+        // Insert inet as a bound value and verify that it matches
+        session
+            .query("INSERT INTO ks.inet_tests (id, val) VALUES (0, ?)", (inet,))
+            .await
+            .unwrap();
+
+        let (read_inet,): (IpAddr,) = session
+            .query("SELECT val from ks.inet_tests WHERE id = 0", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(IpAddr,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_inet, *inet);
+    }
+}
+
+#[tokio::test]
+async fn test_blob() {
+    let session: Session = init_test("blob_tests", "blob").await;
+
+    let long_blob: Vec<u8> = vec![0x11; 1234];
+    let mut long_blob_str: String = "0x".to_string();
+    long_blob_str.extend(std::iter::repeat('1').take(2 * 1234));
+
+    let tests = [
+        ("0x", vec![]),
+        ("0x00", vec![0x00]),
+        ("0x01", vec![0x01]),
+        ("0xff", vec![0xff]),
+        ("0x1122", vec![0x11, 0x22]),
+        ("0x112233", vec![0x11, 0x22, 0x33]),
+        ("0x11223344", vec![0x11, 0x22, 0x33, 0x44]),
+        ("0x1122334455", vec![0x11, 0x22, 0x33, 0x44, 0x55]),
+        ("0x112233445566", vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66]),
+        (
+            "0x11223344556677",
+            vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77],
+        ),
+        (
+            "0x1122334455667788",
+            vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
+        ),
+        (&long_blob_str, long_blob),
+    ];
+
+    for (blob_str, blob) in &tests {
+        // Insert blob as a string and verify that it matches
+        session
+            .query(
+                format!(
+                    "INSERT INTO ks.blob_tests (id, val) VALUES (0, {})",
+                    blob_str
+                ),
+                &[],
+            )
+            .await
+            .unwrap();
+
+        let (read_blob,): (Vec<u8>,) = session
+            .query("SELECT val from ks.blob_tests WHERE id = 0", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(Vec<u8>,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_blob, *blob);
+
+        // Insert blob as a bound value and verify that it matches
+        session
+            .query("INSERT INTO ks.blob_tests (id, val) VALUES (0, ?)", (blob,))
+            .await
+            .unwrap();
+
+        let (read_blob,): (Vec<u8>,) = session
+            .query("SELECT val from ks.blob_tests WHERE id = 0", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(Vec<u8>,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_blob, *blob);
+    }
+}