diff --git a/.github/workflows/ci-go-sql-server-driver.yaml b/.github/workflows/ci-go-sql-server-driver.yaml index 0dca9e6920..0b4fb73bce 100644 --- a/.github/workflows/ci-go-sql-server-driver.yaml +++ b/.github/workflows/ci-go-sql-server-driver.yaml @@ -16,9 +16,9 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-22.04 ] +# os: [ ubuntu-22.04 ] # TODO: failing on MacOS due to races in syncig the default postgres db with no writes -# os: [ ubuntu-22.04, macos-latest ] + os: [ ubuntu-22.04, macos-latest ] steps: - uses: actions/checkout@v6 - name: Set up Go diff --git a/go.mod b/go.mod index 5ffa6e8694..3a7ed4b57f 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/PuerkitoBio/goquery v1.8.1 github.com/cockroachdb/apd/v3 v3.2.3 github.com/cockroachdb/errors v1.7.5 - github.com/dolthub/dolt/go v0.40.5-0.20260717180637-9606cfb035fc + github.com/dolthub/dolt/go v0.40.5-0.20260717212323-c764f22aebc6 github.com/dolthub/eventsapi_schema v0.0.0-20260715220557-d9b4a1c6b4d4 github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 - github.com/dolthub/go-mysql-server v0.20.1-0.20260717163909-4add05e419ac + github.com/dolthub/go-mysql-server v0.20.1-0.20260717195005-e77c2d65f1a5 github.com/dolthub/pg_query_go/v6 v6.0.0-20251215122834-fb20be4254d1 github.com/dolthub/sqllogictest/go v0.0.0-20260624223518-788480b24166 github.com/dolthub/vitess v0.0.0-20260624214226-81d034e0fde8 diff --git a/go.sum b/go.sum index 66582c1dc3..bd14747d0a 100644 --- a/go.sum +++ b/go.sum @@ -246,8 +246,8 @@ github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12 h1:I github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12/go.mod h1:rN7X8BHwkjPcfMQQ2QTAq/xM3leUSGLfb+1Js7Y6TVo= github.com/dolthub/dolt-mcp v0.3.4 h1:AyG5cw+fNWXDHXujtQnqUPZrpWtPg6FN6yYtjv1pP44= github.com/dolthub/dolt-mcp v0.3.4/go.mod h1:bCZ7KHvDYs+M0e+ySgmGiNvLhcwsN7bbf5YCyillLrk= -github.com/dolthub/dolt/go v0.40.5-0.20260717180637-9606cfb035fc h1:2dZ6Xc86zOFf6ubkGDwCHsWfPWao7yxWAXfFdkeA8OM= -github.com/dolthub/dolt/go v0.40.5-0.20260717180637-9606cfb035fc/go.mod h1:CT3SR83WMpcBoEWYc7RSpupK2EiPu1aI7JfeLKPRsf4= +github.com/dolthub/dolt/go v0.40.5-0.20260717212323-c764f22aebc6 h1:SRrDAFGTAed8ZLNQp9KrQSVQZe86SsTA7h0INd0a+G8= +github.com/dolthub/dolt/go v0.40.5-0.20260717212323-c764f22aebc6/go.mod h1:sYSKrZZTCRlhbTXRRpeb7U9cDo2+9MDnd/lvmRYp+zg= github.com/dolthub/eventsapi_schema v0.0.0-20260715220557-d9b4a1c6b4d4 h1:0mg9QEFdkkBwJMxvz1tCjHYmfG2iIC6aShj1InDq9/M= github.com/dolthub/eventsapi_schema v0.0.0-20260715220557-d9b4a1c6b4d4/go.mod h1:SSLraQS/jGLYFgff3vuZ+JbVUct6vyEeMzjLBqWqoyM= github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= @@ -256,8 +256,8 @@ github.com/dolthub/fslock v0.0.5 h1:QoXhBhgY1oumHE26qyE7tgmXUT8qjJwxsIzo54O/B/k= github.com/dolthub/fslock v0.0.5/go.mod h1:sdofYYqE0D79zNZyB4/kmlnsQOVap1C2yByjGKSirEM= github.com/dolthub/go-icu-regex v0.0.0-20260610153742-72563bc7ca83 h1:FEMjCGEroDnY/BXyAffVZxUpXhP2GpoUJyyq5KaLn8c= github.com/dolthub/go-icu-regex v0.0.0-20260610153742-72563bc7ca83/go.mod h1:F3cnm+vMRK1HaU6+rNqQrOCyR03HHhR1GWG2gnPOqaE= -github.com/dolthub/go-mysql-server v0.20.1-0.20260717163909-4add05e419ac h1:Bird54IhXCOIg+OlBnaJ/v7zrdAvHiTuXXhMhp+GSxM= -github.com/dolthub/go-mysql-server v0.20.1-0.20260717163909-4add05e419ac/go.mod h1:AnD2jKQZCf09sM2JhV/cTEtsoEhPNg6pVWjRCBox4Zw= +github.com/dolthub/go-mysql-server v0.20.1-0.20260717195005-e77c2d65f1a5 h1:4kF8QNxodC25HuY+XpcMyv1aQ9bngSlQxCO7SPK9oo4= +github.com/dolthub/go-mysql-server v0.20.1-0.20260717195005-e77c2d65f1a5/go.mod h1:AnD2jKQZCf09sM2JhV/cTEtsoEhPNg6pVWjRCBox4Zw= github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63 h1:OAsXLAPL4du6tfbBgK0xXHZkOlos63RdKYS3Sgw/dfI= github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63/go.mod h1:lV7lUeuDhH5thVGDCKXbatwKy2KW80L4rMT46n+Y2/Q= github.com/dolthub/ishell v0.0.0-20260414231531-5f031e3e9037 h1:oIW9HwuWrhxv+4HZxA+QQSKHLqWFyXZ2FmNjUYwkdiM= diff --git a/integration-tests/go-sql-server-driver/driver/cmd.go b/integration-tests/go-sql-server-driver/driver/cmd.go index 4e448c3596..636498660e 100644 --- a/integration-tests/go-sql-server-driver/driver/cmd.go +++ b/integration-tests/go-sql-server-driver/driver/cmd.go @@ -41,7 +41,12 @@ var DelvePath string const TestUserName = "Bats Tests" const TestEmailAddress = "bats@email.fake" -const ConnectAttempts = 50 +// ConnectAttempts and RetrySleepDuration are used by the test driver to retry connections to a doltgres server +// that is starting up. Besides server startup, the retries must also absorb cluster replication being +// eventually consistent: connections default to the "postgres" database, and on a freshly-booted standby +// that database only exists once its first push from the primary lands (which on a slow CI host can take +// many seconds of first-contact setup). ~30 seconds, matching statusEventualConsistencyAttempts. +const ConnectAttempts = 600 const RetrySleepDuration = 50 * time.Millisecond // EnvDoltgresBinPath is the environment variable used to locate the doltgres diff --git a/integration-tests/go-sql-server-driver/testdef.go b/integration-tests/go-sql-server-driver/testdef.go index c460a39b9c..35a1a81dfd 100644 --- a/integration-tests/go-sql-server-driver/testdef.go +++ b/integration-tests/go-sql-server-driver/testdef.go @@ -22,6 +22,7 @@ import ( "io" "os" "path/filepath" + "strings" "sync" "testing" "text/template" @@ -59,7 +60,11 @@ type Test struct { // Set this environment variable to effectively disable timeouts for debugging. const debugEnvKey = "DOLTGRES_SQL_SERVER_TEST_DEBUG" -var timeout = 20 * time.Second +// timeout bounds each individual query in a test. It must comfortably exceed the +// dolt_cluster_ack_writes_timeout_secs values used in the cluster replication tests (60s): a write on the +// primary legitimately blocks for up to that long waiting for standbys to acknowledge replication, e.g. +// when a standby is slow to complete first contact (jwks setup plus create-on-push) on a loaded CI host. +var timeout = 90 * time.Second func init() { _, ok := os.LookupEnv(debugEnvKey) @@ -629,12 +634,33 @@ func RetryTestRun(t *testing.T, attempts int, test func(TestingT)) { rtt.try(attempts, test) } +// statusEventualConsistencyAttempts bounds how long RunQuery waits for a cluster +// replication status query to converge to its expected result. +// Retries are quite forgiving in these tests because the default `postgres` database in most of these tests is never +// written to, which means it get replicated async by a background thread, rather than by a user write. +// At driver.RetrySleepDuration (50ms) per attempt this is roughly 30 seconds, which also makes status +// queries usable as explicit quiesce barriers before graceful role transitions (which have a fixed +// 10-second internal budget for standbys to catch up). +const statusEventualConsistencyAttempts = 600 + func RunQuery(t *testing.T, conn *sql.Conn, q driver.Query, ports *DynamicResources) { - RetryTestRun(t, q.RetryAttempts, func(t TestingT) { + attempts := q.RetryAttempts + // Replication status is eventually consistent, so status queries always get at + // least the generous eventual-consistency budget, even if the test set a smaller + // (or no) retry_attempts. + if observesReplicationStatus(q) && attempts < statusEventualConsistencyAttempts { + attempts = statusEventualConsistencyAttempts + } + RetryTestRun(t, attempts, func(t TestingT) { RunQueryAttempt(t, conn, q, ports) }) } +// observesReplicationStatus reports whether the query reads the dolt_cluster_status system table +func observesReplicationStatus(q driver.Query) bool { + return q.Query != "" && strings.Contains(strings.ToLower(q.Query), "dolt_cluster_status") +} + func RunQueryAttempt(t TestingT, conn *sql.Conn, q driver.Query, ports *DynamicResources) { args := make([]any, len(q.Args)) for i := range q.Args { diff --git a/integration-tests/go-sql-server-driver/tests/sql-server-cluster.yaml b/integration-tests/go-sql-server-driver/tests/sql-server-cluster.yaml index e2e62c782c..963f5627ab 100644 --- a/integration-tests/go-sql-server-driver/tests/sql-server-cluster.yaml +++ b/integration-tests/go-sql-server-driver/tests/sql-server-cluster.yaml @@ -925,6 +925,14 @@ tests: - exec: "use repo1" - exec: 'create table vals (i int primary key)' - exec: 'insert into vals values (0),(1),(2),(3),(4)' + # Wait for replication to quiesce (including the async first push of the auto-created postgres + # database) before the graceful transition below, which has a fixed 10-second budget for standbys + # to catch up. Status queries are auto-retried by the test runner. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] # Dolt asserts the procedure's per-database caught_up rows here. Doltgres exposes Dolt procedures as # functions returning a single text-array value (first row only), which can't express that result, so # assert the outcome instead: the transition succeeds and the server ends up a standby. @@ -976,10 +984,21 @@ tests: - exec: "use repo1" - exec: 'create table vals (i int primary key)' - exec: 'insert into vals values (0),(1),(2),(3),(4)' + # Wait for replication to quiesce before the graceful transition below, so that its failure + # is deterministically about the standby count rather than about in-flight replication. + # Status queries are auto-retried by the test runner. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] # Unlike Dolt, the database count includes the auto-created default database "postgres" - # (postgres, repo1, mysql users/grants, dolt_branch_control). + # (postgres, repo1, mysql users/grants, dolt_branch_control). The trailing "Only caught up N + # standbys fully" count is not asserted: it depends on whether the single configured standby's + # users/grants and branch-control replication have completed first contact, which is timing + # dependent on slow CI hosts. - query: "select dolt_cluster_transition_to_standby('2', '2')" - error_match: failed to transition from primary to standby gracefully; could not ensure 2 replicas were caught up on all 4 databases. Only caught up 1 standbys fully. + error_match: failed to transition from primary to standby gracefully; could not ensure 2 replicas were caught up on all 4 databases. - name: create new database, primary replicates to standby, fails over, new primary replicates to standby, fails over, new primary has all writes multi_repos: - name: server1 @@ -1021,6 +1040,14 @@ tests: - exec: 'use repo1' - exec: 'create table vals (i int primary key)' - exec: 'insert into vals values (0),(1),(2),(3),(4)' + # Wait for replication to quiesce (including the async first push of the auto-created postgres + # database) before the graceful transition below, which has a fixed 10-second budget for standbys + # to catch up. Status queries are auto-retried by the test runner. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] - query: "select dolt_assume_cluster_role('standby', 2)" result: columns: ["dolt_assume_cluster_role"] @@ -1040,6 +1067,14 @@ tests: queries: - exec: 'use repo1' - exec: 'insert into vals values (5),(6),(7),(8),(9)' + # Wait for replication to quiesce before the graceful transition below; promotion reset each + # database's replication progress, and the auto-created postgres database has no synchronous + # ack to wait on. Status queries are auto-retried by the test runner. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] - query: "select dolt_assume_cluster_role('standby', 3)" result: columns: ["dolt_assume_cluster_role"] @@ -1101,6 +1136,14 @@ tests: - on: server1 queries: - exec: 'create database repo1' + # Wait for replication to quiesce (including the async first push of the auto-created postgres + # database) before the graceful transition below, which has a fixed 10-second budget for standbys + # to catch up. Status queries are auto-retried by the test runner. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] - query: "select dolt_assume_cluster_role('standby', 2)" result: columns: ["dolt_assume_cluster_role"] @@ -1171,6 +1214,14 @@ tests: - exec: 'select dolt_remote(''add'', ''origin'', ''file://my_remote/'')' - exec: 'select dolt_push(''origin'', ''main:main'')' - exec: 'select dolt_clone(''file://repo1/my_remote'', ''repo_cloned'')' + # Wait for replication to quiesce (including the async first pushes of the auto-created postgres + # database and the just-cloned repo_cloned database) before the graceful transition below, which + # has a fixed 10-second budget for standbys to catch up. Status queries are auto-retried. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] - query: "select dolt_assume_cluster_role('standby', 2)" result: columns: ["dolt_assume_cluster_role"] @@ -1227,7 +1278,7 @@ tests: connections: - on: server1 queries: - - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 10' + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' - exec: 'CREATE DATABASE repo1' - exec: 'USE repo1' - exec: 'CREATE TABLE vals (i INT PRIMARY KEY)' @@ -1539,7 +1590,7 @@ tests: connections: - on: server1 queries: - - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 10' + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' - exec: 'create database repo1' - exec: 'use repo1' - exec: 'create table vals (i int primary key)' @@ -1631,7 +1682,7 @@ tests: connections: - on: server1 queries: - - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 10' + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' - exec: 'create database repo1' - exec: 'create database droppedrepo' - exec: 'use droppedrepo' @@ -1694,7 +1745,7 @@ tests: connections: - on: server1 queries: - - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 10' + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' - exec: 'create database repo1' - exec: 'use repo1' - exec: 'create table vals (a int)' @@ -1797,12 +1848,26 @@ tests: rows: - ["dolt_cluster"] - ["postgres"] + # Wait for replication to quiesce (the auto-created postgres database's first push is async) + # before the graceful transition below, which has a fixed 10-second budget for standbys to + # catch up. Status queries are auto-retried by the test runner. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] - exec: "select dolt_cluster_transition_to_standby('2', '1')" - on: server2 queries: - exec: "select dolt_assume_cluster_role('primary', '2')" - on: server2 queries: + # As above: promotion reset the new primary's replication progress. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] - exec: "select dolt_cluster_transition_to_standby('3', '1')" - on: server1 queries: @@ -1874,7 +1939,7 @@ tests: connections: - on: server1 queries: - - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 10' + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' - exec: "create database testdb" - exec: "use testdb" - exec: "create table vals (id int primary key)" @@ -1889,7 +1954,7 @@ tests: columns: ["count"] rows: - ["2"] - - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 10' + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' - exec: "insert into vals values (0),(1),(2)" - on: standby_two queries: @@ -1938,7 +2003,7 @@ tests: queries: - exec: 'CREATE DATABASE repo1' - exec: 'USE repo1' - - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 10' + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' - exec: 'CREATE TABLE t (id SERIAL PRIMARY KEY, v INT)' - exec: 'INSERT INTO t(v) VALUES (0),(0),(0),(0),(0)' - on: server2 @@ -1951,6 +2016,14 @@ tests: retry_attempts: 100 - on: server1 queries: + # Wait for replication to quiesce (including the async first push of the auto-created postgres + # database) before the graceful transition below, which has a fixed 10-second budget for standbys + # to catch up. Status queries are auto-retried by the test runner. + - exec: 'use dolt_cluster' + - query: "select count(*) as not_caught_up from dolt_cluster_status where replication_lag_millis is null or replication_lag_millis <> 0" + result: + columns: ["not_caught_up"] + rows: [["0"]] - exec: "select dolt_assume_cluster_role('standby', 2)" - on: server2 queries: @@ -2187,3 +2260,274 @@ tests: result: columns: ['read_only'] rows: [['1']] +# The tests below exercise cluster replication of the auto-created "postgres" +# default database directly (the only database many customers will ever create) +- name: postgres default database replicates to standby + multi_repos: + - name: server1 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server2_cluster"}}/{database} + bootstrap_role: primary + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server1_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server1 + - name: server2 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server1_cluster"}}/{database} + bootstrap_role: standby + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server2_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server2 + connections: + - on: server1 + queries: + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' + - exec: 'CREATE TABLE vals (i INT PRIMARY KEY)' + - exec: 'INSERT INTO vals VALUES (0),(1),(2),(3),(4)' + - on: server2 + queries: + - query: 'SELECT count(*) FROM vals' + result: + columns: ["count"] + rows: [["5"]] +- name: postgres default database on booted standby is read only + multi_repos: + - name: server1 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server2_cluster"}}/{database} + bootstrap_role: primary + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server1_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server1 + - name: server2 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server1_cluster"}}/{database} + bootstrap_role: standby + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server2_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server2 + connections: + - on: server1 + queries: + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' + - exec: 'CREATE TABLE vals (i INT PRIMARY KEY)' + - exec: 'INSERT INTO vals VALUES (0),(1),(2)' + - on: server2 + queries: + - query: 'SELECT count(*) FROM vals' + result: + columns: ["count"] + rows: [["3"]] + - exec: 'INSERT INTO vals VALUES (3)' + error_match: "postgres is read-only" +- name: postgres default database survives failover + multi_repos: + - name: server1 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server2_cluster"}}/{database} + bootstrap_role: primary + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server1_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server1 + - name: server2 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server1_cluster"}}/{database} + bootstrap_role: standby + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server2_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server2 + connections: + - on: server1 + queries: + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' + - exec: 'CREATE TABLE vals (i INT PRIMARY KEY)' + - exec: 'INSERT INTO vals VALUES (1),(2),(3)' + # Fail over gracefully: demote the primary first, then promote the standby. + - on: server1 + queries: + - query: "select dolt_assume_cluster_role('standby', 2)" + result: + columns: ["dolt_assume_cluster_role"] + rows: [["{0}"]] + - on: server2 + queries: + - query: "select dolt_assume_cluster_role('primary', 2)" + result: + columns: ["dolt_assume_cluster_role"] + rows: [["{0}"]] + - on: server2 + queries: + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' + - query: 'SELECT count(*) FROM vals' + result: + columns: ["count"] + rows: [["3"]] + - exec: 'INSERT INTO vals VALUES (4),(5)' + - on: server1 + queries: + - query: 'SELECT count(*) FROM vals' + result: + columns: ["count"] + rows: [["5"]] +- name: postgres default database replication status + multi_repos: + - name: server1 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server2_cluster"}}/{database} + bootstrap_role: primary + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server1_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server1 + - name: server2 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server1_cluster"}}/{database} + bootstrap_role: standby + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server2_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server2 + connections: + - on: server1 + queries: + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' + - exec: 'CREATE TABLE vals (i INT PRIMARY KEY)' + - exec: 'INSERT INTO vals VALUES (0),(1),(2)' + - on: server1 + database: dolt_cluster + queries: + - query: "select \"database\", standby_remote, role, epoch, replication_lag_millis, current_error from dolt_cluster_status order by \"database\" asc" + result: + columns: ["database","standby_remote","role","epoch","replication_lag_millis","current_error"] + rows: + - ["postgres","standby","primary","1","0","NULL"] +- name: postgres default database sequence val maintained across failover + multi_repos: + - name: server1 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server2_cluster"}}/{database} + bootstrap_role: primary + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server1_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server1 + - name: server2 + with_files: + - name: server.yaml + contents: | + log_level: trace + cluster: + standby_remotes: + - name: standby + remote_url_template: http://localhost:{{get_port "server1_cluster"}}/{database} + bootstrap_role: standby + bootstrap_epoch: 1 + remotesapi: + port: {{get_port "server2_cluster"}} + server: + args: ["--config", "server.yaml"] + dynamic_port: server2 + connections: + - on: server1 + queries: + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' + - exec: 'CREATE TABLE t (id SERIAL PRIMARY KEY, v INT)' + - exec: 'INSERT INTO t(v) VALUES (0),(0),(0),(0),(0)' + - on: server2 + queries: + - query: 'SELECT * FROM t ORDER BY id' + result: + columns: ['id', 'v'] + rows: [['1','0'], ['2','0'], ['3','0'], ['4','0'], ['5','0']] + - on: server1 + queries: + - exec: "select dolt_assume_cluster_role('standby', 2)" + - on: server2 + queries: + - exec: "select dolt_assume_cluster_role('primary', 2)" + - on: server2 + queries: + - exec: 'SET dolt_cluster_ack_writes_timeout_secs = 60' + - exec: 'INSERT INTO t(v) VALUES (1)' + - query: 'SELECT * FROM t ORDER BY id' + result: + columns: ['id', 'v'] + rows: [['1','0'], ['2','0'], ['3','0'], ['4','0'], ['5','0'], ['6','1']] diff --git a/server/connection_handler.go b/server/connection_handler.go index b571e07287..27dcd9a028 100644 --- a/server/connection_handler.go +++ b/server/connection_handler.go @@ -307,10 +307,12 @@ func (h *ConnectionHandler) chooseInitialParameters(startupMessage *pgproto3.Sta } } } - // set initial database + // Set the initial database. Postgres has no concept of a session without a current database: if the client + // doesn't specify one, it defaults to the username (matching libpq). Either way, if the resolved database + // doesn't exist we must reject the connection rather than proceed with a database-less session, which would + // break assumptions throughout the engine. db, ok := startupMessage.Parameters["database"] - dbSpecified := ok && len(db) > 0 - if !dbSpecified { + if !ok || len(db) == 0 { db = h.mysqlConn.User } useStmt := fmt.Sprintf("SET database TO '%s';", db) @@ -322,13 +324,11 @@ func (h *ConnectionHandler) chooseInitialParameters(startupMessage *pgproto3.Sta err = h.doltgresHandler.ComQuery(context.Background(), h.mysqlConn, useStmt, parsed, func(_ *sql.Context, _ *Result) error { return nil }) - // If a database isn't specified, then we attempt to connect to a database with the same name as the user, - // ignoring any error - if err != nil && dbSpecified { + if err != nil { _ = h.send(&pgproto3.ErrorResponse{ Severity: string(ErrorResponseSeverity_Fatal), Code: "3D000", - Message: fmt.Sprintf(`"database "%s" does not exist"`, db), + Message: fmt.Sprintf(`database "%s" does not exist`, db), Routine: "InitPostgres", }) return err