diff --git a/go/db/generate_patches.go b/go/db/generate_patches.go index e1c19817..7d1945b5 100644 --- a/go/db/generate_patches.go +++ b/go/db/generate_patches.go @@ -617,4 +617,9 @@ var generateSQLPatches = []string{ database_instance ADD COLUMN replication_group_primary_port smallint(5) unsigned NOT NULL DEFAULT 0 AFTER replication_group_primary_host `, + ` + ALTER TABLE + database_instance + ADD COLUMN provider_type varchar(20) CHARACTER SET ascii NOT NULL DEFAULT 'mysql' AFTER replication_group_primary_port + `, } diff --git a/go/inst/analysis_dao.go b/go/inst/analysis_dao.go index 02841d0f..444db3d5 100644 --- a/go/inst/analysis_dao.go +++ b/go/inst/analysis_dao.go @@ -51,12 +51,54 @@ func initializeAnalysisDaoPostConfiguration() { recentInstantAnalysis = cache.New(time.Duration(config.RecoveryPollSeconds*2)*time.Second, time.Second) } +// hasPostgreSQLInstances checks whether the orchestrator backend has any +// instances with provider_type='postgresql'. This is used to decide whether +// PostgreSQL analysis should be run alongside MySQL analysis. +func hasPostgreSQLInstances(clusterName string) bool { + query := `SELECT 1 FROM database_instance WHERE provider_type = 'postgresql'` + args := sqlutils.Args() + if clusterName != "" { + query += ` AND cluster_name = ?` + args = append(args, clusterName) + } + query += ` LIMIT 1` + found := false + _ = db.QueryOrchestrator(query, args, func(m sqlutils.RowMap) error { + found = true + return nil + }) + return found +} + +// hasMySQLInstances checks whether the orchestrator backend has any +// instances with provider_type='mysql' (or empty, for backward compat). +func hasMySQLInstances(clusterName string) bool { + query := `SELECT 1 FROM database_instance WHERE provider_type IN ('mysql', '')` + args := sqlutils.Args() + if clusterName != "" { + query += ` AND cluster_name = ?` + args = append(args, clusterName) + } + query += ` LIMIT 1` + found := false + _ = db.QueryOrchestrator(query, args, func(m sqlutils.RowMap) error { + found = true + return nil + }) + return found +} + // GetReplicationAnalysis will check for replication problems (dead master; unreachable master; etc) func GetReplicationAnalysis(clusterName string, hints *ReplicationAnalysisHints) ([]ReplicationAnalysis, error) { - if config.Config.ProviderType == "postgresql" { + // When the global config is set to "postgresql" and there are no MySQL instances, + // use the pure PG path (backward compatibility). + if config.Config.ProviderType == "postgresql" && !hasMySQLInstances(clusterName) { return GetPostgreSQLReplicationAnalysis(clusterName, hints) } + // In multi-cluster mode, run both MySQL and PG analysis and merge results. + // First run MySQL analysis (below), then append PG results if any PG instances exist. + result := []ReplicationAnalysis{} args := sqlutils.Args(config.Config.ReasonableLockedSemiSyncMasterSeconds, ValidSecondsFromSeenToLastAttemptedCheck(), config.Config.ReasonableReplicationLagSeconds, clusterName) @@ -738,6 +780,17 @@ func GetReplicationAnalysis(clusterName string, hints *ReplicationAnalysisHints) return result, log.Errore(err) } // TODO: result, err = getConcensusReplicationAnalysis(result) + + // In multi-cluster mode, also run PostgreSQL analysis and merge results + if hasPostgreSQLInstances(clusterName) { + pgResult, pgErr := GetPostgreSQLReplicationAnalysis(clusterName, hints) + if pgErr != nil { + log.Errore(pgErr) + } else { + result = append(result, pgResult...) + } + } + return result, log.Errore(err) } diff --git a/go/inst/instance.go b/go/inst/instance.go index ee3839c1..8fc5d1a9 100644 --- a/go/inst/instance.go +++ b/go/inst/instance.go @@ -152,6 +152,9 @@ type Instance struct { // Query string provider QSP QueryStringProvider + + // ProviderType indicates the database engine: "mysql" (default) or "postgresql" + ProviderType string } // NewInstance creates a new, empty instance diff --git a/go/inst/instance_dao.go b/go/inst/instance_dao.go index 227bb7a0..9e5c836c 100644 --- a/go/inst/instance_dao.go +++ b/go/inst/instance_dao.go @@ -60,6 +60,32 @@ const ( var instanceReadChan = make(chan bool, backendDBConcurrency) var instanceWriteChan = make(chan bool, backendDBConcurrency) +// detectProviderType determines whether an instance is MySQL or PostgreSQL. +// It checks (in order): +// 1. The orchestrator backend DB for a previously stored provider_type +// 2. Port-based heuristic (5432 -> postgresql) +// 3. The global config.Config.ProviderType as fallback (backward compat) +func detectProviderType(instanceKey *InstanceKey) string { + // Check if we already know this instance's provider type from the backend DB + query := `SELECT provider_type FROM database_instance WHERE hostname = ? AND port = ?` + var providerType string + err := db.QueryOrchestrator(query, sqlutils.Args(instanceKey.Hostname, instanceKey.Port), func(m sqlutils.RowMap) error { + providerType = m.GetString("provider_type") + return nil + }) + if err == nil && providerType != "" { + return providerType + } + + // Port-based heuristic for new instances + if instanceKey.Port == 5432 { + return "postgresql" + } + + // Fall back to global config (backward compatibility) + return config.Config.ProviderType +} + // InstancesByCountReplicas is a sortable type for Instance type InstancesByCountReplicas [](*Instance) @@ -226,7 +252,8 @@ func logReadTopologyInstanceError(instanceKey *InstanceKey, hint string, err err // server and writes the result synchronously to the orchestrator // backend. func ReadTopologyInstance(instanceKey *InstanceKey) (*Instance, error) { - if config.Config.ProviderType == "postgresql" { + providerType := detectProviderType(instanceKey) + if providerType == "postgresql" { inst, err := ReadPostgreSQLTopologyInstance(instanceKey) if err != nil { // Mark the instance as checked-but-not-seen so analysis detects it as dead @@ -360,7 +387,7 @@ func expectReplicationThreadsState(instance *Instance, instanceKey *InstanceKey, // - timing information can be collected for the stages performed. func ReadTopologyInstanceBufferable(instanceKey *InstanceKey, bufferWrites bool, latency *stopwatch.NamedStopwatch) (inst *Instance, skipped bool, err error) { // PostgreSQL path: delegate to PG-specific discovery and skip MySQL logic entirely - if config.Config.ProviderType == "postgresql" { + if detectProviderType(instanceKey) == "postgresql" { if latency != nil { latency.Start("instance") } @@ -1222,7 +1249,7 @@ func ReadReplicationGroupPrimary(instance *Instance) (err error) { func ReadInstanceClusterAttributes(instance *Instance) (err error) { // PostgreSQL provider sets ClusterName directly during discovery. // Skip the master-lookup logic which is MySQL-specific. - if config.Config.ProviderType == "postgresql" { + if instance.ProviderType == "postgresql" || detectProviderType(&instance.Key) == "postgresql" { return nil } var masterOrGroupPrimaryInstanceKey InstanceKey @@ -1462,6 +1489,10 @@ func readInstanceRow(m sqlutils.RowMap) *Instance { Port: m.GetInt("replication_group_primary_port")} _ = instance.ReplicationGroupMembers.ReadJson(m.GetString("replication_group_members")) //instance.ReplicationGroup = m.GetString("replication_group_") + instance.ProviderType = m.GetString("provider_type") + if instance.ProviderType == "" { + instance.ProviderType = "mysql" + } // problems if !instance.IsLastCheckValid { @@ -2780,6 +2811,7 @@ func mkInsertOdkuForInstances(instances []*Instance, instanceWasActuallyFound bo "replication_group_members", "replication_group_primary_host", "replication_group_primary_port", + "provider_type", } var values = make([]string, len(columns)) @@ -2873,6 +2905,11 @@ func mkInsertOdkuForInstances(instances []*Instance, instanceWasActuallyFound bo args = append(args, instance.ReplicationGroupMembers.ToJSONString()) args = append(args, instance.ReplicationGroupPrimaryInstanceKey.Hostname) args = append(args, instance.ReplicationGroupPrimaryInstanceKey.Port) + providerType := instance.ProviderType + if providerType == "" { + providerType = "mysql" + } + args = append(args, providerType) } sql, err := mkInsertOdku("database_instance", columns, values, len(instances), insertIgnore) diff --git a/go/inst/instance_dao_postgresql.go b/go/inst/instance_dao_postgresql.go index f5796169..0bd3be5e 100644 --- a/go/inst/instance_dao_postgresql.go +++ b/go/inst/instance_dao_postgresql.go @@ -57,6 +57,7 @@ func ReadPostgreSQLTopologyInstance(instanceKey *InstanceKey) (*Instance, error) } instance.Version = parsePostgreSQLVersion(version) instance.FlavorName = "PostgreSQL" + instance.ProviderType = "postgresql" // Check whether this instance is in recovery (standby) or is a primary var inRecovery bool diff --git a/go/inst/instance_dao_test.go b/go/inst/instance_dao_test.go index 1acfa4d9..cc1593f1 100644 --- a/go/inst/instance_dao_test.go +++ b/go/inst/instance_dao_test.go @@ -60,17 +60,17 @@ func TestMkInsertOdkuSingle(t *testing.T) { version, major_version, version_comment, binlog_server, read_only, binlog_format, binlog_row_image, log_bin, log_slave_updates, binary_log_file, binary_log_pos, master_host, master_port, slave_sql_running, slave_io_running, replication_sql_thread_state, replication_io_thread_state, has_replication_filters, supports_oracle_gtid, oracle_gtid, master_uuid, ancestry_uuid, executed_gtid_set, gtid_mode, gtid_purged, gtid_errant, mariadb_gtid, pseudo_gtid, - master_log_file, read_master_log_pos, relay_master_log_file, exec_master_log_pos, relay_log_file, relay_log_pos, last_sql_error, last_io_error, seconds_behind_master, slave_lag_seconds, sql_delay, num_slave_hosts, slave_hosts, cluster_name, suggested_cluster_alias, data_center, region, physical_environment, replication_depth, is_co_master, replication_credentials_available, has_replication_credentials, allow_tls, semi_sync_enforced, semi_sync_available, semi_sync_master_enabled, semi_sync_master_timeout, semi_sync_master_wait_for_slave_count, semi_sync_replica_enabled, semi_sync_master_status, semi_sync_master_clients, semi_sync_replica_status, instance_alias, last_discovery_latency, replication_group_name, replication_group_is_single_primary_mode, replication_group_member_state, replication_group_member_role, replication_group_members, replication_group_primary_host, replication_group_primary_port, last_seen) + master_log_file, read_master_log_pos, relay_master_log_file, exec_master_log_pos, relay_log_file, relay_log_pos, last_sql_error, last_io_error, seconds_behind_master, slave_lag_seconds, sql_delay, num_slave_hosts, slave_hosts, cluster_name, suggested_cluster_alias, data_center, region, physical_environment, replication_depth, is_co_master, replication_credentials_available, has_replication_credentials, allow_tls, semi_sync_enforced, semi_sync_available, semi_sync_master_enabled, semi_sync_master_timeout, semi_sync_master_wait_for_slave_count, semi_sync_replica_enabled, semi_sync_master_status, semi_sync_master_clients, semi_sync_replica_status, instance_alias, last_discovery_latency, replication_group_name, replication_group_is_single_primary_mode, replication_group_member_state, replication_group_member_role, replication_group_members, replication_group_primary_host, replication_group_primary_port, provider_type, last_seen) VALUES - (?, ?, NOW(), NOW(), 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) + (?, ?, NOW(), NOW(), 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE hostname=VALUES(hostname), port=VALUES(port), last_checked=VALUES(last_checked), last_attempted_check=VALUES(last_attempted_check), last_check_partial_success=VALUES(last_check_partial_success), uptime=VALUES(uptime), server_id=VALUES(server_id), server_uuid=VALUES(server_uuid), version=VALUES(version), major_version=VALUES(major_version), version_comment=VALUES(version_comment), binlog_server=VALUES(binlog_server), read_only=VALUES(read_only), binlog_format=VALUES(binlog_format), binlog_row_image=VALUES(binlog_row_image), log_bin=VALUES(log_bin), log_slave_updates=VALUES(log_slave_updates), binary_log_file=VALUES(binary_log_file), binary_log_pos=VALUES(binary_log_pos), master_host=VALUES(master_host), master_port=VALUES(master_port), slave_sql_running=VALUES(slave_sql_running), slave_io_running=VALUES(slave_io_running), replication_sql_thread_state=VALUES(replication_sql_thread_state), replication_io_thread_state=VALUES(replication_io_thread_state), has_replication_filters=VALUES(has_replication_filters), supports_oracle_gtid=VALUES(supports_oracle_gtid), oracle_gtid=VALUES(oracle_gtid), master_uuid=VALUES(master_uuid), ancestry_uuid=VALUES(ancestry_uuid), executed_gtid_set=VALUES(executed_gtid_set), gtid_mode=VALUES(gtid_mode), gtid_purged=VALUES(gtid_purged), gtid_errant=VALUES(gtid_errant), mariadb_gtid=VALUES(mariadb_gtid), pseudo_gtid=VALUES(pseudo_gtid), master_log_file=VALUES(master_log_file), read_master_log_pos=VALUES(read_master_log_pos), relay_master_log_file=VALUES(relay_master_log_file), exec_master_log_pos=VALUES(exec_master_log_pos), relay_log_file=VALUES(relay_log_file), relay_log_pos=VALUES(relay_log_pos), last_sql_error=VALUES(last_sql_error), last_io_error=VALUES(last_io_error), seconds_behind_master=VALUES(seconds_behind_master), slave_lag_seconds=VALUES(slave_lag_seconds), sql_delay=VALUES(sql_delay), num_slave_hosts=VALUES(num_slave_hosts), slave_hosts=VALUES(slave_hosts), cluster_name=VALUES(cluster_name), suggested_cluster_alias=VALUES(suggested_cluster_alias), data_center=VALUES(data_center), region=VALUES(region), physical_environment=VALUES(physical_environment), replication_depth=VALUES(replication_depth), is_co_master=VALUES(is_co_master), replication_credentials_available=VALUES(replication_credentials_available), has_replication_credentials=VALUES(has_replication_credentials), allow_tls=VALUES(allow_tls), semi_sync_enforced=VALUES(semi_sync_enforced), semi_sync_available=VALUES(semi_sync_available), semi_sync_master_enabled=VALUES(semi_sync_master_enabled), semi_sync_master_timeout=VALUES(semi_sync_master_timeout), semi_sync_master_wait_for_slave_count=VALUES(semi_sync_master_wait_for_slave_count), semi_sync_replica_enabled=VALUES(semi_sync_replica_enabled), semi_sync_master_status=VALUES(semi_sync_master_status), semi_sync_master_clients=VALUES(semi_sync_master_clients), semi_sync_replica_status=VALUES(semi_sync_replica_status), - instance_alias=VALUES(instance_alias), last_discovery_latency=VALUES(last_discovery_latency), replication_group_name=VALUES(replication_group_name), replication_group_is_single_primary_mode=VALUES(replication_group_is_single_primary_mode), replication_group_member_state=VALUES(replication_group_member_state), replication_group_member_role=VALUES(replication_group_member_role), replication_group_members=VALUES(replication_group_members), replication_group_primary_host=VALUES(replication_group_primary_host), replication_group_primary_port=VALUES(replication_group_primary_port), last_seen=VALUES(last_seen) + instance_alias=VALUES(instance_alias), last_discovery_latency=VALUES(last_discovery_latency), replication_group_name=VALUES(replication_group_name), replication_group_is_single_primary_mode=VALUES(replication_group_is_single_primary_mode), replication_group_member_state=VALUES(replication_group_member_state), replication_group_member_role=VALUES(replication_group_member_role), replication_group_members=VALUES(replication_group_members), replication_group_primary_host=VALUES(replication_group_primary_host), replication_group_primary_port=VALUES(replication_group_primary_port), provider_type=VALUES(provider_type), last_seen=VALUES(last_seen) ` a1 := `i710, 3306, 0, 710, , 5.6.7, 5.6, MySQL, false, false, STATEMENT, FULL, false, false, , 0, , 0, - false, false, 0, 0, false, false, false, , , , , , , false, false, , 0, mysql.000007, 10, , 0, , , {0 false}, {0 false}, 0, 0, [], , , , , , 0, false, false, false, false, 0, false, false, 0, 0, false, false, 0, false, , 0, , false, , , [], , 0, ` + false, false, 0, 0, false, false, false, , , , , , , false, false, , 0, mysql.000007, 10, , 0, , , {0 false}, {0 false}, 0, 0, [], , , , , , 0, false, false, false, false, 0, false, false, 0, 0, false, false, 0, false, , 0, , false, , , [], , 0, mysql, ` sql1, args1, err := mkInsertOdkuForInstances(instances[:1], false, true) test.S(t).ExpectNil(err) @@ -84,21 +84,21 @@ func TestMkInsertOdkuThree(t *testing.T) { // three instances s3 := `INSERT INTO database_instance (hostname, port, last_checked, last_attempted_check, last_check_partial_success, uptime, server_id, server_uuid, version, major_version, version_comment, binlog_server, read_only, binlog_format, binlog_row_image, log_bin, log_slave_updates, binary_log_file, binary_log_pos, master_host, master_port, slave_sql_running, slave_io_running, replication_sql_thread_state, replication_io_thread_state, has_replication_filters, supports_oracle_gtid, oracle_gtid, master_uuid, ancestry_uuid, executed_gtid_set, gtid_mode, gtid_purged, gtid_errant, mariadb_gtid, pseudo_gtid, master_log_file, read_master_log_pos, relay_master_log_file, exec_master_log_pos, relay_log_file, relay_log_pos, last_sql_error, last_io_error, seconds_behind_master, slave_lag_seconds, sql_delay, num_slave_hosts, slave_hosts, cluster_name, suggested_cluster_alias, data_center, region, physical_environment, replication_depth, is_co_master, replication_credentials_available, has_replication_credentials, allow_tls, semi_sync_enforced, semi_sync_available, semi_sync_master_enabled, semi_sync_master_timeout, semi_sync_master_wait_for_slave_count, - semi_sync_replica_enabled, semi_sync_master_status, semi_sync_master_clients, semi_sync_replica_status, instance_alias, last_discovery_latency, replication_group_name, replication_group_is_single_primary_mode, replication_group_member_state, replication_group_member_role, replication_group_members, replication_group_primary_host, replication_group_primary_port, last_seen) + semi_sync_replica_enabled, semi_sync_master_status, semi_sync_master_clients, semi_sync_replica_status, instance_alias, last_discovery_latency, replication_group_name, replication_group_is_single_primary_mode, replication_group_member_state, replication_group_member_role, replication_group_members, replication_group_primary_host, replication_group_primary_port, provider_type, last_seen) VALUES - (?, ?, NOW(), NOW(), 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()), - (?, ?, NOW(), NOW(), 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()), - (?, ?, NOW(), NOW(), 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) + (?, ?, NOW(), NOW(), 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()), + (?, ?, NOW(), NOW(), 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()), + (?, ?, NOW(), NOW(), 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE hostname=VALUES(hostname), port=VALUES(port), last_checked=VALUES(last_checked), last_attempted_check=VALUES(last_attempted_check), last_check_partial_success=VALUES(last_check_partial_success), uptime=VALUES(uptime), server_id=VALUES(server_id), server_uuid=VALUES(server_uuid), version=VALUES(version), major_version=VALUES(major_version), version_comment=VALUES(version_comment), binlog_server=VALUES(binlog_server), read_only=VALUES(read_only), binlog_format=VALUES(binlog_format), binlog_row_image=VALUES(binlog_row_image), log_bin=VALUES(log_bin), log_slave_updates=VALUES(log_slave_updates), binary_log_file=VALUES(binary_log_file), binary_log_pos=VALUES(binary_log_pos), master_host=VALUES(master_host), master_port=VALUES(master_port), slave_sql_running=VALUES(slave_sql_running), slave_io_running=VALUES(slave_io_running), replication_sql_thread_state=VALUES(replication_sql_thread_state), replication_io_thread_state=VALUES(replication_io_thread_state), has_replication_filters=VALUES(has_replication_filters), supports_oracle_gtid=VALUES(supports_oracle_gtid), oracle_gtid=VALUES(oracle_gtid), master_uuid=VALUES(master_uuid), ancestry_uuid=VALUES(ancestry_uuid), executed_gtid_set=VALUES(executed_gtid_set), gtid_mode=VALUES(gtid_mode), gtid_purged=VALUES(gtid_purged), gtid_errant=VALUES(gtid_errant), mariadb_gtid=VALUES(mariadb_gtid), pseudo_gtid=VALUES(pseudo_gtid), master_log_file=VALUES(master_log_file), read_master_log_pos=VALUES(read_master_log_pos), relay_master_log_file=VALUES(relay_master_log_file), exec_master_log_pos=VALUES(exec_master_log_pos), relay_log_file=VALUES(relay_log_file), relay_log_pos=VALUES(relay_log_pos), last_sql_error=VALUES(last_sql_error), last_io_error=VALUES(last_io_error), seconds_behind_master=VALUES(seconds_behind_master), slave_lag_seconds=VALUES(slave_lag_seconds), sql_delay=VALUES(sql_delay), num_slave_hosts=VALUES(num_slave_hosts), slave_hosts=VALUES(slave_hosts), cluster_name=VALUES(cluster_name), suggested_cluster_alias=VALUES(suggested_cluster_alias), data_center=VALUES(data_center), region=VALUES(region), physical_environment=VALUES(physical_environment), replication_depth=VALUES(replication_depth), is_co_master=VALUES(is_co_master), replication_credentials_available=VALUES(replication_credentials_available), has_replication_credentials=VALUES(has_replication_credentials), allow_tls=VALUES(allow_tls), semi_sync_enforced=VALUES(semi_sync_enforced), semi_sync_available=VALUES(semi_sync_available), semi_sync_master_enabled=VALUES(semi_sync_master_enabled), semi_sync_master_timeout=VALUES(semi_sync_master_timeout), semi_sync_master_wait_for_slave_count=VALUES(semi_sync_master_wait_for_slave_count), semi_sync_replica_enabled=VALUES(semi_sync_replica_enabled), semi_sync_master_status=VALUES(semi_sync_master_status), semi_sync_master_clients=VALUES(semi_sync_master_clients), semi_sync_replica_status=VALUES(semi_sync_replica_status), - instance_alias=VALUES(instance_alias), last_discovery_latency=VALUES(last_discovery_latency), replication_group_name=VALUES(replication_group_name), replication_group_is_single_primary_mode=VALUES(replication_group_is_single_primary_mode), replication_group_member_state=VALUES(replication_group_member_state), replication_group_member_role=VALUES(replication_group_member_role), replication_group_members=VALUES(replication_group_members), replication_group_primary_host=VALUES(replication_group_primary_host), replication_group_primary_port=VALUES(replication_group_primary_port), last_seen=VALUES(last_seen) + instance_alias=VALUES(instance_alias), last_discovery_latency=VALUES(last_discovery_latency), replication_group_name=VALUES(replication_group_name), replication_group_is_single_primary_mode=VALUES(replication_group_is_single_primary_mode), replication_group_member_state=VALUES(replication_group_member_state), replication_group_member_role=VALUES(replication_group_member_role), replication_group_members=VALUES(replication_group_members), replication_group_primary_host=VALUES(replication_group_primary_host), replication_group_primary_port=VALUES(replication_group_primary_port), provider_type=VALUES(provider_type), last_seen=VALUES(last_seen) ` a3 := ` - i710, 3306, 0, 710, , 5.6.7, 5.6, MySQL, false, false, STATEMENT, FULL, false, false, , 0, , 0, false, false, 0, 0, false, false, false, , , , , , , false, false, , 0, mysql.000007, 10, , 0, , , {0 false}, {0 false}, 0, 0, [], , , , , , 0, false, false, false, false, 0, false, false, 0, 0, false, false, 0, false, , 0, , false, , , [], , 0, - i720, 3306, 0, 720, , 5.6.7, 5.6, MySQL, false, false, STATEMENT, FULL, false, false, , 0, , 0, false, false, 0, 0, false, false, false, , , , , , , false, false, , 0, mysql.000007, 20, , 0, , , {0 false}, {0 false}, 0, 0, [], , , , , , 0, false, false, false, false, 0, false, false, 0, 0, false, false, 0, false, , 0, , false, , , [], , 0, - i730, 3306, 0, 730, , 5.6.7, 5.6, MySQL, false, false, STATEMENT, FULL, false, false, , 0, , 0, false, false, 0, 0, false, false, false, , , , , , , false, false, , 0, mysql.000007, 30, , 0, , , {0 false}, {0 false}, 0, 0, [], , , , , , 0, false, false, false, false, 0, false, false, 0, 0, false, false, 0, false, , 0, , false, , , [], , 0, + i710, 3306, 0, 710, , 5.6.7, 5.6, MySQL, false, false, STATEMENT, FULL, false, false, , 0, , 0, false, false, 0, 0, false, false, false, , , , , , , false, false, , 0, mysql.000007, 10, , 0, , , {0 false}, {0 false}, 0, 0, [], , , , , , 0, false, false, false, false, 0, false, false, 0, 0, false, false, 0, false, , 0, , false, , , [], , 0, mysql, + i720, 3306, 0, 720, , 5.6.7, 5.6, MySQL, false, false, STATEMENT, FULL, false, false, , 0, , 0, false, false, 0, 0, false, false, false, , , , , , , false, false, , 0, mysql.000007, 20, , 0, , , {0 false}, {0 false}, 0, 0, [], , , , , , 0, false, false, false, false, 0, false, false, 0, 0, false, false, 0, false, , 0, , false, , , [], , 0, mysql, + i730, 3306, 0, 730, , 5.6.7, 5.6, MySQL, false, false, STATEMENT, FULL, false, false, , 0, , 0, false, false, 0, 0, false, false, false, , , , , , , false, false, , 0, mysql.000007, 30, , 0, , , {0 false}, {0 false}, 0, 0, [], , , , , , 0, false, false, false, false, 0, false, false, 0, 0, false, false, 0, false, , 0, , false, , , [], , 0, mysql, ` sql3, args3, err := mkInsertOdkuForInstances(instances[:3], true, true) @@ -107,6 +107,37 @@ func TestMkInsertOdkuThree(t *testing.T) { test.S(t).ExpectEquals(stripSpaces(fmtArgs(args3)), stripSpaces(a3)) } +// TestInstanceProviderTypeDefault verifies that NewInstance has empty ProviderType +// and that the write path defaults it to "mysql". +func TestInstanceProviderTypeDefault(t *testing.T) { + inst := NewInstance() + test.S(t).ExpectEquals(inst.ProviderType, "") +} + +// TestInstanceProviderTypePersistence verifies that provider_type is included in +// the insert/update SQL when set on an instance. +func TestInstanceProviderTypePersistence(t *testing.T) { + instances := mkTestInstances() + instances[0].ProviderType = "postgresql" + sql1, args1, err := mkInsertOdkuForInstances(instances[:1], false, true) + test.S(t).ExpectNil(err) + test.S(t).ExpectTrue(strings.Contains(sql1, "provider_type")) + // The provider_type value should appear in the args + argsStr := fmtArgs(args1) + test.S(t).ExpectTrue(strings.Contains(argsStr, "postgresql")) +} + +// TestInstanceProviderTypeDefaultsToMySQL verifies that empty ProviderType +// defaults to "mysql" in the write path. +func TestInstanceProviderTypeDefaultsToMySQL(t *testing.T) { + instances := mkTestInstances() + // ProviderType is empty (zero value) + _, args1, err := mkInsertOdkuForInstances(instances[:1], false, true) + test.S(t).ExpectNil(err) + argsStr := fmtArgs(args1) + test.S(t).ExpectTrue(strings.Contains(argsStr, "mysql")) +} + func fmtArgs(args []interface{}) string { b := &bytes.Buffer{} for _, a := range args {