forked from skeema/tengo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
routine.go
127 lines (114 loc) · 4.58 KB
/
routine.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
127
package tengo
import (
"fmt"
"strings"
)
// Routine represents a stored procedure or function.
type Routine struct {
Name string `json:"name"`
Type ObjectType `json:"type"` // Will be ObjectTypeProcedure or ObjectTypeFunction
Body string `json:"body"` // Has correct escaping despite I_S mutilating it
ParamString string `json:"paramString"` // Formatted as per original CREATE
ReturnDataType string `json:"returnDataType,omitempty"` // Includes charset/collation when relevant
Definer string `json:"definer"`
DatabaseCollation string `json:"dbCollation"` // from creation time
Comment string `json:"comment,omitempty"`
Deterministic bool `json:"deterministic,omitempty"`
SQLDataAccess string `json:"sqlDataAccess,omitempty"`
SecurityType string `json:"securityType"`
SQLMode string `json:"sqlMode"` // sql_mode in effect at creation time
CreateStatement string `json:"showCreate"` // complete SHOW CREATE obtained from an instance
}
// Definition generates and returns a canonical CREATE PROCEDURE or CREATE
// FUNCTION statement based on the Routine's Go field values.
func (r *Routine) Definition(flavor Flavor) string {
return fmt.Sprintf("%s%s", r.head(flavor), r.Body)
}
// head returns the portion of a CREATE statement prior to the body.
func (r *Routine) head(_ Flavor) string {
var definer, returnClause, characteristics string
atPos := strings.LastIndex(r.Definer, "@")
if atPos >= 0 {
definer = fmt.Sprintf("%s@%s", EscapeIdentifier(r.Definer[0:atPos]), EscapeIdentifier(r.Definer[atPos+1:]))
}
if r.Type == ObjectTypeFunc {
returnClause = fmt.Sprintf(" RETURNS %s", r.ReturnDataType)
}
clauses := make([]string, 0)
if r.SQLDataAccess != "CONTAINS SQL" {
clauses = append(clauses, fmt.Sprintf(" %s\n", r.SQLDataAccess))
}
if r.Deterministic {
clauses = append(clauses, " DETERMINISTIC\n")
}
if r.SecurityType != "DEFINER" {
clauses = append(clauses, fmt.Sprintf(" SQL SECURITY %s\n", r.SecurityType))
}
if r.Comment != "" {
clauses = append(clauses, fmt.Sprintf(" COMMENT '%s'\n", EscapeValueForCreateTable(r.Comment)))
}
characteristics = strings.Join(clauses, "")
return fmt.Sprintf("CREATE DEFINER=%s %s %s(%s)%s\n%s",
definer,
r.Type.Caps(),
EscapeIdentifier(r.Name),
r.ParamString,
returnClause,
characteristics)
}
// Equals returns true if two routines are identical, false otherwise.
func (r *Routine) Equals(other *Routine) bool {
// shortcut if both nil pointers, or both pointing to same underlying struct
if r == other {
return true
}
// if one is nil, but the two pointers aren't equal, then one is non-nil
if r == nil || other == nil {
return false
}
// All fields are simple scalars, so we can just use equality check once we
// know neither is nil
return *r == *other
}
// DropStatement returns a SQL statement that, if run, would drop this routine.
func (r *Routine) DropStatement() string {
return fmt.Sprintf("DROP %s %s", r.Type.Caps(), EscapeIdentifier(r.Name))
}
// parseCreateStatement populates Body, ParamString, and ReturnDataType by
// parsing CreateStatement. It is used during introspection of routines in
// situations where the mysql.proc table is unavailable or does not exist.
func (r *Routine) parseCreateStatement(flavor Flavor, schema string) error {
// Find matching parens around arg list
argStart := strings.IndexRune(r.CreateStatement, '(')
var argEnd int
nestCount := 1
for pos, r := range r.CreateStatement {
if nestCount == 0 {
argEnd = pos
break
} else if pos <= argStart {
continue
} else if r == '(' {
nestCount++
} else if r == ')' {
nestCount--
}
}
if argStart <= 0 || argEnd <= 0 {
return fmt.Errorf("Failed to parse SHOW CREATE %s %s.%s: %s", r.Type.Caps(), EscapeIdentifier(schema), EscapeIdentifier(r.Name), r.CreateStatement)
}
r.ParamString = r.CreateStatement[argStart+1 : argEnd-1]
if r.Type == ObjectTypeFunc {
retStart := argEnd + len(" RETURNS ")
retEnd := retStart + strings.IndexRune(r.CreateStatement[retStart:], '\n')
if retEnd <= 0 {
return fmt.Errorf("Failed to parse SHOW CREATE %s %s.%s: %s", r.Type.Caps(), EscapeIdentifier(schema), EscapeIdentifier(r.Name), r.CreateStatement)
}
r.ReturnDataType = r.CreateStatement[retStart:retEnd]
}
// Attempt to replace r.Body with one that doesn't have character conversion problems
if header := r.head(flavor); strings.HasPrefix(r.CreateStatement, header) {
r.Body = r.CreateStatement[len(header):]
}
return nil
}