Skip to content

Commit

Permalink
Add support for inserting TIMESTAMP_MS, TIMESTAMP_NS and TIMESTAMP_S
Browse files Browse the repository at this point in the history
  • Loading branch information
Giorgi committed Dec 6, 2024
1 parent 3b7b4ba commit e8f9244
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 29 deletions.
11 changes: 8 additions & 3 deletions DuckDB.NET.Bindings/DuckDBWrapperObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,14 @@ public T GetValue<T>()
DuckDBType.UnsignedHugeInt => Cast(NativeMethods.Value.DuckDBGetUHugeInt(this).ToBigInteger()),

DuckDBType.Varchar => Cast(NativeMethods.Value.DuckDBGetVarchar(this)),

//DuckDBType.Date => expr,
//DuckDBType.Time => expr,

#if NET6_0_OR_GREATER
DuckDBType.Date => Cast((DateOnly)NativeMethods.DateTimeHelpers.DuckDBFromDate(NativeMethods.Value.DuckDBGetDate(this))),
DuckDBType.Time => Cast((TimeOnly)NativeMethods.DateTimeHelpers.DuckDBFromTime(NativeMethods.Value.DuckDBGetTime(this))),
#else
DuckDBType.Date => Cast(NativeMethods.DateTimeHelpers.DuckDBFromDate(NativeMethods.Value.DuckDBGetDate(this)).ToDateTime()),
DuckDBType.Time => Cast(NativeMethods.DateTimeHelpers.DuckDBFromTime(NativeMethods.Value.DuckDBGetTime(this)).ToDateTime()),
#endif
//DuckDBType.TimeTz => expr,
DuckDBType.Interval => Cast((TimeSpan)NativeMethods.Value.DuckDBGetInterval(this)),
DuckDBType.Timestamp => Cast(NativeMethods.DateTimeHelpers.DuckDBFromTimestamp(NativeMethods.Value.DuckDBGetTimestamp(this)).ToDateTime()),
Expand Down
12 changes: 12 additions & 0 deletions DuckDB.NET.Bindings/NativeMethods/NativeMethods.Value.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ public static class Value
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp")]
public static extern DuckDBValue DuckDBCreateTimestamp(DuckDBTimestampStruct value);

