Skip to content

Commit

Permalink
feat: support databend module (testcontainers#2779)
Browse files Browse the repository at this point in the history
* feat: support databend module

* chore: add project scaffolding

* chore: more file

* chore: run make lint

* fix: proper pinned version

* fix comment

* add the detail documents

* Update modules/databend/databend.go

Co-authored-by: Steven Hartland <[email protected]>

* fix review comment

* fix

* default database

* Update modules/databend/databend_test.go

Co-authored-by: Steven Hartland <[email protected]>

* fix review comments

* Update modules/databend/databend.go

Co-authored-by: Steven Hartland <[email protected]>

* Update modules/databend/databend.go

Co-authored-by: Steven Hartland <[email protected]>

* fix comment

* fix databend-go module

* fix databend tests

* fix golangci-lint

* Update modules/databend/databend.go

Co-authored-by: Steven Hartland <[email protected]>

* remove WithDatabase

* fix

* chore: rollback pinned version

We'll tackle that in a separate PR

---------

Co-authored-by: Manuel de la Peña <[email protected]>
Co-authored-by: Steven Hartland <[email protected]>
Co-authored-by: Manuel de la Peña <[email protected]>
  • Loading branch information
4 people authored Sep 20, 2024
1 parent fca9698 commit c1bb0bb
Show file tree
Hide file tree
Showing 11 changed files with 643 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
matrix:
go-version: [1.22.x, 1.x]
platform: [ubuntu-latest]
module: [artemis, azurite, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, dolt, elasticsearch, gcloud, grafana-lgtm, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, valkey, vault, vearch, weaviate]
module: [artemis, azurite, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, databend, dolt, elasticsearch, gcloud, grafana-lgtm, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, valkey, vault, vearch, weaviate]
uses: ./.github/workflows/ci-test-go.yml
with:
go-version: ${{ matrix.go-version }}
Expand Down
4 changes: 4 additions & 0 deletions .vscode/.testcontainers-go.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
"name": "module / couchbase",
"path": "../modules/couchbase"
},
{
"name": "module / databend",
"path": "../modules/databend"
},
{
"name": "module / dolt",
"path": "../modules/dolt"
Expand Down
72 changes: 72 additions & 0 deletions docs/modules/databend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Databend

Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

## Introduction

The Testcontainers module for Databend.

## Adding this module to your project dependencies

Please run the following command to add the Databend module to your Go dependencies:

```
go get github.com/testcontainers/testcontainers-go/modules/databend
```

## Usage example

<!--codeinclude-->
[Creating a Databend container](../../modules/databend/examples_test.go) inside_block:runDatabendContainer
<!--/codeinclude-->

## Module Reference

### Run function

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

The Databend module exposes one entrypoint function to create the Databend container, and this function receives three parameters:

```golang
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DatabendContainer, error)
```

- `context.Context`, the Go context.
- `string`, the Docker image to use.
- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.

### Container Options

When starting the Databend container, you can pass options in a variadic way to configure it.

#### Image

If you need to set a different Databend Docker image, you can set a valid Docker image as the second argument in the `Run` function.
E.g. `Run(context.Background(), "datafuselabs/databend:v1.2.615")`.

{% include "../features/common_functional_options.md" %}

#### Set username, password

If you need to set a different user/password/database, you can use `WithUsername`, `WithPassword` options.

!!!info
The default values for the username is `databend`, for password is `databend` and for the default database name is `default`.

### Container Methods

The Databend container exposes the following methods:

#### ConnectionString

This method returns the connection string to connect to the Databend container, using the default `8000` port.
It's possible to pass extra parameters to the connection string, e.g. `sslmode=disable`.
<!--codeinclude-->
[Get connection string](../../modules/databend/databend_test.go) inside_block:connectionString
<!--/codeinclude-->
#### MustGetConnectionString
`MustConnectionString` panics if the address cannot be determined.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ nav:
- modules/cockroachdb.md
- modules/consul.md
- modules/couchbase.md
- modules/databend.md
- modules/dolt.md
- modules/elasticsearch.md
- modules/gcloud.md
Expand Down
5 changes: 5 additions & 0 deletions modules/databend/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include ../../commons-test.mk

.PHONY: test
test:
$(MAKE) test-databend
135 changes: 135 additions & 0 deletions modules/databend/databend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package databend

import (
"context"
"errors"
"fmt"
"strings"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)

const (
databendUser = "databend"
defaultUser = "databend"
defaultPassword = "databend"
defaultDatabaseName = "default"
)

// DatabendContainer represents the Databend container type used in the module
type DatabendContainer struct {
testcontainers.Container
username string
password string
database string
}

var _ testcontainers.ContainerCustomizer = (*DatabendOption)(nil)

// DatabendOption is an option for the Databend container.
type DatabendOption func(*DatabendContainer)

// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface.
func (o DatabendOption) Customize(*testcontainers.GenericContainerRequest) error {
// NOOP to satisfy interface.
return nil
}

// Run creates an instance of the Databend container type
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DatabendContainer, error) {
req := testcontainers.ContainerRequest{
Image: img,
ExposedPorts: []string{"8000/tcp"},
Env: map[string]string{
"QUERY_DEFAULT_USER": defaultUser,
"QUERY_DEFAULT_PASSWORD": defaultPassword,
},
WaitingFor: wait.ForListeningPort("8000/tcp"),
}

genericContainerReq := testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
}

