From 2f0c1b5c32065a0c8c4ade9e2fabf15b81addb5f Mon Sep 17 00:00:00 2001 From: Dmitry Smal Date: Fri, 18 Jan 2019 18:18:48 +0300 Subject: [PATCH 1/3] Using net.JoinHostPort to format host:port strings IPv6 based addrs shoul look like [host]:port instead of host:port --- go/agent/agent_dao.go | 3 ++- go/app/cli.go | 5 +++-- go/db/db.go | 26 +++++++++++++------------- go/db/tls.go | 4 +++- go/http/agents_api.go | 3 ++- go/inst/candidate_database_instance.go | 4 +++- go/inst/instance_key.go | 3 ++- go/process/health_dao.go | 7 ++++--- go/raft/raft.go | 14 +++++++++----- 9 files changed, 41 insertions(+), 28 deletions(-) diff --git a/go/agent/agent_dao.go b/go/agent/agent_dao.go index c30a3d655..be8223aec 100644 --- a/go/agent/agent_dao.go +++ b/go/agent/agent_dao.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "net" "net/http" + "strconv" "strings" "sync" "time" @@ -298,7 +299,7 @@ func baseAgentUri(agentHostname string, agentPort int) string { if config.Config.AgentsUseSSL { protocol = "https" } - uri := fmt.Sprintf("%s://%s:%d/api", protocol, agentHostname, agentPort) + uri := fmt.Sprintf("%s://%s/api", protocol, net.JoinHostPort(agentHostname, strconv.Itoa(agentPort))) log.Debugf("orchestrator-agent uri: %s", uri) return uri } diff --git a/go/app/cli.go b/go/app/cli.go index 2f68f5ad6..d1ec86396 100644 --- a/go/app/cli.go +++ b/go/app/cli.go @@ -23,6 +23,7 @@ import ( "os/user" "regexp" "sort" + "strconv" "strings" "time" @@ -178,7 +179,7 @@ func Cli(command string, strict bool, instance string, destination string, owner } if destination != "" && !strings.Contains(destination, ":") { - destination = fmt.Sprintf("%s:%d", destination, config.Config.DefaultInstancePort) + destination = net.JoinHostPort(destination, strconv.Itoa(config.Config.DefaultInstancePort)) } destinationKey, err := inst.ParseResolveInstanceKey(destination) if err != nil { @@ -984,7 +985,7 @@ func Cli(command string, strict bool, instance string, destination string, owner log.Fatale(err) } for _, clusterPoolInstance := range clusterPoolInstances { - fmt.Println(fmt.Sprintf("%s\t%s\t%s\t%s:%d", clusterPoolInstance.ClusterName, clusterPoolInstance.ClusterAlias, clusterPoolInstance.Pool, clusterPoolInstance.Hostname, clusterPoolInstance.Port)) + fmt.Println(fmt.Sprintf("%s\t%s\t%s\t%s", clusterPoolInstance.ClusterName, clusterPoolInstance.ClusterAlias, clusterPoolInstance.Pool, net.JoinHostPort(clusterPoolInstance.Hostname, strconv.Itoa(clusterPoolInstance.Port)))) } } case registerCliCommand("which-heuristic-cluster-pool-instances", "Pools", `List instances of a given cluster which are in either any pool or in a specific pool`): diff --git a/go/db/db.go b/go/db/db.go index bac6758f8..af291bf06 100644 --- a/go/db/db.go +++ b/go/db/db.go @@ -19,6 +19,8 @@ package db import ( "database/sql" "fmt" + "net" + "strconv" "strings" "sync" "time" @@ -52,11 +54,10 @@ func getMySQLURI() string { if mysqlURI != "" { return mysqlURI } - mysqlURI := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?timeout=%ds&readTimeout=%ds&interpolateParams=true", + mysqlURI := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=%ds&readTimeout=%ds&interpolateParams=true", config.Config.MySQLOrchestratorUser, config.Config.MySQLOrchestratorPassword, - config.Config.MySQLOrchestratorHost, - config.Config.MySQLOrchestratorPort, + net.JoinHostPort(config.Config.MySQLOrchestratorHost, strconv.Itoa(int(config.Config.MySQLOrchestratorPort))), config.Config.MySQLOrchestratorDatabase, config.Config.MySQLConnectTimeoutSeconds, config.Config.MySQLOrchestratorReadTimeoutSeconds, @@ -80,10 +81,10 @@ func OpenTopology(host string, port int) (*sql.DB, error) { } func openTopology(host string, port int, readTimeout int) (db *sql.DB, err error) { - mysql_uri := fmt.Sprintf("%s:%s@tcp(%s:%d)/?timeout=%ds&readTimeout=%ds&interpolateParams=true", + mysql_uri := fmt.Sprintf("%s:%s@tcp(%s)/?timeout=%ds&readTimeout=%ds&interpolateParams=true", config.Config.MySQLTopologyUser, config.Config.MySQLTopologyPassword, - host, port, + net.JoinHostPort(host, strconv.Itoa(port)), config.Config.MySQLConnectTimeoutSeconds, readTimeout, ) @@ -106,11 +107,10 @@ func openTopology(host string, port int, readTimeout int) (db *sql.DB, err error } func openOrchestratorMySQLGeneric() (db *sql.DB, fromCache bool, err error) { - uri := fmt.Sprintf("%s:%s@tcp(%s:%d)/?timeout=%ds&readTimeout=%ds&interpolateParams=true", + uri := fmt.Sprintf("%s:%s@tcp(%s)/?timeout=%ds&readTimeout=%ds&interpolateParams=true", config.Config.MySQLOrchestratorUser, config.Config.MySQLOrchestratorPassword, - config.Config.MySQLOrchestratorHost, - config.Config.MySQLOrchestratorPort, + net.JoinHostPort(config.Config.MySQLOrchestratorHost, strconv.Itoa(int(config.Config.MySQLOrchestratorPort))), config.Config.MySQLConnectTimeoutSeconds, config.Config.MySQLOrchestratorReadTimeoutSeconds, ) @@ -151,8 +151,9 @@ func OpenOrchestrator() (db *sql.DB, err error) { db, fromCache, err = sqlutils.GetDB(getMySQLURI()) if err == nil && !fromCache { // do not show the password but do show what we connect to. - safeMySQLURI := fmt.Sprintf("%s:?@tcp(%s:%d)/%s?timeout=%ds", config.Config.MySQLOrchestratorUser, - config.Config.MySQLOrchestratorHost, config.Config.MySQLOrchestratorPort, config.Config.MySQLOrchestratorDatabase, config.Config.MySQLConnectTimeoutSeconds) + safeMySQLURI := fmt.Sprintf("%s:?@tcp(%s)/%s?timeout=%ds", config.Config.MySQLOrchestratorUser, + net.JoinHostPort(config.Config.MySQLOrchestratorHost, strconv.Itoa(int(config.Config.MySQLOrchestratorPort))), + config.Config.MySQLOrchestratorDatabase, config.Config.MySQLConnectTimeoutSeconds) log.Debugf("Connected to orchestrator backend: %v", safeMySQLURI) if config.Config.MySQLOrchestratorMaxPoolConnections > 0 { log.Debugf("Orchestrator pool SetMaxOpenConns: %d", config.Config.MySQLOrchestratorMaxPoolConnections) @@ -180,9 +181,8 @@ func OpenOrchestrator() (db *sql.DB, err error) { if maxIdleConns < 10 { maxIdleConns = 10 } - log.Infof("Connecting to backend %s:%d: maxConnections: %d, maxIdleConns: %d", - config.Config.MySQLOrchestratorHost, - config.Config.MySQLOrchestratorPort, + log.Infof("Connecting to backend %s: maxConnections: %d, maxIdleConns: %d", + net.JoinHostPort(config.Config.MySQLOrchestratorHost, strconv.Itoa(int(config.Config.MySQLOrchestratorPort))), config.Config.MySQLOrchestratorMaxPoolConnections, maxIdleConns) db.SetMaxIdleConns(maxIdleConns) diff --git a/go/db/tls.go b/go/db/tls.go index fd17e5a1f..99c6cb2d3 100644 --- a/go/db/tls.go +++ b/go/db/tls.go @@ -19,6 +19,8 @@ package db import ( "crypto/tls" "fmt" + "net" + "strconv" "strings" "time" @@ -56,7 +58,7 @@ func init() { } func requiresTLS(host string, port int, mysql_uri string) bool { - cacheKey := fmt.Sprintf("%s:%d", host, port) + cacheKey := net.JoinHostPort(host, strconv.Itoa(port)) if value, found := requireTLSCache.Get(cacheKey); found { readInstanceTLSCacheCounter.Inc(1) diff --git a/go/http/agents_api.go b/go/http/agents_api.go index 806ac4b5f..0b801ee27 100644 --- a/go/http/agents_api.go +++ b/go/http/agents_api.go @@ -18,6 +18,7 @@ package http import ( "fmt" + "net" "net/http" "strconv" "strings" @@ -102,7 +103,7 @@ func (this *HttpAgentsAPI) AgentsInstances(params martini.Params, r render.Rende agents, err := agent.ReadAgents() hostnames := []string{} for _, agent := range agents { - hostnames = append(hostnames, fmt.Sprintf("%s:%d", agent.Hostname, agent.MySQLPort)) + hostnames = append(hostnames, net.JoinHostPort(agent.Hostname, strconv.Itoa(int(agent.MySQLPort)))) } if err != nil { diff --git a/go/inst/candidate_database_instance.go b/go/inst/candidate_database_instance.go index 3bf192192..0ce98438d 100644 --- a/go/inst/candidate_database_instance.go +++ b/go/inst/candidate_database_instance.go @@ -18,6 +18,8 @@ package inst import ( "fmt" + "net" + "strconv" "github.com/github/orchestrator/go/db" ) @@ -45,7 +47,7 @@ func (cdi *CandidateDatabaseInstance) WithCurrentTime() *CandidateDatabaseInstan // String returns a string representation of the CandidateDatabaseInstance struct func (cdi *CandidateDatabaseInstance) String() string { - return fmt.Sprintf("%s:%d %s", cdi.Hostname, cdi.Port, cdi.PromotionRule) + return fmt.Sprintf("%s %s", net.JoinHostPort(cdi.Hostname, strconv.Itoa(cdi.Port)), cdi.PromotionRule) } // Key returns an instance key representing this candidate diff --git a/go/inst/instance_key.go b/go/inst/instance_key.go index 1041dcae4..d3d6f50ae 100644 --- a/go/inst/instance_key.go +++ b/go/inst/instance_key.go @@ -19,6 +19,7 @@ package inst import ( "fmt" "github.com/github/orchestrator/go/config" + "net" "regexp" "strconv" "strings" @@ -171,7 +172,7 @@ func (this *InstanceKey) ReattachedKey() *InstanceKey { // StringCode returns an official string representation of this key func (this *InstanceKey) StringCode() string { - return fmt.Sprintf("%s:%d", this.Hostname, this.Port) + return net.JoinHostPort(this.Hostname, strconv.Itoa(this.Port)) } // DisplayString returns a user-friendly string representation of this key diff --git a/go/process/health_dao.go b/go/process/health_dao.go index f6b596762..68c6715ac 100644 --- a/go/process/health_dao.go +++ b/go/process/health_dao.go @@ -17,9 +17,10 @@ package process import ( + "net" + "strconv" "time" - "fmt" "github.com/github/orchestrator/go/config" "github.com/github/orchestrator/go/db" "github.com/openark/golib/log" @@ -80,8 +81,8 @@ func WriteRegisterNode(nodeHealth *NodeHealth) (healthy bool, err error) { if config.Config.IsSQLite() { dbBackend = config.Config.SQLite3DataFile } else { - dbBackend = fmt.Sprintf("%s:%d", config.Config.MySQLOrchestratorHost, - config.Config.MySQLOrchestratorPort) + dbBackend = net.JoinHostPort(config.Config.MySQLOrchestratorHost, + strconv.Itoa(int(config.Config.MySQLOrchestratorPort))) } sqlResult, err := db.ExecOrchestrator(` insert ignore into node_health diff --git a/go/raft/raft.go b/go/raft/raft.go index 83c11768d..51db16788 100644 --- a/go/raft/raft.go +++ b/go/raft/raft.go @@ -21,6 +21,7 @@ import ( "fmt" "math/rand" "net" + "strconv" "strings" "sync" "sync/atomic" @@ -187,16 +188,19 @@ func normalizeRaftHostnameIP(host string) (string, error) { // normalizeRaftNode attempts to make sure there's a port to the given node. // It consults the DefaultRaftPort when there isn't func normalizeRaftNode(node string) (string, error) { - hostPort := strings.Split(node, ":") - host, err := normalizeRaftHostnameIP(hostPort[0]) + host, port, err := net.SplitHostPort(node) + if err != nil { + host = node + } + host, err = normalizeRaftHostnameIP(host) if err != nil { return host, err } - if len(hostPort) > 1 { - return fmt.Sprintf("%s:%s", host, hostPort[1]), nil + if port != "" { + return net.JoinHostPort(host, port), nil } else if config.Config.DefaultRaftPort != 0 { // No port specified, add one - return fmt.Sprintf("%s:%d", host, config.Config.DefaultRaftPort), nil + return net.JoinHostPort(host, strconv.Itoa(config.Config.DefaultRaftPort)), nil } else { return host, nil } From 449dbc58d2dbc00c133dde418acd6936ae5e1021 Mon Sep 17 00:00:00 2001 From: Dmitry Smal Date: Mon, 21 Jan 2019 18:06:40 +0300 Subject: [PATCH 2/3] IPv6 support in JS and SQL code --- RELEASE_VERSION | 2 +- go/app/cli.go | 6 +- go/http/httpbase.go | 3 +- go/inst/analysis_dao.go | 13 ++-- go/inst/instance_dao.go | 16 +++-- go/inst/maintenance_dao.go | 4 +- go/inst/pool_dao.go | 5 +- go/logic/topology_recovery_dao.go | 4 +- go/raft/raft.go | 8 +-- resources/public/js/agent.js | 2 +- .../public/js/audit-failure-detection.js | 2 +- resources/public/js/audit.js | 38 ++++++------ resources/public/js/cluster-pools.js | 2 +- resources/public/js/cluster.js | 62 +++++++++---------- resources/public/js/clusters-analysis.js | 4 +- resources/public/js/discover.js | 16 ++--- resources/public/js/orchestrator.js | 42 +++++++++---- 17 files changed, 132 insertions(+), 97 deletions(-) diff --git a/RELEASE_VERSION b/RELEASE_VERSION index eea30e595..9bbe65166 100644 --- a/RELEASE_VERSION +++ b/RELEASE_VERSION @@ -1 +1 @@ -3.0.13 +3.0.14 diff --git a/go/app/cli.go b/go/app/cli.go index d1ec86396..717304d64 100644 --- a/go/app/cli.go +++ b/go/app/cli.go @@ -178,8 +178,10 @@ func Cli(command string, strict bool, instance string, destination string, owner rawInstanceKey = nil } - if destination != "" && !strings.Contains(destination, ":") { - destination = net.JoinHostPort(destination, strconv.Itoa(config.Config.DefaultInstancePort)) + if destination != "" { + if _, _, err := net.SplitHostPort(destination); err != nil { + destination = net.JoinHostPort(destination, strconv.Itoa(config.Config.DefaultInstancePort)) + } } destinationKey, err := inst.ParseResolveInstanceKey(destination) if err != nil { diff --git a/go/http/httpbase.go b/go/http/httpbase.go index 29c29378c..583487a83 100644 --- a/go/http/httpbase.go +++ b/go/http/httpbase.go @@ -18,6 +18,7 @@ package http import ( "fmt" + "net" "net/http" "strings" @@ -151,7 +152,7 @@ func getClusterHint(params map[string]string) string { return params["clusterName"] } if params["host"] != "" && params["port"] != "" { - return fmt.Sprintf("%s:%s", params["host"], params["port"]) + return net.JoinHostPort(params["host"], params["port"]) } return "" } diff --git a/go/inst/analysis_dao.go b/go/inst/analysis_dao.go index c353535e7..9b8ed2061 100644 --- a/go/inst/analysis_dao.go +++ b/go/inst/analysis_dao.go @@ -105,9 +105,11 @@ func GetReplicationAnalysis(clusterName string, hints *ReplicationAnalysisHints) OR master_instance.master_port = 0 OR substr(master_instance.master_host, 1, 2) = '//') AS is_master, MIN(master_instance.is_co_master) AS is_co_master, - MIN(CONCAT(master_instance.hostname, - ':', - master_instance.port) = master_instance.cluster_name) AS is_cluster_master, + MIN((CASE WHEN INSTR(master_instance.hostname, ':') > 0 + THEN concat('[', master_instance.hostname, ']:', master_instance.port) + ELSE concat(master_instance.hostname, ':', master_instance.port) + END) + = master_instance.cluster_name) AS is_cluster_master, MIN(master_instance.gtid_mode) AS gtid_mode, COUNT(replica_instance.server_id) AS count_slaves, IFNULL(SUM(replica_instance.last_checked <= replica_instance.last_seen), @@ -122,7 +124,10 @@ func GetReplicationAnalysis(clusterName string, hints *ReplicationAnalysisHints) AND replica_instance.slave_sql_running = 1), 0) AS count_slaves_failing_to_connect_to_master, MIN(master_instance.replication_depth) AS replication_depth, - GROUP_CONCAT(concat(replica_instance.Hostname, ':', replica_instance.Port)) as slave_hosts, + GROUP_CONCAT((CASE WHEN INSTR(replica_instance.hostname, ':') > 0 + THEN concat('[', replica_instance.hostname, ']:', replica_instance.port) + ELSE concat(replica_instance.hostname, ':', replica_instance.port) + END)) as slave_hosts, MIN( master_instance.slave_sql_running = 1 AND master_instance.slave_io_running = 0 diff --git a/go/inst/instance_dao.go b/go/inst/instance_dao.go index 96d104246..6824daeb2 100644 --- a/go/inst/instance_dao.go +++ b/go/inst/instance_dao.go @@ -593,14 +593,16 @@ func ReadTopologyInstanceBufferable(instanceKey *InstanceKey, bufferWrites bool, defer waitGroup.Done() err := sqlutils.QueryRowsMap(db, ` select - substring_index(host, ':', 1) as slave_hostname + host from information_schema.processlist where command IN ('Binlog Dump', 'Binlog Dump GTID') `, func(m sqlutils.RowMap) error { - cname, resolveErr := ResolveHostname(m.GetString("slave_hostname")) + host := m.GetString("host") + host = host[0:strings.LastIndex(host, ":")] + cname, resolveErr := ResolveHostname(host) if resolveErr != nil { logReadTopologyInstanceError(instanceKey, "ResolveHostname: processlist", resolveErr) } @@ -1304,7 +1306,11 @@ func SearchInstances(searchString string) ([](*Instance), error) { or instr(cluster_name, ?) > 0 or instr(version, ?) > 0 or instr(version_comment, ?) > 0 - or instr(concat(hostname, ':', port), ?) > 0 + or instr( + CASE WHEN INSTR(hostname, ':') THEN concat('[', hostname, ']:', port) + ELSE concat(hostname, ':', port) + END, + ?) > 0 or concat(server_id, '') = ? or concat(port, '') = ? ` @@ -1419,8 +1425,8 @@ func ReadDowntimedInstances(clusterName string) ([](*Instance), error) { func ReadClusterCandidateInstances(clusterName string) ([](*Instance), error) { condition := ` cluster_name = ? - and concat(hostname, ':', port) in ( - select concat(hostname, ':', port) + and concat(hostname, ' ', port) in ( + select concat(hostname, ' ', port) from candidate_database_instance where promotion_rule in ('must', 'prefer') ) diff --git a/go/inst/maintenance_dao.go b/go/inst/maintenance_dao.go index 981237b04..206060e90 100644 --- a/go/inst/maintenance_dao.go +++ b/go/inst/maintenance_dao.go @@ -254,8 +254,8 @@ func ExpireMaintenance() error { database_instance_maintenance where explicitly_bounded = 0 - and concat(processing_node_hostname, ':', processing_node_token) not in ( - select concat(hostname, ':', token) from node_health + and concat(processing_node_hostname, ' ', processing_node_token) not in ( + select concat(hostname, ' ', token) from node_health ) `, ) diff --git a/go/inst/pool_dao.go b/go/inst/pool_dao.go index 9a119387d..ed46ac834 100644 --- a/go/inst/pool_dao.go +++ b/go/inst/pool_dao.go @@ -123,7 +123,10 @@ func ReadAllPoolInstancesSubmissions() ([]PoolInstancesSubmission, error) { select pool, min(registered_at) as registered_at, - GROUP_CONCAT(concat(hostname, ':', port)) as hosts + GROUP_CONCAT( + CASE WHEN INSTR(hostname, ':') THEN concat('[', hostname, ']:', port) + ELSE concat(hostname, ':', port) + END) as hosts from database_instance_pool group by diff --git a/go/logic/topology_recovery_dao.go b/go/logic/topology_recovery_dao.go index fb8bdcabc..10a72ce4b 100644 --- a/go/logic/topology_recovery_dao.go +++ b/go/logic/topology_recovery_dao.go @@ -466,8 +466,8 @@ func AcknowledgeCrashedRecoveries() (countAcknowledgedEntries int64, err error) whereClause := ` in_active_period = 1 and end_recovery is null - and concat(processing_node_hostname, ':', processcing_node_token) not in ( - select concat(hostname, ':', token) from node_health + and concat(processing_node_hostname, ' ', processcing_node_token) not in ( + select concat(hostname, ' ', token) from node_health ) ` return acknowledgeRecoveries("orchestrator", "detected crashed recovery", true, whereClause, sqlutils.Args()) diff --git a/go/raft/raft.go b/go/raft/raft.go index 51db16788..95a21d095 100644 --- a/go/raft/raft.go +++ b/go/raft/raft.go @@ -22,7 +22,6 @@ import ( "math/rand" "net" "strconv" - "strings" "sync" "sync/atomic" "time" @@ -99,13 +98,12 @@ func computeLeaderURI() (uri string, err error) { } hostname := config.Config.RaftAdvertise - listenTokens := strings.Split(config.Config.ListenAddress, ":") - if len(listenTokens) < 2 { + _, port, err := net.SplitHostPort(config.Config.ListenAddress) + if err != nil { return uri, fmt.Errorf("computeLeaderURI: cannot determine listen port out of config.Config.ListenAddress: %+v", config.Config.ListenAddress) } - port := listenTokens[1] - uri = fmt.Sprintf("%s://%s:%s", scheme, hostname, port) + uri = fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(hostname, port)) return uri, nil } diff --git a/resources/public/js/agent.js b/resources/public/js/agent.js index c30a19409..095186644 100644 --- a/resources/public/js/agent.js +++ b/resources/public/js/agent.js @@ -48,7 +48,7 @@ $(document).ready(function() { } $("[data-agent=hostname]").html(agent.Hostname) $("[data-agent=hostname_search]").html( - '' + agent.Hostname + '' + '
' + '' + agent.Hostname + '' + '
' ); $("[data-agent=port]").html(agent.Port) $("[data-agent=last_submitted]").html(agent.LastSubmitted) diff --git a/resources/public/js/audit-failure-detection.js b/resources/public/js/audit-failure-detection.js index 52488e0db..812d131e1 100644 --- a/resources/public/js/audit-failure-detection.js +++ b/resources/public/js/audit-failure-detection.js @@ -23,7 +23,7 @@ $(document).ready(function() { hideLoader(); auditEntries.forEach(function(audit) { - var analyzedInstanceDisplay = audit.AnalysisEntry.AnalyzedInstanceKey.Hostname + ":" + audit.AnalysisEntry.AnalyzedInstanceKey.Port; + var analyzedInstanceDisplay = joinHostPort(audit.AnalysisEntry.AnalyzedInstanceKey.Hostname, audit.AnalysisEntry.AnalyzedInstanceKey.Port); var row = $(''); var analysisElement = $('').attr("data-detection-id", audit.Id).text(audit.AnalysisEntry.Analysis); diff --git a/resources/public/js/audit.js b/resources/public/js/audit.js index cb2fca798..192746ee9 100644 --- a/resources/public/js/audit.js +++ b/resources/public/js/audit.js @@ -3,36 +3,36 @@ $(document).ready(function () { showLoader(); var apiUri = "/api/audit/"+currentPage(); if (auditHostname()) { - apiUri = "/api/audit/instance/"+auditHostname()+"/"+auditPort()+"/"+currentPage(); + apiUri = "/api/audit/instance/"+auditHostname()+"/"+auditPort()+"/"+currentPage(); } $.get(appUrl(apiUri), function (auditEntries) { auditEntries = auditEntries || []; displayAudit(auditEntries); - }, "json"); + }, "json"); function displayAudit(auditEntries) { - var baseWebUri = appUrl("/web/audit/"); - if (auditHostname()) { - baseWebUri += "instance/"+auditHostname()+"/"+auditPort()+"/"; + var baseWebUri = appUrl("/web/audit/"); + if (auditHostname()) { + baseWebUri += "instance/"+auditHostname()+"/"+auditPort()+"/"; } hideLoader(); auditEntries.forEach(function (audit) { - var row = jQuery(''); - jQuery('', { text: audit.AuditTimestamp }).appendTo(row); - jQuery('', { text: audit.AuditType }).appendTo(row); - if (audit.AuditInstanceKey.Hostname) { - var uri = appUrl("/web/audit/instance/"+audit.AuditInstanceKey.Hostname+"/"+audit.AuditInstanceKey.Port); - $('', { text: audit.AuditInstanceKey.Hostname+":"+audit.AuditInstanceKey.Port , href: uri}).wrap($("")).parent().appendTo(row); - } else { - jQuery('', { text: audit.AuditInstanceKey.Hostname+":"+audit.AuditInstanceKey.Port }).appendTo(row); - } - jQuery('', { text: audit.Message }).appendTo(row); - row.appendTo('#audit tbody'); - }); + var row = jQuery(''); + jQuery('', { text: audit.AuditTimestamp }).appendTo(row); + jQuery('', { text: audit.AuditType }).appendTo(row); + if (audit.AuditInstanceKey.Hostname) { + var uri = appUrl("/web/audit/instance/"+audit.AuditInstanceKey.Hostname+"/"+audit.AuditInstanceKey.Port); + $('', { text: joinHostPort(audit.AuditInstanceKey.Hostname, audit.AuditInstanceKey.Port) , href: uri}).wrap($("")).parent().appendTo(row); + } else { + jQuery('', { text: joinHostPort(audit.AuditInstanceKey.Hostname, audit.AuditInstanceKey.Port) }).appendTo(row); + } + jQuery('', { text: audit.Message }).appendTo(row); + row.appendTo('#audit tbody'); + }); if (currentPage() <= 0) { - $("#audit .pager .previous").addClass("disabled"); + $("#audit .pager .previous").addClass("disabled"); } if (auditEntries.length == 0) { - $("#audit .pager .next").addClass("disabled"); + $("#audit .pager .next").addClass("disabled"); } $("#audit .pager .previous").not(".disabled").find("a").click(function() { window.location.href = baseWebUri+(currentPage() - 1); diff --git a/resources/public/js/cluster-pools.js b/resources/public/js/cluster-pools.js index e24e0a449..547ab2df0 100644 --- a/resources/public/js/cluster-pools.js +++ b/resources/public/js/cluster-pools.js @@ -111,7 +111,7 @@ $(document).ready(function() { pool.instances.forEach(function(instance) { var instanceId = getInstanceId(instance.Hostname, instance.Port); var problemInstance = problemInstancesMap[instanceId]; - var instanceDisplay = instance.Hostname + ":" + instance.Port; + var instanceDisplay = joinHostPort(instance.Hostname, instance.Port); if (typeof removeTextFromHostnameDisplay != "undefined" && removeTextFromHostnameDisplay()) { instanceDisplay = instanceDisplay.replace(removeTextFromHostnameDisplay(), ''); } diff --git a/resources/public/js/cluster.js b/resources/public/js/cluster.js index 689c5e0ef..186fc1799 100644 --- a/resources/public/js/cluster.js +++ b/resources/public/js/cluster.js @@ -698,9 +698,9 @@ function Cluster() { if (shouldApply) { addAlert( "Cannot move " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " under " + - droppableNode.Key.Hostname + ":" + droppableNode.Key.Port + + joinHostPort(droppableNode.Key.Hostname, droppableNode.Key.Port) + ". " + "You may only move a node down below its sibling or up below its grandparent." ); @@ -917,9 +917,9 @@ function Cluster() { if (shouldApply) { addAlert( "Cannot move replicas of " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " under " + - droppableNode.Key.Hostname + ":" + droppableNode.Key.Port + + joinHostPort(droppableNode.Key.Hostname, droppableNode.Key.Port) + ". " + "You may only repoint or move up the replicas of an instance. Otherwise try Smart Mode." ); @@ -948,9 +948,9 @@ function Cluster() { function relocate(node, siblingNode) { var message = "

relocate

Are you sure you wish to turn " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " into a replica of " + - siblingNode.Key.Hostname + ":" + siblingNode.Key.Port + + joinHostPort(siblingNode.Key.Hostname, siblingNode.Key.Port) + "?" + "

Note

Orchestrator will try and figure out the best relocation path. This may involve multiple steps. " + "

In case multiple steps are involved, failure of one would leave your instance hanging in a different location than you expected, " + @@ -962,9 +962,9 @@ function Cluster() { function relocateReplicas(node, siblingNode, pattern) { pattern = pattern || ""; var message = "

relocate-replicas

Are you sure you wish to relocate replicas of " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " below " + - siblingNode.Key.Hostname + ":" + siblingNode.Key.Port + + joinHostPort(siblingNode.Key.Hostname, siblingNode.Key.Port) + "?" + "

Note

Orchestrator will try and figure out the best relocation path. This may involve multiple steps. " + "

In case multiple steps are involved, failure of one may leave some instances hanging in a different location than you expected, " + @@ -975,7 +975,7 @@ function Cluster() { function repointReplicas(node, siblingNode) { var message = "

repoint-replicas

Are you sure you wish to repoint replicas of " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + "?"; var apiUrl = "/api/repoint-replicas/" + node.Key.Hostname + "/" + node.Key.Port; return executeMoveOperation(message, apiUrl); @@ -983,9 +983,9 @@ function Cluster() { function moveUpReplicas(node, masterNode) { var message = "

move-up-replicas

Are you sure you wish to move up replicas of " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " below " + - masterNode.Key.Hostname + ":" + masterNode.Key.Port + + joinHostPort(masterNode.Key.Hostname, masterNode.Key.Port) + "?"; var apiUrl = "/api/move-up-replicas/" + node.Key.Hostname + "/" + node.Key.Port; return executeMoveOperation(message, apiUrl); @@ -993,9 +993,9 @@ function Cluster() { function matchReplicas(node, otherNode) { var message = "

match-replicas

Are you sure you wish to match replicas of " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " below " + - otherNode.Key.Hostname + ":" + otherNode.Key.Port + + joinHostPort(otherNode.Key.Hostname, otherNode.Key.Port) + "?"; var apiUrl = "/api/match-replicas/" + node.Key.Hostname + "/" + node.Key.Port + "/" + otherNode.Key.Hostname + "/" + otherNode.Key.Port; return executeMoveOperation(message, apiUrl); @@ -1003,9 +1003,9 @@ function Cluster() { function moveBelow(node, siblingNode) { var message = "

move-below

Are you sure you wish to turn " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " into a replica of " + - siblingNode.Key.Hostname + ":" + siblingNode.Key.Port + + joinHostPort(siblingNode.Key.Hostname, siblingNode.Key.Port) + "?"; var apiUrl = "/api/move-below/" + node.Key.Hostname + "/" + node.Key.Port + "/" + siblingNode.Key.Hostname + "/" + siblingNode.Key.Port; return executeMoveOperation(message, apiUrl); @@ -1013,9 +1013,9 @@ function Cluster() { function moveUp(node, grandparentNode) { var message = "

move-up

Are you sure you wish to turn " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " into a replica of " + - grandparentNode.Key.Hostname + ":" + grandparentNode.Key.Port + + joinHostPort(grandparentNode.Key.Hostname, grandparentNode.Key.Port) + "?"; var apiUrl = "/api/move-up/" + node.Key.Hostname + "/" + node.Key.Port; return executeMoveOperation(message, apiUrl); @@ -1023,9 +1023,9 @@ function Cluster() { function takeMaster(node, masterNode) { var message = "

take-master

Are you sure you wish to make " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " master of " + - masterNode.Key.Hostname + ":" + masterNode.Key.Port + + joinHostPort(masterNode.Key.Hostname, masterNode.Key.Port) + "?"; var apiUrl = "/api/take-master/" + node.Key.Hostname + "/" + node.Key.Port; return executeMoveOperation(message, apiUrl); @@ -1033,9 +1033,9 @@ function Cluster() { function matchBelow(node, otherNode) { var message = "

PSEUDO-GTID MODE, match-below

Are you sure you wish to turn " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " into a replica of " + - otherNode.Key.Hostname + ":" + otherNode.Key.Port + + joinHostPort(otherNode.Key.Hostname, otherNode.Key.Port) + "?"; var apiUrl = "/api/match-below/" + node.Key.Hostname + "/" + node.Key.Port + "/" + otherNode.Key.Hostname + "/" + otherNode.Key.Port; return executeMoveOperation(message, apiUrl); @@ -1043,9 +1043,9 @@ function Cluster() { function moveBelowGTID(node, otherNode) { var message = "

GTID MODE, move-below

Are you sure you wish to turn " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " into a replica of " + - otherNode.Key.Hostname + ":" + otherNode.Key.Port + + joinHostPort(otherNode.Key.Hostname, otherNode.Key.Port) + "?"; var apiUrl = "/api/move-below-gtid/" + node.Key.Hostname + "/" + node.Key.Port + "/" + otherNode.Key.Hostname + "/" + otherNode.Key.Port; return executeMoveOperation(message, apiUrl); @@ -1053,9 +1053,9 @@ function Cluster() { function moveReplicasGTID(node, otherNode) { var message = "

GTID MODE, move-replicas

Are you sure you wish to move replicas of " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " below " + - otherNode.Key.Hostname + ":" + otherNode.Key.Port + + joinHostPort(otherNode.Key.Hostname, otherNode.Key.Port) + "?"; var apiUrl = "/api/move-replicas-gtid/" + node.Key.Hostname + "/" + node.Key.Port + "/" + otherNode.Key.Hostname + "/" + otherNode.Key.Port; return executeMoveOperation(message, apiUrl); @@ -1063,9 +1063,9 @@ function Cluster() { function makeCoMaster(node, childNode) { var message = "

make-co-master

Are you sure you wish to make " + - node.Key.Hostname + ":" + node.Key.Port + + joinHostPort(node.Key.Hostname, node.Key.Port) + " and " + - childNode.Key.Hostname + ":" + childNode.Key.Port + + joinHostPort(childNode.Key.Hostname, childNode.Key.Port) + " co-masters?"; bootbox.confirm(anonymizeIfNeedBe(message), function(confirm) { if (confirm) { @@ -1079,7 +1079,7 @@ function Cluster() { function gracefulMasterTakeover(newMasterNode, existingMasterNode) { var message = '

DANGER ZONE

Graceful-master-takeover

Are you sure you wish to promote ' + - newMasterNode.Key.Hostname + ':' + newMasterNode.Key.Port + + joinHostPort(newMasterNode.Key.Hostname, newMasterNode.Key.Port) + ' as master?'; bootbox.confirm(anonymizeIfNeedBe(message), function(confirm) { if (confirm) { @@ -1338,14 +1338,14 @@ function Cluster() { // This is legacy and will be removed function makeMaster(instance) { - var message = "Are you sure you wish to make " + instance.Key.Hostname + ":" + instance.Key.Port + " the new master?" + "

Siblings of " + instance.Key.Hostname + ":" + instance.Key.Port + " will turn to be its children, " + "via Pseudo-GTID." + "

The instance will be set to be writeable (read_only = 0)." + "

Replication on this instance will be stopped, but not reset. You should run RESET SLAVE yourself " + "if this instance will indeed become the master." + "

Pointing your application servers to the new master is on you."; + var message = "Are you sure you wish to make " + joinHostPort(instance.Key.Hostname, instance.Key.Port) + " the new master?" + "

Siblings of " + joinHostPort(instance.Key.Hostname, instance.Key.Port) + " will turn to be its children, " + "via Pseudo-GTID." + "

The instance will be set to be writeable (read_only = 0)." + "

Replication on this instance will be stopped, but not reset. You should run RESET SLAVE yourself " + "if this instance will indeed become the master." + "

Pointing your application servers to the new master is on you."; var apiUrl = "/api/make-master/" + instance.Key.Hostname + "/" + instance.Key.Port; return executeMoveOperation(message, apiUrl); } //This is legacy and will be removed function makeLocalMaster(instance) { - var message = "Are you sure you wish to make " + instance.Key.Hostname + ":" + instance.Key.Port + " a local master?" + "

Siblings of " + instance.Key.Hostname + ":" + instance.Key.Port + " will turn to be its children, " + "via Pseudo-GTID." + "

The instance will replicate from its grandparent."; + var message = "Are you sure you wish to make " + joinHostPort(instance.Key.Hostname, instance.Key.Port) + " a local master?" + "

Siblings of " + joinHostPort(instance.Key.Hostname, instance.Key.Port) + " will turn to be its children, " + "via Pseudo-GTID." + "

The instance will replicate from its grandparent."; var apiUrl = "/api/make-local-master/" + instance.Key.Hostname + "/" + instance.Key.Port; return executeMoveOperation(message, apiUrl); } @@ -1529,7 +1529,7 @@ function Cluster() { if (extraText != '') { analysisContent += '

' + extraText + '
'; } - analysisContent += "
" + analysisEntry.AnalyzedInstanceKey.Hostname + ":" + analysisEntry.AnalyzedInstanceKey.Port + "
"; + analysisContent += "
" + joinHostPort(analysisEntry.AnalyzedInstanceKey.Hostname, analysisEntry.AnalyzedInstanceKey.Port) + "
"; var content = '
'+glyph+'
'+analysisContent+'
'; addSidebarInfoPopoverContent(content, "analysis", false); diff --git a/resources/public/js/clusters-analysis.js b/resources/public/js/clusters-analysis.js index 74b7e9d33..f43b3ddc2 100644 --- a/resources/public/js/clusters-analysis.js +++ b/resources/public/js/clusters-analysis.js @@ -33,7 +33,7 @@ $(document).ready(function() { } function getBlockedRecoveryKey(hostname, port, analysis) { - return hostname + ":" + port + ":" + analysis; + return joinHostPort(hostname, port) + ":" + analysis; } function displayClustersAnalysis(clusters, replicationAnalysis, blockedRecoveries) { @@ -79,7 +79,7 @@ $(document).ready(function() { function displayAnalysisEntry(analysisEntry, popoverElement) { var blockedKey = getBlockedRecoveryKey(analysisEntry.AnalyzedInstanceKey.Hostname, analysisEntry.AnalyzedInstanceKey.Port, analysisEntry.Analysis); - var displayText = '
' + analysisEntry.Analysis + (analysisEntry.IsDowntimed ? '
[downtime till ' + analysisEntry.DowntimeEndTimestamp + ']' : '') + (blockedrecoveriesMap[blockedKey] ? '
Blocked' : '') + "
" + "
" + "" + analysisEntry.AnalyzedInstanceKey.Hostname + ":" + analysisEntry.AnalyzedInstanceKey.Port + ""; + var displayText = '
' + analysisEntry.Analysis + (analysisEntry.IsDowntimed ? '
[downtime till ' + analysisEntry.DowntimeEndTimestamp + ']' : '') + (blockedrecoveriesMap[blockedKey] ? '
Blocked' : '') + "
" + "
" + "" + joinHostPort(analysisEntry.AnalyzedInstanceKey.Hostname, analysisEntry.AnalyzedInstanceKey.Port) + ""; if (analysisEntry.IsDowntimed) { displayText = '
' + displayText + '
'; } else if (blockedrecoveriesMap[blockedKey]) { diff --git a/resources/public/js/discover.js b/resources/public/js/discover.js index 722511ac3..ec5a2233e 100644 --- a/resources/public/js/discover.js +++ b/resources/public/js/discover.js @@ -23,11 +23,11 @@ function discover(hostname, port) { if (operationResult.Code == "ERROR" || operationResult.Details == null) { addAlert(operationResult.Message) } else { - var instance = operationResult.Details; - addInfo('Discovered
' - +instance.Key.Hostname+":"+instance.Key.Port+'' - ); - } - }, "json"); - -} \ No newline at end of file + var instance = operationResult.Details; + addInfo('Discovered ' + +joinHostPort(instance.Key.Hostname,instance.Key.Port)+'' + ); + } + }, "json"); + +} diff --git a/resources/public/js/orchestrator.js b/resources/public/js/orchestrator.js index 191bde34c..33ed13f9e 100644 --- a/resources/public/js/orchestrator.js +++ b/resources/public/js/orchestrator.js @@ -32,6 +32,26 @@ var errorMapping = { } }; +function splitHostPort(addr) { + if (addr.indexOf(':') < 0) { + return [addr, undefined] + } + chunks = addr.split(':') + port = chunks.pop() + host = chunks.join(':') + if (host.startsWith('[') && host.endsWith(']')) { + host = host.substring(1, host.length - 1) + } + return [host, port] +} + +function joinHostPort(host, port) { + if (host.indexOf(':') > -1) { + return '[' + host + ']:' + port + } + return host + ':' + port +} + function updateCountdownDisplay() { if ($.cookie("auto-refresh") == "true") { $("#refreshCountdown").html(' ' + secondsTillRefresh + 's'); @@ -157,7 +177,7 @@ function getInstanceTitle(host, port) { if (host == "") { return ""; } - return canonizeInstanceTitle(host + ":" + port); + return canonizeInstanceTitle(joinHostPort(host, port)); } @@ -309,7 +329,7 @@ function openNodeModal(node) { // This very instance; will not move below itself return; } - var title = canonizeInstanceTitle(equivalence.Key.Hostname + ':' + equivalence.Key.Port); + var title = canonizeInstanceTitle(joinHostPort(equivalence.Key.Hostname, equivalence.Key.Port)); $('#node_modal [data-btn-group=move-equivalent] ul').append('
  • ' + title + '
  • '); }); @@ -429,7 +449,7 @@ function openNodeModal(node) { apiCommand("/api/reattach-replica-master-host/" + node.Key.Hostname + "/" + node.Key.Port); }); $('#node_modal button[data-btn=reset-slave]').click(function() { - var message = "

    Are you sure you wish to reset " + node.Key.Hostname + ":" + node.Key.Port + + var message = "

    Are you sure you wish to reset " + joinHostPort(node.Key.Hostname, node.Key.Port) + "?" + "

    This will stop and break the replication." + "

    FYI, this is a destructive operation that cannot be easily reverted"; @@ -441,7 +461,7 @@ function openNodeModal(node) { return false; }); $('#node_modal [data-btn=gtid-errant-reset-master]').click(function() { - var message = "

    Are you sure you wish to reset master on " + node.Key.Hostname + ":" + node.Key.Port + + var message = "

    Are you sure you wish to reset master on " + joinHostPort(node.Key.Hostname, node.Key.Port) + "?" + "

    This will purge binary logs on server."; bootbox.confirm(message, function(confirm) { @@ -467,7 +487,7 @@ function openNodeModal(node) { apiCommand("/api/set-writeable/" + node.Key.Hostname + "/" + node.Key.Port); }); $('#node_modal button[data-btn=enable-gtid]').click(function() { - var message = "

    Are you sure you wish to enable GTID on " + node.Key.Hostname + ":" + node.Key.Port + + var message = "

    Are you sure you wish to enable GTID on " + joinHostPort(node.Key.Hostname, node.Key.Port) + "?" + "

    Replication might break as consequence"; bootbox.confirm(message, function(confirm) { @@ -477,7 +497,7 @@ function openNodeModal(node) { }); }); $('#node_modal button[data-btn=disable-gtid]').click(function() { - var message = "

    Are you sure you wish to disable GTID on " + node.Key.Hostname + ":" + node.Key.Port + + var message = "

    Are you sure you wish to disable GTID on " + joinHostPort(node.Key.Hostname, node.Key.Port) + "?" + "

    Replication might break as consequence"; bootbox.confirm(message, function(confirm) { @@ -487,7 +507,7 @@ function openNodeModal(node) { }); }); $('#node_modal button[data-btn=forget-instance]').click(function() { - var message = "

    Are you sure you wish to forget " + node.Key.Hostname + ":" + node.Key.Port + + var message = "

    Are you sure you wish to forget " + joinHostPort(node.Key.Hostname, node.Key.Port) + "?" + "

    It may be re-discovered if accessible from an existing instance through replication topology."; bootbox.confirm(message, function(confirm) { @@ -559,7 +579,7 @@ function openNodeModal(node) { $('#node_modal button[data-btn=regroup-replicas]').show(); } $('#node_modal button[data-btn=regroup-replicas]').click(function() { - var message = "

    Are you sure you wish to regroup replicas of " + node.Key.Hostname + ":" + node.Key.Port + + var message = "

    Are you sure you wish to regroup replicas of " + joinHostPort(node.Key.Hostname, node.Key.Port) + "?" + "

    This will attempt to promote one replica over its siblings"; bootbox.confirm(message, function(confirm) { @@ -578,7 +598,7 @@ function openNodeModal(node) { if (isSilentUI()) { apiCommand(apiUrl); } else { - var message = "

    Are you sure you want " + node.Key.Hostname + ":" + node.Key.Port + + var message = "

    Are you sure you want " + joinHostPort(node.Key.Hostname, node.Key.Port) + " to take its siblings?"; bootbox.confirm(message, function(confirm) { if (confirm) { @@ -616,9 +636,9 @@ function openNodeModal(node) { function normalizeInstance(instance) { instance.id = getInstanceId(instance.Key.Hostname, instance.Key.Port); - instance.title = instance.Key.Hostname + ':' + instance.Key.Port; + instance.title = joinHostPort(instance.Key.Hostname, instance.Key.Port); instance.canonicalTitle = instance.title; - instance.masterTitle = instance.MasterKey.Hostname + ":" + instance.MasterKey.Port; + instance.masterTitle = joinHostPort(instance.MasterKey.Hostname, instance.MasterKey.Port); instance.masterId = getInstanceId(instance.MasterKey.Hostname, instance.MasterKey.Port); From 05c121b6e32dbfdfb54e6de275f47f870109167b Mon Sep 17 00:00:00 2001 From: Dmitry Smal Date: Mon, 21 Jan 2019 18:24:58 +0300 Subject: [PATCH 3/3] concat(a,b,c,d) support in sqlutils --- vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go | 1 + 1 file changed, 1 insertion(+) diff --git a/vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go b/vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go index 5937aa42a..fedcec14d 100644 --- a/vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go +++ b/vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go @@ -72,6 +72,7 @@ var sqlite3GeneralConversions = []regexpMap{ rmap(`(?i)\bconcat[(][\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*[)]`, `($1 || $2)`), rmap(`(?i)\bconcat[(][\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*[)]`, `($1 || $2 || $3)`), + rmap(`(?i)\bconcat[(][\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*[)]`, `($1 || $2 || $3 || $4)`), rmap(`(?i) rlike `, ` like `),