[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp_tz")]
public static extern DuckDBValue DuckDBCreateTimestampTz(DuckDBTimestampStruct value);

[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp_s")]
public static extern DuckDBValue DuckDBCreateTimestampS(DuckDBTimestampStruct value);

[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp_ms")]
public static extern DuckDBValue DuckDBCreateTimestampMs(DuckDBTimestampStruct value);

[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp_ns")]
public static extern DuckDBValue DuckDBCreateTimestampNs(DuckDBTimestampStruct value);

[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_interval")]
public static extern DuckDBValue DuckDBCreateInterval(DuckDBInterval value);

Expand Down
25 changes: 22 additions & 3 deletions DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ public static DuckDBValue ToDuckDBValue(this object? item, DuckDBLogicalType log
(DuckDBType.Varchar, string value) => StringToDuckDBValue(value),
(DuckDBType.Uuid, Guid value) => GuidToDuckDBValue(value),

(DuckDBType.Timestamp, DateTime value) => NativeMethods.Value.DuckDBCreateTimestamp(NativeMethods.DateTimeHelpers.DuckDBToTimestamp(DuckDBTimestamp.FromDateTime(value))),
(DuckDBType.Timestamp, DateTime value) => DateTimeToTimestamp(DuckDBType.Timestamp, value),
(DuckDBType.TimestampS, DateTime value) => DateTimeToTimestamp(DuckDBType.TimestampS, value, divisor: 1000000),
(DuckDBType.TimestampMs, DateTime value) => DateTimeToTimestamp(DuckDBType.TimestampMs, value, divisor: 1000),
(DuckDBType.TimestampNs, DateTime value) => DateTimeToTimestamp(DuckDBType.TimestampNs, value, factor: 1000, extra: value.Nanoseconds()),
(DuckDBType.Interval, TimeSpan value) => NativeMethods.Value.DuckDBCreateInterval(value),
(DuckDBType.Date, DateTime value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate((DuckDBDateOnly)value)),
(DuckDBType.Date, DuckDBDateOnly value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate(value)),
Expand All @@ -49,7 +52,8 @@ public static DuckDBValue ToDuckDBValue(this object? item, DuckDBLogicalType log
(DuckDBType.Date, DateOnly value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate(value)),
(DuckDBType.Time, TimeOnly value) => NativeMethods.Value.DuckDBCreateTime(NativeMethods.DateTimeHelpers.DuckDBToTime(value)),
#endif
(DuckDBType.TimeTz, DateTimeOffset value) => DateTimeOffsetToDuckDBValue(value),
(DuckDBType.TimeTz, DateTimeOffset value) => DateTimeOffsetToTimeTzDuckDBValue(value),
(DuckDBType.TimestampTz, DateTimeOffset value) => NativeMethods.Value.DuckDBCreateTimestamp(NativeMethods.DateTimeHelpers.DuckDBToTimestamp(DuckDBTimestamp.FromDateTime(value.DateTime))),
(DuckDBType.Blob, byte[] value) => NativeMethods.Value.DuckDBCreateBlob(value, value.Length),
(DuckDBType.List, ICollection value) => CreateCollectionValue(logicalType, value, true),
(DuckDBType.Array, ICollection value) => CreateCollectionValue(logicalType, value, false),
Expand All @@ -67,6 +71,21 @@ T ConvertTo<T>()
throw new ArgumentOutOfRangeException($"Cannot bind parameter type {item.GetType().FullName} to column of type {duckDBType}");
}
}

DuckDBValue DateTimeToTimestamp(DuckDBType type, DateTime value, int factor = 1, int divisor = 1, int extra = 0)
{
var timestamp = NativeMethods.DateTimeHelpers.DuckDBToTimestamp(DuckDBTimestamp.FromDateTime(value));

timestamp.Micros = timestamp.Micros * factor / divisor;

return type switch

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (windows-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (windows-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (windows-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (windows-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (windows-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (windows-latest)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (macos-14)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (macos-14)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (macos-14)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (macos-14)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (macos-14)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.

Check warning on line 81 in DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

View workflow job for this annotation

GitHub Actions / Build library (macos-14)

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'DuckDB.NET.Native.DuckDBType.Invalid' is not covered.
{
DuckDBType.Timestamp => NativeMethods.Value.DuckDBCreateTimestamp(timestamp),
DuckDBType.TimestampS => NativeMethods.Value.DuckDBCreateTimestampS(timestamp),
DuckDBType.TimestampMs => NativeMethods.Value.DuckDBCreateTimestampMs(timestamp),
DuckDBType.TimestampNs => NativeMethods.Value.DuckDBCreateTimestampNs(timestamp),
};
}
}

private static DuckDBValue CreateCollectionValue(DuckDBLogicalType logicalType, ICollection collection, bool isList)
Expand Down Expand Up @@ -108,7 +127,7 @@ private static DuckDBValue DecimalToDuckDBValue(decimal value)
return NativeMethods.Value.DuckDBCreateVarchar(handle);
}

private static DuckDBValue DateTimeOffsetToDuckDBValue(DateTimeOffset val)
private static DuckDBValue DateTimeOffsetToTimeTzDuckDBValue(DateTimeOffset val)
{
var duckDBToTime = NativeMethods.DateTimeHelpers.DuckDBToTime((DuckDBTimeOnly)val.DateTime);
var duckDBCreateTimeTz = NativeMethods.DateTimeHelpers.DuckDBCreateTimeTz(duckDBToTime.Micros, (int)val.Offset.TotalSeconds);
Expand Down
71 changes: 48 additions & 23 deletions DuckDB.NET.Test/Parameters/TimestampTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using DuckDB.NET.Data;
using DuckDB.NET.Native;
using FluentAssertions;
using System;
using Xunit;

namespace DuckDB.NET.Test.Parameters;
Expand Down Expand Up @@ -79,9 +80,28 @@ public void InsertAndQueryTest(int year, int mon, int day, byte hour, byte minut
{
var expectedValue = new DateTime(year, mon, day, hour, minute, second).AddTicks(microsecond * 10);

Command.CommandText = "CREATE TABLE TimestampTestTable (a INTEGER, b TIMESTAMP);";
Command.ExecuteNonQuery();
TestTimestampInsert("TIMESTAMP", DuckDBType.Timestamp, expectedValue);

TestTimestampInsert("TIMESTAMP_S", DuckDBType.TimestampS, expectedValue);

TestTimestampInsert("TIMESTAMP_MS", DuckDBType.TimestampMs, expectedValue);

TestTimestampInsert("TIMESTAMP_NS", DuckDBType.TimestampNs, expectedValue);
}

private void TestTimestampInsert(string timestampType, DuckDBType duckDBType, DateTime expectedValue)
{
expectedValue = duckDBType switch
{
DuckDBType.TimestampS => Trim(expectedValue, TimeSpan.TicksPerSecond),
DuckDBType.TimestampMs => Trim(expectedValue, TimeSpan.TicksPerMillisecond),
DuckDBType.TimestampNs => Trim(expectedValue, TimeSpan.FromTicks(100).Ticks),
_ => expectedValue
};

Command.CommandText = $"CREATE OR Replace TABLE TimestampTestTable (a INTEGER, b {timestampType});";
Command.ExecuteNonQuery();

Command.CommandText = "INSERT INTO TimestampTestTable (a, b) VALUES (42, ?);";
Command.Parameters.Add(new DuckDBParameter(expectedValue));
Command.ExecuteNonQuery();
Expand All @@ -94,32 +114,37 @@ public void InsertAndQueryTest(int year, int mon, int day, byte hour, byte minut

reader.GetFieldType(1).Should().Be(typeof(DateTime));

var dateTime = reader.GetDateTime(1);
var databaseValue = reader.GetDateTime(1);

dateTime.Year.Should().Be(year);
dateTime.Month.Should().Be(mon);
dateTime.Day.Should().Be(day);
dateTime.Hour.Should().Be(hour);
dateTime.Minute.Should().Be(minute);
dateTime.Second.Should().Be(second);
dateTime.Millisecond.Should().Be(microsecond / 1000);
databaseValue.Year.Should().Be(expectedValue.Year);
databaseValue.Month.Should().Be(expectedValue.Month);
databaseValue.Day.Should().Be(expectedValue.Day);
databaseValue.Hour.Should().Be(expectedValue.Hour);
databaseValue.Minute.Should().Be(expectedValue.Minute);
databaseValue.Second.Should().Be(expectedValue.Second);

dateTime.TimeOfDay.Should().Be(expectedValue.TimeOfDay);
databaseValue.Millisecond.Should().Be(expectedValue.Millisecond);
databaseValue.Microsecond.Should().Be(expectedValue.Microsecond);
databaseValue.Nanosecond.Should().Be(expectedValue.Nanosecond);

databaseValue.TimeOfDay.Should().Be(expectedValue.TimeOfDay);

var dateTimeNullable = reader.GetFieldValue<DateTime?>(1);
dateTime = dateTimeNullable.Value;
databaseValue = dateTimeNullable.Value;

dateTime.Year.Should().Be(year);
dateTime.Month.Should().Be(mon);
dateTime.Day.Should().Be(day);
dateTime.Hour.Should().Be(hour);
dateTime.Minute.Should().Be(minute);
dateTime.Second.Should().Be(second);
dateTime.Millisecond.Should().Be(microsecond / 1000);
databaseValue.Year.Should().Be(expectedValue.Year);
databaseValue.Month.Should().Be(expectedValue.Month);
databaseValue.Day.Should().Be(expectedValue.Day);
databaseValue.Hour.Should().Be(expectedValue.Hour);
databaseValue.Minute.Should().Be(expectedValue.Minute);
databaseValue.Second.Should().Be(expectedValue.Second);

dateTime.TimeOfDay.Should().Be(expectedValue.TimeOfDay);
databaseValue.Millisecond.Should().Be(expectedValue.Millisecond);
databaseValue.Microsecond.Should().Be(expectedValue.Microsecond);
databaseValue.Nanosecond.Should().Be(expectedValue.Nanosecond);

Command.CommandText = "DROP TABLE TimestampTestTable;";
Command.ExecuteNonQuery();
databaseValue.TimeOfDay.Should().Be(expectedValue.TimeOfDay);
}

public static DateTime Trim(DateTime date, long ticks) => new(date.Ticks - (date.Ticks % ticks), date.Kind);
}

0 comments on commit e8f9244

Please sign in to comment.