From b59aad1939e474b45d49d81ae20a48f9bdca0dc0 Mon Sep 17 00:00:00 2001 From: elianddb Date: Wed, 5 Nov 2025 13:03:29 -0800 Subject: [PATCH] read_only always true on standby --- go/cmd/dolt/commands/engine/sqlengine.go | 16 +- go/cmd/dolt/commands/sqlserver/server.go | 7 +- .../tests/sql-server-cluster.yaml | 204 ++++++++++++++++++ 3 files changed, 221 insertions(+), 6 deletions(-) diff --git a/go/cmd/dolt/commands/engine/sqlengine.go b/go/cmd/dolt/commands/engine/sqlengine.go index 6a09dfdffce..80ee455df58 100644 --- a/go/cmd/dolt/commands/engine/sqlengine.go +++ b/go/cmd/dolt/commands/engine/sqlengine.go @@ -183,12 +183,18 @@ func NewSqlEngine( config.ClusterController.SetIsStandbyCallback(func(isStandby bool) { pro.SetIsStandby(isStandby) - // Standbys are read only, primaries are not. - // We only change this here if the server was not forced read - // only by its startup config. - if !config.IsReadOnly { - engine.ReadOnly.Store(isStandby) + // Standbys are read only, primaries respect the config. Update both engine.ReadOnly and the read_only system + // variable. + readOnly := isStandby || config.IsReadOnly + engine.ReadOnly.Store(readOnly) + + readOnlyInt := int8(0) + if readOnly { + readOnlyInt = 1 } + sql.SystemVariables.AssignValues(map[string]interface{}{ + "read_only": readOnlyInt, + }) }) // Load in privileges from file, if it exists diff --git a/go/cmd/dolt/commands/sqlserver/server.go b/go/cmd/dolt/commands/sqlserver/server.go index 9a39336ac19..036bfc386da 100644 --- a/go/cmd/dolt/commands/sqlserver/server.go +++ b/go/cmd/dolt/commands/sqlserver/server.go @@ -325,7 +325,12 @@ func ConfigureServices( return err } - // Set the read_only system variable based on the engine configuration + // Set the read_only system variable based on the engine configuration. If cluster replication is enabled, + // the read_only value is managed by the cluster role callback instead. + if config.ClusterController != nil { + return nil + } + sqlCtx := sql.NewEmptyContext() readOnlyValue := int8(0) if config.IsReadOnly { 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 b05632423fa..cbdd1e62652 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 @@ -2105,3 +2105,207 @@ tests: result: columns: ['id', 'v'] rows: [['1','0'], ['2','0'], ['3','0'], ['4','0'], ['5','0'], ['6','1']] +# https://github.com/dolthub/dolt/issues/10015 +- name: read_only system variable reflects cluster role + multi_repos: + - name: server1 + with_files: + - name: server.yaml + contents: | + log_level: trace + listener: + host: 0.0.0.0 + port: {{get_port "server1"}} + 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 + listener: + host: 0.0.0.0 + port: {{get_port "server2"}} + 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: 'CREATE DATABASE repo1' + - exec: 'USE repo1' + - exec: 'CREATE TABLE vals (i INT PRIMARY KEY)' + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['primary']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['0']] + - on: server2 + queries: + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['standby']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['1']] + - on: server1 + queries: + - exec: 'USE repo1' + - exec: "CALL dolt_assume_cluster_role('standby', 2)" + - on: server1 + queries: + - exec: 'USE repo1' + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['standby']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['1']] + - on: server2 + queries: + - exec: "CALL dolt_assume_cluster_role('primary', 2)" + - on: server2 + queries: + - exec: 'USE repo1' + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['primary']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['0']] + - on: server2 + queries: + - exec: 'USE repo1' + - exec: "CALL dolt_assume_cluster_role('standby', 3)" + - on: server1 + queries: + - exec: "CALL dolt_assume_cluster_role('primary', 3)" + - on: server1 + queries: + - exec: 'USE repo1' + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['primary']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['0']] +# https://github.com/dolthub/dolt/issues/10015 +- name: read_only system variable with behavior config respects cluster role + multi_repos: + - name: server1 + with_files: + - name: server.yaml + contents: | + log_level: trace + behavior: + read_only: true + listener: + host: 0.0.0.0 + port: {{get_port "server1"}} + 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 + behavior: + read_only: true + listener: + host: 0.0.0.0 + port: {{get_port "server2"}} + 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: + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['primary']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['1']] + - on: server2 + queries: + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['standby']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['1']] + - on: server1 + queries: + - exec: "CALL dolt_assume_cluster_role('standby', 2)" + - on: server1 + queries: + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['standby']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['1']] + - on: server2 + queries: + - exec: "CALL dolt_assume_cluster_role('primary', 2)" + - on: server2 + queries: + - query: 'SELECT @@GLOBAL.dolt_cluster_role' + result: + columns: ['@@GLOBAL.dolt_cluster_role'] + rows: [['primary']] + - query: 'SELECT @@GLOBAL.read_only' + result: + columns: ['@@GLOBAL.read_only'] + rows: [['1']]