Skip to content
This repository was archived by the owner on Mar 29, 2026. It is now read-only.

Commit 6dcdccb

Browse files
authored
Merge pull request #58 from sphildreth/sph-2026-03-21.01
Sph 2026 03 21.01
2 parents 9113288 + e640dcb commit 6dcdccb

15 files changed

Lines changed: 976 additions & 23 deletions

File tree

bindings/dotnet/src/DecentDB.AdoNet/DecentDB.AdoNet.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2+
<Import Project="..\DecentDB.NativeAssets.props" />
3+
24
<PropertyGroup>
35
<TargetFramework>net10.0</TargetFramework>
46
<ImplicitUsings>enable</ImplicitUsings>
@@ -32,9 +34,9 @@
3234
<ItemGroup>
3335
<None Include="../DecentDB.MicroOrm/README.md" Pack="true" PackagePath="README.md" />
3436
<None Include="../DecentDB.MicroOrm/icon.png" Pack="true" PackagePath="\" />
35-
<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')" />
36-
<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')" />
37-
<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')" />
37+
<None Include="$(DecentDBLinuxRuntimeAssetSource)" Pack="true" PackagePath="runtimes/linux-x64/native/libdecentdb.so" Condition="'$(DecentDBLinuxRuntimeAssetSource)' != '' and Exists('$(DecentDBLinuxRuntimeAssetSource)')" />
38+
<None Include="$(DecentDBMacRuntimeAssetSource)" Pack="true" PackagePath="runtimes/osx-x64/native/libdecentdb.dylib" Condition="'$(DecentDBMacRuntimeAssetSource)' != '' and Exists('$(DecentDBMacRuntimeAssetSource)')" />
39+
<None Include="$(DecentDBWindowsRuntimeAssetSource)" Pack="true" PackagePath="runtimes/win-x64/native/decentdb.dll" Condition="'$(DecentDBWindowsRuntimeAssetSource)' != '' and Exists('$(DecentDBWindowsRuntimeAssetSource)')" />
3840
</ItemGroup>
3941

4042
<Target Name="IncludeProjectReferencesInPackage" DependsOnTargets="ResolveReferences">

bindings/dotnet/src/DecentDB.AdoNet/DecentDBCommand.cs

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public sealed class DecentDBCommand : DbCommand
2020
private readonly DecentDBParameterCollection _parameterCollection;
2121
private DecentDBTransaction? _transaction;
2222
private PreparedStatement? _statement;
23+
private PreparedStatement? _preparedStatement;
24+
private string? _preparedSql;
25+
private Native.DecentDB? _preparedDb;
2326
private bool _disposed;
2427

2528
public DecentDBCommand()
@@ -55,6 +58,7 @@ public override string CommandText
5558
{
5659
throw new InvalidOperationException("Cannot change CommandText while command is executing");
5760
}
61+
InvalidatePreparedStatement();
5862
_commandText = value ?? string.Empty;
5963
}
6064
}
@@ -96,6 +100,7 @@ protected override DbConnection? DbConnection
96100
{
97101
throw new InvalidOperationException("Cannot change connection while command is executing");
98102
}
103+
InvalidatePreparedStatement();
99104
_connection = null;
100105
return;
101106
}
@@ -108,6 +113,10 @@ protected override DbConnection? DbConnection
108113
{
109114
throw new InvalidOperationException("Cannot change connection while command is executing");
110115
}
116+
if (!ReferenceEquals(_connection, conn))
117+
{
118+
InvalidatePreparedStatement();
119+
}
111120
_connection = conn;
112121
}
113122
}
@@ -142,9 +151,7 @@ public override int ExecuteNonQuery()
142151
var statements = SqlStatementSplitter.Split(_commandText);
143152
if (statements.Count <= 1)
144153
{
145-
using var reader = ExecuteDbDataReader(CommandBehavior.Default);
146-
while (reader.Read()) { }
147-
return reader.RecordsAffected;
154+
return ExecuteSingleNonQuery();
148155
}
149156

150157
// Multi-statement: execute each individually, sum affected rows
@@ -292,6 +299,18 @@ public override void Prepare()
292299
{
293300
throw new InvalidOperationException("Connection must be open to prepare command");
294301
}
302+
303+
var statements = SqlStatementSplitter.Split(_commandText);
304+
if (statements.Count != 1)
305+
{
306+
return;
307+
}
308+
309+
var (sql, paramMap) = SqlParameterRewriter.Rewrite(_commandText, _parameters);
310+
SqlParameterRewriter.ClampOffsetParameters(sql, paramMap);
311+
sql = SqlParameterRewriter.StripUpdateDeleteAlias(sql);
312+
313+
EnsurePreparedStatement(sql, resetForExecution: false);
295314
}
296315

