Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Date types simplified #59

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions DuckDB.NET.Data/DuckDBDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
using DuckDB.NET.Data.Types;

namespace DuckDB.NET.Data
{
Expand Down Expand Up @@ -66,10 +67,7 @@ public override string GetDataTypeName(int ordinal)
}

public override DateTime GetDateTime(int ordinal)
{
var text = GetString(ordinal);
return DateTime.Parse(text, null, DateTimeStyles.RoundtripKind);
}
=> Types.DuckDBTimestamp.Load(queryResult, ordinal, currentRow);

public override decimal GetDecimal(int ordinal)
{
Expand Down
13 changes: 12 additions & 1 deletion DuckDB.NET.Data/DuckDBParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Data;
using System.Data.Common;
using DuckDB.NET.Data.Internal;
using DuckDB.NET.Data.Types;

namespace DuckDB.NET.Data;

Expand Down Expand Up @@ -48,7 +49,7 @@ public DuckDBParameter()

}

public DuckDBParameter(string name, DbType type, object value)
private DuckDBParameter(string name, DbType type, object value)
{
parameterName = name;
dbType = type;
Expand Down Expand Up @@ -207,6 +208,16 @@ public DuckDBParameter(string name, string value)
{
}

public DuckDBParameter(string name, DateTime value)
: this(name, DbType.DateTime, Types.DuckDBTimestamp.FromDateTime(value))
{
}

public DuckDBParameter(DateTime value)
: this(DbType.DateTime, Types.DuckDBTimestamp.FromDateTime(value))
{
}

public override void ResetDbType()
{
DbType = DbType.String;
Expand Down
1 change: 1 addition & 0 deletions DuckDB.NET.Data/Internal/DbTypeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal static class DbTypeMap
{typeof(ushort), DbType.UInt16},
{typeof(uint), DbType.UInt32},
{typeof(ulong), DbType.UInt64},
{typeof(DateTime), DbType.DateTime}
};


Expand Down
16 changes: 13 additions & 3 deletions DuckDB.NET.Data/Internal/PreparedStatement.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using DuckDB.NET.Data.Types;

namespace DuckDB.NET.Data;

Expand Down Expand Up @@ -86,12 +87,21 @@ private static void BindParameter(DuckDBPreparedStatement preparedStatement, lon
return;
}

if (!Binders.TryGetValue(parameter.DbType, out var binder))
DuckDBState result;
if (parameter.Value is IDuckDBParameterValue parameterValue)
{
throw new InvalidOperationException($"Unable to bind value of type {parameter.DbType}.");
result = parameterValue.Bind(preparedStatement, index);
}
else
{
if (!Binders.TryGetValue(parameter.DbType, out var binder))
{
throw new InvalidOperationException($"Unable to bind value of type {parameter.DbType}.");
}

result = binder(preparedStatement, index, parameter.Value);
}

var result = binder(preparedStatement, index, parameter.Value);
if (!result.IsSuccess())
{
var errorMessage = NativeMethods.PreparedStatements.DuckDBPrepareError(preparedStatement).ToManagedString(false);
Expand Down
122 changes: 122 additions & 0 deletions DuckDB.NET.Data/Types/DuckDBTimestamp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System;

namespace DuckDB.NET.Data.Types;

internal class DuckDBTimestamp : IDuckDBParameterValue
{
private readonly DuckDBTimestampStruct nativeValue;

private DuckDBTimestamp(DuckDBTimestampStruct native)
{
nativeValue = native;
}

public DuckDBState Bind(DuckDBPreparedStatement preparedStatement, long index)
{
return NativeMethods.PreparedStatements.DuckDBParamType(preparedStatement, index) switch
{
DuckDBType.DuckdbTypeDate => BindDate(preparedStatement, index),
DuckDBType.DuckdbTypeTime => BindTime(preparedStatement, index),
DuckDBType.DuckdbTypeTimestamp => BindTimestamp(preparedStatement, index),
DuckDBType.DuckdbTypeInvalid => BindTimestamp(preparedStatement, index),
_ => throw new ArgumentOutOfRangeException("Unexpected target data type.")
};
}

private DuckDBState BindDate(DuckDBPreparedStatement preparedStatement, long index)
{
var date = NativeMethods.DateTime.DuckDBToDate(nativeValue.Date);
return NativeMethods.PreparedStatements.DuckDBBindDate(preparedStatement, index, date);
}

private DuckDBState BindTime(DuckDBPreparedStatement preparedStatement, long index)
{
var time = NativeMethods.DateTime.DuckDBToTime(nativeValue.Time);
return NativeMethods.PreparedStatements.DuckDBBindTime(preparedStatement, index, time);
}

private DuckDBState BindTimestamp(DuckDBPreparedStatement preparedStatement, long index)
{
var timestamp = NativeMethods.DateTime.DuckDBToTimestamp(nativeValue);
return NativeMethods.PreparedStatements.DuckDBBindTimestamp(preparedStatement, index, timestamp);
}

public static DateTime Load(DuckDBResult result, long col, long row)
{
return (NativeMethods.Query.DuckDBColumnType(result, col) switch
{
DuckDBType.DuckdbTypeTimestamp => LoadTimestamp(result, col, row),
DuckDBType.DuckdbTypeTime => LoadTime(result, col, row),
DuckDBType.DuckdbTypeDate => LoadDate(result, col, row),
_ => throw new ArgumentOutOfRangeException("Unexpected data type.")
}).ToDateTime();
}

private static DuckDBTimestamp LoadTimestamp(DuckDBResult result, long col, long row)
{
var timestamp = NativeMethods.Types.DuckDbValueTimestamp(result, col, row);
var timestampStruct = NativeMethods.DateTime.DuckDBFromTimestamp(timestamp);
return new DuckDBTimestamp(timestampStruct);
}

private static DuckDBTimestamp LoadTime(DuckDBResult result, long col, long row)
{
var time = NativeMethods.Types.DuckDbValueTime(result, col, row);
var timeStruct = NativeMethods.DateTime.DuckDBFromTime(time);
var timestamp = new DuckDBTimestampStruct
{
Time = timeStruct,
Date = new DuckDBDateStruct()
};
return new DuckDBTimestamp(timestamp);
}

private static DuckDBTimestamp LoadDate(DuckDBResult result, long col, long row)
{
var date = NativeMethods.Types.DuckDbValueDate(result, col, row);
var dateStruct = NativeMethods.DateTime.DuckDBFromDate(date);
var timestamp = new DuckDBTimestampStruct
{
Date = dateStruct,
Time = new DuckDBTimeStruct()
};
return new DuckDBTimestamp(timestamp);
}

public DateTime ToDateTime()
{
return new DateTime(
Math.Max(nativeValue.Date.Year, DateTime.MinValue.Year),
Math.Max(nativeValue.Date.Month, DateTime.MinValue.Month),
Math.Max(nativeValue.Date.Day, DateTime.MinValue.Day),
Math.Max(nativeValue.Time.Hour, DateTime.MinValue.Hour),
Math.Max(nativeValue.Time.Min, DateTime.MinValue.Minute),
Math.Max(nativeValue.Time.Sec, DateTime.MinValue.Second),
Math.Max(nativeValue.Time.Msec, DateTime.MinValue.Millisecond)
);
}

public static DuckDBTimestamp FromDateTime(DateTime dateTime)
{
var nativeDate = new DuckDBDateStruct
{
Year = dateTime.Year,
Month = (byte)dateTime.Month,
Day = (byte)dateTime.Day
};

var nativeTime = new DuckDBTimeStruct
{
Hour = (byte)dateTime.Hour,
Min = (byte)dateTime.Minute,
Sec = (byte)dateTime.Second,
Msec = dateTime.Millisecond
};

var value = new DuckDBTimestampStruct {
Date = nativeDate,
Time = nativeTime
};
return new DuckDBTimestamp(value);
}
}
8 changes: 8 additions & 0 deletions DuckDB.NET.Data/Types/IDuckDBParameterValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace DuckDB.NET.Data.Types;

internal interface IDuckDBParameterValue
{
DuckDBState Bind(DuckDBPreparedStatement preparedStatement, long index);
}
118 changes: 118 additions & 0 deletions DuckDB.NET.Test/DateTimeTests/DateTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System;
using DuckDB.NET.Data;
using FluentAssertions;
using Xunit;

namespace DuckDB.NET.Test.DateTimeTests;

public class DateTests
{
[Theory]
[InlineData(1992, 09, 20)]
[InlineData(2022, 05, 04)]
[InlineData(2022, 04, 05)]
[InlineData(1, 1, 1)]
public void QueryScalarTest(int year, int mon, int day)
{
using var connection = new DuckDBConnection(DuckDBConnectionStringBuilder.InMemoryConnectionString);
connection.Open();

using var cmd = connection.CreateCommand();
cmd.CommandText = $"SELECT DATE '{year}-{mon}-{day}';";

var scalar = cmd.ExecuteScalar();

scalar.Should().BeOfType<DateTime>();

var dateOnly = (DateTime) scalar;

dateOnly.Year.Should().Be(year);
dateOnly.Month.Should().Be(mon);
dateOnly.Day.Should().Be(day);
dateOnly.Hour.Should().Be(DateTime.MinValue.Hour);
dateOnly.Minute.Should().Be(DateTime.MinValue.Minute);
dateOnly.Second.Should().Be(DateTime.MinValue.Second);
dateOnly.Minute.Should().Be(DateTime.MinValue.Millisecond);
}

[Theory]
[InlineData(1992, 09, 20)]
[InlineData(2022, 05, 04)]
[InlineData(2022, 04, 05)]
[InlineData(1, 1, 1)]
public void BindParamTest(int year, int mon, int day)
{
using var connection = new DuckDBConnection(DuckDBConnectionStringBuilder.InMemoryConnectionString);
connection.Open();

var expectedValue = new DateTime(year, mon, day);

using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT ?::DATE;";
cmd.Parameters.Add(new DuckDBParameter(expectedValue));

var scalar = cmd.ExecuteScalar();

scalar.Should().BeOfType<DateTime>();

var dateOnly = (DateTime) scalar;

dateOnly.Year.Should().Be(year);
dateOnly.Month.Should().Be(mon);
dateOnly.Day.Should().Be(day);
dateOnly.Hour.Should().Be(DateTime.MinValue.Hour);
dateOnly.Minute.Should().Be(DateTime.MinValue.Minute);
dateOnly.Second.Should().Be(DateTime.MinValue.Second);
dateOnly.Minute.Should().Be(DateTime.MinValue.Millisecond);

dateOnly.Should().Be(expectedValue);
}

[Theory]
[InlineData(1992, 09, 20)]
[InlineData(2022, 05, 04)]
[InlineData(2022, 04, 05)]
[InlineData(1, 1, 1)]
public void BindAndInsert(int year, int mon, int day)
{
using var connection = new DuckDBConnection(DuckDBConnectionStringBuilder.InMemoryConnectionString);
connection.Open();

var expectedValue = new DateTime(year, mon, day);

using var cmd = connection.CreateCommand();

try
{
cmd.CommandText = "CREATE TABLE date_test (d DATE);";
cmd.ExecuteNonQuery();

cmd.CommandText = "INSERT INTO date_test (d) VALUES (?);";
cmd.Parameters.Add(new DuckDBParameter(expectedValue));
cmd.ExecuteNonQuery();

cmd.CommandText = "SELECT * FROM date_test;";
cmd.Parameters.Clear();
var scalar = cmd.ExecuteScalar();

scalar.Should().BeOfType<DateTime>();

var dateOnly = (DateTime) scalar;

dateOnly.Year.Should().Be(year);
dateOnly.Month.Should().Be(mon);
dateOnly.Day.Should().Be(day);
dateOnly.Hour.Should().Be(DateTime.MinValue.Hour);
dateOnly.Minute.Should().Be(DateTime.MinValue.Minute);
dateOnly.Second.Should().Be(DateTime.MinValue.Second);
dateOnly.Minute.Should().Be(DateTime.MinValue.Millisecond);

dateOnly.Should().Be(expectedValue);
}
finally
{
cmd.CommandText = "DROP TABLE date_test;";
cmd.ExecuteNonQuery();
}
}
}
Loading