for _, opt := range opts {
if err := opt.Customize(&genericContainerReq); err != nil {
return nil, err
}
}

username := req.Env["QUERY_DEFAULT_USER"]
password := req.Env["QUERY_DEFAULT_PASSWORD"]
if password == "" && username == "" {
return nil, errors.New("empty password and user")
}

container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
var c *DatabendContainer
if container != nil {
c = &DatabendContainer{
Container: container,
password: password,
username: username,
database: defaultDatabaseName,
}
}

if err != nil {
return c, fmt.Errorf("generic container: %w", err)
}

return c, nil
}

// MustConnectionString panics if the address cannot be determined.
func (c *DatabendContainer) MustConnectionString(ctx context.Context, args ...string) string {
addr, err := c.ConnectionString(ctx, args...)
if err != nil {
panic(err)
}
return addr
}

func (c *DatabendContainer) ConnectionString(ctx context.Context, args ...string) (string, error) {
containerPort, err := c.MappedPort(ctx, "8000/tcp")
if err != nil {
return "", fmt.Errorf("mapped port: %w", err)
}

host, err := c.Host(ctx)
if err != nil {
return "", err
}

extraArgs := ""
if len(args) > 0 {
extraArgs = "?" + strings.Join(args, "&")
}
if c.database == "" {
return "", errors.New("database name is empty")
}

// databend://databend:databend@localhost:8000/default?sslmode=disable
connectionString := fmt.Sprintf("databend://%s:%s@%s:%s/%s%s", c.username, c.password, host, containerPort.Port(), c.database, extraArgs)
return connectionString, nil
}

// WithUsername sets the username for the Databend container.
// WithUsername is [Run] option that configures the default query user by setting
// the `QUERY_DEFAULT_USER` container environment variable.
func WithUsername(username string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) error {
req.Env["QUERY_DEFAULT_USER"] = username
return nil
}
}

// WithPassword sets the password for the Databend container.
func WithPassword(password string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) error {
req.Env["QUERY_DEFAULT_PASSWORD"] = password
return nil
}
}
74 changes: 74 additions & 0 deletions modules/databend/databend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package databend_test

import (
"context"
"database/sql"
"testing"

_ "github.com/datafuselabs/databend-go"
"github.com/stretchr/testify/require"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/databend"
)

func TestDatabend(t *testing.T) {
ctx := context.Background()

ctr, err := databend.Run(ctx, "datafuselabs/databend:v1.2.615")
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)

// perform assertions
// connectionString {
connectionString, err := ctr.ConnectionString(ctx, "sslmode=disable")
// }
require.NoError(t, err)

mustConnectionString := ctr.MustConnectionString(ctx, "sslmode=disable")
require.Equal(t, connectionString, mustConnectionString)

db, err := sql.Open("databend", connectionString)
require.NoError(t, err)
defer db.Close()

err = db.Ping()
require.NoError(t, err)

_, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" +
" `col_1` VARCHAR(128) NOT NULL, \n" +
" `col_2` VARCHAR(128) NOT NULL \n" +
")")
require.NoError(t, err)
}

func TestDatabendWithDefaultUserAndPassword(t *testing.T) {
ctx := context.Background()

ctr, err := databend.Run(ctx,
"datafuselabs/databend:v1.2.615",
databend.WithUsername("databend"))
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)

// perform assertions
connectionString, err := ctr.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)

db, err := sql.Open("databend", connectionString)
require.NoError(t, err)
defer db.Close()
err = db.Ping()
require.NoError(t, err)

var i int
row := db.QueryRow("select 1")
err = row.Scan(&i)
require.NoError(t, err)

_, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" +
" `col_1` VARCHAR(128) NOT NULL, \n" +
" `col_2` VARCHAR(128) NOT NULL \n" +
")")
require.NoError(t, err)
}
Loading

0 comments on commit c1bb0bb

Please sign in to comment.