Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ public static partial class PreparedStatements
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial DuckDBState DuckDBBindHugeInt(DuckDBPreparedStatement preparedStatement, long index, DuckDBHugeInt val);

[LibraryImport(DuckDbLibrary, EntryPoint = "duckdb_bind_decimal")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial DuckDBState DuckDBBindDecimal(DuckDBPreparedStatement preparedStatement, long index, DuckDBDecimal val);

[LibraryImport(DuckDbLibrary, EntryPoint = "duckdb_bind_uint8")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial DuckDBState DuckDBBindUInt8(DuckDBPreparedStatement preparedStatement, long index, byte val);
Expand Down Expand Up @@ -98,6 +102,14 @@ public static partial class PreparedStatements
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial DuckDBState DuckDBBindTimestamp(DuckDBPreparedStatement preparedStatement, long index, DuckDBTimestampStruct val);

[LibraryImport(DuckDbLibrary, EntryPoint = "duckdb_bind_timestamp_tz")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial DuckDBState DuckDBBindTimestampTz(DuckDBPreparedStatement preparedStatement, long index, DuckDBTimestampStruct val);

[LibraryImport(DuckDbLibrary, EntryPoint = "duckdb_bind_interval")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial DuckDBState DuckDBBindInterval(DuckDBPreparedStatement preparedStatement, long index, DuckDBInterval val);

[LibraryImport(DuckDbLibrary, EntryPoint = "duckdb_bind_varchar", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial DuckDBState DuckDBBindVarchar(DuckDBPreparedStatement preparedStatement, long index, string val);
Expand Down
232 changes: 211 additions & 21 deletions DuckDB.NET.Data/PreparedStatement/ClrToDuckDBConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ public static DuckDBValue ToDuckDBValue(this object? item, DuckDBLogicalType log
{
(DuckDBType.Boolean, bool value) => NativeMethods.Value.DuckDBCreateBool(value),

(DuckDBType.TinyInt, _) => TryConvertTo<sbyte>(out var result) ? NativeMethods.Value.DuckDBCreateInt8(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.SmallInt, _) => TryConvertTo<short>(out var result) ? NativeMethods.Value.DuckDBCreateInt16(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.Integer, _) => TryConvertTo<int>(out var result) ? NativeMethods.Value.DuckDBCreateInt32(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.BigInt, _) => TryConvertTo<long>(out var result) ? NativeMethods.Value.DuckDBCreateInt64(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.TinyInt, _) => TryConvertTo(item, out sbyte result) ? NativeMethods.Value.DuckDBCreateInt8(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.SmallInt, _) => TryConvertTo(item, out short result) ? NativeMethods.Value.DuckDBCreateInt16(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.Integer, _) => TryConvertTo(item, out int result) ? NativeMethods.Value.DuckDBCreateInt32(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.BigInt, _) => TryConvertTo(item, out long result) ? NativeMethods.Value.DuckDBCreateInt64(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),

(DuckDBType.UnsignedTinyInt, _) => TryConvertTo<byte>(out var result) ? NativeMethods.Value.DuckDBCreateUInt8(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.UnsignedSmallInt, _) => TryConvertTo<ushort>(out var result) ? NativeMethods.Value.DuckDBCreateUInt16(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.UnsignedInteger, _) => TryConvertTo<uint>(out var result) ? NativeMethods.Value.DuckDBCreateUInt32(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.UnsignedBigInt, _) => TryConvertTo<ulong>(out var result) ? NativeMethods.Value.DuckDBCreateUInt64(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.UnsignedTinyInt, _) => TryConvertTo(item, out byte result) ? NativeMethods.Value.DuckDBCreateUInt8(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.UnsignedSmallInt, _) => TryConvertTo(item, out ushort result) ? NativeMethods.Value.DuckDBCreateUInt16(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.UnsignedInteger, _) => TryConvertTo(item, out uint result) ? NativeMethods.Value.DuckDBCreateUInt32(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),
(DuckDBType.UnsignedBigInt, _) => TryConvertTo(item, out ulong result) ? NativeMethods.Value.DuckDBCreateUInt64(result) : NativeMethods.Value.DuckDBCreateVarchar(item.ToString()),

(DuckDBType.Float, float value) => NativeMethods.Value.DuckDBCreateFloat(value),
(DuckDBType.Double, double value) => NativeMethods.Value.DuckDBCreateDouble(value),
Expand Down Expand Up @@ -97,25 +97,212 @@ public static DuckDBValue ToDuckDBValue(this object? item, DuckDBLogicalType log
_ when ValueCreators.TryGetValue(dbType, out var converter) => converter(item),
_ => NativeMethods.Value.DuckDBCreateVarchar(item.ToString())
};
}

bool TryConvertTo<T>(out T result) where T : struct
public static bool TryBindScalarValue(this object? item, DuckDBPreparedStatement statement, long index, DuckDBType duckDBType, DbType dbType, out DuckDBState result)
{
if (item.IsNull())
{
try
{
if (item is T parsable)
{
result = parsable;
return true;
}
result = NativeMethods.PreparedStatements.DuckDBBindNull(statement, index);
return true;
}

result = (T)Convert.ChangeType(item, typeof(T));
switch (duckDBType, item)
{
case (DuckDBType.Boolean, bool booleanValue):
result = NativeMethods.PreparedStatements.DuckDBBindBoolean(statement, index, booleanValue);
return true;
}
catch (Exception)
{

case (DuckDBType.TinyInt, _):
result = TryConvertTo(item, out sbyte int8Value)
? NativeMethods.PreparedStatements.DuckDBBindInt8(statement, index, int8Value)
: NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, item.ToString()!);
return true;
case (DuckDBType.SmallInt, _):
result = TryConvertTo(item, out short int16Value)
? NativeMethods.PreparedStatements.DuckDBBindInt16(statement, index, int16Value)
: NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, item.ToString()!);
return true;
case (DuckDBType.Integer, _):
result = TryConvertTo(item, out int int32Value)
? NativeMethods.PreparedStatements.DuckDBBindInt32(statement, index, int32Value)
: NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, item.ToString()!);
return true;
case (DuckDBType.BigInt, _):
result = TryConvertTo(item, out long int64Value)
? NativeMethods.PreparedStatements.DuckDBBindInt64(statement, index, int64Value)
: NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, item.ToString()!);
return true;

case (DuckDBType.UnsignedTinyInt, _):
result = TryConvertTo(item, out byte uint8Value)
? NativeMethods.PreparedStatements.DuckDBBindUInt8(statement, index, uint8Value)
: NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, item.ToString()!);
return true;
case (DuckDBType.UnsignedSmallInt, _):
result = TryConvertTo(item, out ushort uint16Value)
? NativeMethods.PreparedStatements.DuckDBBindUInt16(statement, index, uint16Value)
: NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, item.ToString()!);
return true;
case (DuckDBType.UnsignedInteger, _):
result = TryConvertTo(item, out uint uint32Value)
? NativeMethods.PreparedStatements.DuckDBBindUInt32(statement, index, uint32Value)
: NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, item.ToString()!);
return true;
case (DuckDBType.UnsignedBigInt, _):
result = TryConvertTo(item, out ulong uint64Value)
? NativeMethods.PreparedStatements.DuckDBBindUInt64(statement, index, uint64Value)
: NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, item.ToString()!);
return true;

case (DuckDBType.Float, float floatValue):
result = NativeMethods.PreparedStatements.DuckDBBindFloat(statement, index, floatValue);
return true;
case (DuckDBType.Double, double doubleValue):
result = NativeMethods.PreparedStatements.DuckDBBindDouble(statement, index, doubleValue);
return true;
case (DuckDBType.Decimal, decimal decimalValue):
result = NativeMethods.PreparedStatements.DuckDBBindDecimal(statement, index, ToDuckDBDecimal(decimalValue));
return true;
case (DuckDBType.HugeInt, BigInteger hugeIntValue):
result = NativeMethods.PreparedStatements.DuckDBBindHugeInt(statement, index, new DuckDBHugeInt(hugeIntValue));
return true;

case (DuckDBType.Varchar, string stringValue):
result = NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, stringValue);
return true;
case (DuckDBType.Timestamp, DateTime timestampValue):
result = NativeMethods.PreparedStatements.DuckDBBindTimestamp(statement, index, timestampValue.ToTimestampStruct(duckDBType));
return true;
case (DuckDBType.TimestampTz, DateTime timestampTzValue):
result = NativeMethods.PreparedStatements.DuckDBBindTimestampTz(statement, index, timestampTzValue.ToTimestampStruct(duckDBType));
return true;
case (DuckDBType.TimestampTz, DateTimeOffset timestampOffsetValue):
result = NativeMethods.PreparedStatements.DuckDBBindTimestampTz(statement, index, timestampOffsetValue.ToTimestampStruct());
return true;
case (DuckDBType.Interval, TimeSpan intervalValue):
result = NativeMethods.PreparedStatements.DuckDBBindInterval(statement, index, intervalValue);
return true;
case (DuckDBType.Date, DateTime dateTimeValue):
result = NativeMethods.PreparedStatements.DuckDBBindDate(statement, index, ((DuckDBDateOnly)dateTimeValue).ToDuckDBDate());
return true;
case (DuckDBType.Date, DuckDBDateOnly duckDBDateValue):
result = NativeMethods.PreparedStatements.DuckDBBindDate(statement, index, duckDBDateValue.ToDuckDBDate());
return true;
case (DuckDBType.Date, DateOnly dateOnlyValue):
result = NativeMethods.PreparedStatements.DuckDBBindDate(statement, index, ((DuckDBDateOnly)dateOnlyValue).ToDuckDBDate());
return true;
case (DuckDBType.Time, DateTime timeDateTimeValue):
result = NativeMethods.PreparedStatements.DuckDBBindTime(statement, index, NativeMethods.DateTimeHelpers.DuckDBToTime((DuckDBTimeOnly)timeDateTimeValue));
return true;
case (DuckDBType.Time, DuckDBTimeOnly duckDBTimeValue):
result = NativeMethods.PreparedStatements.DuckDBBindTime(statement, index, NativeMethods.DateTimeHelpers.DuckDBToTime(duckDBTimeValue));
return true;
case (DuckDBType.Time, TimeOnly timeOnlyValue):
result = NativeMethods.PreparedStatements.DuckDBBindTime(statement, index, NativeMethods.DateTimeHelpers.DuckDBToTime(timeOnlyValue));
return true;
case (DuckDBType.Blob, byte[] blobValue):
result = NativeMethods.PreparedStatements.DuckDBBindBlob(statement, index, blobValue, blobValue.LongLength);
return true;
}

