Skip to content

Commit

Permalink
Write decimal to appender
Browse files Browse the repository at this point in the history
  • Loading branch information
Giorgi committed Apr 18, 2024
1 parent 975b555 commit ae6c764
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 40 deletions.
4 changes: 3 additions & 1 deletion DuckDB.NET.Data/DuckDBAppender.cs
Expand Up @@ -101,7 +101,9 @@ private unsafe void InitVectorWriters()
var vector = NativeMethods.DataChunks.DuckDBDataChunkGetVector(dataChunk, index);
var vectorData = NativeMethods.Vectors.DuckDBVectorGetData(vector);

vectorWriters[index] = new DataChunkVectorWriter(vector, vectorData);
var logicalType = logicalTypes[index];
var columnType = NativeMethods.LogicalType.DuckDBGetTypeId(logicalType);
vectorWriters[index] = columnType == DuckDBType.Decimal ? new DataChunkDecimalVectorWriter(vector, vectorData, logicalType) : new DataChunkVectorWriter(vector, vectorData);
}
}

Expand Down
72 changes: 43 additions & 29 deletions DuckDB.NET.Data/DuckDBAppenderRow.cs
Expand Up @@ -10,55 +10,54 @@ public class DuckDBAppenderRow
private int columnIndex = 0;
private readonly Native.DuckDBAppender appender;
private readonly string qualifiedTableName;
private readonly DataChunkVectorWriter[] vectors;
private readonly DataChunkVectorWriter[] vectorWriters;
private readonly ulong rowIndex;

internal DuckDBAppenderRow(Native.DuckDBAppender appender, string qualifiedTableName, DataChunkVectorWriter[] vectors, ulong rowIndex)
internal DuckDBAppenderRow(Native.DuckDBAppender appender, string qualifiedTableName, DataChunkVectorWriter[] vectorWriters, ulong rowIndex)
{
this.appender = appender;
this.qualifiedTableName = qualifiedTableName;
this.vectors = vectors;
this.vectorWriters = vectorWriters;
this.rowIndex = rowIndex;
}

public void EndRow()
{
if (columnIndex < vectors.Length)
if (columnIndex < vectorWriters.Length)
{
throw new InvalidOperationException($"The table {qualifiedTableName} has {vectors.Length} columns but you specified only {columnIndex} values");
throw new InvalidOperationException($"The table {qualifiedTableName} has {vectorWriters.Length} columns but you specified only {columnIndex} values");
}
}

public DuckDBAppenderRow AppendNullValue() => Append<int>(null); //Doesn't matter what type T we pass to Append when passing null.

public DuckDBAppenderRow AppendValue(bool? value) => Append(value);

#if NET6_0_OR_GREATER

public DuckDBAppenderRow AppendValue(byte[]? value) => AppendSpan(value);

public DuckDBAppenderRow AppendValue(Span<byte> value) => AppendSpan(value);
#endif

public DuckDBAppenderRow AppendValue(string? value)
{
if (value == null)
{
return AppendNullValue();
}

CheckColumnAccess();

using var unmanagedString = value.ToUnmanagedString();

vectors[columnIndex].AppendString(unmanagedString, rowIndex);

columnIndex++;

return this;
return AppendHelper(value, (writer, data) => writer.AppendString(data!, rowIndex));
}

public DuckDBAppenderRow AppendNullValue()
public DuckDBAppenderRow AppendDecimal(decimal? value)
{
Append<int>(null);
return this;
return AppendHelper(value, (writer, data) =>
{
if (writer is DataChunkDecimalVectorWriter decimalVectorWriter)
{
decimalVectorWriter.AppendDecimal(data!.Value, rowIndex);
}
else
{
throw new InvalidOperationException("Cannot write decimal to non-decimal column");
}
});
}

public DuckDBAppenderRow AppendValue(BigInteger? value, bool unsigned = false)
Expand Down Expand Up @@ -133,18 +132,33 @@ public DuckDBAppenderRow AppendValue(BigInteger? value, bool unsigned = false)

if (value == null)
{
vectors[columnIndex].AppendNull(rowIndex);
vectorWriters[columnIndex].AppendNull(rowIndex);
}
else
{
vectors[columnIndex].AppendValue(value.Value, rowIndex);
vectorWriters[columnIndex].AppendValue(value.Value, rowIndex);
}

columnIndex++;

return this;
}

private DuckDBAppenderRow AppendHelper<T>(T value, Action<DataChunkVectorWriter, T> appendAction)
{
if (value == null)
{
return AppendNullValue();
}

CheckColumnAccess();

appendAction(vectorWriters[columnIndex], value);

columnIndex++;
return this;
}

#if NET6_0_OR_GREATER
private unsafe DuckDBAppenderRow AppendSpan(Span<byte> val)
{
Expand All @@ -154,10 +168,10 @@ private unsafe DuckDBAppenderRow AppendSpan(Span<byte> val)
}

CheckColumnAccess();

fixed (byte* pSource = val)
{
vectors[columnIndex].AppendBlob(pSource, val.Length, rowIndex);
vectorWriters[columnIndex].AppendBlob(pSource, val.Length, rowIndex);
}

columnIndex++;
Expand All @@ -168,9 +182,9 @@ private unsafe DuckDBAppenderRow AppendSpan(Span<byte> val)

