From 5619dd3265189512f641099467a4bbcde4fa2d15 Mon Sep 17 00:00:00 2001 From: Leon Zhang Date: Tue, 25 Dec 2018 22:10:25 +0800 Subject: [PATCH] code format add some test case --- advisor/explainer.go | 84 ++-------- advisor/index.go | 4 +- advisor/rules_test.go | 10 +- ast/rewrite.go | 348 +++++++++++++++++++++--------------------- cmd/soar/soar_test.go | 24 +++ common/config.go | 2 +- 6 files changed, 227 insertions(+), 245 deletions(-) diff --git a/advisor/explainer.go b/advisor/explainer.go index c8b7c5f..579a1e8 100644 --- a/advisor/explainer.go +++ b/advisor/explainer.go @@ -32,20 +32,13 @@ var explainRules map[string]Rule // [table_name]"suggest text" var tablesSuggests map[string][]string -/* -var explainIgnoreTables = []string{ - "dual", - "", -} -*/ - // explain建议的形式 // Item: EXP.XXX // Severity: L[0-8] // Summary: full table scan, not use index, full index scan... // Content: XX TABLE xxx -// +// checkExplainSelectType func checkExplainSelectType(exp *database.ExplainInfo) { // 判断是否跳过不检查 if len(common.Config.ExplainWarnSelectType) == 1 { @@ -70,7 +63,7 @@ func checkExplainSelectType(exp *database.ExplainInfo) { } } -// 用户可以设置AccessType的建议级别,匹配到的查询会给出建议 +// checkExplainAccessType 用户可以设置AccessType的建议级别,匹配到的查询会给出建议 func checkExplainAccessType(exp *database.ExplainInfo) { // 判断是否跳过不检查 if len(common.Config.ExplainWarnAccessType) == 1 { @@ -95,43 +88,28 @@ func checkExplainAccessType(exp *database.ExplainInfo) { } } -// TODO: /* +// TODO: func checkExplainPossibleKeys(exp *database.ExplainInfo) { - // 判断是否跳过不检查 - if common.Config.ExplainMinPossibleKeys == 0 { - return - } - - rows := exp.ExplainRows - if exp.ExplainFormat == database.JSONFormatExplain { - // JSON形式遍历分析不方便,转成Row格式统一处理 - rows = database.ConvertExplainJSON2Row(exp.ExplainJSON) - } - for _, row := range rows { - if len(row.PossibleKeys) < common.Config.ExplainMinPossibleKeys { - tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("PossibleKeys:%d < %d", - len(row.PossibleKeys), common.Config.ExplainMinPossibleKeys)) - } - } } -*/ -// TODO: -/* func checkExplainKeyLen(exp *database.ExplainInfo) { } -*/ -// TODO: -/* func checkExplainKey(exp *database.ExplainInfo) { - // 小于最小使用试用key数量 - //return intval($explainResult) < intval($userCond); - //explain-min-keys int + // 小于最小使用试用key数量 + //return intval($explainResult) < intval($userCond); + //explain-min-keys int +} + +func checkExplainExtra(exp *database.ExplainInfo) { + // 包含用户配置的逗号分隔关键词之一则提醒 + // return self::contains($explainResult, $userCond); + // explain-warn-extra []string } */ +// checkExplainRef ... func checkExplainRef(exp *database.ExplainInfo) { rows := exp.ExplainRows if exp.ExplainFormat == database.JSONFormatExplain { @@ -148,6 +126,7 @@ func checkExplainRef(exp *database.ExplainInfo) { } } +// checkExplainRows ... func checkExplainRows(exp *database.ExplainInfo) { // 判断是否跳过不检查 if common.Config.ExplainMaxRows <= 0 { @@ -167,15 +146,7 @@ func checkExplainRows(exp *database.ExplainInfo) { } } -// TODO: -/* -func checkExplainExtra(exp *database.ExplainInfo) { - // 包含用户配置的逗号分隔关键词之一则提醒 - // return self::contains($explainResult, $userCond); - // explain-warn-extra []string -} -*/ - +// checkExplainFiltered ... func checkExplainFiltered(exp *database.ExplainInfo) { // 判断是否跳过不检查 if common.Config.ExplainMaxFiltered <= 0.001 { @@ -235,30 +206,7 @@ func ExplainAdvisor(exp *database.ExplainInfo) map[string]Rule { Func: (*Query4Audit).RuleOK, } } - /* - for t, s := range tablesSuggests { - // 检查explain对应的表是否需要跳过,如dual,空表等 - ig := false - for _, ti := range explainIgnoreTables { - if ti == t { - ig = true - } - } - if ig { - continue - } - ruleId := fmt.Sprintf("EXP.%03d", explainRuleId+1) - explainRuleId = explainRuleId + 1 - explainRules[ruleId] = Rule{ - Item: ruleId, - Severity: "L0", - Summary: fmt.Sprintf("表 `%s` 查询效率不高", t), - Content: fmt.Sprint("原因:", strings.Join(s, ",")), - Case: "", - Func: (*Query4Audit).RuleOK, - } - } - */ + // TODO: 检查explain对应的表是否需要跳过,如dual,空表等 return explainRules } diff --git a/advisor/index.go b/advisor/index.go index 52594e3..e664cff 100644 --- a/advisor/index.go +++ b/advisor/index.go @@ -665,7 +665,7 @@ func (idxAdv *IndexAdvisor) buildIndex(idxList map[string]map[string][]*common.C continue } - idxName := "idx_" + strings.Join(colNames, "_") + idxName := common.Config.IdxPrefix + strings.Join(colNames, "_") // 索引名称最大长度64 if len(idxName) > IndexNameMaxLength { @@ -699,7 +699,7 @@ func (idxAdv *IndexAdvisor) buildIndexWithNoEnv(indexList map[string]map[string] common.Log.Warn("can not get the meta info of column '%s'", col.Name) continue } - idxName := "idx_" + col.Name + idxName := common.Config.IdxPrefix + col.Name // 库、表、列名需要用反撇转义 alterSQL := fmt.Sprintf("alter table `%s`.`%s` add index `%s` (`%s`)", idxAdv.vEnv.RealDB(col.DB), col.Table, idxName, col.Name) if col.DB == "" { diff --git a/advisor/rules_test.go b/advisor/rules_test.go index 051dbf4..d2ec535 100644 --- a/advisor/rules_test.go +++ b/advisor/rules_test.go @@ -48,9 +48,15 @@ func TestListHeuristicRules(t *testing.T) { func TestInBlackList(t *testing.T) { common.Log.Debug("Entering function: %s", common.GetFunctionName()) + sqls := []string{ + "select", + "select 1", + } common.BlackList = []string{"select"} - if !InBlackList("select 1") { - t.Error("should be true") + for _, sql := range sqls { + if !InBlackList(sql) { + t.Error("should be true") + } } common.Log.Debug("Exiting function: %s", common.GetFunctionName()) } diff --git a/ast/rewrite.go b/ast/rewrite.go index cf4bab2..bc459b8 100644 --- a/ast/rewrite.go +++ b/ast/rewrite.go @@ -40,180 +40,184 @@ type Rule struct { } // RewriteRules SQL重写规则,注意这个规则是有序的,先后顺序不能乱 -var RewriteRules = []Rule{ - { - Name: "dml2select", - Description: "将数据库更新请求转换为只读查询请求,便于执行EXPLAIN", - Original: "DELETE FROM film WHERE length > 100", - Suggest: "select * from film where length > 100", - Func: (*Rewrite).RewriteDML2Select, - }, - { - Name: "star2columns", - Description: "为SELECT *补全表的列信息", - Original: "SELECT * FROM film", - Suggest: "select film.film_id, film.title from film", - Func: (*Rewrite).RewriteStar2Columns, - }, - { - Name: "insertcolumns", - Description: "为INSERT补全表的列信息", - Original: "insert into film values(1,2,3,4,5)", - Suggest: "insert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5)", - Func: (*Rewrite).RewriteInsertColumns, - }, - { - Name: "having", - Description: "将查询的 HAVING 子句改写为 WHERE 中的查询条件", - Original: "SELECT state, COUNT(*) FROM Drivers GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state", - Suggest: "select state, COUNT(*) from Drivers where state in ('GA', 'TX') group by state order by state asc", - Func: (*Rewrite).RewriteHaving, - }, - { - Name: "orderbynull", - Description: "如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 ORDER BY NULL", - Original: "SELECT sum(col1) FROM tbl GROUP BY col", - Suggest: "select sum(col1) from tbl group by col order by null", - Func: (*Rewrite).RewriteAddOrderByNull, - }, - { - Name: "unionall", - Description: "可以接受重复的时间,使用 UNION ALL 替代 UNION 以提高查询效率", - Original: "select country_id from city union select country_id from country", - Suggest: "select country_id from city union all select country_id from country", - Func: (*Rewrite).RewriteUnionAll, - }, - { - Name: "or2in", - Description: "将同一列不同条件的 OR 查询转写为 IN 查询", - Original: "select country_id from city where col1 = 1 or (col2 = 1 or col2 = 2 ) or col1 = 3;", - Suggest: "select country_id from city where (col2 in (1, 2)) or col1 in (1, 3);", - Func: (*Rewrite).RewriteOr2In, - }, - { - Name: "innull", - Description: "如果 IN 条件中可能有 NULL 值而又想匹配 NULL 值时,建议添加OR col IS NULL", - Original: "暂不支持", - Suggest: "暂不支持", - Func: (*Rewrite).RewriteInNull, - }, - // 把所有跟 or 相关的重写完之后才进行 or 转 union 的重写 - { - Name: "or2union", - Description: "将不同列的 OR 查询转为 UNION 查询,建议结合 unionall 重写策略一起使用", - Original: "暂不支持", - Suggest: "暂不支持", - Func: (*Rewrite).RewriteOr2Union, - }, - { - Name: "dmlorderby", - Description: "删除 DML 更新操作中无意义的 ORDER BY", - Original: "DELETE FROM tbl WHERE col1=1 ORDER BY col", - Suggest: "delete from tbl where col1 = 1", - Func: (*Rewrite).RewriteRemoveDMLOrderBy, - }, - /* +var RewriteRules []Rule + +func init() { + RewriteRules = []Rule{ + { + Name: "dml2select", + Description: "将数据库更新请求转换为只读查询请求,便于执行EXPLAIN", + Original: "DELETE FROM film WHERE length > 100", + Suggest: "select * from film where length > 100", + Func: (*Rewrite).RewriteDML2Select, + }, + { + Name: "star2columns", + Description: "为SELECT *补全表的列信息", + Original: "SELECT * FROM film", + Suggest: "select film.film_id, film.title from film", + Func: (*Rewrite).RewriteStar2Columns, + }, + { + Name: "insertcolumns", + Description: "为INSERT补全表的列信息", + Original: "insert into film values(1,2,3,4,5)", + Suggest: "insert into film(film_id, title, description, release_year, language_id) values (1, 2, 3, 4, 5)", + Func: (*Rewrite).RewriteInsertColumns, + }, + { + Name: "having", + Description: "将查询的 HAVING 子句改写为 WHERE 中的查询条件", + Original: "SELECT state, COUNT(*) FROM Drivers GROUP BY state HAVING state IN ('GA', 'TX') ORDER BY state", + Suggest: "select state, COUNT(*) from Drivers where state in ('GA', 'TX') group by state order by state asc", + Func: (*Rewrite).RewriteHaving, + }, + { + Name: "orderbynull", + Description: "如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 ORDER BY NULL", + Original: "SELECT sum(col1) FROM tbl GROUP BY col", + Suggest: "select sum(col1) from tbl group by col order by null", + Func: (*Rewrite).RewriteAddOrderByNull, + }, + { + Name: "unionall", + Description: "可以接受重复的时间,使用 UNION ALL 替代 UNION 以提高查询效率", + Original: "select country_id from city union select country_id from country", + Suggest: "select country_id from city union all select country_id from country", + Func: (*Rewrite).RewriteUnionAll, + }, + { + Name: "or2in", + Description: "将同一列不同条件的 OR 查询转写为 IN 查询", + Original: "select country_id from city where col1 = 1 or (col2 = 1 or col2 = 2 ) or col1 = 3;", + Suggest: "select country_id from city where (col2 in (1, 2)) or col1 in (1, 3);", + Func: (*Rewrite).RewriteOr2In, + }, + { + Name: "innull", + Description: "如果 IN 条件中可能有 NULL 值而又想匹配 NULL 值时,建议添加OR col IS NULL", + Original: "暂不支持", + Suggest: "暂不支持", + Func: (*Rewrite).RewriteInNull, + }, + // 把所有跟 or 相关的重写完之后才进行 or 转 union 的重写 + { + Name: "or2union", + Description: "将不同列的 OR 查询转为 UNION 查询,建议结合 unionall 重写策略一起使用", + Original: "暂不支持", + Suggest: "暂不支持", + Func: (*Rewrite).RewriteOr2Union, + }, + { + Name: "dmlorderby", + Description: "删除 DML 更新操作中无意义的 ORDER BY", + Original: "DELETE FROM tbl WHERE col1=1 ORDER BY col", + Suggest: "delete from tbl where col1 = 1", + Func: (*Rewrite).RewriteRemoveDMLOrderBy, + }, + /* + { + Name: "groupbyconst", + Description: "删除无意义的GROUP BY常量", + Original: "SELECT sum(col1) FROM tbl GROUP BY 1;", + Suggest: "select sum(col1) from tbl", + Func: (*Rewrite).RewriteGroupByConst, + }, + */ + { + Name: "sub2join", + Description: "将子查询转换为JOIN查询", + Original: "暂不支持", + Suggest: "暂不支持", + Func: (*Rewrite).RewriteSubQuery2Join, + }, { - Name: "groupbyconst", - Description: "删除无意义的GROUP BY常量", + Name: "join2sub", + Description: "将JOIN查询转换为子查询", + Original: "暂不支持", + Suggest: "暂不支持", + Func: (*Rewrite).RewriteJoin2SubQuery, + }, + { + Name: "distinctstar", + Description: "DISTINCT *对有主键的表没有意义,可以将DISTINCT删掉", + Original: "SELECT DISTINCT * FROM film;", + Suggest: "SELECT * FROM film", + Func: (*Rewrite).RewriteDistinctStar, + }, + { + Name: "standard", + Description: "SQL标准化,如:关键字转换为小写", Original: "SELECT sum(col1) FROM tbl GROUP BY 1;", - Suggest: "select sum(col1) from tbl", - Func: (*Rewrite).RewriteGroupByConst, + Suggest: "select sum(col1) from tbl group by 1", + Func: (*Rewrite).RewriteStandard, + }, + { + Name: "mergealter", + Description: "合并同一张表的多条ALTER语句", + Original: "ALTER TABLE t2 DROP COLUMN c;ALTER TABLE t2 DROP COLUMN d;", + Suggest: "ALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;", + }, + { + Name: "alwaystrue", + Description: "删除无用的恒真判断条件", + Original: "SELECT count(col) FROM tbl where 'a'= 'a' or ('b' = 'b' and a = 'b');", + Suggest: "select count(col) from tbl where (a = 'b');", + Func: (*Rewrite).RewriteAlwaysTrue, + }, + { + Name: "countstar", + Description: "不建议使用COUNT(col)或COUNT(常量),建议改写为COUNT(*)", + Original: "SELECT count(col) FROM tbl GROUP BY 1;", + Suggest: "SELECT count(*) FROM tbl GROUP BY 1;", + Func: (*Rewrite).RewriteCountStar, + }, + { + Name: "innodb", + Description: "建表时建议使用InnoDB引擎,非 InnoDB 引擎表自动转 InnoDB", + Original: "CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT);", + Suggest: "create table t1 (\n\tid bigint(20) not null auto_increment\n) ENGINE=InnoDB;", + Func: (*Rewrite).RewriteInnoDB, }, - */ - { - Name: "sub2join", - Description: "将子查询转换为JOIN查询", - Original: "暂不支持", - Suggest: "暂不支持", - Func: (*Rewrite).RewriteSubQuery2Join, - }, - { - Name: "join2sub", - Description: "将JOIN查询转换为子查询", - Original: "暂不支持", - Suggest: "暂不支持", - Func: (*Rewrite).RewriteJoin2SubQuery, - }, - { - Name: "distinctstar", - Description: "DISTINCT *对有主键的表没有意义,可以将DISTINCT删掉", - Original: "SELECT DISTINCT * FROM film;", - Suggest: "SELECT * FROM film", - Func: (*Rewrite).RewriteDistinctStar, - }, - { - Name: "standard", - Description: "SQL标准化,如:关键字转换为小写", - Original: "SELECT sum(col1) FROM tbl GROUP BY 1;", - Suggest: "select sum(col1) from tbl group by 1", - Func: (*Rewrite).RewriteStandard, - }, - { - Name: "mergealter", - Description: "合并同一张表的多条ALTER语句", - Original: "ALTER TABLE t2 DROP COLUMN c;ALTER TABLE t2 DROP COLUMN d;", - Suggest: "ALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;", - }, - { - Name: "alwaystrue", - Description: "删除无用的恒真判断条件", - Original: "SELECT count(col) FROM tbl where 'a'= 'a' or ('b' = 'b' and a = 'b');", - Suggest: "select count(col) from tbl where (a = 'b');", - Func: (*Rewrite).RewriteAlwaysTrue, - }, - { - Name: "countstar", - Description: "不建议使用COUNT(col)或COUNT(常量),建议改写为COUNT(*)", - Original: "SELECT count(col) FROM tbl GROUP BY 1;", - Suggest: "SELECT count(*) FROM tbl GROUP BY 1;", - Func: (*Rewrite).RewriteCountStar, - }, - { - Name: "innodb", - Description: "建表时建议使用InnoDB引擎,非 InnoDB 引擎表自动转 InnoDB", - Original: "CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT);", - Suggest: "create table t1 (\n\tid bigint(20) not null auto_increment\n) ENGINE=InnoDB;", - Func: (*Rewrite).RewriteInnoDB, - }, - { - Name: "autoincrement", - Description: "将autoincrement初始化为1", - Original: "CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;", - Suggest: "create table t1(id bigint(20) not null auto_increment) ENGINE=InnoDB auto_increment=1;", - Func: (*Rewrite).RewriteAutoIncrement, - }, - { - Name: "intwidth", - Description: "整型数据类型修改默认显示宽度", - Original: "create table t1 (id int(20) not null auto_increment) ENGINE=InnoDB;", - Suggest: "create table t1 (id int(10) not null auto_increment) ENGINE=InnoDB;", - Func: (*Rewrite).RewriteIntWidth, - }, - { - Name: "truncate", - Description: "不带 WHERE 条件的 DELETE 操作建议修改为 TRUNCATE", - Original: "DELETE FROM tbl", - Suggest: "truncate table tbl", - Func: (*Rewrite).RewriteTruncate, - }, - { - Name: "rmparenthesis", - Description: "去除没有意义的括号", - Original: "select col from table where (col = 1);", - Suggest: "select col from table where col = 1;", - Func: (*Rewrite).RewriteRmParenthesis, - }, - // delimiter要放在最后,不然补不上 - { - Name: "delimiter", - Description: "补全DELIMITER", - Original: "use sakila", - Suggest: "use sakila;", - Func: (*Rewrite).RewriteDelimiter, - }, - // TODO in to exists - // TODO exists to in + { + Name: "autoincrement", + Description: "将autoincrement初始化为1", + Original: "CREATE TABLE t1(id bigint(20) NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123802;", + Suggest: "create table t1(id bigint(20) not null auto_increment) ENGINE=InnoDB auto_increment=1;", + Func: (*Rewrite).RewriteAutoIncrement, + }, + { + Name: "intwidth", + Description: "整型数据类型修改默认显示宽度", + Original: "create table t1 (id int(20) not null auto_increment) ENGINE=InnoDB;", + Suggest: "create table t1 (id int(10) not null auto_increment) ENGINE=InnoDB;", + Func: (*Rewrite).RewriteIntWidth, + }, + { + Name: "truncate", + Description: "不带 WHERE 条件的 DELETE 操作建议修改为 TRUNCATE", + Original: "DELETE FROM tbl", + Suggest: "truncate table tbl", + Func: (*Rewrite).RewriteTruncate, + }, + { + Name: "rmparenthesis", + Description: "去除没有意义的括号", + Original: "select col from table where (col = 1);", + Suggest: "select col from table where col = 1;", + Func: (*Rewrite).RewriteRmParenthesis, + }, + // delimiter要放在最后,不然补不上 + { + Name: "delimiter", + Description: "补全DELIMITER", + Original: "use sakila", + Suggest: "use sakila;", + Func: (*Rewrite).RewriteDelimiter, + }, + // TODO in to exists + // TODO exists to in + } } // ListRewriteRules 打印SQL重写规则 @@ -842,7 +846,7 @@ func (rw *Rewrite) RewriteAddOrderByNull() *Rewrite { } // RewriteOr2Union or2union: 将 OR 查询转写为 UNION ALL TODO: 暂无对应 HeuristicRules -// https://sqlperformance.com/2014/09/sql-plan/rewriting-queries-improve-performance +// TODO: https://sqlperformance.com/2014/09/sql-plan/rewriting-queries-improve-performance func (rw *Rewrite) RewriteOr2Union() *Rewrite { return rw } diff --git a/cmd/soar/soar_test.go b/cmd/soar/soar_test.go index 2d4beb0..46050fd 100644 --- a/cmd/soar/soar_test.go +++ b/cmd/soar/soar_test.go @@ -18,6 +18,7 @@ package main import ( "flag" + "fmt" "testing" "github.com/XiaoMi/soar/common" @@ -42,32 +43,42 @@ func TestMain(m *testing.M) { } func Test_Main(_ *testing.T) { + common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Config.OnlineDSN.Disable = true common.Config.LogLevel = 0 common.Config.Query = "select * from film;alter table city add index idx_country_id(country_id);" main() + common.Log.Debug("Exiting function: %s", common.GetFunctionName()) } func Test_Main_More(_ *testing.T) { + common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Config.LogLevel = 0 common.Config.Profiling = true common.Config.Explain = true common.Config.Query = "select * from film where country_id = 1;use sakila;alter table city add index idx_country_id(country_id);" + orgRerportType := common.Config.ReportType for _, typ := range []string{ "json", "html", "markdown", "fingerprint", "compress", "pretty", "rewrite", + "ast", "tiast", "ast-json", "tiast-json", "tokenize", } { common.Config.ReportType = typ main() } + common.Config.ReportType = orgRerportType + common.Log.Debug("Exiting function: %s", common.GetFunctionName()) } func Test_Main_checkConfig(t *testing.T) { + common.Log.Debug("Entering function: %s", common.GetFunctionName()) if checkConfig() != 0 { t.Error("checkConfig error") } + common.Log.Debug("Exiting function: %s", common.GetFunctionName()) } func Test_Main_initQuery(t *testing.T) { + common.Log.Debug("Entering function: %s", common.GetFunctionName()) // direct query query := initQuery("select 1") if query != "select 1" { @@ -79,4 +90,17 @@ func Test_Main_initQuery(t *testing.T) { // TODO: read from stdin // initQuery("") + common.Log.Debug("Exiting function: %s", common.GetFunctionName()) +} + +func Test_Main_reportTool(t *testing.T) { + common.Log.Debug("Entering function: %s", common.GetFunctionName()) + orgRerportType := common.Config.ReportType + types := []string{"html", "md2html", "explain-digest", "chardet", "remove-comment"} + for _, tp := range types { + common.Config.ReportType = tp + fmt.Println(reportTool(tp, []byte{})) + } + common.Config.ReportType = orgRerportType + common.Log.Debug("Exiting function: %s", common.GetFunctionName()) } diff --git a/common/config.go b/common/config.go index c9272d9..e6f0f13 100644 --- a/common/config.go +++ b/common/config.go @@ -294,7 +294,7 @@ func parseDSN(odbc string, d *Dsn) *Dsn { userInfo = res[1] hostInfo = res[2] query = res[4] - } else if res := regexp.MustCompile(`^(.*?)($|\?)(.*)`).FindStringSubmatch(odbc); len(res) > 3 { + } else if res := regexp.MustCompile(`^(.*?)($|\?)(.*)`).FindStringSubmatch(odbc); len(res) > 3 { // hostInfo hostInfo = res[1] query = res[3] -- GitLab