Skip to content

Commit

Permalink
Merge b09a31f into b7f3e52
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan committed Aug 20, 2022
2 parents b7f3e52 + b09a31f commit 6bda227
Show file tree
Hide file tree
Showing 10 changed files with 595 additions and 21 deletions.
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 @@ -61,10 +62,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
14 changes: 13 additions & 1 deletion DuckDB.NET.Data/DuckDBParameter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
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 @@ -47,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 @@ -146,6 +148,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
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 @@ -80,12 +81,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

0 comments on commit 6bda227

Please sign in to comment.