Skip to content
This repository was archived by the owner on Mar 29, 2026. It is now read-only.
Merged
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
8 changes: 5 additions & 3 deletions bindings/dotnet/src/DecentDB.AdoNet/DecentDB.AdoNet.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\DecentDB.NativeAssets.props" />

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down Expand Up @@ -32,9 +34,9 @@
<ItemGroup>
<None Include="../DecentDB.MicroOrm/README.md" Pack="true" PackagePath="README.md" />
<None Include="../DecentDB.MicroOrm/icon.png" Pack="true" PackagePath="\" />
<None Include="../DecentDB.MicroOrm/runtimes/linux-x64/native/libdecentdb.so" Pack="true" PackagePath="runtimes/linux-x64/native/libdecentdb.so" Condition="Exists('../DecentDB.MicroOrm/runtimes/linux-x64/native/libdecentdb.so')" />
<None Include="../DecentDB.MicroOrm/runtimes/osx-x64/native/libdecentdb.dylib" Pack="true" PackagePath="runtimes/osx-x64/native/libdecentdb.dylib" Condition="Exists('../DecentDB.MicroOrm/runtimes/osx-x64/native/libdecentdb.dylib')" />
<None Include="../DecentDB.MicroOrm/runtimes/win-x64/native/decentdb.dll" Pack="true" PackagePath="runtimes/win-x64/native/decentdb.dll" Condition="Exists('../DecentDB.MicroOrm/runtimes/win-x64/native/decentdb.dll')" />
<None Include="$(DecentDBLinuxRuntimeAssetSource)" Pack="true" PackagePath="runtimes/linux-x64/native/libdecentdb.so" Condition="'$(DecentDBLinuxRuntimeAssetSource)' != '' and Exists('$(DecentDBLinuxRuntimeAssetSource)')" />
<None Include="$(DecentDBMacRuntimeAssetSource)" Pack="true" PackagePath="runtimes/osx-x64/native/libdecentdb.dylib" Condition="'$(DecentDBMacRuntimeAssetSource)' != '' and Exists('$(DecentDBMacRuntimeAssetSource)')" />
<None Include="$(DecentDBWindowsRuntimeAssetSource)" Pack="true" PackagePath="runtimes/win-x64/native/decentdb.dll" Condition="'$(DecentDBWindowsRuntimeAssetSource)' != '' and Exists('$(DecentDBWindowsRuntimeAssetSource)')" />
</ItemGroup>

<Target Name="IncludeProjectReferencesInPackage" DependsOnTargets="ResolveReferences">
Expand Down
118 changes: 115 additions & 3 deletions bindings/dotnet/src/DecentDB.AdoNet/DecentDBCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public sealed class DecentDBCommand : DbCommand
private readonly DecentDBParameterCollection _parameterCollection;
private DecentDBTransaction? _transaction;
private PreparedStatement? _statement;
private PreparedStatement? _preparedStatement;
private string? _preparedSql;
private Native.DecentDB? _preparedDb;
private bool _disposed;

public DecentDBCommand()
Expand Down Expand Up @@ -55,6 +58,7 @@ public override string CommandText
{
throw new InvalidOperationException("Cannot change CommandText while command is executing");
}
InvalidatePreparedStatement();
_commandText = value ?? string.Empty;
}
}
Expand Down Expand Up @@ -96,6 +100,7 @@ protected override DbConnection? DbConnection
{
throw new InvalidOperationException("Cannot change connection while command is executing");
}
InvalidatePreparedStatement();
_connection = null;
return;
}
Expand All @@ -108,6 +113,10 @@ protected override DbConnection? DbConnection
{
throw new InvalidOperationException("Cannot change connection while command is executing");
}
if (!ReferenceEquals(_connection, conn))
{
InvalidatePreparedStatement();
}
_connection = conn;
}
}
Expand Down Expand Up @@ -142,9 +151,7 @@ public override int ExecuteNonQuery()
var statements = SqlStatementSplitter.Split(_commandText);
if (statements.Count <= 1)
{
using var reader = ExecuteDbDataReader(CommandBehavior.Default);
while (reader.Read()) { }
return reader.RecordsAffected;
return ExecuteSingleNonQuery();
}