// These logical types need the exact duckdb_value representation because the scalar C API
// has no equivalent binder, or because its timestamp unit differs from duckdb_bind_timestamp.
if (item is Guid ||
item is DateTime && duckDBType is DuckDBType.TimestampS or DuckDBType.TimestampMs or DuckDBType.TimestampNs ||
item is DateTimeOffset && duckDBType == DuckDBType.TimeTz ||
item is ICollection && (item is not byte[] || duckDBType is DuckDBType.List or DuckDBType.Array))
{
result = default;
return false;
}

switch (dbType)
{
case DbType.Currency:
result = NativeMethods.PreparedStatements.DuckDBBindDecimal(statement, index, ToDuckDBDecimal((decimal)item));
return true;
case DbType.Boolean:
result = NativeMethods.PreparedStatements.DuckDBBindBoolean(statement, index, (bool)item);
return true;
case DbType.SByte:
result = NativeMethods.PreparedStatements.DuckDBBindInt8(statement, index, (sbyte)item);
return true;
case DbType.Int16:
result = NativeMethods.PreparedStatements.DuckDBBindInt16(statement, index, (short)item);
return true;
case DbType.Int32:
result = NativeMethods.PreparedStatements.DuckDBBindInt32(statement, index, (int)item);
return true;
case DbType.Int64:
result = NativeMethods.PreparedStatements.DuckDBBindInt64(statement, index, (long)item);
return true;
case DbType.Byte:
result = NativeMethods.PreparedStatements.DuckDBBindUInt8(statement, index, (byte)item);
return true;
case DbType.UInt16:
result = NativeMethods.PreparedStatements.DuckDBBindUInt16(statement, index, (ushort)item);
return true;
case DbType.UInt32:
result = NativeMethods.PreparedStatements.DuckDBBindUInt32(statement, index, (uint)item);
return true;
case DbType.UInt64:
result = NativeMethods.PreparedStatements.DuckDBBindUInt64(statement, index, (ulong)item);
return true;
case DbType.Single:
result = NativeMethods.PreparedStatements.DuckDBBindFloat(statement, index, (float)item);
return true;
case DbType.Double:
result = NativeMethods.PreparedStatements.DuckDBBindDouble(statement, index, (double)item);
return true;
case DbType.String:
result = NativeMethods.PreparedStatements.DuckDBBindVarchar(statement, index, (string)item);
return true;
case DbType.VarNumeric:
result = NativeMethods.PreparedStatements.DuckDBBindHugeInt(statement, index, new DuckDBHugeInt((BigInteger)item));
return true;
case DbType.Binary:
var bytes = (byte[])item;
result = NativeMethods.PreparedStatements.DuckDBBindBlob(statement, index, bytes, bytes.LongLength);
return true;
case DbType.Date:
var date = (item is DateOnly dateOnly ? (DuckDBDateOnly)dateOnly : (DuckDBDateOnly)item).ToDuckDBDate();
result = NativeMethods.PreparedStatements.DuckDBBindDate(statement, index, date);
return true;
case DbType.Time:
var time = NativeMethods.DateTimeHelpers.DuckDBToTime(item is TimeOnly timeOnly ? (DuckDBTimeOnly)timeOnly : (DuckDBTimeOnly)item);
result = NativeMethods.PreparedStatements.DuckDBBindTime(statement, index, time);
return true;
case DbType.DateTime:
var timestamp = (item is DateTime dateTime ? (DuckDBTimestamp)dateTime : (DuckDBTimestamp)item).ToDuckDBTimestampStruct();
result = NativeMethods.PreparedStatements.DuckDBBindTimestamp(statement, index, timestamp);
return true;
case DbType.DateTimeOffset:
result = NativeMethods.PreparedStatements.DuckDBBindTimestampTz(statement, index, ((DateTimeOffset)item).ToTimestampStruct());
return true;
default:
result = default;
return false;
}
}