297316
internal static void BindParameter(PreparedStatement stmt, int index1Based, DbParameter parameter)
@@ -389,13 +408,106 @@ internal void FinalizeStatement()
389408
_statement = null;
390409
}
391410

411+
private int ExecuteSingleNonQuery()
412+
{
413+
if (_connection == null)
414+
{
415+
throw new InvalidOperationException("Command has no connection");
416+
}
417+
418+
var (sql, paramMap) = SqlParameterRewriter.Rewrite(_commandText, _parameters);
419+
SqlParameterRewriter.ClampOffsetParameters(sql, paramMap);
420+
sql = SqlParameterRewriter.StripUpdateDeleteAlias(sql);
421+
422+
var observation = _connection.TryStartSqlObservation(sql, SnapshotParameters(paramMap));
423+
424+
try
425+
{
426+
var stmt = EnsurePreparedStatement(sql, resetForExecution: true);
427+
428+
foreach (var kvp in paramMap)
429+
{
430+
BindParameter(stmt, kvp.Key, kvp.Value);
431+
}
432+
433+
var stepResult = stmt.Step();
434+
while (stepResult == 1)
435+
{
436+
stepResult = stmt.Step();
437+
}
438+
439+
if (stepResult < 0)
440+
{
441+
var ex = new DecentDBException(stmt.RowsAffected > 0 ? (int)stmt.RowsAffected : stepResult,
442+
_connection.GetNativeDb().LastErrorMessage, sql);
443+
InvalidatePreparedStatement();
444+
throw ex;
445+
}
446+
447+
if (observation != null)
448+
{
449+
_connection.CompleteSqlObservation(observation, stmt.RowsAffected, exception: null);
450+
}
451+
452+
return (int)stmt.RowsAffected;
453+
}
454+
catch (Exception ex)
455+
{
456+
InvalidatePreparedStatement();
457+
458+
if (observation != null)
459+
{
460+
_connection.CompleteSqlObservation(observation, rowsAffected: 0, ex);
461+
}
462+
463+
throw;
464+
}
465+
}
466+
467+
private PreparedStatement EnsurePreparedStatement(string sql, bool resetForExecution)
468+
{
469+
if (_connection == null)
470+
{
471+
throw new InvalidOperationException("Command has no connection");
472+
}
473+
474+
var nativeDb = _connection.GetNativeDb();
475+
if (_preparedStatement != null &&
476+
ReferenceEquals(_preparedDb, nativeDb) &&
477+
string.Equals(_preparedSql, sql, StringComparison.Ordinal))
478+
{
479+
if (resetForExecution)
480+
{
481+
_preparedStatement.Reset().ClearBindings();
482+
}
483+
484+
return _preparedStatement;
485+
}
486+
487+
InvalidatePreparedStatement();
488+
489+
_preparedStatement = nativeDb.Prepare(sql);
490+
_preparedSql = sql;
491+
_preparedDb = nativeDb;
492+
return _preparedStatement;
493+
}
494+
495+
private void InvalidatePreparedStatement()
496+
{
497+
_preparedStatement?.Dispose();
498+
_preparedStatement = null;
499+
_preparedSql = null;
500+
_preparedDb = null;
501+
}
502+
392503
protected override void Dispose(bool disposing)
393504
{
394505
if (_disposed) return;
395506

396507
if (disposing)
397508
{
398509
FinalizeStatement();
510+
InvalidatePreparedStatement();
399511
}
400512

401513
_disposed = true;

bindings/dotnet/src/DecentDB.AdoNet/DecentDBDataReader.cs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,33 @@ public override int RecordsAffected
5151
}
5252
}
5353

54+
private string GetStringValue(int ordinal)
55+
{
56+
var type = _statement.ColumnType(ordinal);
57+
if (type == 5)
58+
{
59+
var bytes = _statement.GetBlob(ordinal);
60+
if (bytes.Length == 16)
61+
{
62+
return new Guid(bytes).ToString();
63+
}
64+
}
65+
66+
return _statement.GetText(ordinal);
67+
}
68+
69+
private static DateTime FromUnixEpochMicroseconds(long micros)
70+
{
71+
return new DateTime(micros * 10L + DateTime.UnixEpoch.Ticks, DateTimeKind.Utc);
72+
}
73+
74+
private long GetInt64Value(int ordinal)
75+
{
76+
return _statement.ColumnType(ordinal) == 17
77+
? DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal)
78+
: _statement.GetInt64(ordinal);
79+
}
80+
5481
public override object this[int ordinal] => GetValue(ordinal);
5582

