Skip to content

Commit e8f9244

Browse files
committed
Add support for inserting TIMESTAMP_MS, TIMESTAMP_NS and TIMESTAMP_S
1 parent 3b7b4ba commit e8f9244

File tree

4 files changed

+90
-29
lines changed

4 files changed

+90
-29
lines changed

DuckDB.NET.Bindings/DuckDBWrapperObjects.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,14 @@ public T GetValue<T>()
147147
DuckDBType.UnsignedHugeInt => Cast(NativeMethods.Value.DuckDBGetUHugeInt(this).ToBigInteger()),
148148

149149
DuckDBType.Varchar => Cast(NativeMethods.Value.DuckDBGetVarchar(this)),
150-
151-
//DuckDBType.Date => expr,
152-
//DuckDBType.Time => expr,
150+
151+
#if NET6_0_OR_GREATER
152+
DuckDBType.Date => Cast((DateOnly)NativeMethods.DateTimeHelpers.DuckDBFromDate(NativeMethods.Value.DuckDBGetDate(this))),
153+
DuckDBType.Time => Cast((TimeOnly)NativeMethods.DateTimeHelpers.DuckDBFromTime(NativeMethods.Value.DuckDBGetTime(this))),
154+
#else
155+
DuckDBType.Date => Cast(NativeMethods.DateTimeHelpers.DuckDBFromDate(NativeMethods.Value.DuckDBGetDate(this)).ToDateTime()),
156+
DuckDBType.Time => Cast(NativeMethods.DateTimeHelpers.DuckDBFromTime(NativeMethods.Value.DuckDBGetTime(this)).ToDateTime()),
157+
#endif
153158
//DuckDBType.TimeTz => expr,
154159
DuckDBType.Interval => Cast((TimeSpan)NativeMethods.Value.DuckDBGetInterval(this)),
155160
DuckDBType.Timestamp => Cast(NativeMethods.DateTimeHelpers.DuckDBFromTimestamp(NativeMethods.Value.DuckDBGetTimestamp(this)).ToDateTime()),

DuckDB.NET.Bindings/NativeMethods/NativeMethods.Value.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ public static class Value
6565
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp")]
6666
public static extern DuckDBValue DuckDBCreateTimestamp(DuckDBTimestampStruct value);
6767

68+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp_tz")]
69+
public static extern DuckDBValue DuckDBCreateTimestampTz(DuckDBTimestampStruct value);
70+
71+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp_s")]
72+
public static extern DuckDBValue DuckDBCreateTimestampS(DuckDBTimestampStruct value);
73+
74+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp_ms")]
75+
public static extern DuckDBValue DuckDBCreateTimestampMs(DuckDBTimestampStruct value);
76+
77+
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_timestamp_ns")]
78+
public static extern DuckDBValue DuckDBCreateTimestampNs(DuckDBTimestampStruct value);
79+
6880
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_create_interval")]
6981
public static extern DuckDBValue DuckDBCreateInterval(DuckDBInterval value);
7082

