diff --git a/README.md b/README.md
index 533c257ea..d0ac06734 100644
--- a/README.md
+++ b/README.md
@@ -104,6 +104,7 @@ Examples:
GOOSE_DRIVER=mysql GOOSE_DBSTRING="user:password@/dbname" goose status
GOOSE_DRIVER=redshift GOOSE_DBSTRING="postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" goose status
GOOSE_DRIVER=clickhouse GOOSE_DBSTRING="clickhouse://user:password@qwerty.clickhouse.cloud:9440/dbname?secure=true&skip_verify=false" goose status
+ GOOSE_DRIVER=clickhouse-replicated GOOSE_CLICKHOUSE_CLUSTER_NAME=example GOOSE_DBSTRING="clickhouse://user:password@qwerty.clickhouse.cloud:9440/dbname?secure=true&skip_verify=false" goose status
Options:
diff --git a/cmd/goose/main.go b/cmd/goose/main.go
index 8a8f86f92..b83b040b8 100644
--- a/cmd/goose/main.go
+++ b/cmd/goose/main.go
@@ -269,9 +269,10 @@ Examples:
goose tidb "user:password@/dbname?parseTime=true" status
goose mssql "sqlserver://user:password@dbname:1433?database=master" status
goose clickhouse "tcp://127.0.0.1:9000" status
+ goose clickhouse-replicated "tcp://127.0.0.1:9000" status
goose vertica "vertica://user:password@localhost:5433/dbname?connection_load_balance=1" status
goose ydb "grpcs://localhost:2135/local?go_query_mode=scripting&go_fake_tx=scripting&go_query_bind=declare,numeric" status
- goose turso "libsql://dbname.turso.io?authToken=token" status
+ goose turso "libsql://dbname.turso.io?authToken=token" status
GOOSE_DRIVER=sqlite3 GOOSE_DBSTRING=./foo.db goose status
GOOSE_DRIVER=sqlite3 GOOSE_DBSTRING=./foo.db goose create init sql
@@ -279,7 +280,8 @@ Examples:
GOOSE_DRIVER=mysql GOOSE_DBSTRING="user:password@/dbname" goose status
GOOSE_DRIVER=redshift GOOSE_DBSTRING="postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" goose status
GOOSE_DRIVER=turso GOOSE_DBSTRING="libsql://dbname.turso.io?authToken=token" goose status
- GOOSE_DRIVER=clickhouse GOOSE_DBSTRING="clickhouse://user:password@qwerty.clickhouse.cloud:9440/dbname?secure=true&skip_verify=false" goose status
+ GOOSE_DRIVER=clickhouse GOOSE_DBSTRING="clickhouse://user:password@qwerty.clickhouse.cloud:9440/dbname?secure=true&skip_verify=false" goose status
+ GOOSE_DRIVER=clickhouse-replicated GOOSE_CLICKHOUSE_CLUSTER_NAME=example GOOSE_DBSTRING="clickhouse://user:password@qwerty.clickhouse.cloud:9440/dbname?secure=true&skip_verify=false" goose status
Options:
`
@@ -302,23 +304,23 @@ Commands:
)
var sqlMigrationTemplate = template.Must(template.New("goose.sql-migration").Parse(`-- Thank you for giving goose a try!
---
+--
-- This file was automatically created running goose init. If you're familiar with goose
-- feel free to remove/rename this file, write some SQL and goose up. Briefly,
---
+--
-- Documentation can be found here: https://pressly.github.io/goose
--
-- A single goose .sql file holds both Up and Down migrations.
---
+--
-- All goose .sql files are expected to have a -- +goose Up annotation.
-- The -- +goose Down annotation is optional, but recommended, and must come after the Up annotation.
---
--- The -- +goose NO TRANSACTION annotation may be added to the top of the file to run statements
+--
+-- The -- +goose NO TRANSACTION annotation may be added to the top of the file to run statements
-- outside a transaction. Both Up and Down migrations within this file will be run without a transaction.
---
--- More complex statements that have semicolons within them must be annotated with
+--
+-- More complex statements that have semicolons within them must be annotated with
-- the -- +goose StatementBegin and -- +goose StatementEnd annotations to be properly recognized.
---
+--
-- Use GitHub issues for reporting bugs and requesting features, enjoy!
-- +goose Up
diff --git a/database/dialect.go b/database/dialect.go
index ba2da5cab..570062a01 100644
--- a/database/dialect.go
+++ b/database/dialect.go
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
+ "github.com/pressly/goose/v3/internal/cfg"
"github.com/pressly/goose/v3/internal/dialect/dialectquery"
)
@@ -13,17 +14,18 @@ import (
type Dialect string
const (
- DialectClickHouse Dialect = "clickhouse"
- DialectMSSQL Dialect = "mssql"
- DialectMySQL Dialect = "mysql"
- DialectPostgres Dialect = "postgres"
- DialectRedshift Dialect = "redshift"
- DialectSQLite3 Dialect = "sqlite3"
- DialectTiDB Dialect = "tidb"
- DialectTurso Dialect = "turso"
- DialectVertica Dialect = "vertica"
- DialectYdB Dialect = "ydb"
- DialectStarrocks Dialect = "starrocks"
+ DialectClickHouse Dialect = "clickhouse"
+ DialectClickHouseReplicated Dialect = "clickhouse-replicated"
+ DialectMSSQL Dialect = "mssql"
+ DialectMySQL Dialect = "mysql"
+ DialectPostgres Dialect = "postgres"
+ DialectRedshift Dialect = "redshift"
+ DialectSQLite3 Dialect = "sqlite3"
+ DialectTiDB Dialect = "tidb"
+ DialectTurso Dialect = "turso"
+ DialectVertica Dialect = "vertica"
+ DialectYdB Dialect = "ydb"
+ DialectStarrocks Dialect = "starrocks"
)
// NewStore returns a new [Store] implementation for the given dialect.
@@ -36,16 +38,19 @@ func NewStore(dialect Dialect, tablename string) (Store, error) {
}
lookup := map[Dialect]dialectquery.Querier{
DialectClickHouse: &dialectquery.Clickhouse{},
- DialectMSSQL: &dialectquery.Sqlserver{},
- DialectMySQL: &dialectquery.Mysql{},
- DialectPostgres: &dialectquery.Postgres{},
- DialectRedshift: &dialectquery.Redshift{},
- DialectSQLite3: &dialectquery.Sqlite3{},
- DialectTiDB: &dialectquery.Tidb{},
- DialectVertica: &dialectquery.Vertica{},
- DialectYdB: &dialectquery.Ydb{},
- DialectTurso: &dialectquery.Turso{},
- DialectStarrocks: &dialectquery.Starrocks{},
+ DialectClickHouseReplicated: &dialectquery.ClickhouseReplicated{
+ ClusterName: cfg.GOOSECLICKHOUSECLUSTERNAME,
+ },
+ DialectMSSQL: &dialectquery.Sqlserver{},
+ DialectMySQL: &dialectquery.Mysql{},
+ DialectPostgres: &dialectquery.Postgres{},
+ DialectRedshift: &dialectquery.Redshift{},
+ DialectSQLite3: &dialectquery.Sqlite3{},
+ DialectTiDB: &dialectquery.Tidb{},
+ DialectVertica: &dialectquery.Vertica{},
+ DialectYdB: &dialectquery.Ydb{},
+ DialectTurso: &dialectquery.Turso{},
+ DialectStarrocks: &dialectquery.Starrocks{},
}
querier, ok := lookup[dialect]
if !ok {
diff --git a/internal/cfg/cfg.go b/internal/cfg/cfg.go
index aa9707633..a52700ebd 100644
--- a/internal/cfg/cfg.go
+++ b/internal/cfg/cfg.go
@@ -3,9 +3,10 @@ package cfg
import "os"
var (
- GOOSEDRIVER = envOr("GOOSE_DRIVER", "")
- GOOSEDBSTRING = envOr("GOOSE_DBSTRING", "")
- GOOSEMIGRATIONDIR = envOr("GOOSE_MIGRATION_DIR", DefaultMigrationDir)
+ GOOSECLICKHOUSECLUSTERNAME = envOr("GOOSE_CLICKHOUSE_CLUSTER_NAME", "{cluster}")
+ GOOSEDRIVER = envOr("GOOSE_DRIVER", "")
+ GOOSEDBSTRING = envOr("GOOSE_DBSTRING", "")
+ GOOSEMIGRATIONDIR = envOr("GOOSE_MIGRATION_DIR", DefaultMigrationDir)
// https://no-color.org/
GOOSENOCOLOR = envOr("NO_COLOR", "false")
)
@@ -22,6 +23,7 @@ type EnvVar struct {
func List() []EnvVar {
return []EnvVar{
+ {Name: "GOOSE_CLICKHOUSER_CLUSTER_NAME", Value: GOOSECLICKHOUSECLUSTERNAME},
{Name: "GOOSE_DRIVER", Value: GOOSEDRIVER},
{Name: "GOOSE_DBSTRING", Value: GOOSEDBSTRING},
{Name: "GOOSE_MIGRATION_DIR", Value: GOOSEMIGRATIONDIR},
diff --git a/internal/dialect/dialectquery/clickhouse.go b/internal/dialect/dialectquery/clickhouse.go
index 8c046fb56..fc914c2a7 100644
--- a/internal/dialect/dialectquery/clickhouse.go
+++ b/internal/dialect/dialectquery/clickhouse.go
@@ -43,12 +43,14 @@ func (c *Clickhouse) GetLatestVersion(tableName string) string {
return fmt.Sprintf(q, tableName)
}
-type ClickhouseReplicated struct{}
+type ClickhouseReplicated struct {
+ ClusterName string
+}
var _ Querier = (*ClickhouseReplicated)(nil)
func (c *ClickhouseReplicated) CreateTable(tableName string) string {
- q := `CREATE TABLE IF NOT EXISTS %s ON CLUSTER '{cluster}' (
+ q := `CREATE TABLE IF NOT EXISTS %s ON CLUSTER '%s' (
version_id Int64,
is_applied UInt8,
date Date default now(),
@@ -56,7 +58,7 @@ func (c *ClickhouseReplicated) CreateTable(tableName string) string {
)
ENGINE = ReplicatedMergeTree()
ORDER BY (date)`
- return fmt.Sprintf(q, tableName)
+ return fmt.Sprintf(q, tableName, c.ClusterName)
}
func (c *ClickhouseReplicated) InsertVersion(tableName string) string {
diff --git a/internal/dialect/store.go b/internal/dialect/store.go
index cac7b244d..766a6d9fd 100644
--- a/internal/dialect/store.go
+++ b/internal/dialect/store.go
@@ -6,6 +6,7 @@ import (
"fmt"
"time"
+ "github.com/pressly/goose/v3/internal/cfg"
"github.com/pressly/goose/v3/internal/dialect/dialectquery"
)
@@ -64,7 +65,9 @@ func NewStore(d Dialect) (Store, error) {
case Clickhouse:
querier = &dialectquery.Clickhouse{}
case ClickhouseReplicated:
- querier = &dialectquery.ClickhouseReplicated{}
+ querier = &dialectquery.ClickhouseReplicated{
+ ClusterName: cfg.GOOSECLICKHOUSECLUSTERNAME,
+ }
case Vertica:
querier = &dialectquery.Vertica{}
case Ydb:
diff --git a/internal/testing/integration/database_test.go b/internal/testing/integration/database_test.go
index 649020255..0ea75761d 100644
--- a/internal/testing/integration/database_test.go
+++ b/internal/testing/integration/database_test.go
@@ -88,6 +88,27 @@ func TestClickhouseRemote(t *testing.T) {
require.Equal(t, 265, count)
}
+func TestClickhouseReplicated(t *testing.T) {
+ t.Parallel()
+
+ db0, db1, cleanup, err := testdb.NewClickHouseReplicated(testdb.WithDebug(false))
+ require.NoError(t, err)
+ t.Cleanup(cleanup)
+
+ testDatabase(t, database.DialectClickHouseReplicated, db0, "testdata/migrations/clickhouse-replicated")
+
+ rows, err := db1.Query(`SELECT count(*) FROM clickstream`)
+ require.NoError(t, err)
+ var result int
+ for rows.Next() {
+ err = rows.Scan(&result)
+ require.NoError(t, err)
+ }
+ require.Equal(t, result, 3)
+ require.NoError(t, rows.Close())
+ require.NoError(t, rows.Err())
+}
+
func TestMySQL(t *testing.T) {
t.Parallel()
diff --git a/internal/testing/integration/testdata/migrations/clickhouse-replicated/00001_a.sql b/internal/testing/integration/testdata/migrations/clickhouse-replicated/00001_a.sql
new file mode 100644
index 000000000..2c2cbfccf
--- /dev/null
+++ b/internal/testing/integration/testdata/migrations/clickhouse-replicated/00001_a.sql
@@ -0,0 +1,55 @@
+-- +goose Up
+CREATE TABLE IF NOT EXISTS trips ON CLUSTER '{cluster}'
+(
+ `trip_id` UInt32,
+ `vendor_id` Enum8('1' = 1, '2' = 2, '3' = 3, '4' = 4, 'CMT' = 5, 'VTS' = 6, 'DDS' = 7, 'B02512' = 10, 'B02598' = 11, 'B02617' = 12, 'B02682' = 13, 'B02764' = 14, '' = 15),
+ `pickup_date` Date,
+ `pickup_datetime` DateTime,
+ `dropoff_date` Date,
+ `dropoff_datetime` DateTime,
+ `store_and_fwd_flag` UInt8,
+ `rate_code_id` UInt8,
+ `pickup_longitude` Float64,
+ `pickup_latitude` Float64,
+ `dropoff_longitude` Float64,
+ `dropoff_latitude` Float64,
+ `passenger_count` UInt8,
+ `trip_distance` Float64,
+ `fare_amount` Float32,
+ `extra` Float32,
+ `mta_tax` Float32,
+ `tip_amount` Float32,
+ `tolls_amount` Float32,
+ `ehail_fee` Float32,
+ `improvement_surcharge` Float32,
+ `total_amount` Float32,
+ `payment_type` Enum8('UNK' = 0, 'CSH' = 1, 'CRE' = 2, 'NOC' = 3, 'DIS' = 4),
+ `trip_type` UInt8,
+ `pickup` FixedString(25),
+ `dropoff` FixedString(25),
+ `cab_type` Enum8('yellow' = 1, 'green' = 2, 'uber' = 3),
+ `pickup_nyct2010_gid` Int8,
+ `pickup_ctlabel` Float32,
+ `pickup_borocode` Int8,
+ `pickup_ct2010` String,
+ `pickup_boroct2010` FixedString(7),
+ `pickup_cdeligibil` String,
+ `pickup_ntacode` FixedString(4),
+ `pickup_ntaname` String,
+ `pickup_puma` UInt16,
+ `dropoff_nyct2010_gid` UInt8,
+ `dropoff_ctlabel` Float32,
+ `dropoff_borocode` UInt8,
+ `dropoff_ct2010` String,
+ `dropoff_boroct2010` FixedString(7),
+ `dropoff_cdeligibil` String,
+ `dropoff_ntacode` FixedString(4),
+ `dropoff_ntaname` String,
+ `dropoff_puma` UInt16
+)
+ENGINE = ReplicatedMergeTree()
+PARTITION BY toYYYYMM(pickup_date)
+ORDER BY pickup_datetime;
+
+-- +goose Down
+DROP TABLE IF EXISTS trips ON CLUSTER '{cluster}' SYNC;
diff --git a/internal/testing/integration/testdata/migrations/clickhouse-replicated/00002_b.sql b/internal/testing/integration/testdata/migrations/clickhouse-replicated/00002_b.sql
new file mode 100644
index 000000000..82a3f7692
--- /dev/null
+++ b/internal/testing/integration/testdata/migrations/clickhouse-replicated/00002_b.sql
@@ -0,0 +1,13 @@
+-- +goose Up
+CREATE TABLE IF NOT EXISTS clickstream ON CLUSTER '{cluster}' (
+ customer_id String,
+ time_stamp Date,
+ click_event_type String,
+ country_code FixedString(2),
+ source_id UInt64
+)
+ENGINE = ReplicatedMergeTree()
+ORDER BY (time_stamp);
+
+-- +goose Down
+DROP TABLE IF EXISTS clickstream ON CLUSTER '{cluster}' SYNC;
diff --git a/internal/testing/integration/testdata/migrations/clickhouse-replicated/00003_c.sql b/internal/testing/integration/testdata/migrations/clickhouse-replicated/00003_c.sql
new file mode 100644
index 000000000..ecf714b58
--- /dev/null
+++ b/internal/testing/integration/testdata/migrations/clickhouse-replicated/00003_c.sql
@@ -0,0 +1,8 @@
+-- +goose Up
+INSERT INTO clickstream VALUES ('customer1', '2021-10-02', 'add_to_cart', 'US', 568239 );
+
+INSERT INTO clickstream (customer_id, time_stamp, click_event_type) VALUES ('customer2', '2021-10-30', 'remove_from_cart' );
+
+INSERT INTO clickstream (* EXCEPT(country_code)) VALUES ('customer3', '2021-11-07', 'checkout', 307493 );
+
+-- +goose Down
diff --git a/internal/testing/testdb/clickhouse-replicated.go b/internal/testing/testdb/clickhouse-replicated.go
new file mode 100644
index 000000000..ba0e764f6
--- /dev/null
+++ b/internal/testing/testdb/clickhouse-replicated.go
@@ -0,0 +1,133 @@
+package testdb
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "log"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/ory/dockertest/v3"
+ "github.com/ory/dockertest/v3/docker"
+ "github.com/sethvargo/go-retry"
+)
+
+const clickhouseReplicatedNetworkName = "goose-clickhouse-replicated-tests"
+
+func newClickHouseReplicated(opts ...OptionsFunc) (*sql.DB, *sql.DB, func(), error) {
+ option := &options{}
+ for _, f := range opts {
+ f(option)
+ }
+
+ pool, err := dockertest.NewPool("")
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ net, err := pool.CreateNetwork(clickhouseReplicatedNetworkName)
+ if err != nil {
+ if !strings.Contains(err.Error(), "already exists") {
+ return nil, nil, nil, err
+ }
+
+ nets, err := pool.NetworksByName(clickhouseReplicatedNetworkName)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if len(nets) != 1 {
+ return nil, nil, nil, fmt.Errorf("found more than one network with name %s", clickhouseReplicatedNetworkName)
+ }
+
+ net = &nets[0]
+ }
+
+ zk, err := startZookeeper(pool, net)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ path, err := filepath.Abs("./../../../")
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ db0, cleanup0, err := NewClickHouse(
+ WithName("clickhouse0"),
+ WithEnv([]string{"REPLICA_NAME=clickhouse0"}),
+ WithNetwork(net),
+ WithMounts([]string{
+ path + "/testdata/clickhouse-replicated:/etc/clickhouse-server/conf.d",
+ }))
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ db1, cleanup1, err := NewClickHouse(
+ WithName("clickhouse1"),
+ WithEnv([]string{"REPLICA_NAME=clickhouse1"}),
+ WithNetwork(net),
+ WithMounts([]string{
+ path + "/testdata/clickhouse-replicated:/etc/clickhouse-server/conf.d",
+ }))
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ cleanup := func() {
+ if option.debug {
+ return
+ }
+
+ cleanup0()
+ cleanup1()
+ if err := pool.Purge(zk); err != nil {
+ log.Printf("failed to purge resource: %v", err)
+ }
+ if err := pool.RemoveNetwork(net); err != nil {
+ log.Printf("failed to purge network %s: %v", net.Network.Name, err)
+ }
+ }
+
+ return db0, db1, cleanup, nil
+}
+
+func startZookeeper(pool *dockertest.Pool, net *dockertest.Network) (*dockertest.Resource, error) {
+ runOptions := &dockertest.RunOptions{
+ Name: "zookeeper",
+ Repository: "zookeeper",
+ Tag: "3.7.2",
+ Labels: map[string]string{"goose_test": "1"},
+ PortBindings: make(map[docker.Port][]docker.PortBinding),
+ NetworkID: net.Network.ID,
+ }
+ zk, err := pool.RunWithOptions(
+ runOptions,
+ func(config *docker.HostConfig) {
+ // Set AutoRemove to true so that stopped container goes away by itself.
+ config.AutoRemove = true
+ config.RestartPolicy = docker.RestartPolicy{Name: "no"}
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ backoff := retry.WithMaxDuration(1*time.Minute, retry.NewConstant(2*time.Second))
+ if err := retry.Do(context.Background(), backoff, func(ctx context.Context) error {
+ exitCode, err := zk.Exec([]string{"zkCli.sh", "ls", "/"}, dockertest.ExecOptions{})
+ if err != nil {
+ return retry.RetryableError(err)
+ }
+ if exitCode != 0 {
+ return retry.RetryableError(fmt.Errorf("zk cmd returns %d", exitCode))
+ }
+ return nil
+ }); err != nil {
+ return nil, fmt.Errorf("could not connect to docker database: %v", err)
+ }
+
+ return zk, nil
+}
diff --git a/internal/testing/testdb/clickhouse.go b/internal/testing/testdb/clickhouse.go
index ae5b50013..39016dbed 100644
--- a/internal/testing/testdb/clickhouse.go
+++ b/internal/testing/testdb/clickhouse.go
@@ -34,6 +34,7 @@ func newClickHouse(opts ...OptionsFunc) (*sql.DB, func(), error) {
if err != nil {
return nil, nil, err
}
+
runOptions := &dockertest.RunOptions{
Repository: CLICKHOUSE_IMAGE,
Tag: CLICKHOUSE_VERSION,
@@ -46,6 +47,18 @@ func newClickHouse(opts ...OptionsFunc) (*sql.DB, func(), error) {
Labels: map[string]string{"goose_test": "1"},
PortBindings: make(map[docker.Port][]docker.PortBinding),
}
+ if option.network != nil {
+ runOptions.NetworkID = option.network.Network.ID
+ }
+ if len(option.name) > 0 {
+ runOptions.Name = option.name
+ }
+ if len(option.mounts) > 0 {
+ runOptions.Mounts = option.mounts
+ }
+ if len(option.env) > 0 {
+ runOptions.Env = append(runOptions.Env, option.env...)
+ }
// Port 8123 is used for HTTP, but we're using the TCP protocol endpoint (port 9000).
// Ref: https://clickhouse.com/docs/en/interfaces/http/
// Ref: https://clickhouse.com/docs/en/interfaces/tcp/
@@ -62,6 +75,7 @@ func newClickHouse(opts ...OptionsFunc) (*sql.DB, func(), error) {
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
},
)
+
if err != nil {
return nil, nil, err
}
@@ -100,6 +114,9 @@ func clickHouseOpenDB(address string, tlsConfig *tls.Config, debug bool) *sql.DB
TLS: tlsConfig,
Settings: clickhouse.Settings{
"max_execution_time": 60,
+ // Next settings allows to execute queries on clickhouse replicated cluster with same manner as single node cluster
+ "insert_quorum": 2,
+ "select_sequential_consistency": 1,
},
DialTimeout: 5 * time.Second,
Compression: &clickhouse.Compression{
@@ -107,8 +124,8 @@ func clickHouseOpenDB(address string, tlsConfig *tls.Config, debug bool) *sql.DB
},
Debug: debug,
})
- db.SetMaxIdleConns(5)
- db.SetMaxOpenConns(10)
+ db.SetMaxIdleConns(1)
+ db.SetMaxOpenConns(1)
db.SetConnMaxLifetime(time.Hour)
return db
}
diff --git a/internal/testing/testdb/options.go b/internal/testing/testdb/options.go
index bd8681208..376a140cf 100644
--- a/internal/testing/testdb/options.go
+++ b/internal/testing/testdb/options.go
@@ -1,6 +1,12 @@
package testdb
+import "github.com/ory/dockertest/v3"
+
type options struct {
+ env []string
+ mounts []string
+ network *dockertest.Network
+ name string
bindPort int
debug bool
}
@@ -14,3 +20,19 @@ func WithBindPort(n int) OptionsFunc {
func WithDebug(b bool) OptionsFunc {
return func(o *options) { o.debug = b }
}
+
+func WithMounts(m []string) OptionsFunc {
+ return func(o *options) { o.mounts = m }
+}
+
+func WithName(n string) OptionsFunc {
+ return func(o *options) { o.name = n }
+}
+
+func WithEnv(e []string) OptionsFunc {
+ return func(o *options) { o.env = e }
+}
+
+func WithNetwork(n *dockertest.Network) OptionsFunc {
+ return func(o *options) { o.network = n }
+}
diff --git a/internal/testing/testdb/testdb.go b/internal/testing/testdb/testdb.go
index 72a2b19d5..80acdefb9 100644
--- a/internal/testing/testdb/testdb.go
+++ b/internal/testing/testdb/testdb.go
@@ -7,6 +7,11 @@ func NewClickHouse(options ...OptionsFunc) (db *sql.DB, cleanup func(), err erro
return newClickHouse(options...)
}
+// NewClickHouseReplicated starts Zookeeper and two ClickHouse docker containers. Returns db connections for each db and a docker cleanup function.
+func NewClickHouseReplicated(options ...OptionsFunc) (db0 *sql.DB, db1 *sql.DB, cleanup func(), err error) {
+ return newClickHouseReplicated(options...)
+}
+
// NewPostgres starts a PostgreSQL docker container. Returns db connection and a docker cleanup function.
func NewPostgres(options ...OptionsFunc) (db *sql.DB, cleanup func(), err error) {
return newPostgres(options...)
diff --git a/testdata/clickhouse-replicated/macros.xml b/testdata/clickhouse-replicated/macros.xml
new file mode 100644
index 000000000..972fc083a
--- /dev/null
+++ b/testdata/clickhouse-replicated/macros.xml
@@ -0,0 +1,7 @@
+
+
+ goose
+ 0
+
+
+
diff --git a/testdata/clickhouse-replicated/remote_servers.xml b/testdata/clickhouse-replicated/remote_servers.xml
new file mode 100644
index 000000000..d1763eb87
--- /dev/null
+++ b/testdata/clickhouse-replicated/remote_servers.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ true
+
+ clickhouse0
+ 9000
+
+
+ clickhouse1
+ 9000
+
+
+
+
+
diff --git a/testdata/clickhouse-replicated/zookeeper.xml b/testdata/clickhouse-replicated/zookeeper.xml
new file mode 100644
index 000000000..ec539cb14
--- /dev/null
+++ b/testdata/clickhouse-replicated/zookeeper.xml
@@ -0,0 +1,11 @@
+
+ /clickhouse/{cluster}/tables/{shard}/{database}/{table}
+ {replica}
+
+
+
+ zookeeper
+ 2181
+
+
+