Skip to content
Draft
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
24 changes: 24 additions & 0 deletions DuckDB.NET.Data/DataChunk/Writer/VectorDataWriterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,36 @@ public void WriteNull(ulong rowIndex)

public void WriteValue<T>(T value, ulong rowIndex)
{
static InvalidOperationException GetIncompatibleTypeException(DuckDBType columnType, Type valueType)
=> new($"{valueType.Name} type was passed for a {columnType} column.");

if (value == null)
{
WriteNull(rowIndex);
return;
}

if (rowIndex == 0)
{
var type = value.GetType();

switch (columnType)
{
case DuckDBType.TinyInt: if (type != typeof(sbyte)) throw GetIncompatibleTypeException(DuckDBType.TinyInt, type); break;
case DuckDBType.SmallInt: if (type != typeof(short)) throw GetIncompatibleTypeException(DuckDBType.SmallInt, type); break;
case DuckDBType.Integer: if (type != typeof(int)) throw GetIncompatibleTypeException(DuckDBType.Integer, type); break;
case DuckDBType.BigInt: if (type != typeof(long)) throw GetIncompatibleTypeException(DuckDBType.BigInt, type); break;
case DuckDBType.UnsignedTinyInt: if (type != typeof(byte)) throw GetIncompatibleTypeException(DuckDBType.UnsignedTinyInt, type); break;
case DuckDBType.UnsignedSmallInt: if (type != typeof(ushort)) throw GetIncompatibleTypeException(DuckDBType.UnsignedSmallInt, type); break;
case DuckDBType.UnsignedInteger: if (type != typeof(uint)) throw GetIncompatibleTypeException(DuckDBType.UnsignedInteger, type); break;
case DuckDBType.UnsignedBigInt: if (type != typeof(ulong)) throw GetIncompatibleTypeException(DuckDBType.UnsignedBigInt, type); break;
case DuckDBType.Float: if (type != typeof(float)) throw GetIncompatibleTypeException(DuckDBType.Float, type); break;
case DuckDBType.Double: if (type != typeof(double)) throw GetIncompatibleTypeException(DuckDBType.Double, type); break;
default:
break;
}
}

_ = value switch
{
bool val => AppendBool(val, rowIndex),
Expand Down
33 changes: 33 additions & 0 deletions DuckDB.NET.Data/DuckDBAppenderRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,40 @@ public void EndRow()
#region Append Signed Int

public IDuckDBAppenderRow AppendValue(sbyte? value) => AppendValueInternal(value);
public IDuckDBAppenderRow AppendValue(sbyte value) => AppendValueInternal(value);


public IDuckDBAppenderRow AppendValue(short? value) => AppendValueInternal(value);
public IDuckDBAppenderRow AppendValue(short value) => AppendValueInternal(value);


public IDuckDBAppenderRow AppendValue(int? value) => AppendValueInternal(value);
public IDuckDBAppenderRow AppendValue(int value) => AppendValueInternal(value);


public IDuckDBAppenderRow AppendValue(long? value) => AppendValueInternal(value);
public IDuckDBAppenderRow AppendValue(long value) => AppendValueInternal(value);


#endregion

#region Append Unsigned Int

public IDuckDBAppenderRow AppendValue(byte? value) => AppendValueInternal(value);
public IDuckDBAppenderRow AppendValue(byte value) => AppendValueInternal(value);


public IDuckDBAppenderRow AppendValue(ushort? value) => AppendValueInternal(value);
public IDuckDBAppenderRow AppendValue(ushort value) => AppendValueInternal(value);


public IDuckDBAppenderRow AppendValue(uint? value) => AppendValueInternal(value);
public IDuckDBAppenderRow AppendValue(uint value) => AppendValueInternal(value);


public IDuckDBAppenderRow AppendValue(ulong? value) => AppendValueInternal(value);
public IDuckDBAppenderRow AppendValue(ulong value) => AppendValueInternal(value);


#endregion

Expand Down Expand Up @@ -181,13 +197,30 @@ public interface IDuckDBAppenderRow
IDuckDBAppenderRow AppendValue(Guid? value);
IDuckDBAppenderRow AppendValue(BigInteger? value);
IDuckDBAppenderRow AppendValue(sbyte? value);
IDuckDBAppenderRow AppendValue(sbyte value);

IDuckDBAppenderRow AppendValue(short? value);
IDuckDBAppenderRow AppendValue(short value);

IDuckDBAppenderRow AppendValue(int? value);
IDuckDBAppenderRow AppendValue(int value);

IDuckDBAppenderRow AppendValue(long? value);
IDuckDBAppenderRow AppendValue(long value);

IDuckDBAppenderRow AppendValue(byte? value);
IDuckDBAppenderRow AppendValue(byte value);

IDuckDBAppenderRow AppendValue(ushort? value);
IDuckDBAppenderRow AppendValue(ushort value);

IDuckDBAppenderRow AppendValue(uint? value);
IDuckDBAppenderRow AppendValue(uint value);

IDuckDBAppenderRow AppendValue(ulong? value);
IDuckDBAppenderRow AppendValue(ulong value);


IDuckDBAppenderRow AppendValue<TEnum>(TEnum? value) where TEnum : Enum;
IDuckDBAppenderRow AppendValue(float? value);
IDuckDBAppenderRow AppendValue(double? value);
Expand Down
2 changes: 1 addition & 1 deletion DuckDB.NET.Test/DuckDBManagedAppenderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ public void TooManyAppendValueThrowsException()
var row = appender.CreateRow();
row
.AppendValue(true)
.AppendValue((byte)1)
.AppendValue((sbyte)1)
.AppendValue("test")
.EndRow();

Expand Down
178 changes: 178 additions & 0 deletions DuckDB.NET.Test/DuckDBManagedAppenderTypeValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using Xunit;
using System;

namespace DuckDB.NET.Test;

public class DuckDBManagedAppenderTypeValidationTests(DuckDBDatabaseFixture db) : DuckDBTestBase(db)
{
[Fact]
public void TryAppendFloatToDouble()
{
Command.CommandText = "create table appender_type_test_double (value double);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_double");
{
var dbRow = appender.CreateRow();

Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue((float)1).EndRow());
}
}

[Fact]
public void TryAppendDoubleToFloat()
{
Command.CommandText = "create table appender_type_test_real (value real);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_real");
{
var dbRow = appender.CreateRow();

Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue((double)1).EndRow());
}
}