// Multi-statement: execute each individually, sum affected rows
Expand Down Expand Up @@ -292,6 +299,18 @@ public override void Prepare()
{
throw new InvalidOperationException("Connection must be open to prepare command");
}

var statements = SqlStatementSplitter.Split(_commandText);
if (statements.Count != 1)
{
return;
}

var (sql, paramMap) = SqlParameterRewriter.Rewrite(_commandText, _parameters);
SqlParameterRewriter.ClampOffsetParameters(sql, paramMap);
sql = SqlParameterRewriter.StripUpdateDeleteAlias(sql);

EnsurePreparedStatement(sql, resetForExecution: false);
}

internal static void BindParameter(PreparedStatement stmt, int index1Based, DbParameter parameter)
Expand Down Expand Up @@ -389,13 +408,106 @@ internal void FinalizeStatement()
_statement = null;
}

private int ExecuteSingleNonQuery()
{
if (_connection == null)
{
throw new InvalidOperationException("Command has no connection");
}

var (sql, paramMap) = SqlParameterRewriter.Rewrite(_commandText, _parameters);
SqlParameterRewriter.ClampOffsetParameters(sql, paramMap);
sql = SqlParameterRewriter.StripUpdateDeleteAlias(sql);

var observation = _connection.TryStartSqlObservation(sql, SnapshotParameters(paramMap));

try
{
var stmt = EnsurePreparedStatement(sql, resetForExecution: true);

foreach (var kvp in paramMap)
{
BindParameter(stmt, kvp.Key, kvp.Value);
}

var stepResult = stmt.Step();
while (stepResult == 1)
{
stepResult = stmt.Step();
}

if (stepResult < 0)
{
var ex = new DecentDBException(stmt.RowsAffected > 0 ? (int)stmt.RowsAffected : stepResult,
_connection.GetNativeDb().LastErrorMessage, sql);
InvalidatePreparedStatement();
throw ex;
}

if (observation != null)
{
_connection.CompleteSqlObservation(observation, stmt.RowsAffected, exception: null);
}

return (int)stmt.RowsAffected;
}
catch (Exception ex)
{
InvalidatePreparedStatement();

if (observation != null)
{
_connection.CompleteSqlObservation(observation, rowsAffected: 0, ex);
}

throw;
}
}

private PreparedStatement EnsurePreparedStatement(string sql, bool resetForExecution)
{
if (_connection == null)
{
throw new InvalidOperationException("Command has no connection");
}

var nativeDb = _connection.GetNativeDb();
if (_preparedStatement != null &&
ReferenceEquals(_preparedDb, nativeDb) &&
string.Equals(_preparedSql, sql, StringComparison.Ordinal))
{
if (resetForExecution)
{
_preparedStatement.Reset().ClearBindings();
}

return _preparedStatement;
}

InvalidatePreparedStatement();

_preparedStatement = nativeDb.Prepare(sql);
_preparedSql = sql;
_preparedDb = nativeDb;
return _preparedStatement;
}

private void InvalidatePreparedStatement()
{
_preparedStatement?.Dispose();
_preparedStatement = null;
_preparedSql = null;
_preparedDb = null;
}

protected override void Dispose(bool disposing)
{
if (_disposed) return;

if (disposing)
{
FinalizeStatement();
InvalidatePreparedStatement();
}

_disposed = true;
Expand Down
51 changes: 40 additions & 11 deletions bindings/dotnet/src/DecentDB.AdoNet/DecentDBDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,33 @@ public override int RecordsAffected
}
}

private string GetStringValue(int ordinal)
{
var type = _statement.ColumnType(ordinal);
if (type == 5)
{
var bytes = _statement.GetBlob(ordinal);
if (bytes.Length == 16)
{
return new Guid(bytes).ToString();
}
}

return _statement.GetText(ordinal);
}

private static DateTime FromUnixEpochMicroseconds(long micros)
{
return new DateTime(micros * 10L + DateTime.UnixEpoch.Ticks, DateTimeKind.Utc);
}

