Skip to content

QuestDB

QuestDB is a high-performance, open-source time-series database designed for fast ingestion and low-latency SQL queries. It exposes its SQL interface over the PostgreSQL wire protocol and ingests time-series data over the InfluxDB Line Protocol (ILP).

Add the following dependency to your project file:

NuGet
1
dotnet add package Testcontainers.QuestDb

You can start a QuestDB container instance from any .NET application. To create and start a container instance with the default configuration, use the module-specific builder as shown below:

1
2
var questDbContainer = new QuestDbBuilder("questdb/questdb:10.0.1").Build();
await questDbContainer.StartAsync();

The following example utilizes the xUnit.net module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public void ConnectionStateReturnsOpen()
{
    // Given
    using DbConnection connection = fixture.CreateConnection();

    // When
    connection.Open();

    // Then
    Assert.Equal(ConnectionState.Open, connection.State);
    Assert.Equal(fixture.Container.GetConnectionString(), fixture.Container.GetConnectionString(ConnectionMode.Host));
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task IlpIngestReturnsRecord()
{
    // Given
    using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));

    var ilpAddress = new Uri(fixture.Container.GetIlpAddress());

    using var sender = Sender.New($"tcp::addr={ilpAddress.Host}:{ilpAddress.Port};");

    await using var createTableCommand = fixture.CreateCommand("CREATE TABLE sensors (id SYMBOL, temperature DOUBLE, ts TIMESTAMP) TIMESTAMP(ts) PARTITION BY DAY WAL;");

    await using var selectTemperatureCommand = fixture.CreateCommand("SELECT temperature FROM sensors;");

    _ = await createTableCommand.ExecuteNonQueryAsync(cts.Token)
        .ConfigureAwait(true);

    // When
    await sender.Table("sensors").Symbol("id", "1").Column("temperature", 21.5).AtNowAsync(cts.Token)
        .ConfigureAwait(true);

    await sender.SendAsync(cts.Token)
        .ConfigureAwait(true);

    // Then
    object temperature;

    do
    {
        temperature = await selectTemperatureCommand.ExecuteScalarAsync(cts.Token)
            .ConfigureAwait(true);
    }
    while (temperature == null);

    Assert.Equal(21.5, temperature);
}

The test example creates a table and queries it over the PostgreSQL wire protocol, and ingests a record over ILP. Use GetConnectionString() to configure a PostgreSQL client such as Npgsql, and GetIlpAddress() to configure the QuestDB .NET client. The REST API and the Web Console are available at GetBaseAddress().

The default configuration uses the username quest and password quest. Use WithUsername(string) and WithPassword(string) to configure different credentials.

The test example uses the following NuGet dependencies:

1
2
3
4
5
6
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="coverlet.collector"/>
<PackageReference Include="xunit.runner.visualstudio"/>
<PackageReference Include="xunit.v3"/>
<PackageReference Include="Npgsql"/>
<PackageReference Include="net-questdb-client"/>

To execute the tests, use the command dotnet test from a terminal.

Tip

For the complete source code of this example and additional information, please refer to our test projects.