private static bool TryConvertTo<T>(object item, out T result) where T : struct
{
try
{
if (item is T parsable)
{
result = parsable;
return true;
}

result = (T)Convert.ChangeType(item, typeof(T));
return true;
}
catch (Exception)
{
result = default;
return false;
}
}

Expand Down Expand Up @@ -160,13 +347,16 @@ private static DuckDBValue[] BuildValues(DuckDBLogicalType childType, ICollectio
}

private static DuckDBValue DecimalToDuckDBValue(decimal value)
=> NativeMethods.Value.DuckDBCreateDecimal(ToDuckDBDecimal(value));

private static DuckDBDecimal ToDuckDBDecimal(decimal value)
{
var mantissa = value.GetMantissa();

var width = mantissa.IsZero
? value.Scale + 1
: Math.Max((int)BigInteger.Log10(BigInteger.Abs(mantissa)) + 1, value.Scale + 1);

return NativeMethods.Value.DuckDBCreateDecimal(new DuckDBDecimal((byte)width, value.Scale, new DuckDBHugeInt(mantissa)));
return new DuckDBDecimal((byte)width, value.Scale, new DuckDBHugeInt(mantissa));
}
}
11 changes: 7 additions & 4 deletions DuckDB.NET.Data/PreparedStatement/PreparedStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,12 @@ private static void BindParameter(DuckDBPreparedStatement preparedStatement, lon
using var parameterLogicalType = NativeMethods.PreparedStatements.DuckDBParamLogicalType(preparedStatement, index);
var duckDBType = NativeMethods.LogicalType.DuckDBGetTypeId(parameterLogicalType);

using var duckDBValue = parameter.Value.ToDuckDBValue(parameterLogicalType, duckDBType, parameter.DbType);

var result = NativeMethods.PreparedStatements.DuckDBBindValue(preparedStatement, index, duckDBValue);
DuckDBState result;
if (!parameter.Value.TryBindScalarValue(preparedStatement, index, duckDBType, parameter.DbType, out result))
{
using var duckDBValue = parameter.Value.ToDuckDBValue(parameterLogicalType, duckDBType, parameter.DbType);
result = NativeMethods.PreparedStatements.DuckDBBindValue(preparedStatement, index, duckDBValue);
}

if (!result.IsSuccess())
{
Expand All @@ -145,4 +148,4 @@ public void Dispose()
{
statement.Dispose();
}
}
}
Loading
Loading