Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving a DateTime to Cassandra Date column

Tags:

c#

cassandra

Cassandra .NET driver documentation is unbelievably poor, I'm trying to scrap together something functional but I waste so much time trying to change code from Java documents that I found.

I am trying to write data to a simple table using Cassandra driver. The table already exists and there's date inside. I have created a mapping and added some columns. Here's a cut-off version to demonstrate:

For<Profile>().TableName("profiles")
    .PartitionKey(p => p.IntegerId)
    .Column(p => p.IntegerId, cm => cm.WithName("profileid"))
    .Column(p => p.BirthDate, cm => cm.WithName("dateofbirth"))

There are more columns and tables but that this the important part.

Then saving is done from a simple generic method:

public async Task<bool> Add<T>(T item) where T : EntityBase, new()
{
    await _mapper.InsertIfNotExistsAsync(item);
}

Again there's more code there, but the relevant parts are here. What's important is that I'm using InsertIfNotExists and using generic method that works with a base entity.

dateofbirth column in Cassandra is of type Date. When I run the Insert method I get exception that the length of Date should be 4 bytes instead of 8 (I assume I need to cut off the time part of the DateTime).

I tried using WithType on the mapping and creating a TypeSerializer similar to what was described at this question, but had no luck. Anyone has a working code that saves this type (and possibly other types) to Cassandra?

Here's the code for the date codec that was adapted from the internet, and how it was used, it might be (very) wrong:

public class DateCodec : TypeSerializer<DateTime>
{
    private static TypeSerializer<LocalDate> _innerSerializer;

    public DateCodec(TypeSerializer<LocalDate> serializer)
    {
        _innerSerializer = serializer;
        TypeInfo = new CustomColumnInfo("LocalDate");
    }

    public override IColumnInfo TypeInfo { get; }

    public override DateTime Deserialize(ushort protocolVersion, byte[] buffer, int offset, int length, IColumnInfo typeInfo)
    {
        var result = _innerSerializer.Deserialize(protocolVersion, buffer, offset, length, typeInfo);
        return new DateTime(result.Year, result.Month, result.Day);
    }

    public override ColumnTypeCode CqlType { get; }

    public override byte[] Serialize(ushort protocolVersion, DateTime value)
    {
        return _innerSerializer.Serialize(protocolVersion, new LocalDate(value.Year, value.Month, value.Day));
    }
}

Usage:

TypeSerializerDefinitions definitions = new TypeSerializerDefinitions();
definitions.Define(new DateCodec(TypeSerializer.PrimitiveLocalDateSerializer));

var cluster = Cluster.Builder()
    .AddContactPoints(...)
    .WithCredentials(...)
    .WithTypeSerializers(definitions)
    .Build();
like image 547
user3043457 Avatar asked May 09 '18 18:05

user3043457


People also ask

How is timestamp stored in Cassandra?

In Cassandra 3.4 and later, timestamps are displayed in cqlsh in sub-second precision by default, as shown below. Applications reading a timestamp may use the sub-second portion of the timestamp, as Cassandra stored millisecond-precision timestamps in all versions.

How do I add a timestamp in CQL?

Inserting the current timestamp Use functions to insert the current date into date or timestamp fields as follows: Current date and time into timestamp field: toTimestamp(now()) sets the timestamp to the current time of the coordinator.


1 Answers

C# driver uses LocalDate class to represent date from Cassandra, so either need to change your declaration of dateofbirth to use it, or develop corresponding codec.

You can check the documentation on date and time representation for the C# driver: https://docs.datastax.com/en/developer/csharp-driver/3.5/features/datatypes/datetime/

Update after update of the question with code sample:

Define table & insert sample data:

cqlsh> create table test.dt(id int primary key, d date);
cqlsh> insert into test.dt(id, d) values(1, '2018-05-17');
cqlsh> insert into test.dt(id, d) values(2, '2018-05-16');
cqlsh> insert into test.dt(id, d) values(3, '2018-05-15');

Following converted works for me:

public class DateCodec : TypeSerializer<DateTime>
{
    private static readonly TypeSerializer<LocalDate> serializer = 
         TypeSerializer.PrimitiveLocalDateSerializer;

    public override ColumnTypeCode CqlType
    {
        get { return ColumnTypeCode.Date; }
    }

    public DateCodec() { }

    public override DateTime Deserialize(ushort protocolVersion, byte[] buffer, 
         int offset, int length, IColumnInfo typeInfo)
    {
        var result = serializer.Deserialize(protocolVersion, buffer,
                offset, length, typeInfo);
        return new DateTime(result.Year, result.Month, result.Day);
    }

    public override byte[] Serialize(ushort protocolVersion, DateTime value)
    {
        return serializer.Serialize(protocolVersion, 
            new LocalDate(value.Year, value.Month, value.Day));
    }
}

Main program:

TypeSerializerDefinitions definitions = new TypeSerializerDefinitions();
definitions.Define(new DateCodec());

var cluster = Cluster.Builder()
         .AddContactPoints("localhost")
         .WithTypeSerializers(definitions)
         .Build();
var session = cluster.Connect();
var rs = session.Execute("SELECT * FROM test.dt");
foreach (var row in rs)
{
    var id = row.GetValue<int>("id");
    var date = row.GetValue<DateTime>("d");
    Console.WriteLine("id=" + id + ", date=" + date);
}

var pq = session.Prepare("insert into test.dt(id, d) values(?, ?);");
var bound = pq.Bind(10, new DateTime(2018, 04, 01));
session.Execute(bound);

Gives as result following:

id=1, date=5/17/18 12:00:00 AM
id=2, date=5/16/18 12:00:00 AM
id=3, date=5/15/18 12:00:00 AM

And checking from cqlsh:

cqlsh> SELECT * from test.dt ;

 id | d
----+------------
 10 | 2018-04-01
  1 | 2018-05-17
  2 | 2018-05-16
  3 | 2018-05-15
like image 72
Alex Ott Avatar answered Oct 02 '22 23:10

Alex Ott