[Fact]
public void TryAppendIntToUInt32()
{
Command.CommandText = "create table appender_type_test_uint (value uinteger);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_uint");
{
var dbRow = appender.CreateRow();

Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue(1).EndRow());
}
}

[Fact]
public void TryAppendIntToTinyInt()
{
Command.CommandText = "create table appender_type_test_tinyint (value tinyint);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_tinyint");
{
var dbRow = appender.CreateRow();

// TinyInt expects sbyte, passing int should cause validation to fail
Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue(1).EndRow());
}
}

[Fact]
public void AppendShortToSmallint()
{
Command.CommandText = "create table appender_type_test_smallint (value smallint);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_smallint");
{
var dbRow = appender.CreateRow();

dbRow.AppendValue((short)1).EndRow();
}
}

[Fact]
public void AppendNullableShortToSmallint()
{
Command.CommandText = "create table appender_type_test_smallint_nullable (value smallint);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_smallint_nullable");
{
var dbRow = appender.CreateRow();

dbRow.AppendValue((short?)null).EndRow();
}
}

[Fact]
public void TryAppendLongToInteger()
{
Command.CommandText = "create table appender_type_test_integer (value integer);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_integer");
{
var dbRow = appender.CreateRow();

// Integer expects int, passing long should fail validation
Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue(1L).EndRow());
}
}

[Fact]
public void TryAppendIntToBigInt()
{
Command.CommandText = "create table appender_type_test_bigint (value bigint);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_bigint");
{
var dbRow = appender.CreateRow();

// BigInt expects long, passing int should fail validation
Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue(1).EndRow());
}
}

[Fact]
public void TryAppendIntToUnsignedTinyInt()
{
Command.CommandText = "create table appender_type_test_utinyint (value utinyint);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_utinyint");
{
var dbRow = appender.CreateRow();

// UTINYINT expects byte, passing int should fail validation
Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue(1).EndRow());
}
}

[Fact]
public void TryAppendIntToUnsignedSmallInt()
{
Command.CommandText = "create table appender_type_test_usmallint (value usmallint);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_usmallint");
{
var dbRow = appender.CreateRow();

// USMALLINT expects ushort, passing int should fail validation
Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue(1).EndRow());
}
}

[Fact]
public void TryAppendLongToUnsignedBigInt()
{
Command.CommandText = "create table appender_type_test_ubigint (value ubigint);";

Command.ExecuteNonQuery();

using var appender = Connection.CreateAppender("", "appender_type_test_ubigint");
{
var dbRow = appender.CreateRow();

// UBIGINT expects ulong, passing long should fail validation
Assert.Throws<InvalidOperationException>(() => dbRow.AppendValue(1L).EndRow());
}
}
}