11use crate :: batch:: { Batch , BatchStatement } ;
2+ use crate :: deserialize:: DeserializeValue ;
23use crate :: prepared_statement:: PreparedStatement ;
34use crate :: query:: Query ;
45use crate :: retry_policy:: { QueryInfo , RetryDecision , RetryPolicy , RetrySession } ;
@@ -25,12 +26,12 @@ use assert_matches::assert_matches;
2526use futures:: { FutureExt , StreamExt as _, TryStreamExt } ;
2627use itertools:: Itertools ;
2728use scylla_cql:: frame:: request:: query:: { PagingState , PagingStateResponse } ;
28- use scylla_cql:: frame:: response:: result:: ColumnType ;
29- use scylla_cql:: frame:: response :: result :: Row ;
29+ use scylla_cql:: frame:: response:: result:: { ColumnType , Row } ;
30+ use scylla_cql:: frame:: value :: CqlVarint ;
3031use scylla_cql:: types:: serialize:: row:: { SerializeRow , SerializedValues } ;
3132use scylla_cql:: types:: serialize:: value:: SerializeValue ;
32- use std:: collections:: BTreeSet ;
3333use std:: collections:: { BTreeMap , HashMap } ;
34+ use std:: collections:: { BTreeSet , HashSet } ;
3435use std:: sync:: atomic:: { AtomicBool , Ordering } ;
3536use std:: sync:: Arc ;
3637use tokio:: net:: TcpListener ;
@@ -3084,3 +3085,68 @@ async fn test_manual_primary_key_computation() {
30843085 . await ;
30853086 }
30863087}
3088+
3089+ /// ScyllaDB does not distinguish empty collections from nulls. That is, INSERTing an empty collection
3090+ /// is equivalent to nullifying the corresponding column.
3091+ /// As pointed out in [#1001](https://github.com/scylladb/scylla-rust-driver/issues/1001), it's a nice
3092+ /// QOL feature to be able to deserialize empty CQL collections to empty Rust collections instead of
3093+ /// `None::<RustCollection>`. This test checks that.
3094+ #[ tokio:: test]
3095+ async fn test_deserialize_empty_collections ( ) {
3096+ // Setup session.
3097+ let ks = unique_keyspace_name ( ) ;
3098+ let session = create_new_session_builder ( ) . build ( ) . await . unwrap ( ) ;
3099+ session. query_unpaged ( format ! ( "CREATE KEYSPACE IF NOT EXISTS {} WITH REPLICATION = {{'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1}}" , ks) , & [ ] ) . await . unwrap ( ) ;
3100+ session. use_keyspace ( & ks, true ) . await . unwrap ( ) ;
3101+
3102+ async fn deserialize_empty_collection <
3103+ Collection : Default + for < ' frame > DeserializeValue < ' frame , ' frame > + SerializeValue ,
3104+ > (
3105+ session : & Session ,
3106+ collection_name : & str ,
3107+ collection_type_params : & str ,
3108+ ) -> Collection {
3109+ // Create a table for the given collection type.
3110+ let table_name = "test_empty_" . to_owned ( ) + collection_name;
3111+ let query = format ! (
3112+ "CREATE TABLE {} (n int primary key, c {}<{}>)" ,
3113+ table_name, collection_name, collection_type_params
3114+ ) ;
3115+ session. query_unpaged ( query, ( ) ) . await . unwrap ( ) ;
3116+
3117+ // Populate the table with an empty collection, effectively inserting null as the collection.
3118+ session
3119+ . query_unpaged (
3120+ format ! ( "INSERT INTO {} (n, c) VALUES (?, ?)" , table_name, ) ,
3121+ ( 0 , Collection :: default ( ) ) ,
3122+ )
3123+ . await
3124+ . unwrap ( ) ;
3125+
3126+ let query_rows_result = session
3127+ . query_unpaged ( format ! ( "SELECT c FROM {}" , table_name) , ( ) )
3128+ . await
3129+ . unwrap ( )
3130+ . into_rows_result ( )
3131+ . unwrap ( )
3132+ . unwrap ( ) ;
3133+ let ( collection, ) = query_rows_result. first_row :: < ( Collection , ) > ( ) . unwrap ( ) ;
3134+
3135+ // Drop the table
3136+ collection
3137+ }
3138+
3139+ let list = deserialize_empty_collection :: < Vec < i32 > > ( & session, "list" , "int" ) . await ;
3140+ assert ! ( list. is_empty( ) ) ;
3141+
3142+ let set = deserialize_empty_collection :: < HashSet < i64 > > ( & session, "set" , "bigint" ) . await ;
3143+ assert ! ( set. is_empty( ) ) ;
3144+
3145+ let map = deserialize_empty_collection :: < HashMap < bool , CqlVarint > > (
3146+ & session,
3147+ "map" ,
3148+ "boolean, varint" ,
3149+ )
3150+ . await ;
3151+ assert ! ( map. is_empty( ) ) ;
3152+ }
0 commit comments