Skip to content
Open
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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
<PackageVersion Include="Net.IBM.Data.Db2-lnx" Version="9.0.0.100"/>
<PackageVersion Include="Net.IBM.Data.Db2-osx" Version="9.0.0.100"/>
<PackageVersion Include="Net.IBM.Data.Db2" Version="9.0.0.100"/>
<PackageVersion Include="net-questdb-client" Version="2.1.0"/>
<PackageVersion Include="Npgsql" Version="6.0.11"/>
<PackageVersion Include="OllamaSharp" Version="5.1.13"/>
<PackageVersion Include="OpenSearch.Client" Version="1.8.0"/>
Expand Down
1 change: 1 addition & 0 deletions Testcontainers.dic
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ myregistryhost
nlcm
npipe
postgre
questdb
ramsize
rebalance
redpanda
Expand Down
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Pulsar", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Qdrant", "src\Testcontainers.Qdrant\Testcontainers.Qdrant.csproj", "{7C98973D-53D7-49F9-BDFE-E3268F402584}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.QuestDb", "src\Testcontainers.QuestDb\Testcontainers.QuestDb.csproj", "{DE67DE99-A32E-4321-8008-8DDD6703C47C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RabbitMq", "src\Testcontainers.RabbitMq\Testcontainers.RabbitMq.csproj", "{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RavenDb", "src\Testcontainers.RavenDb\Testcontainers.RavenDb.csproj", "{F6394475-D6F1-46E2-81BF-4BA78A40B878}"
Expand Down Expand Up @@ -252,6 +254,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Pulsar.Tests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Qdrant.Tests", "tests\Testcontainers.Qdrant.Tests\Testcontainers.Qdrant.Tests.csproj", "{9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.QuestDb.Tests", "tests\Testcontainers.QuestDb.Tests\Testcontainers.QuestDb.Tests.csproj", "{4FC555ED-3E8A-4CE9-98A3-876A44E524A8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RabbitMq.Tests", "tests\Testcontainers.RabbitMq.Tests\Testcontainers.RabbitMq.Tests.csproj", "{19564567-1736-4626-B406-17E4E02F18B2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RavenDb.Tests", "tests\Testcontainers.RavenDb.Tests\Testcontainers.RavenDb.Tests.csproj", "{D53726B6-5447-47E6-B881-A44EFF6E5534}"
Expand Down Expand Up @@ -478,6 +482,10 @@ Global
{7C98973D-53D7-49F9-BDFE-E3268F402584}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C98973D-53D7-49F9-BDFE-E3268F402584}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C98973D-53D7-49F9-BDFE-E3268F402584}.Release|Any CPU.Build.0 = Release|Any CPU
{DE67DE99-A32E-4321-8008-8DDD6703C47C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE67DE99-A32E-4321-8008-8DDD6703C47C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE67DE99-A32E-4321-8008-8DDD6703C47C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE67DE99-A32E-4321-8008-8DDD6703C47C}.Release|Any CPU.Build.0 = Release|Any CPU
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -750,6 +758,10 @@ Global
{9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D}.Release|Any CPU.Build.0 = Release|Any CPU
{4FC555ED-3E8A-4CE9-98A3-876A44E524A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FC555ED-3E8A-4CE9-98A3-876A44E524A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FC555ED-3E8A-4CE9-98A3-876A44E524A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FC555ED-3E8A-4CE9-98A3-876A44E524A8}.Release|Any CPU.Build.0 = Release|Any CPU
{19564567-1736-4626-B406-17E4E02F18B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19564567-1736-4626-B406-17E4E02F18B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19564567-1736-4626-B406-17E4E02F18B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -928,6 +940,8 @@ Global
{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{D05FCB31-793E-43E0-BD6C-077013AE9113} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{9DCE3E7F-B341-4AD0-BAAA-C3B91EB91B0D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{DE67DE99-A32E-4321-8008-8DDD6703C47C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{4FC555ED-3E8A-4CE9-98A3-876A44E524A8} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{19564567-1736-4626-B406-17E4E02F18B2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{D53726B6-5447-47E6-B881-A44EFF6E5534} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{31EE94A0-E721-4073-B6F1-DD912D004DEF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
Expand Down
2 changes: 2 additions & 0 deletions Testcontainers.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<Project Path="src/Testcontainers.PubSub/Testcontainers.PubSub.csproj"/>
<Project Path="src/Testcontainers.Pulsar/Testcontainers.Pulsar.csproj"/>
<Project Path="src/Testcontainers.Qdrant/Testcontainers.Qdrant.csproj"/>
<Project Path="src/Testcontainers.QuestDb/Testcontainers.QuestDb.csproj"/>
<Project Path="src/Testcontainers.RabbitMq/Testcontainers.RabbitMq.csproj"/>
<Project Path="src/Testcontainers.RavenDb/Testcontainers.RavenDb.csproj"/>
<Project Path="src/Testcontainers.Redis/Testcontainers.Redis.csproj"/>
Expand Down Expand Up @@ -128,6 +129,7 @@
<Project Path="tests/Testcontainers.PubSub.Tests/Testcontainers.PubSub.Tests.csproj"/>
<Project Path="tests/Testcontainers.Pulsar.Tests/Testcontainers.Pulsar.Tests.csproj"/>
<Project Path="tests/Testcontainers.Qdrant.Tests/Testcontainers.Qdrant.Tests.csproj"/>
<Project Path="tests/Testcontainers.QuestDb.Tests/Testcontainers.QuestDb.Tests.csproj"/>
<Project Path="tests/Testcontainers.RabbitMq.Tests/Testcontainers.RabbitMq.Tests.csproj"/>
<Project Path="tests/Testcontainers.RavenDb.Tests/Testcontainers.RavenDb.Tests.csproj"/>
<Project Path="tests/Testcontainers.Redis.Tests/Testcontainers.Redis.Tests.csproj"/>
Expand Down
1 change: 1 addition & 0 deletions docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ await moduleNameContainer.StartAsync();
| PubSub | `gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators` | [NuGet](https://www.nuget.org/packages/Testcontainers.PubSub) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.PubSub) |
| Pulsar | `apachepulsar/pulsar:3.0.6` | [NuGet](https://www.nuget.org/packages/Testcontainers.Pulsar) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Pulsar) |
| Qdrant | `qdrant/qdrant:v1.13.4` | [NuGet](https://www.nuget.org/packages/Testcontainers.Qdrant) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Qdrant) |
| QuestDB | `questdb/questdb:9.2.3` | [NuGet](https://www.nuget.org/packages/Testcontainers.QuestDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.QuestDb) |
| RabbitMQ | `rabbitmq:3.11` | [NuGet](https://www.nuget.org/packages/Testcontainers.RabbitMq) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.RabbitMq) |
| RavenDB | `ravendb/ravendb:5.4-ubuntu-latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.RavenDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.RavenDb) |
| Redis | `redis:7.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.Redis) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Redis) |
Expand Down
190 changes: 190 additions & 0 deletions docs/modules/questdb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# QuestDB

[QuestDB](https://questdb.com/) is a high-performance open-source time-series database designed for fast ingestion and low-latency SQL queries. It supports multiple ingestion protocols including PostgreSQL wire protocol for queries and InfluxDB Line Protocol (ILP) for high-speed time-series data ingestion.

Add the following dependency to your project file:

```shell title="NuGet"
dotnet add package Testcontainers.QuestDb
```

You can start a QuestDB container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method.

=== "Usage Example"
```csharp
--8<-- "tests/Testcontainers.QuestDb.Tests/QuestDbContainerTest.cs:UseQuestDbContainer"
```

The test example uses the following NuGet dependencies:

=== "Package References"
```xml
--8<-- "tests/Testcontainers.QuestDb.Tests/Testcontainers.QuestDb.Tests.csproj:PackageReferences"
```

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

--8<-- "docs/modules/_call_out_test_projects.txt"

## Connection Methods

### PostgreSQL Wire Protocol (SQL Queries)

QuestDB supports the PostgreSQL wire protocol for querying data:

```csharp
var connectionString = questDbContainer.GetConnectionString();
// Returns: "Host=localhost;Port=xxxxx;Database=qdb;Username=admin;Password=quest"

using var connection = new NpgsqlConnection(connectionString);
connection.Open();

using var command = new NpgsqlCommand("SELECT * FROM sensors ORDER BY ts DESC LIMIT 10;", connection);
using var reader = command.ExecuteReader();
```

### InfluxDB Line Protocol (High-Speed Ingestion)

For high-performance time-series data ingestion, use ILP over TCP:

```csharp
var ilpHost = questDbContainer.GetInfluxLineProtocolHost();
var ilpPort = questDbContainer.GetInfluxLineProtocolPort();

using var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(ilpHost, ilpPort);

using var stream = tcpClient.GetStream();
using var writer = new StreamWriter(stream) { AutoFlush = true };

// Send ILP format: measurement,tags fields timestamp
await writer.WriteLineAsync("sensors,device_id=001,location=warehouse temperature=22.5,humidity=65.2");
```

**ILP Format:**
```
measurement,tag1=value1,tag2=value2 field1=value1,field2=value2 timestamp
```

### REST API

For direct REST API access:

```csharp
var restApiAddress = questDbContainer.GetRestApiAddress();

using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(restApiAddress);

// Execute SQL via REST
var response = await httpClient.GetAsync("/exec?query=SELECT * FROM sensors");
```

### Web Console

Access the interactive Web Console:

```csharp
var webConsoleUrl = questDbContainer.GetWebConsoleUrl();
// Open in browser: http://localhost:xxxxx
```

## Configuration

### Custom Credentials

```csharp
var questDbContainer = new QuestDbBuilder("questdb/questdb:9.2.3")
.WithUsername("myuser")
.WithPassword("mypassword")
.Build();
```

## Example: Combined SQL + ILP

```csharp
// 1. Create table via SQL
await using var connection = new NpgsqlConnection(questDbContainer.GetConnectionString());
await connection.OpenAsync();

await using var createCommand = new NpgsqlCommand(
"CREATE TABLE IF NOT EXISTS sensors (ts TIMESTAMP, device_id SYMBOL, temperature DOUBLE, humidity DOUBLE) timestamp(ts) PARTITION BY DAY;",
connection);
await createCommand.ExecuteNonQueryAsync();

// 2. Ingest data via ILP (high-speed)
using var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(
questDbContainer.GetInfluxLineProtocolHost(),
questDbContainer.GetInfluxLineProtocolPort());

using var stream = tcpClient.GetStream();
using var writer = new StreamWriter(stream) { AutoFlush = true };

for (int i = 0; i < 10000; i++)
{
await writer.WriteLineAsync($"sensors,device_id=dev{i % 10} temperature={20 + i % 30},humidity={50 + i % 40}");
}

// 3. Query results via SQL
await using var queryCommand = new NpgsqlCommand(
@"SELECT device_id,
AVG(temperature) as avg_temp,
AVG(humidity) as avg_humidity
FROM sensors
WHERE ts > dateadd('h', -1, now())
GROUP BY device_id;",
connection);

await using var reader = await queryCommand.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var deviceId = reader.GetString(0);
var avgTemp = reader.GetDouble(1);
var avgHumidity = reader.GetDouble(2);
Console.WriteLine($"{deviceId}: {avgTemp}°C, {avgHumidity}%");
}
```

## Time-Series Features

QuestDB extends SQL with powerful time-series operators:

### SAMPLE BY (Downsampling)
```sql
SELECT ts, AVG(temperature)
FROM sensors
WHERE ts > dateadd('d', -7, now())
SAMPLE BY 1h;
```

### LATEST ON (Deduplication)
```sql
SELECT *
FROM sensors
LATEST ON ts PARTITION BY device_id;
```

### ASOF JOIN (Time-series Join)
```sql
SELECT *
FROM trades
ASOF JOIN quotes
ON symbol;
```

## Protocol Selection Guide

| Protocol | Use Case | Performance | Complexity |
|----------|----------|-------------|------------|
| **ILP (TCP)** | High-speed ingestion | ⚡ Fastest | Simple |
| **PostgreSQL** | SQL queries, transactions | Fast | Standard SQL |
| **REST API** | Ad-hoc queries, web apps | Moderate | JSON/HTTP |

**Recommendation:** Use ILP for ingestion, PostgreSQL for queries.

## References
- [QuestDB Documentation](https://questdb.com/docs/)
- [QuestDB ILP Reference](https://questdb.com/docs/reference/api/ilp/overview/)
- [QuestDB Docker Hub](https://hub.docker.com/r/questdb/questdb)
- [Npgsql - .NET PostgreSQL Client](https://www.npgsql.org/)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ nav:
- modules/playwright.md
- modules/postgres.md
- modules/qdrant.md
- modules/questdb.md
- modules/rabbitmq.md
- modules/toxiproxy.md
- contributing.md
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.QuestDb/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
Loading
Loading