Skip to content

Commit e68a909

Browse files
authored
feat: support decimal (#52)
1 parent 321ec73 commit e68a909

33 files changed

+2106
-1487
lines changed

README.md

Lines changed: 34 additions & 33 deletions
Large diffs are not rendered by default.

net-questdb-client.sln

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.30114.105
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "net-questdb-client", "src\net-questdb-client\net-questdb-client.csproj", "{456B1860-0102-48D7-861A-5F9963F3887B}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tcp-client-test", "src\tcp-client-test\tcp-client-test.csproj", "{22F903D9-4367-46A2-A25A-F4A6BF9105C6}"
9-
EndProject
108
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-basic", "src\example-basic\example-basic.csproj", "{121EAA4D-3A73-468C-8CAB-A2A4BEF848CF}"
119
EndProject
1210
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-auth-tls", "src\example-auth-tls\example-auth-tls.csproj", "{FBB8181C-6BAB-46C2-A47A-D3566A3997FE}"
@@ -21,7 +19,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-streaming", "src\ex
2119
EndProject
2220
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}"
2321
EndProject
24-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-aot", "example-aot\example-aot.csproj", "{5341FCF0-F71D-4160-8D6E-B5EFDF92E9E8}"
22+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example-aot", "src\example-aot\example-aot.csproj", "{5341FCF0-F71D-4160-8D6E-B5EFDF92E9E8}"
2523
EndProject
2624
Global
2725
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -36,10 +34,6 @@ Global
3634
{456B1860-0102-48D7-861A-5F9963F3887B}.Debug|Any CPU.Build.0 = Debug|Any CPU
3735
{456B1860-0102-48D7-861A-5F9963F3887B}.Release|Any CPU.ActiveCfg = Release|Any CPU
3836
{456B1860-0102-48D7-861A-5F9963F3887B}.Release|Any CPU.Build.0 = Release|Any CPU
39-
{22F903D9-4367-46A2-A25A-F4A6BF9105C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40-
{22F903D9-4367-46A2-A25A-F4A6BF9105C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
41-
{22F903D9-4367-46A2-A25A-F4A6BF9105C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
42-
{22F903D9-4367-46A2-A25A-F4A6BF9105C6}.Release|Any CPU.Build.0 = Release|Any CPU
4337
{121EAA4D-3A73-468C-8CAB-A2A4BEF848CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
4438
{121EAA4D-3A73-468C-8CAB-A2A4BEF848CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
4539
{121EAA4D-3A73-468C-8CAB-A2A4BEF848CF}.Release|Any CPU.ActiveCfg = Release|Any CPU

src/dummy-http-server/DummyHttpServer.cs

Lines changed: 115 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,37 @@ public class DummyHttpServer : IDisposable
4242
private int _port = 29743;
4343
private readonly TimeSpan? _withStartDelay;
4444

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

5059
bld.Services.AddLogging(builder =>
5160
{
5261
builder.AddFilter("Microsoft", LogLevel.Warning)
53-
.AddFilter("System", LogLevel.Warning)
54-
.AddConsole();
62+
.AddFilter("System", LogLevel.Warning)
63+
.AddConsole();
5564
});
5665

57-
IlpEndpoint.WithTokenAuth = withTokenAuth;
58-
IlpEndpoint.WithBasicAuth = withBasicAuth;
66+
IlpEndpoint.WithTokenAuth = withTokenAuth;
67+
IlpEndpoint.WithBasicAuth = withBasicAuth;
5968
IlpEndpoint.WithRetriableError = withRetriableError;
60-
IlpEndpoint.WithErrorMessage = withErrorMessage;
69+
IlpEndpoint.WithErrorMessage = withErrorMessage;
6170
_withStartDelay = withStartDelay;
6271

6372
if (withTokenAuth)
6473
{
6574
bld.Services.AddAuthenticationJwtBearer(s => s.SigningKey = SigningKey)
66-
.AddAuthorization();
75+
.AddAuthorization();
6776
}
6877

6978

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

8493
o.Limits.MaxRequestBodySize = 1073741824;
8594
o.ListenLocalhost(29474,
86-
options => { options.UseHttps(); });
95+
options => { options.UseHttps(); });
8796
o.ListenLocalhost(29473);
8897
});
8998

@@ -108,26 +117,43 @@ public void Dispose()
108117
_app.StopAsync().Wait();
109118
}
110119

120+
/// <summary>
121+
/// Clears the in-memory receive buffers and resets the endpoint error state and counter.
122+
/// </summary>
123+
/// <remarks>
124+
/// Empties IlpEndpoint.ReceiveBuffer and IlpEndpoint.ReceiveBytes, sets IlpEndpoint.LastError to null,
125+
/// and sets IlpEndpoint.Counter to zero.
126+
/// </remarks>
111127
public void Clear()
112128
{
113129
IlpEndpoint.ReceiveBuffer.Clear();
114130
IlpEndpoint.ReceiveBytes.Clear();
115131
IlpEndpoint.LastError = null;
116-
IlpEndpoint.Counter = 0;
132+
IlpEndpoint.Counter = 0;
117133
}
118134

135+
/// <summary>
136+
/// Starts the HTTP server on the specified port and configures the supported protocol versions.
137+
/// </summary>
138+
/// <param name="port">Port to listen on (defaults to 29743).</param>
139+
/// <param name="versions">Array of supported protocol versions; defaults to {1, 2, 3} when null.</param>
140+
/// <returns>A task that completes after any configured startup delay has elapsed and the server's background run task has been initiated.</returns>
119141
public async Task StartAsync(int port = 29743, int[]? versions = null)
120142
{
121143
if (_withStartDelay.HasValue)
122144
{
123145
await Task.Delay(_withStartDelay.Value);
124146
}
125-
versions ??= new[] { 1, 2, };
126-
SettingsEndpoint.Versions = versions;
127-
_port = port;
128-
_app.RunAsync($"http://localhost:{port}");
147+
148+
versions ??= new[] { 1, 2, 3, };
149+
SettingsEndpoint.Versions = versions;
150+
_port = port;
151+
_ = _app.RunAsync($"http://localhost:{port}");
129152
}
130153

154+
/// <summary>
155+
/// Starts the web application and listens for HTTP requests on http://localhost:{_port}.
156+
/// </summary>
131157
public async Task RunAsync()
132158
{
133159
await _app.RunAsync($"http://localhost:{_port}");
@@ -138,12 +164,20 @@ public async Task StopAsync()
138164
await _app.StopAsync();
139165
}
140166

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

146-
public List<byte> GetReceiveBytes()
176+
/// <summary>
177+
/// Gets the in-memory list of bytes received by the ILP endpoint.
178+
/// </summary>
179+
/// <returns>The mutable list of bytes received by the endpoint.</returns>
180+
public List<byte> GetReceivedBytes()
147181
{
148182
return IlpEndpoint.ReceiveBytes;
149183
}
@@ -160,14 +194,18 @@ public async Task<bool> Healthcheck()
160194
}
161195

