Exception on UDT => CLR mapping of collection property which has null value

Description

Exception on mapping collection in UDT to CLR collection, if value in DB is null

Example:
DB
CREATE TYPE IF NOT EXISTS settings (
ids frozen<list<int>>
);

CREATE TABLE IF NOT EXISTS test_users (
user_id int,
settings frozen<settings>,
PRIMARY KEY (user_id)
);

INSERT INTO test_users (user_id, settings) VALUES (1, { "ids": null });
SELECT * FROM test_users;

user_id | languages
--------+------------
1 | {ids: null}

C# Code to reproduce the bug:

using System;
using System.Collections.Generic;
using System.Linq;
using Cassandra;

namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var cluster = Cluster.Builder()
.WithConnectionString("Contact Points=127.0.0.1;Port=9042")
.Build();

var connection = cluster.ConnectAsync().GetAwaiter().GetResult();
connection.UserDefinedTypes.Define(UdtMap.For<Settings>("settings", "users").Map(clr => clr.Ids, "ids"));
var rowSet = connection.Execute("SELECT settings FROM users.test_users WHERE user_id=1");
var user = rowSet.First();
var settings = user.GetValue<Settings>("settings");

Console.WriteLine($"Settings: {settings.Ids}");
Console.ReadLine();
}
}

public class Settings
{
public IList<int> Ids { get; set; }
}
}

--------------------------------------
An unhandled exception has occurred while executing the request.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentNullException: Value cannot be null.
Parameter name: collection
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at Cassandra.Mapping.TypeConversion.TypeConverter.ConvertToList[T](IEnumerable`1 list)
— End of inner exception stack trace —
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at Cassandra.Mapping.TypeConversion.TypeConverter.ConvertToUdtFieldFromDbValue(Type dbType, Type valueType, Object value)
at Cassandra.UdtMap.ToObject(Object[] values)
at Cassandra.Serialization.UdtSerializer.Deserialize(UInt16 protocolVersion, Byte[] buffer, Int32 offset, Int32 length, IColumnInfo typeInfo)
at Cassandra.Serialization.Serializer.Deserialize(Byte[] buffer, Int32 offset, Int32 length, ColumnTypeCode typeCode, IColumnInfo typeInfo)
at Cassandra.Serialization.CollectionSerializer.Deserialize(UInt16 protocolVersion, Byte[] buffer, Int32 offset, Int32 length, IColumnInfo typeInfo)
at Cassandra.Serialization.Serializer.Deserialize(Byte[] buffer, Int32 offset, Int32 length, ColumnTypeCode typeCode, IColumnInfo typeInfo)
at Cassandra.FrameReader.ReadFromBytes(Byte[] buffer, Int32 offset, Int32 length, ColumnTypeCode typeCode, IColumnInfo typeInfo)
at Cassandra.OutputRows.ProcessRowItem(FrameReader reader)
at Cassandra.OutputRows.ProcessRows(RowSet rs, FrameReader reader)
at Cassandra.OutputRows..ctor(FrameReader reader, Nullable`1 traceId)
at Cassandra.Responses.ResultResponse..ctor(Frame frame)
at Cassandra.Responses.ResultResponse.Create(Frame frame)
at Cassandra.FrameParser.Parse(Frame frame)

int[] failures like the IList but with a bit different exception
{System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentNullException: Value cannot be null.
Parameter name: source
at System.Linq.Enumerable.Select[TSource,TResult](IEnumerable`1 source, Func`2 selector)
at Cassandra.Mapping.TypeConversion.TypeConverter.ConvertToArrayFromDb[TSource,TResult](IEnumerable`1 listFromDatabase)

IEnumerable<int> does not fail in case of null, but does not map in case of value, but it's a different story.

Why, I believe, the issue is blocker:
1. The same driver enables writing null in collection inside UDT;
2. Once null has been written, it cannot be read again. Besides, system failures on an attempt of reading such data. That's critical. Fortunately, I noticed such behavior before going into production. If it had been revealed in prod, it would be a catastrophe.
3. I haven't found a workaround: no hooks in mapping pipeline to inject some fixes into; no alternatives.

Environment

None

Activity

Show:
Joao Reis
August 28, 2019, 10:14 AM

I'm able to reproduce this bug with the provided code sample, thanks for the detailed bug report.

As for workarounds, I see the mapping happens correctly when IEnumerable<int> is used instead of IList<int> so this seems to be the only workaround that works at this time. Are you able to use this workaround for now while this bug isn't fixed?

Andrey Braynin
August 28, 2019, 7:22 PM

IEnumerable seems OK.

My statement “IEnumerable<int> does not fail in case of null, but does not map in case of value, but it's a different story” was not correct. In fact, it does map.

So could be a workaround.

Thank you!

Joao Reis
February 13, 2020, 6:13 PM

Flag added

merged to oss

Joao Reis
February 13, 2020, 6:44 PM

Flag removed

merged to dse

Assignee

Unassigned

Reporter

Andrey Braynin

Labels

Reproduced in

3.11.0

PM Priority

None

Fix versions

External issue ID

None

Doc Impact

None

Reviewer

None

Pull Request

None

Epic Link

None

Sprint

Pull Requests

None

Size

None

Components

Affects versions

Priority

Blocker
Configure