DuckDB.NET.Data/Internal/ClrToDuckDBConverter.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ public static DuckDBValue ToDuckDBValue(this object? item, DuckDBLogicalType log
3939
(DuckDBType.Varchar, string value) => StringToDuckDBValue(value),
4040
(DuckDBType.Uuid, Guid value) => GuidToDuckDBValue(value),
4141

42-
(DuckDBType.Timestamp, DateTime value) => NativeMethods.Value.DuckDBCreateTimestamp(NativeMethods.DateTimeHelpers.DuckDBToTimestamp(DuckDBTimestamp.FromDateTime(value))),
42+
(DuckDBType.Timestamp, DateTime value) => DateTimeToTimestamp(DuckDBType.Timestamp, value),
43+
(DuckDBType.TimestampS, DateTime value) => DateTimeToTimestamp(DuckDBType.TimestampS, value, divisor: 1000000),
44+
(DuckDBType.TimestampMs, DateTime value) => DateTimeToTimestamp(DuckDBType.TimestampMs, value, divisor: 1000),
45+
(DuckDBType.TimestampNs, DateTime value) => DateTimeToTimestamp(DuckDBType.TimestampNs, value, factor: 1000, extra: value.Nanoseconds()),
4346
(DuckDBType.Interval, TimeSpan value) => NativeMethods.Value.DuckDBCreateInterval(value),
4447
(DuckDBType.Date, DateTime value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate((DuckDBDateOnly)value)),
4548
(DuckDBType.Date, DuckDBDateOnly value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate(value)),
@@ -49,7 +52,8 @@ public static DuckDBValue ToDuckDBValue(this object? item, DuckDBLogicalType log
4952
(DuckDBType.Date, DateOnly value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate(value)),
5053
(DuckDBType.Time, TimeOnly value) => NativeMethods.Value.DuckDBCreateTime(NativeMethods.DateTimeHelpers.DuckDBToTime(value)),
5154
#endif
52-
(DuckDBType.TimeTz, DateTimeOffset value) => DateTimeOffsetToDuckDBValue(value),
55+
(DuckDBType.TimeTz, DateTimeOffset value) => DateTimeOffsetToTimeTzDuckDBValue(value),
56+
(DuckDBType.TimestampTz, DateTimeOffset value) => NativeMethods.Value.DuckDBCreateTimestamp(NativeMethods.DateTimeHelpers.DuckDBToTimestamp(DuckDBTimestamp.FromDateTime(value.DateTime))),
5357
(DuckDBType.Blob, byte[] value) => NativeMethods.Value.DuckDBCreateBlob(value, value.Length),
5458
(DuckDBType.List, ICollection value) => CreateCollectionValue(logicalType, value, true),
5559
(DuckDBType.Array, ICollection value) => CreateCollectionValue(logicalType, value, false),
@@ -67,6 +71,21 @@ T ConvertTo<T>()
6771
throw new ArgumentOutOfRangeException($"Cannot bind parameter type {item.GetType().FullName} to column of type {duckDBType}");
6872
}
6973
}
74+
75+
DuckDBValue DateTimeToTimestamp(DuckDBType type, DateTime value, int factor = 1, int divisor = 1, int extra = 0)
76+
{
77+
var timestamp = NativeMethods.DateTimeHelpers.DuckDBToTimestamp(DuckDBTimestamp.FromDateTime(value));
78+
79+
timestamp.Micros = timestamp.Micros * factor / divisor;
80+
81+
return type switch
82+
{
83+
DuckDBType.Timestamp => NativeMethods.Value.DuckDBCreateTimestamp(timestamp),
84+
DuckDBType.TimestampS => NativeMethods.Value.DuckDBCreateTimestampS(timestamp),
85+
DuckDBType.TimestampMs => NativeMethods.Value.DuckDBCreateTimestampMs(timestamp),
86+
DuckDBType.TimestampNs => NativeMethods.Value.DuckDBCreateTimestampNs(timestamp),
87+
};
88+
}
7089
}
7190

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

111-
private static DuckDBValue DateTimeOffsetToDuckDBValue(DateTimeOffset val)
130+
private static DuckDBValue DateTimeOffsetToTimeTzDuckDBValue(DateTimeOffset val)
112131
{
113132
var duckDBToTime = NativeMethods.DateTimeHelpers.DuckDBToTime((DuckDBTimeOnly)val.DateTime);
114133
var duckDBCreateTimeTz = NativeMethods.DateTimeHelpers.DuckDBCreateTimeTz(duckDBToTime.Micros, (int)val.Offset.TotalSeconds);

DuckDB.NET.Test/Parameters/TimestampTests.cs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
using System;
21
using DuckDB.NET.Data;
2+
using DuckDB.NET.Native;
33
using FluentAssertions;
4+
using System;
45
using Xunit;
56

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

82-
Command.CommandText = "CREATE TABLE TimestampTestTable (a INTEGER, b TIMESTAMP);";
83-
Command.ExecuteNonQuery();
83+
TestTimestampInsert("TIMESTAMP", DuckDBType.Timestamp, expectedValue);
84+
85+
TestTimestampInsert("TIMESTAMP_S", DuckDBType.TimestampS, expectedValue);
86+
87+
TestTimestampInsert("TIMESTAMP_MS", DuckDBType.TimestampMs, expectedValue);
88+
89+
TestTimestampInsert("TIMESTAMP_NS", DuckDBType.TimestampNs, expectedValue);
90+
}
8491

