提交 482a96e8 编写于 作者: martianzhang's avatar martianzhang

fix -config arg parse bug

  1. fix -config arg parse bug
  2. code & comment lint
上级 6bdf1d96
......@@ -20,7 +20,7 @@ package common
var TestSQLs []string
func init() {
// 所有的SQL都要以分号结尾,-list-test-sqls参数会打印这个list,以分号结尾可方便测试
// 所有的SQL都要以分号结尾,-list-test-sqls 参数会打印这个 list,以分号结尾可方便测试
// 如:./soar -list-test-sql | ./soar
TestSQLs = []string{
// single equality
......@@ -61,7 +61,7 @@ func init() {
"SELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;", // INDEX(length, release_year)",
"SELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;", // INDEX(length, release_year)",
"SELECT * FROM film ORDER BY release_year LIMIT 10;", // 不能单独给release_year加索引
"SELECT film_id FROM film ORDER BY release_year LIMIT 10;", // TODO: INDEX(release_year),film_id是主键查询列满足索引覆盖的情况才会使用到release_year索引
"SELECT film_id FROM film ORDER BY release_year LIMIT 10;", // TODO: INDEX(release_year),film_id 是主键查询列满足索引覆盖的情况才会使用到 release_year 索引
"SELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;", // INDEX(length) This "range" is compatible with ORDER BY
"SELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;", // INDEX(length) also works
"SELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;", // INDEX(address_id)
......@@ -82,8 +82,8 @@ func init() {
// Join
// 内连接 INNER JOIN
// 在mysql中,inner join...on , join...on , 逗号...WHERE ,cross join...on是一样的含义。
// 但是在标准SQL中,它们并不等价,标准SQL中INNER JOIN与ON共同使用, CROSS JOIN用于其他情况。
// 逗号不支持on和using语法, 逗号的优先级要低于INNER JOIN, CROSS JOIN, LEFT JOIN
// 但是在标准SQL中,它们并不等价,标准 SQL 中 INNER JOIN 与 ON共同使用, CROSS JOIN用于其他情况。
// 逗号不支持 on 和 using 语法, 逗号的优先级要低于INNER JOIN, CROSS JOIN, LEFT JOIN
// ON子句的语法格式为:tb1.col1 = tb2.col2列名可以不同,筛选连接后的结果,两表的对应列值相同才在结果集中。
// 当模式设计对联接表的列采用了相同的命名样式时,就可以使用 USING 语法来简化 ON 语法
......@@ -130,7 +130,7 @@ func init() {
"SELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;",
// STRAIGHT_JOIN 实际上与内连接 INNER JOIN 表现完全一致,
// 不同的是使用了 STRAIGHT_JOIN后指定表载入的顺序,city先于country载入
// 不同的是使用了 STRAIGHT_JOIN 后指定表载入的顺序,city 先于 country 载入
"SELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;",
// SEMI JOIN
......
......@@ -41,11 +41,11 @@ type Configration struct {
// +++++++++++++++测试环境+++++++++++++++++
OnlineDSN *dsn `yaml:"online-dsn"` // 线上环境数据库配置
TestDSN *dsn `yaml:"test-dsn"` // 测试环境数据库配置
AllowOnlineAsTest bool `yaml:"allow-online-as-test"` // 允许Online环境也可以当作Test环境
AllowOnlineAsTest bool `yaml:"allow-online-as-test"` // 允许 Online 环境也可以当作 Test 环境
DropTestTemporary bool `yaml:"drop-test-temporary"` // 是否清理Test环境产生的临时库表
CleanupTestDatabase bool `yaml:"cleanup-test-database"` // 清理残余的测试数据库(程序异常退出或未开启drop-test-temporary) issue #48
OnlySyntaxCheck bool `yaml:"only-syntax-check"` // 只做语法检查不输出优化建议
SamplingStatisticTarget int `yaml:"sampling-statistic-target"` // 数据采样因子,对应postgres的default_statistics_target
SamplingStatisticTarget int `yaml:"sampling-statistic-target"` // 数据采样因子,对应 PostgreSQL 的 default_statistics_target
Sampling bool `yaml:"sampling"` // 数据采样开关
Profiling bool `yaml:"profiling"` // 在开启数据采样的情况下,在测试环境执行进行profile
Trace bool `yaml:"trace"` // 在开启数据采样的情况下,在测试环境执行进行Trace
......@@ -55,29 +55,29 @@ type Configration struct {
Delimiter string `yaml:"delimiter"` // SQL分隔符
// +++++++++++++++日志相关+++++++++++++++++
// 日志级别,这里使用了beego的log
// 日志级别,这里使用了 beego 的 log
// [0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug]
LogLevel int `yaml:"log-level"`
// 日志输出位置,默认日志输出到控制台
// 目前只支持['console', 'file']两种形式,如非console形式这里需要指定文件的路径,可以是相对路径
LogOutput string `yaml:"log-output"`
// 优化建议输出格式,目前支持: json, text, markdown格式,如指定其他格式会给pretty.Println的输出
// 优化建议输出格式,目前支持: json, text, markdown格式,如指定其他格式会给 pretty.Println 的输出
ReportType string `yaml:"report-type"`
// 当ReportType为html格式时使用的css风格,如不指定会提供一个默认风格。CSS可以是本地文件,也可以是一个URL
// 当 ReportType 为 html 格式时使用的 css 风格,如不指定会提供一个默认风格。CSS可 以是本地文件,也可以是一个URL
ReportCSS string `yaml:"report-css"`
// 当ReportType为html格式时使用的javascript脚本,如不指定默认会加载SQL pretty使用的javascript。像CSS一样可以是本地文件,也可以是一个URL
// 当 ReportType 为 html 格式时使用的 javascript 脚本,如不指定默认会加载SQL pretty 使用的 javascript。像CSS一样可以是本地文件,也可以是一个URL
ReportJavascript string `yaml:"report-javascript"`
// 当ReportType为html格式时,HTML的title
// 当ReportType 为 html 格式时,HTML 的 title
ReportTitle string `yaml:"report-title"`
// blackfriday markdown2html config
MarkdownExtensions int `yaml:"markdown-extensions"` // markdown转html支持的扩展包, 参考blackfriday
MarkdownHTMLFlags int `yaml:"markdown-html-flags"` // markdown转html支持的flag, 参考blackfriday, default 0
MarkdownExtensions int `yaml:"markdown-extensions"` // markdown 转 html 支持的扩展包, 参考blackfriday
MarkdownHTMLFlags int `yaml:"markdown-html-flags"` // markdown 转 html 支持的 flag, 参考blackfriday, default 0
// ++++++++++++++优化建议相关++++++++++++++
IgnoreRules []string `yaml:"ignore-rules"` // 忽略的优化建议规则
RewriteRules []string `yaml:"rewrite-rules"` // 生效的重写规则
BlackList string `yaml:"blacklist"` // blacklist中的SQL不会被评审,可以是指纹,也可以是正则
MaxJoinTableCount int `yaml:"max-join-table-count"` // 单条SQL中JOIN表的最大数量
BlackList string `yaml:"blacklist"` // blacklist 中的 SQL 不会被评审,可以是指纹,也可以是正则
MaxJoinTableCount int `yaml:"max-join-table-count"` // 单条 SQL 中 JOIN 表的最大数量
MaxGroupByColsCount int `yaml:"max-group-by-cols-count"` // 单条SQL中GroupBy包含列的最大数量
MaxDistinctCount int `yaml:"max-distinct-count"` // 单条SQL中Distinct的最大数量
MaxIdxColsCount int `yaml:"max-index-cols-count"` // 复合索引中包含列的最大数量
......@@ -474,7 +474,7 @@ func readCmdFlags() error {
return nil
}
config := flag.String("config", "", "Config file path")
_ = flag.String("config", "", "Config file path")
// +++++++++++++++测试环境+++++++++++++++++
onlineDSN := flag.String("online-dsn", FormatDSN(Config.OnlineDSN), "OnlineDSN, 线上环境数据库配置, username:password@ip:port/schema")
testDSN := flag.String("test-dsn", FormatDSN(Config.TestDSN), "TestDSN, 测试环境数据库配置, username:password@ip:port/schema")
......@@ -486,7 +486,7 @@ func readCmdFlags() error {
trace := flag.Bool("trace", Config.Trace, "Trace, 开启数据采样的情况下在测试环境执行Trace")
explain := flag.Bool("explain", Config.Explain, "Explain, 是否开启Explain执行计划分析")
sampling := flag.Bool("sampling", Config.Sampling, "Sampling, 数据采样开关")
samplingStatisticTarget := flag.Int("sampling-statistic-target", Config.SamplingStatisticTarget, "SamplingStatisticTarget, 数据采样因子,对应postgres的default_statistics_target")
samplingStatisticTarget := flag.Int("sampling-statistic-target", Config.SamplingStatisticTarget, "SamplingStatisticTarget, 数据采样因子,对应 postgres 的 default_statistics_target")
connTimeOut := flag.Int("conn-time-out", Config.ConnTimeOut, "ConnTimeOut, 数据库连接超时时间,单位秒")
queryTimeOut := flag.Int("query-time-out", Config.QueryTimeOut, "QueryTimeOut, 数据库SQL执行超时时间,单位秒")
delimiter := flag.String("delimiter", Config.Delimiter, "Delimiter, SQL分隔符")
......@@ -494,19 +494,19 @@ func readCmdFlags() error {
logLevel := flag.Int("log-level", Config.LogLevel, "LogLevel, 日志级别, [0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug]")
logOutput := flag.String("log-output", Config.LogOutput, "LogOutput, 日志输出位置")
reportType := flag.String("report-type", Config.ReportType, "ReportType, 化建议输出格式,目前支持: json, text, markdown, html等")
reportCSS := flag.String("report-css", Config.ReportCSS, "ReportCSS, 当ReportType为html格式时使用的css风格,如不指定会提供一个默认风格。CSS可以是本地文件,也可以是一个URL")
reportJavascript := flag.String("report-javascript", Config.ReportJavascript, "ReportJavascript, 当ReportType为html格式时使用的javascript脚本,如不指定默认会加载SQL pretty使用的javascript。像CSS一样可以是本地文件,也可以是一个URL")
reportTitle := flag.String("report-title", Config.ReportTitle, "ReportTitle, 当ReportType为html格式时,HTML的title")
reportCSS := flag.String("report-css", Config.ReportCSS, "ReportCSS, 当 ReportType 为 html 格式时使用的 css 风格,如不指定会提供一个默认风格。CSS可以是本地文件,也可以是一个URL")
reportJavascript := flag.String("report-javascript", Config.ReportJavascript, "ReportJavascript, 当 ReportType 为 html 格式时使用的javascript脚本,如不指定默认会加载SQL pretty 使用的 javascript。像CSS一样可以是本地文件,也可以是一个URL")
reportTitle := flag.String("report-title", Config.ReportTitle, "ReportTitle, 当 ReportType 为 html 格式时,HTML 的 title")
// +++++++++++++++markdown+++++++++++++++++
markdownExtensions := flag.Int("markdown-extensions", Config.MarkdownExtensions, "MarkdownExtensions, markdownhtml支持的扩展包, 参考blackfriday")
markdownHTMLFlags := flag.Int("markdown-html-flags", Config.MarkdownHTMLFlags, "MarkdownHTMLFlags, markdown转html支持的flag, 参考blackfriday")
markdownExtensions := flag.Int("markdown-extensions", Config.MarkdownExtensions, "MarkdownExtensions, markdownhtml支持的扩展包, 参考blackfriday")
markdownHTMLFlags := flag.Int("markdown-html-flags", Config.MarkdownHTMLFlags, "MarkdownHTMLFlags, markdown 转 html 支持的 flag, 参考blackfriday")
// ++++++++++++++优化建议相关++++++++++++++
ignoreRules := flag.String("ignore-rules", strings.Join(Config.IgnoreRules, ","), "IgnoreRules, 忽略的优化建议规则")
rewriteRules := flag.String("rewrite-rules", strings.Join(Config.RewriteRules, ","), "RewriteRules, 生效的重写规则")
blackList := flag.String("blacklist", Config.BlackList, "blacklist中的SQL不会被评审,可以是指纹,也可以是正则")
maxJoinTableCount := flag.Int("max-join-table-count", Config.MaxJoinTableCount, "MaxJoinTableCount, 单条SQL中JOIN表的最大数量")
maxGroupByColsCount := flag.Int("max-group-by-cols-count", Config.MaxGroupByColsCount, "MaxGroupByColsCount, 单条SQL中GroupBy包含列的最大数量")
maxDistinctCount := flag.Int("max-distinct-count", Config.MaxDistinctCount, "MaxDistinctCount, 单条SQL中Distinct的最大数量")
blackList := flag.String("blacklist", Config.BlackList, "blacklist 中的 SQ L不会被评审,可以是指纹,也可以是正则")
maxJoinTableCount := flag.Int("max-join-table-count", Config.MaxJoinTableCount, "MaxJoinTableCount, 单条 SQL 中 JOIN 表的最大数量")
maxGroupByColsCount := flag.Int("max-group-by-cols-count", Config.MaxGroupByColsCount, "MaxGroupByColsCount, 单条 SQL 中 GroupBy 包含列的最大数量")
maxDistinctCount := flag.Int("max-distinct-count", Config.MaxDistinctCount, "MaxDistinctCount, 单条 SQL 中 Distinct 的最大数量")
maxIdxColsCount := flag.Int("max-index-cols-count", Config.MaxIdxColsCount, "MaxIdxColsCount, 复合索引中包含列的最大数量")
maxTotalRows := flag.Int64("max-total-rows", Config.MaxTotalRows, "MaxTotalRows, 计算散粒度时,当数据行数大于MaxTotalRows即开启数据库保护模式,不计算散粒度")
maxQueryCost := flag.Int64("max-query-cost", Config.MaxQueryCost, "MaxQueryCost, last_query_cost 超过该值时将给予警告")
......@@ -540,7 +540,7 @@ func readCmdFlags() error {
// +++++++++++++++++其他+++++++++++++++++++
printConfig := flag.Bool("print-config", false, "Print configs")
ver := flag.Bool("version", false, "Print version info")
query := flag.String("query", Config.Query, "待评审的SQL或SQL文件,如SQL中包含特殊字符建议使用文件名。")
query := flag.String("query", Config.Query, "待评审的 SQL 或 SQL 文件,如 SQL 中包含特殊字符建议使用文件名。")
listHeuristicRules := flag.Bool("list-heuristic-rules", Config.ListHeuristicRules, "ListHeuristicRules, 打印支持的评审规则列表")
listRewriteRules := flag.Bool("list-rewrite-rules", Config.ListRewriteRules, "ListRewriteRules, 打印支持的重写规则列表")
listTestSQLs := flag.Bool("list-test-sqls", Config.ListTestSqls, "ListTestSqls, 打印测试case用于测试")
......@@ -548,20 +548,13 @@ func readCmdFlags() error {
verbose := flag.Bool("verbose", Config.Verbose, "Verbose")
dryrun := flag.Bool("dry-run", Config.DryRun, "是否在预演环境执行")
maxPrettySQLLength := flag.Int("max-pretty-sql-length", Config.MaxPrettySQLLength, "MaxPrettySQLLength, 超出该长度的SQL会转换成指纹输出")
// 一个不存在log-level,用于更新usage。
// 因为vitess里面也用了flag,这些vitess的参数我们不需要关注
// 一个不存在 log-level,用于更新 usage。
// 因为 vitess 里面也用了 flag,这些 vitess 的参数我们不需要关注
if !Config.Verbose && runtime.GOOS != "windows" {
flag.Usage = usage
}
flag.Parse()
if *config != "" {
err := Config.readConfigFile(*config)
if err != nil {
fmt.Println(err.Error())
}
}
Config.OnlineDSN = parseDSN(*onlineDSN, Config.OnlineDSN)
Config.TestDSN = parseDSN(*testDSN, Config.TestDSN)
Config.AllowOnlineAsTest = *allowOnlineAsTest
......@@ -841,3 +834,34 @@ func ListReportTypes() {
}
}
}
// ArgConfig get -config arg value from cli
func ArgConfig() string {
var configFile string
if len(os.Args) > 1 && strings.HasPrefix(os.Args[1], "-config") {
if os.Args[1] == "-config" && len(os.Args) > 2 {
if os.Args[2] == "=" && len(os.Args) > 3 {
// -config = soar.yaml not support
fmt.Println("wrong format, no space between '=', eg: -config=soar.yaml")
} else {
// -config soar.yaml
configFile = os.Args[2]
}
if strings.HasPrefix(configFile, "=") {
// -config =soar.yaml
configFile = strings.Split(configFile, "=")[1]
}
}
if strings.Contains(os.Args[1], "=") {
// -config=soar.yaml
configFile = strings.Split(os.Args[1], "=")[1]
}
} else {
for i, c := range os.Args {
if strings.HasPrefix(c, "-config") && i != 1 {
fmt.Println("-config must be the first arg")
}
}
}
return configFile
}
......@@ -18,6 +18,7 @@ package common
import (
"flag"
"os"
"runtime"
"testing"
......@@ -83,3 +84,29 @@ func TestListReportTypes(t *testing.T) {
t.Fatal(err)
}
}
func TestArgConfig(t *testing.T) {
testArgs1 := [][]string{
{"soar", "-config", "=", "soar.yaml"},
{"soar", "-print-config", "-config", "soar.yaml"},
}
testArgs2 := [][]string{
{"soar", "-config", "soar.yaml"},
{"soar", "-config", "=soar.yaml"},
{"soar", "-config=soar.yaml"},
}
for _, args := range testArgs1 {
os.Args = args
configFile := ArgConfig()
if configFile != "" {
t.Errorf("should return '', but got %s", configFile)
}
}
for _, args := range testArgs2 {
os.Args = args
configFile := ArgConfig()
if configFile != "soar.yaml" {
t.Errorf("should return soar.yaml, but got %s", configFile)
}
}
}
......@@ -25,7 +25,7 @@ import (
"github.com/astaxie/beego/logs"
)
// Log 使用beego的log
// Log 使用 beego 的 log
var Log *logs.BeeLogger
// BaseDir 日志打印在binary的根路径
......@@ -95,7 +95,7 @@ func fileName(original string) string {
return original[i+1:]
}
// LogIfError 简化if err != nil打Error日志代码长度
// LogIfError 简化if err != nil 打 Error 日志代码长度
func LogIfError(err error, format string, v ...interface{}) {
if err != nil {
_, fn, line, _ := runtime.Caller(1)
......@@ -109,7 +109,7 @@ func LogIfError(err error, format string, v ...interface{}) {
}
}
// LogIfWarn 简化if err != nil打Warn日志代码长度
// LogIfWarn 简化if err != nil 打 Warn 日志代码长度
func LogIfWarn(err error, format string, v ...interface{}) {
if err != nil {
_, fn, line, _ := runtime.Caller(1)
......
......@@ -35,14 +35,14 @@ func TestLogger(t *testing.T) {
func TestCaller(t *testing.T) {
caller := Caller()
if caller != "testing.tRunner" {
t.Error("get caller failer")
t.Error("get caller failed")
}
}
func TestGetFunctionName(t *testing.T) {
f := GetFunctionName()
if f != "TestGetFunctionName" {
t.Error("get functionname failer")
t.Error("get functionname failed")
}
}
......
此差异已折叠。
......@@ -50,7 +50,7 @@ func TestMarkdown2Html(t *testing.T) {
t.Fatal(err)
}
// golden文件拷贝成html文件,这步是给人看的
// golden 文件拷贝成 html 文件,这步是给人看的
gd, err := os.OpenFile("testdata/"+t.Name()+".golden", os.O_RDONLY, 0666)
if nil != err {
t.Fatal(err)
......
......@@ -21,7 +21,7 @@ import (
"strings"
)
// Meta 以'database'为key, DB的map,按db->table->column组织的元数据
// Meta 以 'database' 为 key, DB 的 map, 按 db->table->column 组织的元数据
type Meta map[string]*DB
// DB 数据库相关的结构体
......@@ -110,7 +110,7 @@ func JoinColumnsName(cols []*Column, sep string) string {
return strings.Trim(name, sep)
}
// Tables 获取Meta中指定db的所有表名
// Tables 获取 Meta 中指定 db 的所有表名
// Input:数据库名
// Output:表名组成的list
func (b Meta) Tables(db string) []string {
......@@ -132,7 +132,7 @@ func (b Meta) SetDefault(defaultDB string) Meta {
for db := range b {
if db == "" {
// 当获取到的join中的DB为空的时候,说明SQL未显示的指定DB,即使用的是rEnv默认DB,需要将表合并到原DB
// 当获取到的 join 中的 DB 为空的时候,说明 SQL 未显示的指定 DB,即使用的是 rEnv 默认 DB, 需要将表合并到原 DB
if _, ok := b[defaultDB]; ok {
for tbName, table := range b[""].Table {
if _, ok := b[defaultDB].Table[tbName]; ok {
......@@ -156,8 +156,8 @@ func (b Meta) SetDefault(defaultDB string) Meta {
return b
}
// MergeColumn 将使用到的列按db->table组织去重
// 注意:Column中的db, table信息可能为空,需要提前通过env环境补齐再调用该函数。
// MergeColumn 将使用到的列按 db->table 组织去重
// 注意:Column 中的 db, table 信息可能为空,需要提前通过env环境补齐再调用该函数。
// @input: 目标列list, 源列list(可以将多个源合并到一个目标列list)
// @output: 合并后的列list
func MergeColumn(dst []*Column, src ...*Column) []*Column {
......
......@@ -32,10 +32,8 @@ func HandleSignal(f func()) {
syscall.SIGQUIT)
go func() {
select {
case n := <-sc:
Log.Info("receive signal %v, closing", n)
f()
}
n := <-sc
Log.Info("receive signal %v, closing", n)
f()
}()
}
/*
* Copyright 2018 Xiaomi, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package common
import (
"fmt"
"testing"
)
func TestHandleSignal(t *testing.T) {
HandleSignal(func() {
fmt.Println("done")
})
}
......@@ -28,7 +28,7 @@ import (
"sort"
)
// GoldenDiff 从gofmt学来的测试方法
// GoldenDiff 从 gofmt 学来的测试方法
// https://medium.com/soon-london/testing-with-golden-files-in-go-7fccc71c43d3
func GoldenDiff(f func(), name string, update *bool) error {
var b bytes.Buffer
......
......@@ -39,7 +39,7 @@ const (
JSONFormatExplain // JSON格式输出
)
// ExplainFormatType EXPLAIN支持的FORMAT_TYPE
// ExplainFormatType EXPLAIN 支持的 FORMAT_TYPE
var ExplainFormatType = map[string]int{
"traditional": 0,
"json": 1,
......@@ -80,7 +80,7 @@ type ExplainRow struct {
AccessType string
PossibleKeys []string
Key string
KeyLen string // 索引长度,如果发生了index_merge, KeyLen格式为N,N,所以不能定义为整型
KeyLen string // 索引长度,如果发生了index_merge, KeyLen 格式为 N,N,所以不能定义为整型
Ref []string
Rows int
Filtered float64 // 5.6 JSON, 5.7+, 5.5 EXTENDED
......@@ -88,7 +88,7 @@ type ExplainRow struct {
Extra string
}
// ExplainWarning explain extended后SHOW WARNINGS输出的结果
// ExplainWarning explain extended 后 SHOW WARNINGS 输出的结果
type ExplainWarning struct {
Level string
Code int
......@@ -117,7 +117,7 @@ type ExplainJSONMaterializedFromSubquery struct {
QueryBlock *ExplainJSONQueryBlock `json:"query_block"`
}
// 该变量用于存放JSON到Traditional模式的所有ExplainJSONTable
// 该变量用于存放 JSON 到 Traditional 模式的所有 ExplainJSONTable
var explainJSONTables []*ExplainJSONTable
// ExplainJSONTable JSON
......@@ -451,8 +451,8 @@ func FormatJSONIntoTraditional(explainJSON string) []*ExplainRow {
return explainRows
}
// ConvertExplainJSON2Row 将JSON格式转成ROW格式,为方便统一做优化建议
// 但是会损失一些JSON特有的分析结果
// ConvertExplainJSON2Row 将 JSON 格式转成 ROW 格式,为方便统一做优化建议
// 但是会损失一些 JSON 特有的分析结果
func ConvertExplainJSON2Row(explainJSON *ExplainJSON) []*ExplainRow {
buf, err := json.Marshal(explainJSON)
if err != nil {
......@@ -461,8 +461,8 @@ func ConvertExplainJSON2Row(explainJSON *ExplainJSON) []*ExplainRow {
return FormatJSONIntoTraditional(string(buf))
}
// 用于检测MySQL版本是否低于MySQL5.6
// 低于5.6 返回 true, 表示需要改写非SELECT的SQL --> SELECT
// 用于检测 MySQL 版本是否低于 MySQL5.6
// 低于5.6 返回 true, 表示需要改写非 SELECT 的 SQL --> SELECT
func (db *Connector) supportExplainWrite() (bool, error) {
defer func() {
err := recover()
......@@ -471,8 +471,8 @@ func (db *Connector) supportExplainWrite() (bool, error) {
}
}()
// 5.6以上版本支持EXPLAIN UPDATE/DELETE等语句,但需要开启写入
// 如开启了read_only,EXPLAIN UPDATE/DELETE也会受限制
// 5.6以上版本支持 EXPLAIN UPDATE/DELETE 等语句,但需要开启写入
// 如开启了 read_only, EXPLAIN UPDATE/DELETE 也会受限制
if common.Config.TestDSN.Version >= 560 {
readOnly, err := db.SingleIntValue("read_only")
if err != nil {
......@@ -506,8 +506,8 @@ func (db *Connector) explainAbleSQL(sql string) (string, error) {
}
switch stmt.(type) {
case *sqlparser.Insert, *sqlparser.Update, *sqlparser.Delete: // REPLACE和INSERT的AST基本相同,只是Action不同
// 判断Explain的SQL是否需要被改写
case *sqlparser.Insert, *sqlparser.Update, *sqlparser.Delete: // REPLACE 和 INSERT 的 AST 基本相同,只是 Action 不同
// 判断 Explain 的 SQL 是否需要被改写
need, err := db.supportExplainWrite()
if err != nil {
common.Log.Error("explainAbleSQL db.supportExplainWrite Error: %v", err)
......@@ -901,12 +901,12 @@ func parseJSONExplainText(content string) (*ExplainJSON, error) {
return explainJSON, err
}
// ParseExplainResult 分析mysql执行explain的结果,返回ExplainInfo结构化数据
// ParseExplainResult 分析 mysql 执行 explain 的结果,返回 ExplainInfo 结构化数据
func ParseExplainResult(res *QueryResult, formatType int) (exp *ExplainInfo, err error) {
exp = &ExplainInfo{
ExplainFormat: formatType,
}
// JSON格式直接调用文本方式解析
// JSON 格式直接调用文本方式解析
if formatType == JSONFormatExplain {
exp.ExplainJSON, err = parseJSONExplainText(res.Rows[0].Str(0))
return exp, err
......@@ -917,11 +917,11 @@ func ParseExplainResult(res *QueryResult, formatType int) (exp *ExplainInfo, err
for i, f := range res.Result.Fields() {
colIdx[i] = strings.ToLower(f.Name)
}
// 补全ExplainRows
// 补全 ExplainRows
var explainrows []*ExplainRow
for _, row := range res.Rows {
expRow := &ExplainRow{Partitions: "NULL", Filtered: 0.00}
// list到map的转换
// list 到 map 的转换
for i := range row {
switch colIdx[i] {
case "id":
......@@ -981,7 +981,7 @@ func ParseExplainResult(res *QueryResult, formatType int) (exp *ExplainInfo, err
return exp, err
}
// Explain 获取SQL的explain信息
// Explain 获取 SQL 的 explain 信息
func (db *Connector) Explain(sql string, explainType int, formatType int) (exp *ExplainInfo, err error) {
exp = &ExplainInfo{SQL: sql}
if explainType != TraditionalExplainType {
......@@ -1009,11 +1009,11 @@ func (db *Connector) Explain(sql string, explainType int, formatType int) (exp *
return exp, err
}
// PrintMarkdownExplainTable 打印markdown格式的explain table
// PrintMarkdownExplainTable 打印 markdown 格式的 explain table
func PrintMarkdownExplainTable(exp *ExplainInfo) string {
var buf []string
rows := exp.ExplainRows
// JSON转换为TRADITIONAL格式
// JSON 转换为 TRADITIONAL 格式
if exp.ExplainFormat == JSONFormatExplain {
buf = append(buf, fmt.Sprint("以下为JSON格式转为传统格式EXPLAIN表格", "\n\n"))
rows = ConvertExplainJSON2Row(exp.ExplainJSON)
......
......@@ -65,7 +65,7 @@ func (db *Connector) Query(sql string, params ...interface{}) (*QueryResult, err
return nil, errors.New("TestDsn Disable")
}
// 数据库安全性检查:如果Connector的IP端口与TEST环境不一致,则启用SQL白名单
// 数据库安全性检查:如果 Connector 的 IP 端口与 TEST 环境不一致,则启用SQL白名单
// 不在白名单中的SQL不允许执行
// 执行环境与test环境不相同
if db.Addr != common.Config.TestDSN.Addr && db.dangerousQuery(sql) {
......@@ -97,7 +97,7 @@ func (db *Connector) Query(sql string, params ...interface{}) (*QueryResult, err
}
}
// SHOW WARNINGS并不会影响last_query_cost
// SHOW WARNINGS 并不会影响 last_query_cost
if common.Config.ShowLastQueryCost {
cost, _, err := conn.Query("SHOW SESSION STATUS LIKE 'last_query_cost'")
if err == nil {
......@@ -278,7 +278,7 @@ func RemoveSQLComments(sql []byte) []byte {
})
}
// 为了防止在Online环境进行误操作,通过dangerousQuery来判断能否在Online执行
// 为了防止在 Online 环境进行误操作,通过 dangerousQuery 来判断能否在 Online 执行
func (db *Connector) dangerousQuery(query string) bool {
queries, err := sqlparser.SplitStatementToPieces(strings.TrimSpace(strings.ToLower(query)))
if err != nil {
......
......@@ -42,7 +42,7 @@ type ProfilingRow struct {
// Profiling 执行SQL,并对其Profiling
func (db *Connector) Profiling(sql string, params ...interface{}) (*QueryResult, error) {
// 过滤不需要profiling的SQL
// 过滤不需要 profiling 的 SQL
switch sqlparser.Preview(sql) {
case sqlparser.StmtSelect, sqlparser.StmtUpdate, sqlparser.StmtDelete:
default:
......@@ -54,7 +54,7 @@ func (db *Connector) Profiling(sql string, params ...interface{}) (*QueryResult,
return nil, errors.New("TestDsn Disable")
}
// 数据库安全性检查:如果Connector的IP端口与TEST环境不一致,则启用SQL白名单
// 数据库安全性检查:如果 Connector 的 IP 端口与 TEST 环境不一致,则启用 SQL 白名单
// 不在白名单中的SQL不允许执行
// 执行环境与test环境不相同
if db.Addr != common.Config.TestDSN.Addr && db.dangerousQuery(sql) {
......
......@@ -52,7 +52,7 @@ func (db *Connector) SamplingData(remote Connector, tables ...string) error {
// 计算需要泵取的数据量
wantRowsCount := 300 * common.Config.SamplingStatisticTarget
// 设置数据采样单条SQL中value的数量
// 设置数据采样单条 SQL 中 value 的数量
// 该数值越大,在内存中缓存的data就越多,但相对的,插入时速度就越快
maxValCount := 200
......@@ -188,7 +188,7 @@ func startSampling(conn, localConn mysql.Conn, database, table string, factor fl
}
// 非text/varchar类的数据类型,如果dump出的数据为空,则说明该值为null值
// 应转换其value为null,如果用空('')进行替代,会导致出现语法错误。
// 应转换其 value 为 null,如果用空('')进行替代,会导致出现语法错误。
if len(dataTypes) == len(res.Fields()) && values[i] == "" &&
(!strings.Contains(dataTypes[i], "char") ||
!strings.Contains(dataTypes[i], "text")) {
......
......@@ -41,7 +41,7 @@ type tableStatusRow struct {
Engine string // 该表使用的存储引擎
Version int // 该表的 .frm 文件版本号
RowFormat string // 该表使用的行存储格式
Rows int64 // 表行数InnoDB 引擎中为预估值,甚至可能会有40%~50%的数值偏差
Rows int64 // 表行数, InnoDB 引擎中为预估值,甚至可能会有40%~50%的数值偏差
AvgRowLength int // 平均行长度
// MyISAM: Data_length 为数据文件的大小,单位为 bytes
......@@ -333,7 +333,7 @@ func NewTableDesc(tableName string) *TableDesc {
}
}
// ShowColumns 获取DB中所有的columns
// ShowColumns 获取 DB 中所有的 columns
func (db *Connector) ShowColumns(tableName string) (*TableDesc, error) {
tbDesc := NewTableDesc(tableName)
......@@ -545,8 +545,8 @@ type ReferenceValue struct {
// ShowReference 查找所有的外键信息
func (db *Connector) ShowReference(dbName string, tbName ...string) ([]ReferenceValue, error) {
var referenceValues []ReferenceValue
sql := `SELECT C.REFERENCED_TABLE_SCHEMA,C.REFERENCED_TABLE_NAME,C.TABLE_SCHEMA,C.TABLE_NAME,C.CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE C JOIN INFORMATION_SCHEMA. TABLES T ON T.TABLE_NAME = C.TABLE_NAME
sql := `SELECT C.REFERENCED_TABLE_SCHEMA,C.REFERENCED_TABLE_NAME,C.TABLE_SCHEMA,C.TABLE_NAME,C.CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE C JOIN INFORMATION_SCHEMA. TABLES T ON T.TABLE_NAME = C.TABLE_NAME
WHERE C.REFERENCED_TABLE_NAME IS NOT NULL`
sql = sql + fmt.Sprintf(` AND C.TABLE_SCHEMA = "%s"`, dbName)
......
......@@ -49,7 +49,7 @@ func (db *Connector) Trace(sql string, params ...interface{}) (*QueryResult, err
return nil, errors.New("version < 5.6, not support trace")
}
// 过滤不需要Trace的SQL
// 过滤不需要 Trace 的 SQL
switch sqlparser.Preview(sql) {
case sqlparser.StmtSelect, sqlparser.StmtUpdate, sqlparser.StmtDelete:
sql = "explain " + sql
......@@ -63,7 +63,7 @@ func (db *Connector) Trace(sql string, params ...interface{}) (*QueryResult, err
return nil, errors.New("TestDsn Disable")
}
// 数据库安全性检查:如果Connector的IP端口与TEST环境不一致,则启用SQL白名单
// 数据库安全性检查:如果 Connector 的 IP 端口与 TEST 环境不一致,则启用SQL白名单
// 不在白名单中的SQL不允许执行
// 执行环境与test环境不相同
if db.Addr != common.Config.TestDSN.Addr && db.dangerousQuery(sql) {
......
......@@ -175,7 +175,7 @@ func (ve *VirtualEnv) CleanupTestDatabase() {
continue
}
subHour := time.Now().Sub(pastTime).Hours()
subHour := time.Since(pastTime).Hours()
if subHour > float64(minHour) {
if _, err := ve.Query("drop database %s", testDatabase); err != nil {
common.Log.Error("CleanupTestDatabase failed Error: %s", err.Error())
......@@ -188,7 +188,6 @@ func (ve *VirtualEnv) CleanupTestDatabase() {
}
common.Log.Debug("CleanupTestDatabase done")
return
}
// BuildVirtualEnv rEnv为SQL源环境,DB使用的信息从接口获取
......@@ -375,7 +374,7 @@ func (ve VirtualEnv) createDatabase(rEnv database.Connector, dbName string) erro
如果一个SQL中存在多个数据库,则只能有一个数据库是没有在SQL中被显示指定的(即DSN中指定的数据库)
TODO:
在一些可能的情况下,由于数据库配置的不一致(如SQL_MODE不同)导致remote环境的库表无法正确的在测试环境进行同步,
soar能够做出判断并进行session级别的修改,但是这一阶段可用性保证应该是由用户提供两个完全相同(或测试环境兼容线上环境)
soar 能够做出判断并进行 session 级别的修改,但是这一阶段可用性保证应该是由用户提供两个完全相同(或测试环境兼容线上环境)
的数据库环境来实现的。
*/
func (ve VirtualEnv) createTable(rEnv database.Connector, dbName, tbName string) error {
......
......@@ -115,6 +115,10 @@ func TestNewVirtualEnv(t *testing.T) {
func TestCleanupTestDatabase(t *testing.T) {
vEnv, _ := BuildEnv()
if common.Config.TestDSN.Disable {
common.Log.Warn("common.Config.TestDSN.Disable=true, by pass TestCleanupTestDatabase")
return
}
vEnv.Query("drop database if exists optimizer_060102150405_xxxxxxxxxxxxxxxx")
_, err := vEnv.Query("create database optimizer_060102150405_xxxxxxxxxxxxxxxx")
if err != nil {
......@@ -123,7 +127,7 @@ func TestCleanupTestDatabase(t *testing.T) {
vEnv.CleanupTestDatabase()
_, err = vEnv.Query("show create database optimizer_060102150405_xxxxxxxxxxxxxxxx")
if err == nil {
t.Error("optimizer_060102150405_xxxxxxxxxxxxxxxx exist, should be droped")
t.Error("optimizer_060102150405_xxxxxxxxxxxxxxxx exist, should be dropped")
}
vEnv.Query("drop database if exists optimizer_060102150405")
......@@ -134,7 +138,7 @@ func TestCleanupTestDatabase(t *testing.T) {
vEnv.CleanupTestDatabase()
_, err = vEnv.Query("drop database optimizer_060102150405")
if err != nil {
t.Error("optimizer_060102150405 not exist, should not be droped")
t.Error("optimizer_060102150405 not exist, should not be dropped")
}
}
......
......@@ -1016,68 +1016,68 @@
{
"checksumSHA1": "w8FCRjH70gM6QttB9QrEh9Y1x64=",
"path": "vitess.io/vitess",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "aKn1oKcY74N8TRLm3Ayt7Q4bbI4=",
"path": "vitess.io/vitess/go/bytes2",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "JVCEN4UGRmg3TofIBdzZMZ3G0Ww=",
"path": "vitess.io/vitess/go/hack",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "e1WJ7vCnVrlQQQlc6n/FewCDMso=",
"path": "vitess.io/vitess/go/sqltypes",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "ntFIQYkBS51G6y+FEkjFW40+HOU=",
"path": "vitess.io/vitess/go/vt/log",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "XozR8bmeSR5KTe/nlUJkpJY2HKI=",
"path": "vitess.io/vitess/go/vt/proto/query",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "OnWsUHLDKcO3spwH0jD55SvKD24=",
"path": "vitess.io/vitess/go/vt/proto/topodata",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "sBAuZ/itMR8U8qbK4yLHxkP6Cpc=",
"path": "vitess.io/vitess/go/vt/proto/vtgate",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "pLWM+SPGZs3k+IhjktE/cGUlpM0=",
"path": "vitess.io/vitess/go/vt/proto/vtrpc",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "re3V8oX+ujxHbNZuB+QEtrcXxE8=",
"path": "vitess.io/vitess/go/vt/sqlparser",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
},
{
"checksumSHA1": "oF4XzuOzwvj1iduX/lYqNSyY/HM=",
"path": "vitess.io/vitess/go/vt/vterrors",
"revision": "3db5b2f0eb75f490c755bf20d35fd3afdacdd920",
"revisionTime": "2018-10-30T14:25:51Z"
"revision": "6fca9975675109decbf1c389641597929824eeba",
"revisionTime": "2018-10-31T20:10:04Z"
}
],
"rootPath": "github.com/XiaoMi/soar"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册