157 lines
3.6 KiB
Go
157 lines
3.6 KiB
Go
package exporter
|
|
|
|
import (
|
|
"database/sql"
|
|
)
|
|
|
|
type ExplainRow struct {
|
|
ID sql.NullInt64
|
|
SelectType sql.NullString
|
|
Table sql.NullString
|
|
Type sql.NullString
|
|
PossibleKeys sql.NullString
|
|
Key sql.NullString
|
|
KeyLen sql.NullString
|
|
Ref sql.NullString
|
|
Rows sql.NullInt64
|
|
Filtered sql.NullFloat64
|
|
Extra sql.NullString
|
|
}
|
|
|
|
func RunExplain(db *sql.DB, q string, args []interface{}) ([]ExplainRow, int, error) {
|
|
rows, err := db.Query("EXPLAIN "+q, args...)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
defer rows.Close()
|
|
res := []ExplainRow{}
|
|
cols, _ := rows.Columns()
|
|
for rows.Next() {
|
|
vals := make([]interface{}, len(cols))
|
|
dest := make([]interface{}, len(cols))
|
|
for i := range vals {
|
|
dest[i] = &vals[i]
|
|
}
|
|
if err := rows.Scan(dest...); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
r := ExplainRow{}
|
|
if len(cols) >= 10 {
|
|
toRow(vals, &r)
|
|
}
|
|
res = append(res, r)
|
|
}
|
|
score := 100
|
|
for _, r := range res {
|
|
if r.Type.String == "ALL" {
|
|
score -= 50
|
|
}
|
|
if r.Rows.Int64 > 1000000 {
|
|
score -= 30
|
|
}
|
|
if r.Extra.Valid {
|
|
if contains(r.Extra.String, "Using temporary") || contains(r.Extra.String, "Using filesort") {
|
|
score -= 20
|
|
}
|
|
}
|
|
}
|
|
if score < 0 {
|
|
score = 0
|
|
}
|
|
return res, score, nil
|
|
}
|
|
|
|
func toRow(vals []interface{}, r *ExplainRow) {
|
|
if s, ok := vals[0].([]byte); ok {
|
|
r.ID.Int64 = toInt64(string(s))
|
|
r.ID.Valid = true
|
|
}
|
|
if s, ok := vals[1].([]byte); ok {
|
|
r.SelectType.String = string(s)
|
|
r.SelectType.Valid = true
|
|
}
|
|
if s, ok := vals[2].([]byte); ok {
|
|
r.Table.String = string(s)
|
|
r.Table.Valid = true
|
|
}
|
|
if s, ok := vals[3].([]byte); ok {
|
|
r.Type.String = string(s)
|
|
r.Type.Valid = true
|
|
}
|
|
if s, ok := vals[4].([]byte); ok {
|
|
r.PossibleKeys.String = string(s)
|
|
r.PossibleKeys.Valid = true
|
|
}
|
|
if s, ok := vals[5].([]byte); ok {
|
|
r.Key.String = string(s)
|
|
r.Key.Valid = true
|
|
}
|
|
if s, ok := vals[6].([]byte); ok {
|
|
r.KeyLen.String = string(s)
|
|
r.KeyLen.Valid = true
|
|
}
|
|
if s, ok := vals[7].([]byte); ok {
|
|
r.Ref.String = string(s)
|
|
r.Ref.Valid = true
|
|
}
|
|
if s, ok := vals[8].([]byte); ok {
|
|
r.Rows.Int64 = toInt64(string(s))
|
|
r.Rows.Valid = true
|
|
}
|
|
if s, ok := vals[9].([]byte); ok {
|
|
r.Filtered.Float64 = toFloat64(string(s))
|
|
r.Filtered.Valid = true
|
|
}
|
|
if len(vals) > 10 {
|
|
if s, ok := vals[10].([]byte); ok {
|
|
r.Extra.String = string(s)
|
|
r.Extra.Valid = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func contains(s, sub string) bool {
|
|
for i := 0; i+len(sub) <= len(s); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func toInt64(s string) int64 {
|
|
var n int64
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
if c < '0' || c > '9' {
|
|
continue
|
|
}
|
|
n = n*10 + int64(c-'0')
|
|
}
|
|
return n
|
|
}
|
|
|
|
func toFloat64(s string) float64 {
|
|
var n float64
|
|
var d float64
|
|
var seen bool
|
|
d = 1
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
if c == '.' {
|
|
seen = true
|
|
continue
|
|
}
|
|
if c < '0' || c > '9' {
|
|
continue
|
|
}
|
|
if !seen {
|
|
n = n*10 + float64(c-'0')
|
|
} else {
|
|
d *= 10
|
|
n = n + float64(c-'0')/d
|
|
}
|
|
}
|
|
return n
|
|
}
|