92+
private void TestTimestampInsert(string timestampType, DuckDBType duckDBType, DateTime expectedValue)
93+
{
94+
expectedValue = duckDBType switch
95+
{
96+
DuckDBType.TimestampS => Trim(expectedValue, TimeSpan.TicksPerSecond),
97+
DuckDBType.TimestampMs => Trim(expectedValue, TimeSpan.TicksPerMillisecond),
98+
DuckDBType.TimestampNs => Trim(expectedValue, TimeSpan.FromTicks(100).Ticks),
99+
_ => expectedValue
100+
};
101+
102+
Command.CommandText = $"CREATE OR Replace TABLE TimestampTestTable (a INTEGER, b {timestampType});";
103+
Command.ExecuteNonQuery();
104+
85105
Command.CommandText = "INSERT INTO TimestampTestTable (a, b) VALUES (42, ?);";
86106
Command.Parameters.Add(new DuckDBParameter(expectedValue));
87107
Command.ExecuteNonQuery();
@@ -94,32 +114,37 @@ public void InsertAndQueryTest(int year, int mon, int day, byte hour, byte minut
94114

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

97-
var dateTime = reader.GetDateTime(1);
117+
var databaseValue = reader.GetDateTime(1);
98118

99-
dateTime.Year.Should().Be(year);
100-
dateTime.Month.Should().Be(mon);
101-
dateTime.Day.Should().Be(day);
102-
dateTime.Hour.Should().Be(hour);
103-
dateTime.Minute.Should().Be(minute);
104-
dateTime.Second.Should().Be(second);
105-
dateTime.Millisecond.Should().Be(microsecond / 1000);
119+
databaseValue.Year.Should().Be(expectedValue.Year);
120+
databaseValue.Month.Should().Be(expectedValue.Month);
121+
databaseValue.Day.Should().Be(expectedValue.Day);
122+
databaseValue.Hour.Should().Be(expectedValue.Hour);
123+
databaseValue.Minute.Should().Be(expectedValue.Minute);
124+
databaseValue.Second.Should().Be(expectedValue.Second);
106125

107-
dateTime.TimeOfDay.Should().Be(expectedValue.TimeOfDay);
126+
databaseValue.Millisecond.Should().Be(expectedValue.Millisecond);
127+
databaseValue.Microsecond.Should().Be(expectedValue.Microsecond);
128+
databaseValue.Nanosecond.Should().Be(expectedValue.Nanosecond);
129+
130+
databaseValue.TimeOfDay.Should().Be(expectedValue.TimeOfDay);
108131

109132
var dateTimeNullable = reader.GetFieldValue<DateTime?>(1);
110-
dateTime = dateTimeNullable.Value;
133+
databaseValue = dateTimeNullable.Value;
111134

112-
dateTime.Year.Should().Be(year);
113-
dateTime.Month.Should().Be(mon);
114-
dateTime.Day.Should().Be(day);
115-
dateTime.Hour.Should().Be(hour);
116-
dateTime.Minute.Should().Be(minute);
117-
dateTime.Second.Should().Be(second);
118-
dateTime.Millisecond.Should().Be(microsecond / 1000);
135+
databaseValue.Year.Should().Be(expectedValue.Year);
136+
databaseValue.Month.Should().Be(expectedValue.Month);
137+
databaseValue.Day.Should().Be(expectedValue.Day);
138+
databaseValue.Hour.Should().Be(expectedValue.Hour);
139+
databaseValue.Minute.Should().Be(expectedValue.Minute);
140+
databaseValue.Second.Should().Be(expectedValue.Second);
119141

120-
dateTime.TimeOfDay.Should().Be(expectedValue.TimeOfDay);
142+
databaseValue.Millisecond.Should().Be(expectedValue.Millisecond);
143+
databaseValue.Microsecond.Should().Be(expectedValue.Microsecond);
144+
databaseValue.Nanosecond.Should().Be(expectedValue.Nanosecond);
121145

122-
Command.CommandText = "DROP TABLE TimestampTestTable;";
123-
Command.ExecuteNonQuery();
146+
databaseValue.TimeOfDay.Should().Be(expectedValue.TimeOfDay);
124147
}
148+
149+
public static DateTime Trim(DateTime date, long ticks) => new(date.Ticks - (date.Ticks % ticks), date.Kind);
125150
}

0 commit comments

Comments
 (0)