5683
public override object this[string name] => GetValue(GetOrdinal(name));
@@ -72,6 +99,7 @@ public override string GetDataTypeName(int ordinal)
7299
4 => "TEXT",
73100
5 => "BLOB",
74101
12 => "DECIMAL",
102+
17 => "TIMESTAMP",
75103
_ => "UNKNOWN"
76104
};
77105
}
@@ -88,6 +116,7 @@ public override Type GetFieldType(int ordinal)
88116
4 => typeof(string),
89117
5 => typeof(byte[]),
90118
12 => typeof(decimal),
119+
17 => typeof(DateTime),
91120
_ => typeof(object)
92121
};
93122
}
@@ -108,7 +137,7 @@ public override object GetValue(int ordinal)
108137
4 => _statement.GetText(ordinal),
109138
5 => _statement.GetBlob(ordinal),
110139
12 => _statement.GetDecimal(ordinal),
111-
17 => new DateTime(DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal) * 10L + DateTime.UnixEpoch.Ticks, DateTimeKind.Utc),
140+
17 => FromUnixEpochMicroseconds(DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal)),
112141
_ => DBNull.Value
113142
};
114143
}
@@ -127,7 +156,7 @@ public override T GetFieldValue<T>(int ordinal)
127156
object boxed;
128157
if (nonNullableType == typeof(string))
129158
{
130-
boxed = _statement.GetText(ordinal);
159+
boxed = GetStringValue(ordinal);
131160
}
132161
else if (nonNullableType == typeof(short))
133162
{
@@ -139,7 +168,7 @@ public override T GetFieldValue<T>(int ordinal)
139168
}
140169
else if (nonNullableType == typeof(long))
141170
{
142-
boxed = _statement.GetInt64(ordinal);
171+
boxed = GetInt64Value(ordinal);
143172
}
144173
else if (nonNullableType == typeof(bool))
145174
{
@@ -160,12 +189,12 @@ public override T GetFieldValue<T>(int ordinal)
160189
else if (nonNullableType == typeof(DateTime))
161190
{
162191
var micros = DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal);
163-
boxed = new DateTime(micros * 10L + DateTime.UnixEpoch.Ticks, DateTimeKind.Utc);
192+
boxed = FromUnixEpochMicroseconds(micros);
164193
}
165194
else if (nonNullableType == typeof(DateTimeOffset))
166195
{
167196
var micros = DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal);
168-
boxed = new DateTimeOffset(micros * 10L + DateTime.UnixEpoch.Ticks, TimeSpan.Zero);
197+
boxed = new DateTimeOffset(FromUnixEpochMicroseconds(micros), TimeSpan.Zero);
169198
}
170199
else if (nonNullableType == typeof(DateOnly))
171200
{
@@ -221,7 +250,7 @@ public override int GetInt32(int ordinal)
221250

222251
public override long GetInt64(int ordinal)
223252
{
224-
return _statement.GetInt64(ordinal);
253+
return GetInt64Value(ordinal);
225254
}
226255

227256
public override double GetDouble(int ordinal)
@@ -231,7 +260,7 @@ public override double GetDouble(int ordinal)
231260

232261
public override string GetString(int ordinal)
233262
{
234-
return _statement.GetText(ordinal);
263+
return GetStringValue(ordinal);
235264
}
236265

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

256285
public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length)
257286
{
258-
var str = _statement.GetText(ordinal);
287+
var str = GetStringValue(ordinal);
259288
if (buffer == null)
260289
{
261290
return str.Length;
@@ -269,7 +298,7 @@ public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int
269298

270299
public override char GetChar(int ordinal)
271300
{
272-
var str = _statement.GetText(ordinal);
301+
var str = GetStringValue(ordinal);
273302
return str.Length > 0 ? str[0] : '\0';
274303
}
275304

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

297326
public override DateTime GetDateTime(int ordinal)
298327
{
299-
var ms = _statement.GetInt64(ordinal);
300-
return DateTimeOffset.FromUnixTimeMilliseconds(ms).UtcDateTime;
328+
var micros = DecentDBNativeUnsafe.decentdb_column_datetime(_statement.Handle, ordinal);
329+
return FromUnixEpochMicroseconds(micros);
301330
}
302331

303332
public override decimal GetDecimal(int ordinal)

0 commit comments

Comments
 (0)