/
signature.go
126 lines (115 loc) · 2.67 KB
/
signature.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// +build go1.9
package apmgocql
import (
"strings"
"go.elastic.co/apm/internal/sqlscanner"
)
// querySignature returns the "signature" for a Cassandra query:
// a high level description of the operation.
//
// For DDL statements (CREATE, DROP, ALTER, etc.), we we only
// report the first keyword, on the grounds that these statements
// are not expected to be common within the hot code paths of
// an application. For DML statements, we extract and include
// the table name in the signature.
func querySignature(query string) string {
s := sqlscanner.NewScanner(query)
for s.Scan() {
if s.Token() != sqlscanner.COMMENT {
break
}
}
scanUntil := func(until sqlscanner.Token) bool {
for s.Scan() {
if s.Token() == until {
return true
}
}
return false
}
scanToken := func(tok sqlscanner.Token) bool {
for s.Scan() {
switch s.Token() {
case tok:
return true
case sqlscanner.COMMENT:
default:
return false
}
}
return false
}
switch s.Token() {
case sqlscanner.DELETE:
if !scanUntil(sqlscanner.FROM) {
break
}
if !scanToken(sqlscanner.IDENT) {
break
}
tableName := s.Text()
for scanToken(sqlscanner.PERIOD) && scanToken(sqlscanner.IDENT) {
tableName += "." + s.Text()
}
return "DELETE FROM " + tableName
case sqlscanner.INSERT:
if !scanUntil(sqlscanner.INTO) {
break
}
if !scanToken(sqlscanner.IDENT) {
break
}
tableName := s.Text()
for scanToken(sqlscanner.PERIOD) && scanToken(sqlscanner.IDENT) {
tableName += "." + s.Text()
}
return "INSERT INTO " + tableName
case sqlscanner.SELECT:
var level int
scanLoop:
for s.Scan() {
switch tok := s.Token(); tok {
case sqlscanner.LPAREN:
level++
case sqlscanner.RPAREN:
level--
case sqlscanner.FROM:
if level != 0 {
continue scanLoop
}
if !scanToken(sqlscanner.IDENT) {
break scanLoop
}
tableName := s.Text()
for scanToken(sqlscanner.PERIOD) && scanToken(sqlscanner.IDENT) {
tableName += "." + s.Text()
}
return "SELECT FROM " + tableName
}
}
case sqlscanner.TRUNCATE:
if !scanUntil(sqlscanner.IDENT) {
break
}
tableName := s.Text()
for scanToken(sqlscanner.PERIOD) && scanToken(sqlscanner.IDENT) {
tableName += "." + s.Text()
}
return "TRUNCATE " + tableName
case sqlscanner.UPDATE:
if !scanToken(sqlscanner.IDENT) {
break
}
tableName := s.Text()
for scanToken(sqlscanner.PERIOD) && scanToken(sqlscanner.IDENT) {
tableName += "." + s.Text()
}
return "UPDATE " + tableName
}
// If all else fails, just return the first token of the query.
fields := strings.Fields(query)
if len(fields) == 0 {
return ""
}
return strings.ToUpper(fields[0])
}