diff --git a/DuckDB.NET.Data/DataChunk/Writer/VectorDataWriterBase.cs b/DuckDB.NET.Data/DataChunk/Writer/VectorDataWriterBase.cs index cbd7bfb..3553479 100644 --- a/DuckDB.NET.Data/DataChunk/Writer/VectorDataWriterBase.cs +++ b/DuckDB.NET.Data/DataChunk/Writer/VectorDataWriterBase.cs @@ -30,12 +30,36 @@ public void WriteNull(ulong rowIndex) public void WriteValue(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), diff --git a/DuckDB.NET.Data/DuckDBAppenderRow.cs b/DuckDB.NET.Data/DuckDBAppenderRow.cs index 33ee54d..5ef6f4c 100644 --- a/DuckDB.NET.Data/DuckDBAppenderRow.cs +++ b/DuckDB.NET.Data/DuckDBAppenderRow.cs @@ -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 @@ -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? value) where TEnum : Enum; IDuckDBAppenderRow AppendValue(float? value); IDuckDBAppenderRow AppendValue(double? value); diff --git a/DuckDB.NET.Test/DuckDBManagedAppenderTests.cs b/DuckDB.NET.Test/DuckDBManagedAppenderTests.cs index 34e91f9..b2ef84e 100644 --- a/DuckDB.NET.Test/DuckDBManagedAppenderTests.cs +++ b/DuckDB.NET.Test/DuckDBManagedAppenderTests.cs @@ -376,7 +376,7 @@ public void TooManyAppendValueThrowsException() var row = appender.CreateRow(); row .AppendValue(true) - .AppendValue((byte)1) + .AppendValue((sbyte)1) .AppendValue("test") .EndRow(); diff --git a/DuckDB.NET.Test/DuckDBManagedAppenderTypeValidationTests.cs b/DuckDB.NET.Test/DuckDBManagedAppenderTypeValidationTests.cs new file mode 100644 index 0000000..edf2c0f --- /dev/null +++ b/DuckDB.NET.Test/DuckDBManagedAppenderTypeValidationTests.cs @@ -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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => dbRow.AppendValue(1L).EndRow()); + } + } +} \ No newline at end of file