private void CheckColumnAccess()
{
if (columnIndex >= vectors.Length)
if (columnIndex >= vectorWriters.Length)
{
throw new IndexOutOfRangeException($"The table {qualifiedTableName} has {vectors.Length} columns but you are trying to append value for column {columnIndex + 1}");
throw new IndexOutOfRangeException($"The table {qualifiedTableName} has {vectorWriters.Length} columns but you are trying to append value for column {columnIndex + 1}");
}
}
}
}
52 changes: 43 additions & 9 deletions DuckDB.NET.Data/Internal/Writer/DataChunkVectorWriter.cs
@@ -1,12 +1,11 @@
using System;
using System.Numerics;
using DuckDB.NET.Native;

namespace DuckDB.NET.Data.Internal.Writer;

unsafe class DataChunkVectorWriter(IntPtr vector, void* vectorData)
internal unsafe class DataChunkVectorWriter(IntPtr vector, void* vectorData)
{
private readonly IntPtr vector = vector;
private readonly unsafe void* vectorData = vectorData;
private unsafe ulong* validity;

public unsafe void AppendNull(ulong rowIndex)
Expand All @@ -20,18 +19,53 @@ public unsafe void AppendNull(ulong rowIndex)
NativeMethods.ValidityMask.DuckDBValiditySetRowValidity(validity, rowIndex, false);
}

public unsafe void AppendValue<T>(T val, ulong rowIndex) where T : unmanaged
public unsafe void AppendValue<T>(T value, ulong rowIndex) where T : unmanaged
{
((T*)vectorData)[rowIndex] = val;
((T*)vectorData)[rowIndex] = value;
}

public void AppendString(SafeUnmanagedMemoryHandle val, ulong rowIndex)
public void AppendString(string value, ulong rowIndex)
{
NativeMethods.Vectors.DuckDBVectorAssignStringElement(vector, rowIndex, val);
using var unmanagedString = value.ToUnmanagedString();
NativeMethods.Vectors.DuckDBVectorAssignStringElement(vector, rowIndex, unmanagedString);
}

public void AppendBlob(byte* val, int length, ulong rowIndex)
public void AppendBlob(byte* value, int length, ulong rowIndex)
{
NativeMethods.Vectors.DuckDBVectorAssignStringElementLength(vector, rowIndex, val, length);
NativeMethods.Vectors.DuckDBVectorAssignStringElementLength(vector, rowIndex, value, length);
}
}

class DataChunkDecimalVectorWriter : DataChunkVectorWriter
{
private readonly byte scale;
private readonly DuckDBType decimalType;

public unsafe DataChunkDecimalVectorWriter(IntPtr vector, void* vectorData, DuckDBLogicalType logicalType) : base(vector, vectorData)
{
scale = NativeMethods.LogicalType.DuckDBDecimalScale(logicalType);
decimalType = NativeMethods.LogicalType.DuckDBDecimalInternalType(logicalType);
}

public void AppendDecimal(decimal value, ulong rowIndex)
{
var power = Math.Pow(10, scale);

switch (decimalType)
{
case DuckDBType.SmallInt:
AppendValue<short>((short)decimal.Multiply(value, new decimal(power)), rowIndex);
break;
case DuckDBType.Integer:
AppendValue<int>((int)decimal.Multiply(value, new decimal(power)), rowIndex);
break;
case DuckDBType.BigInt:
AppendValue<long>((long)decimal.Multiply(value, new decimal(power)), rowIndex);
break;
case DuckDBType.HugeInt:
var bigInteger = BigInteger.Multiply(new BigInteger(value), new BigInteger(power));
AppendValue(new DuckDBHugeInt(bigInteger), rowIndex);
break;
}
}
}
4 changes: 3 additions & 1 deletion DuckDB.NET.Test/ManagedAppenderTests.cs
Expand Up @@ -16,7 +16,7 @@ public class DuckDBManagedAppenderTests(DuckDBDatabaseFixture db) : DuckDBTestBa
public void ManagedAppenderTests()
{
var table = "CREATE TABLE managedAppenderTest(a BOOLEAN, b TINYINT, c SMALLINT, d INTEGER, e BIGINT, f UTINYINT, " +
"g USMALLINT, h UINTEGER, i UBIGINT, j REAL, k DOUBLE, l VARCHAR, m TIMESTAMP, n Date, o HugeInt, p UHugeInt);";
"g USMALLINT, h UINTEGER, i UBIGINT, j REAL, k DOUBLE, l VARCHAR, m TIMESTAMP, n Date, o HugeInt, p UHugeInt, q decimal(9, 4));";
Command.CommandText = table;
Command.ExecuteNonQuery();

Expand Down Expand Up @@ -44,6 +44,7 @@ public void ManagedAppenderTests()
.AppendNullValue()
.AppendValue(new BigInteger(ulong.MaxValue) + i)
.AppendValue(new BigInteger(ulong.MaxValue) * 2 + i, true)
.AppendDecimal(i + i / 100m)
.EndRow();
}
}
Expand All @@ -70,6 +71,7 @@ public void ManagedAppenderTests()
reader.IsDBNull(13).Should().BeTrue();
reader.GetFieldValue<BigInteger>(14).Should().Be(new BigInteger(ulong.MaxValue) + readRowIndex);
reader.GetFieldValue<BigInteger>(15).Should().Be(new BigInteger(ulong.MaxValue) * 2 + readRowIndex);
reader.GetDecimal(16).Should().Be(readRowIndex + readRowIndex / 100m);

readRowIndex++;
}
Expand Down

0 comments on commit ae6c764

Please sign in to comment.