162196

197+
/// <summary>
198+
/// Generates a JWT for the test server when the provided credentials match the server's static username and password.
199+
/// </summary>
200+
/// <returns>The JWT string when credentials are valid; <c>null</c> otherwise. The issued token is valid for one day.</returns>
163201
public string? GetJwtToken(string username, string password)
164202
{
165203
if (username == Username && password == Password)
166204
{
167205
var jwtToken = JwtBearer.CreateToken(o =>
168206
{
169207
o.SigningKey = SigningKey;
170-
o.ExpireAt = DateTime.UtcNow.AddDays(1);
208+
o.ExpireAt = DateTime.UtcNow.AddDays(1);
171209
});
172210
return jwtToken;
173211
}
@@ -180,87 +218,89 @@ public int GetCounter()
180218
return IlpEndpoint.Counter;
181219
}
182220

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

189232
var i = 0;
190233
for (; i < bytes.Length; i++)
191234
{
192-
if (bytes[i] == (byte)'=')
235+
if (bytes[i] == (byte)'=' && i > 0 && bytes[i - 1] == (byte)'=')
193236
{
194-
if (bytes[i - 1] == (byte)'=')
237+
sb.Append(Encoding.UTF8.GetString(bytes, lastAppend, i + 1 - lastAppend));
238+
switch (bytes[++i])
195239
{
196-
sb.Append(Encoding.UTF8.GetString(bytes, lastAppend, i + 1 - lastAppend));
197-
switch (bytes[++i])
198-
{
199-
case 14:
200-
sb.Append("ARRAY<");
201-
var type = bytes[++i];
240+
case 14:
241+
sb.Append("ARRAY<");
242+
var type = bytes[++i];
202243

203-
Debug.Assert(type == 10);
204-
var dims = bytes[++i];
244+
Debug.Assert(type == 10);
245+
var dims = bytes[++i];
205246

206-
++i;
247+
++i;
207248

208-
long length = 0;
209-
for (var j = 0; j < dims; j++)
249+
long length = 0;
250+
for (var j = 0; j < dims; j++)
251+
{
252+
var lengthBytes = bytes.AsSpan()[i..(i + 4)];
253+
var lengthValue = MemoryMarshal.Cast<byte, uint>(lengthBytes)[0];
254+
if (length == 0)
210255
{
211-
var lengthBytes = bytes.AsSpan()[i..(i + 4)];
212-
var lengthValue = MemoryMarshal.Cast<byte, uint>(lengthBytes)[0];
213-
if (length == 0)
214-
{
215-
length = lengthValue;
216-
}
217-
else
218-
{
219-
length *= lengthValue;
220-
}
221-
222-
sb.Append(lengthValue);
223-
sb.Append(',');
224-
i += 4;
256+
length = lengthValue;
225257
}
226-
227-
sb.Remove(sb.Length - 1, 1);
228-
sb.Append('>');
229-
230-
var doubleBytes =
231-
MemoryMarshal.Cast<byte, double>(bytes.AsSpan().Slice(i, (int)(length * 8)));
232-
233-
234-
sb.Append('[');
235-
for (var j = 0; j < length; j++)
258+
else
236259
{
237-
sb.Append(doubleBytes[j]);
238-
sb.Append(',');
260+
length *= lengthValue;
239261
}
240262

241-
sb.Remove(sb.Length - 1, 1);
242-
sb.Append(']');
243-
244-
i += (int)(length * 8);
245-
i--;
246-
break;
247-
case 16:
248-
sb.Remove(sb.Length - 1, 1);
249-
var doubleValue = MemoryMarshal.Cast<byte, double>(bytes.AsSpan().Slice(++i, 8));
250-
sb.Append(doubleValue[0].ToString(CultureInfo.InvariantCulture));
251-
i += 8;
252-
i--;
253-
break;
254-
default:
255-
throw new NotImplementedException();
256-
}
257-
258-
lastAppend = i + 1;
263+
sb.Append(lengthValue);
264+
sb.Append(',');
265+
i += 4;
266+
}
267+
268+
sb.Remove(sb.Length - 1, 1);
269+
sb.Append('>');
270+
271+
var doubleBytes =
272+
MemoryMarshal.Cast<byte, double>(bytes.AsSpan().Slice(i, (int)(length * 8)));
273+
274+
275+
sb.Append('[');
276+
for (var j = 0; j < length; j++)
277+
{
278+
sb.Append(doubleBytes[j].ToString(CultureInfo.InvariantCulture));
279+
sb.Append(',');
280+
}
281+
282+
sb.Remove(sb.Length - 1, 1);
283+
sb.Append(']');
284+
285+
i += (int)(length * 8);
286+
i--;
287+
break;
288+
case 16:
289+
sb.Remove(sb.Length - 1, 1);
290+
var doubleValue = MemoryMarshal.Cast<byte, double>(bytes.AsSpan().Slice(++i, 8));
291+
sb.Append(doubleValue[0].ToString(CultureInfo.InvariantCulture));
292+
i += 8;
293+
i--;
294+
break;
295+
default:
296+
throw new NotImplementedException($"Type {bytes[i]} not implemented");
259297
}
298+
299+
lastAppend = i + 1;
260300
}
261301
}
262302

263303
sb.Append(Encoding.UTF8.GetString(bytes, lastAppend, i - lastAppend));
264304
return sb.ToString();
265305
}
266-
}
306+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<ProjectReference Include="..\src\net-questdb-client\net-questdb-client.csproj"/>
14+
<ProjectReference Include="..\net-questdb-client\net-questdb-client.csproj"/>
1515
</ItemGroup>
1616

1717
</Project>

src/net-questdb-client-benchmarks/BenchInserts.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
using BenchmarkDotNet.Attributes;
2727
using dummy_http_server;
2828
using QuestDB;
29-
using tcp_client_test;
29+
using net_questdb_client_tests;
3030

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

src/net-questdb-client-benchmarks/net-questdb-client-benchmarks.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
<ItemGroup>
1717
<ProjectReference Include="..\dummy-http-server\dummy-http-server.csproj"/>
18+
<ProjectReference Include="..\net-questdb-client-tests\net-questdb-client-tests.csproj" />
1819
<ProjectReference Include="..\net-questdb-client\net-questdb-client.csproj"/>
19-
<ProjectReference Include="..\tcp-client-test\tcp-client-test.csproj"/>
2020
</ItemGroup>
2121

2222
<ItemGroup>

0 commit comments

Comments
 (0)