Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
008c5ed
refactor: move the example-aot project to the proper folder
RaphDal Oct 16, 2025
31f2972
feat: add BufferV3 and support for DECIMAL column values in IBuffer i…
RaphDal Oct 16, 2025
e509af7
feat: add support for decimal
RaphDal Oct 17, 2025
af2acea
Merge branch 'main' into rd_decimal
RaphDal Oct 17, 2025
16635e9
fix: update Buffer creation for ProtocolVersion.Auto to use BufferV3
RaphDal Oct 17, 2025
ea890c8
typo: fix typo in readme
RaphDal Oct 17, 2025
cfdb79e
📝 Add docstrings to `rd_decimal`
coderabbitai[bot] Oct 17, 2025
c9492d7
refactor: replace direct byte comparison with WaitAssert for improved…
RaphDal Oct 17, 2025
4850e7f
fix: handle null value in Column method of ISender interface
RaphDal Oct 17, 2025
5319a31
fix: handle null values in DECIMAL case of JsonSpecTestRunner
RaphDal Oct 17, 2025
e85fd36
refactor: remove unused using directive for System.Runtime.InteropSer…
RaphDal Oct 17, 2025
079195e
refactor: improve XML documentation for Buffer and IBuffer interfaces
RaphDal Oct 17, 2025
d9cd6d1
Merge pull request #53 from questdb/coderabbitai/docstrings/ea890c8
RaphDal Oct 17, 2025
92eadc4
fix: handle nullable decimal values in BufferV3
RaphDal Oct 17, 2025
6564091
fix: fix culture in dummy servers.
RaphDal Oct 17, 2025
d2b5163
refactor: enhance XML documentation across Buffer classes and senders
RaphDal Oct 17, 2025
3bf58c8
Update src/net-questdb-client/Senders/ISender.cs
RaphDal Oct 17, 2025
89d923c
Update src/net-questdb-client/Buffers/BufferV3.cs
RaphDal Oct 17, 2025
82a07fc
Update src/net-questdb-client/Senders/HttpSender.cs
RaphDal Oct 17, 2025
81db9f5
fix: correct error message wording for invalid table and column names
RaphDal Oct 17, 2025
c20de68
fix: ensure two's complement conversion is unchecked for negative man…
RaphDal Oct 17, 2025
b59e1ce
fix: improve buffer management in BufferV1 by resetting line start le…
RaphDal Oct 17, 2025
1be64bc
fix: update timestamp in TcpTests to use a specific date for consistency
RaphDal Oct 17, 2025
1495cb1
fix: update TcpTests to use AtNowAsync for current timestamp in test …
RaphDal Oct 20, 2025
bc20a0d
fix: don't write column when the value is null
RaphDal Oct 24, 2025
331083e
test: add BufferTests for decimal negation scenarios
RaphDal Oct 24, 2025
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
67 changes: 34 additions & 33 deletions README.md

Large diffs are not rendered by default.

