Skip to content
This repository was archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
PMM-2087,PMM-2129,PMM-2014,PMM-1928,PMM-1745,PMM-854: Fixes for mixed…
Browse files Browse the repository at this point in the history
… db + "SELECT dual";
  • Loading branch information
arvenil committed Feb 23, 2018
1 parent 2dfd7a4 commit a35189e
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 188 deletions.
2 changes: 1 addition & 1 deletion app/controllers/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (c *Query) GetTables(id string) revel.Result {
}

queryHandler := query.NewMySQLHandler(dbm, stats.NullStats())
tables, _, err := queryHandler.Tables(classId, shared.TableParser)
tables, err := queryHandler.Tables(classId, shared.TableParser)
if err != nil {
return c.Error(err, "Query.GetTables: queryHandler.Tables")
}
Expand Down
21 changes: 8 additions & 13 deletions app/qan/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func (h *MySQLMetricWriter) getClassId(checksum string) (uint, error) {
}

func (h *MySQLMetricWriter) newClass(instanceId uint, subsystem string, class *event.Class, lastSeen string) (uint, error) {
var queryAbstract, queryQuery string
var queryAbstract, queryFingerprint string
var tables interface{}

switch subsystem {
Expand All @@ -277,24 +277,24 @@ func (h *MySQLMetricWriter) newClass(instanceId uint, subsystem string, class *e

// Truncate long fingerprints and abstracts to avoid MySQL warning 1265:
// Data truncated for column 'abstract'
if len(query.Query) > MAX_FINGERPRINT {
query.Query = query.Query[0:MAX_FINGERPRINT-3] + "..."
if len(query.Fingerprint) > MAX_FINGERPRINT {
query.Fingerprint = query.Fingerprint[0:MAX_FINGERPRINT-3] + "..."
}
if len(query.Abstract) > MAX_ABSTRACT {
query.Abstract = query.Abstract[0:MAX_ABSTRACT-3] + "..."
}
queryAbstract = query.Abstract
queryQuery = query.Query
queryFingerprint = query.Fingerprint
case instance.SubsystemNameMongo:
queryAbstract = class.Fingerprint
queryQuery = class.Fingerprint
queryFingerprint = class.Fingerprint
}

// Create the query class which is internally identified by its query_class_id.
// The query checksum is the class is identified externally (in a QAN report).
// Since this is the first time we've seen the query, firstSeen=lastSeen.
t := time.Now()
res, err := h.stmtInsertQueryClass.Exec(class.Id, queryAbstract, queryQuery, tables, lastSeen, lastSeen)
res, err := h.stmtInsertQueryClass.Exec(class.Id, queryAbstract, queryFingerprint, tables, lastSeen, lastSeen)

h.stats.TimingDuration(h.stats.System("insert-query-class"), time.Now().Sub(t), h.stats.SampleRate)
if err != nil {
Expand Down Expand Up @@ -327,13 +327,11 @@ func (h *MySQLMetricWriter) getQueryAndTables(class *event.Class) (query.QueryIn
return queryInfo, "", fmt.Errorf("empty fingerprint")
}

class.Fingerprint = strings.TrimSpace(class.Fingerprint)
// If we have a query example, that's better to parse than a fingerprint
queryExample := class.Fingerprint
queryExample := ""
if class.Example != nil && class.Example.Query != "" {
queryExample = class.Example.Query
}
query, err := h.m.Parse(queryExample, schema)
query, err := h.m.Parse(class.Fingerprint, queryExample, schema)
if err != nil {
return queryInfo, "", err
}
Expand All @@ -342,9 +340,6 @@ func (h *MySQLMetricWriter) getQueryAndTables(class *event.Class) (query.QueryIn
bytes, _ := json.Marshal(query.Tables)
tables = string(bytes)
}
// We still want to store the fingerprint in the database
// even if an example is available
query.Query = class.Fingerprint
return query, tables, nil
}

Expand Down
36 changes: 24 additions & 12 deletions app/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,47 +180,59 @@ func (h *MySQLHandler) UpdateTables(classId uint, tables []queryProto.Table) err
return nil
}

func (h *MySQLHandler) Tables(classId uint, m *queryService.Mini) ([]queryProto.Table, bool, error) {
created := false

func (h *MySQLHandler) Tables(classId uint, m *queryService.Mini) ([]queryProto.Table, error) {
// First try to get the tables. If we're lucky, they've already been parsed
// and we're done.
var tablesJSON string
err := h.dbm.DB().QueryRow("SELECT COALESCE(tables, '') FROM query_classes WHERE query_class_id = ?", classId).Scan(&tablesJSON)
if err != nil {
return nil, created, mysql.Error(err, "Tables: SELECT query_classes (tables)")
return nil, mysql.Error(err, "Tables: SELECT query_classes (tables)")
}

// We're lucky: we already have tables.
if tablesJSON != "" {
var tables []queryProto.Table
if err := json.Unmarshal([]byte(tablesJSON), &tables); err != nil {
return nil, created, err
return nil, err
}
return tables, created, nil
return tables, nil
}

// We're not lucky: this query hasn't been parsed yet, so do it now, if possible.
var fingerprint string
err = h.dbm.DB().QueryRow("SELECT fingerprint FROM query_classes WHERE query_class_id = ?", classId).Scan(&fingerprint)
if err != nil {
return nil, created, mysql.Error(err, "Tables: SELECT query_classes (fingerprint)")
return nil, mysql.Error(err, "Tables: SELECT query_classes (fingerprint)")
}

// Get database from latest example.
var example, db string
err = h.dbm.DB().QueryRow(
"SELECT query, db "+
" FROM query_examples "+
" JOIN query_classes c USING (query_class_id)"+
" JOIN instances i USING (instance_id)"+
" WHERE query_class_id = ?"+
" ORDER BY period DESC",
classId,
).Scan(&example, &db)
if err != nil {
return nil, mysql.Error(err, "Tables: SELECT query_examples (db)")
}

// If this returns an error, then youtube/vitess/go/sqltypes/sqlparser
// doesn't support the query type.
tableInfo, err := m.Parse(fingerprint, "")
tableInfo, err := m.Parse(fingerprint, example, db)
if err != nil {
return nil, created, shared.ErrNotImplemented
return nil, shared.ErrNotImplemented
}

// The sqlparser was able to handle the query, so marshal the tables
// into a string and update the tables column so next time we don't
// have to parse the query.
if err := h.UpdateTables(classId, tableInfo.Tables); err != nil {
return nil, created, err
return nil, err
}
created = true

return tableInfo.Tables, created, nil
return tableInfo.Tables, nil
}
14 changes: 14 additions & 0 deletions app/query/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/percona/qan-api/app/db"
"github.com/percona/qan-api/app/query"
"github.com/percona/qan-api/config"
queryService "github.com/percona/qan-api/service/query"
"github.com/percona/qan-api/stats"
"github.com/percona/qan-api/test"
testDb "github.com/percona/qan-api/tests/setup/db"
Expand Down Expand Up @@ -91,3 +92,16 @@ func (s *TestSuite) TestSimple(t *C) {

assert.Equal(t, expect, got)
}

func (s *TestSuite) TestTables(t *C) {
m := queryService.NewMini(config.ApiRootDir + "/service/query")
go m.Run()
defer m.Stop()

s.testDb.LoadDataInfiles(config.TestDir + "/qan/may-2015")

qh := query.NewMySQLHandler(db.DBManager, s.nullStats)
got, err := qh.Tables(328, m)
assert.NoError(t, err)
assert.Equal(t, []queryProto.Table([]queryProto.Table{{Db: "percona", Table: "cache"}}), got)
}
Loading

0 comments on commit a35189e

Please sign in to comment.