private long GetInt64Value(int ordinal)
{
return _statement.ColumnType(ordinal) == 17
? DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal)
: _statement.GetInt64(ordinal);
}

public override object this[int ordinal] => GetValue(ordinal);

public override object this[string name] => GetValue(GetOrdinal(name));
Expand All @@ -72,6 +99,7 @@ public override string GetDataTypeName(int ordinal)
4 => "TEXT",
5 => "BLOB",
12 => "DECIMAL",
17 => "TIMESTAMP",
_ => "UNKNOWN"
};
}
Expand All @@ -88,6 +116,7 @@ public override Type GetFieldType(int ordinal)
4 => typeof(string),
5 => typeof(byte[]),
12 => typeof(decimal),
17 => typeof(DateTime),
_ => typeof(object)
};
}
Expand All @@ -108,7 +137,7 @@ public override object GetValue(int ordinal)
4 => _statement.GetText(ordinal),
5 => _statement.GetBlob(ordinal),
12 => _statement.GetDecimal(ordinal),
17 => new DateTime(DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal) * 10L + DateTime.UnixEpoch.Ticks, DateTimeKind.Utc),
17 => FromUnixEpochMicroseconds(DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal)),
_ => DBNull.Value
};
}
Expand All @@ -127,7 +156,7 @@ public override T GetFieldValue<T>(int ordinal)
object boxed;
if (nonNullableType == typeof(string))
{
boxed = _statement.GetText(ordinal);
boxed = GetStringValue(ordinal);
}
else if (nonNullableType == typeof(short))
{
Expand All @@ -139,7 +168,7 @@ public override T GetFieldValue<T>(int ordinal)
}
else if (nonNullableType == typeof(long))
{
boxed = _statement.GetInt64(ordinal);
boxed = GetInt64Value(ordinal);
}
else if (nonNullableType == typeof(bool))
{
Expand All @@ -160,12 +189,12 @@ public override T GetFieldValue<T>(int ordinal)
else if (nonNullableType == typeof(DateTime))
{
var micros = DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal);
boxed = new DateTime(micros * 10L + DateTime.UnixEpoch.Ticks, DateTimeKind.Utc);
boxed = FromUnixEpochMicroseconds(micros);
}
else if (nonNullableType == typeof(DateTimeOffset))
{
var micros = DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal);
boxed = new DateTimeOffset(micros * 10L + DateTime.UnixEpoch.Ticks, TimeSpan.Zero);
boxed = new DateTimeOffset(FromUnixEpochMicroseconds(micros), TimeSpan.Zero);
}
else if (nonNullableType == typeof(DateOnly))
{
Expand Down Expand Up @@ -221,7 +250,7 @@ public override int GetInt32(int ordinal)

public override long GetInt64(int ordinal)
{
return _statement.GetInt64(ordinal);
return GetInt64Value(ordinal);
}

public override double GetDouble(int ordinal)
Expand All @@ -231,7 +260,7 @@ public override double GetDouble(int ordinal)

public override string GetString(int ordinal)
{
return _statement.GetText(ordinal);
return GetStringValue(ordinal);
}

public override bool GetBoolean(int ordinal)
Expand All @@ -255,7 +284,7 @@ public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int

public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length)
{
var str = _statement.GetText(ordinal);
var str = GetStringValue(ordinal);
if (buffer == null)
{
return str.Length;
Expand All @@ -269,7 +298,7 @@ public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int

public override char GetChar(int ordinal)
{
var str = _statement.GetText(ordinal);
var str = GetStringValue(ordinal);
return str.Length > 0 ? str[0] : '\0';
}

Expand All @@ -296,8 +325,8 @@ public override Guid GetGuid(int ordinal)

public override DateTime GetDateTime(int ordinal)
{
var ms = _statement.GetInt64(ordinal);
return DateTimeOffset.FromUnixTimeMilliseconds(ms).UtcDateTime;
var micros = DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal);
return FromUnixEpochMicroseconds(micros);
}

public override decimal GetDecimal(int ordinal)
Expand Down
Loading
Loading