8 changes: 1 addition & 7 deletions net-questdb-client.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "net-questdb-client", "src\net-questdb-client\net-questdb-client.csproj", "{456B1860-0102-48D7-861A-5F9963F3887B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tcp-client-test", "src\tcp-client-test\tcp-client-test.csproj", "{22F903D9-4367-46A2-A25A-F4A6BF9105C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-basic", "src\example-basic\example-basic.csproj", "{121EAA4D-3A73-468C-8CAB-A2A4BEF848CF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-auth-tls", "src\example-auth-tls\example-auth-tls.csproj", "{FBB8181C-6BAB-46C2-A47A-D3566A3997FE}"
Expand All @@ -21,7 +19,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-streaming", "src\ex
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-auth-http-tls", "src\example-auth-http-tls\example-auth-http-tls.csproj", "{24D93DBB-3783-423F-81CC-6B9BFD33F6CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-aot", "example-aot\example-aot.csproj", "{5341FCF0-F71D-4160-8D6E-B5EFDF92E9E8}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-aot", "src\example-aot\example-aot.csproj", "{5341FCF0-F71D-4160-8D6E-B5EFDF92E9E8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -36,10 +34,6 @@ Global
{456B1860-0102-48D7-861A-5F9963F3887B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{456B1860-0102-48D7-861A-5F9963F3887B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{456B1860-0102-48D7-861A-5F9963F3887B}.Release|Any CPU.Build.0 = Release|Any CPU
{22F903D9-4367-46A2-A25A-F4A6BF9105C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22F903D9-4367-46A2-A25A-F4A6BF9105C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22F903D9-4367-46A2-A25A-F4A6BF9105C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22F903D9-4367-46A2-A25A-F4A6BF9105C6}.Release|Any CPU.Build.0 = Release|Any CPU
{121EAA4D-3A73-468C-8CAB-A2A4BEF848CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{121EAA4D-3A73-468C-8CAB-A2A4BEF848CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{121EAA4D-3A73-468C-8CAB-A2A4BEF848CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
190 changes: 115 additions & 75 deletions src/dummy-http-server/DummyHttpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,37 @@ public class DummyHttpServer : IDisposable
private int _port = 29743;
private readonly TimeSpan? _withStartDelay;

/// <summary>
/// Initializes a configurable in-process dummy HTTP server used for testing endpoints.
/// </summary>
/// <param name="withTokenAuth">If true, enable JWT bearer authentication and authorization.</param>
/// <param name="withBasicAuth">If true, enable basic authentication behavior in the test endpoint.</param>
/// <param name="withRetriableError">If true, configure the test endpoint to produce retriable error responses.</param>
/// <param name="withErrorMessage">If true, include error messages in test error responses.</param>
/// <param name="withStartDelay">Optional delay applied when starting the server.</param>
/// <param name="requireClientCert">If true, require client TLS certificates for HTTPS connections.</param>
public DummyHttpServer(bool withTokenAuth = false, bool withBasicAuth = false, bool withRetriableError = false,
bool withErrorMessage = false, TimeSpan? withStartDelay = null, bool requireClientCert = false)
bool withErrorMessage = false, TimeSpan? withStartDelay = null, bool requireClientCert = false)
{
var bld = WebApplication.CreateBuilder();

bld.Services.AddLogging(builder =>
{
builder.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddConsole();
.AddFilter("System", LogLevel.Warning)
.AddConsole();
});

IlpEndpoint.WithTokenAuth = withTokenAuth;
IlpEndpoint.WithBasicAuth = withBasicAuth;
IlpEndpoint.WithTokenAuth = withTokenAuth;
IlpEndpoint.WithBasicAuth = withBasicAuth;
IlpEndpoint.WithRetriableError = withRetriableError;
IlpEndpoint.WithErrorMessage = withErrorMessage;
IlpEndpoint.WithErrorMessage = withErrorMessage;
_withStartDelay = withStartDelay;

if (withTokenAuth)
{
bld.Services.AddAuthenticationJwtBearer(s => s.SigningKey = SigningKey)
.AddAuthorization();
.AddAuthorization();
}


Expand All @@ -83,7 +92,7 @@ public DummyHttpServer(bool withTokenAuth = false, bool withBasicAuth = false, b

o.Limits.MaxRequestBodySize = 1073741824;
o.ListenLocalhost(29474,
options => { options.UseHttps(); });
options => { options.UseHttps(); });
o.ListenLocalhost(29473);
});

Expand All @@ -108,26 +117,43 @@ public void Dispose()
_app.StopAsync().Wait();
}

/// <summary>
/// Clears the in-memory receive buffers and resets the endpoint error state and counter.
/// </summary>
/// <remarks>
/// Empties IlpEndpoint.ReceiveBuffer and IlpEndpoint.ReceiveBytes, sets IlpEndpoint.LastError to null,
/// and sets IlpEndpoint.Counter to zero.
/// </remarks>
public void Clear()
{
IlpEndpoint.ReceiveBuffer.Clear();
IlpEndpoint.ReceiveBytes.Clear();
IlpEndpoint.LastError = null;
IlpEndpoint.Counter = 0;
IlpEndpoint.Counter = 0;
}

/// <summary>
/// Starts the HTTP server on the specified port and configures the supported protocol versions.
/// </summary>
/// <param name="port">Port to listen on (defaults to 29743).</param>
/// <param name="versions">Array of supported protocol versions; defaults to {1, 2, 3} when null.</param>
/// <returns>A task that completes after any configured startup delay has elapsed and the server's background run task has been initiated.</returns>
public async Task StartAsync(int port = 29743, int[]? versions = null)
{
if (_withStartDelay.HasValue)
{
await Task.Delay(_withStartDelay.Value);
}
versions ??= new[] { 1, 2, };
SettingsEndpoint.Versions = versions;
_port = port;
_app.RunAsync($"http://localhost:{port}");

versions ??= new[] { 1, 2, 3, };
SettingsEndpoint.Versions = versions;
_port = port;
_ = _app.RunAsync($"http://localhost:{port}");
}

/// <summary>
/// Starts the web application and listens for HTTP requests on http://localhost:{_port}.
/// </summary>
public async Task RunAsync()
{
await _app.RunAsync($"http://localhost:{_port}");
Expand All @@ -138,12 +164,20 @@ public async Task StopAsync()
await _app.StopAsync();
}

/// <summary>
/// Gets the server's in-memory text buffer of received data.
/// </summary>
/// <returns>The mutable <see cref="StringBuilder"/> containing the accumulated received text; modifying it updates the server's buffer.</returns>
public StringBuilder GetReceiveBuffer()
{
return IlpEndpoint.ReceiveBuffer;
}

public List<byte> GetReceiveBytes()
/// <summary>
/// Gets the in-memory list of bytes received by the ILP endpoint.
/// </summary>
/// <returns>The mutable list of bytes received by the endpoint.</returns>
public List<byte> GetReceivedBytes()
{
return IlpEndpoint.ReceiveBytes;
}
Expand All @@ -160,14 +194,18 @@ public async Task<bool> Healthcheck()
}


/// <summary>
/// Generates a JWT for the test server when the provided credentials match the server's static username and password.
/// </summary>
/// <returns>The JWT string when credentials are valid; <c>null</c> otherwise. The issued token is valid for one day.</returns>
public string? GetJwtToken(string username, string password)
{
if (username == Username && password == Password)
{
var jwtToken = JwtBearer.CreateToken(o =>
{
o.SigningKey = SigningKey;
o.ExpireAt = DateTime.UtcNow.AddDays(1);
o.ExpireAt = DateTime.UtcNow.AddDays(1);
});
return jwtToken;
}
Expand All @@ -180,87 +218,89 @@ public int GetCounter()
return IlpEndpoint.Counter;
}

/// <summary>
/// Produces a human-readable string representation of the server's received-bytes buffer, interpreting embedded markers and formatting arrays and numeric values.
/// </summary>
/// <returns>The formatted textual representation of the received bytes buffer.</returns>
/// <exception cref="NotImplementedException">Thrown when the buffer contains an unsupported type code.</exception>
public string PrintBuffer()
{
var bytes = GetReceiveBytes().ToArray();
var sb = new StringBuilder();
var bytes = GetReceivedBytes().ToArray();
var sb = new StringBuilder();
var lastAppend = 0;

var i = 0;
for (; i < bytes.Length; i++)
{
if (bytes[i] == (byte)'=')
if (bytes[i] == (byte)'=' && i > 0 && bytes[i - 1] == (byte)'=')
{
if (bytes[i - 1] == (byte)'=')
sb.Append(Encoding.UTF8.GetString(bytes, lastAppend, i + 1 - lastAppend));
switch (bytes[++i])
{
sb.Append(Encoding.UTF8.GetString(bytes, lastAppend, i + 1 - lastAppend));
switch (bytes[++i])
{
case 14:
sb.Append("ARRAY<");
var type = bytes[++i];
case 14:
sb.Append("ARRAY<");
var type = bytes[++i];

Debug.Assert(type == 10);
var dims = bytes[++i];
Debug.Assert(type == 10);
var dims = bytes[++i];

++i;
++i;

long length = 0;
for (var j = 0; j < dims; j++)
long length = 0;
for (var j = 0; j < dims; j++)
{
var lengthBytes = bytes.AsSpan()[i..(i + 4)];
var lengthValue = MemoryMarshal.Cast<byte, uint>(lengthBytes)[0];
if (length == 0)
{
var lengthBytes = bytes.AsSpan()[i..(i + 4)];
var lengthValue = MemoryMarshal.Cast<byte, uint>(lengthBytes)[0];
if (length == 0)
{
length = lengthValue;
}
else
{
length *= lengthValue;
}

sb.Append(lengthValue);
sb.Append(',');
i += 4;
length = lengthValue;
}

sb.Remove(sb.Length - 1, 1);
sb.Append('>');

var doubleBytes =
MemoryMarshal.Cast<byte, double>(bytes.AsSpan().Slice(i, (int)(length * 8)));


sb.Append('[');
for (var j = 0; j < length; j++)
else
{
sb.Append(doubleBytes[j]);
sb.Append(',');
length *= lengthValue;
}

sb.Remove(sb.Length - 1, 1);
sb.Append(']');

i += (int)(length * 8);
i--;
break;
case 16:
sb.Remove(sb.Length - 1, 1);
var doubleValue = MemoryMarshal.Cast<byte, double>(bytes.AsSpan().Slice(++i, 8));
sb.Append(doubleValue[0].ToString(CultureInfo.InvariantCulture));
i += 8;
i--;
break;
default:
throw new NotImplementedException();
}

lastAppend = i + 1;
sb.Append(lengthValue);
sb.Append(',');
i += 4;
}

sb.Remove(sb.Length - 1, 1);
sb.Append('>');

var doubleBytes =
MemoryMarshal.Cast<byte, double>(bytes.AsSpan().Slice(i, (int)(length * 8)));


sb.Append('[');
for (var j = 0; j < length; j++)
{
sb.Append(doubleBytes[j].ToString(CultureInfo.InvariantCulture));
sb.Append(',');
}

sb.Remove(sb.Length - 1, 1);
sb.Append(']');

i += (int)(length * 8);
i--;
break;
case 16:
sb.Remove(sb.Length - 1, 1);
var doubleValue = MemoryMarshal.Cast<byte, double>(bytes.AsSpan().Slice(++i, 8));
sb.Append(doubleValue[0].ToString(CultureInfo.InvariantCulture));
i += 8;
i--;
break;
default:
throw new NotImplementedException($"Type {bytes[i]} not implemented");
}

lastAppend = i + 1;
}
}

sb.Append(Encoding.UTF8.GetString(bytes, lastAppend, i - lastAppend));
return sb.ToString();
}
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\src\net-questdb-client\net-questdb-client.csproj"/>
<ProjectReference Include="..\net-questdb-client\net-questdb-client.csproj"/>
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/net-questdb-client-benchmarks/BenchInserts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
using BenchmarkDotNet.Attributes;
using dummy_http_server;
using QuestDB;
using tcp_client_test;
using net_questdb_client_tests;

#pragma warning disable CS0414 // Field is assigned but its value is never used

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

<ItemGroup>
<ProjectReference Include="..\dummy-http-server\dummy-http-server.csproj"/>
<ProjectReference Include="..\net-questdb-client-tests\net-questdb-client-tests.csproj" />
<ProjectReference Include="..\net-questdb-client\net-questdb-client.csproj"/>
<ProjectReference Include="..\tcp-client-test\tcp-client-test.csproj"/>
</ItemGroup>

<ItemGroup>
Expand Down
Loading