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 February 13, 2020 at 6:44 PM

Flag removed

merged to dse

Joao Reis February 13, 2020 at 6:13 PM

Flag added

merged to oss

Andrey Braynin August 28, 2019 at 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 August 28, 2019 at 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?

Fixed

Details

Assignee

Reporter

Labels

Reproduced in

Fix versions

Sprint

Components

Affects versions

Priority

Created August 28, 2019 at 3:06 AM
Updated March 24, 2020 at 7:25 PM
Resolved March 24, 2020 at 7:25 PM