This repository has been archived by the owner on Jun 14, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
/
flavor.go
340 lines (297 loc) · 12.1 KB
/
flavor.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
package tengo
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// Vendor distinguishes between different database distributions/forks
type Vendor int
// Constants representing different supported vendors
const (
VendorUnknown Vendor = iota
VendorMySQL
VendorPercona
VendorMariaDB
)
func (v Vendor) String() string {
switch v {
case VendorMySQL:
return "mysql"
case VendorPercona:
return "percona"
case VendorMariaDB:
return "mariadb"
default:
return "unknown"
}
}
// ParseVendor takes a version comment string (e.g. @@version_comment MySQL
// variable) and returns the corresponding Vendor constant, defaulting to
// VendorUnknown if the string is not recognized.
func ParseVendor(versionComment string) Vendor {
versionComment = strings.ToLower(versionComment)
// The following loop assumes VendorUnknown==0 (and skips it by starting at 1),
// but otherwise makes no assumptions about the number of vendors; it loops
// until it hits a positive number that also yields "unknown" by virtue of
// the default clause in Vendor.String()'s switch statement.
for n := 1; Vendor(n).String() != VendorUnknown.String(); n++ {
if strings.Contains(versionComment, Vendor(n).String()) {
return Vendor(n)
}
}
return VendorUnknown
}
var reVersion = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)`)
// ParseVersion takes a version string (e.g. @@version variable from MySQL)
// and returns a 3-element array of major, minor, and patch numbers. If parsing
// failed, the returned value will be {0, 0, 0}.
func ParseVersion(version string) (result [3]int) {
matches := reVersion.FindStringSubmatch(version)
if matches != nil {
var err error
for n := range result {
result[n], err = strconv.Atoi(matches[n+1])
if err != nil {
return [3]int{0, 0, 0}
}
}
}
return
}
// Flavor represents a database server release, including vendor along with
// major and minor version number, and optionally the patch number (or 0 if
// unknown or irrelevant).
type Flavor struct {
Vendor Vendor
Major int
Minor int
Patch int
}
// FlavorUnknown represents a flavor that cannot be parsed. This is the zero
// value for Flavor.
var FlavorUnknown = Flavor{VendorUnknown, 0, 0, 0}
// FlavorMySQL55 represents MySQL 5.5.x. This constant omits a patch number;
// avoid direct equality comparisons and ideally only use this in tests.
var FlavorMySQL55 = Flavor{VendorMySQL, 5, 5, 0}
// FlavorMySQL56 represents MySQL 5.6.x. This constant omits a patch number;
// avoid direct equality comparisons and ideally only use this in tests.
var FlavorMySQL56 = Flavor{VendorMySQL, 5, 6, 0}
// FlavorMySQL57 represents MySQL 5.7.x. This constant omits a patch number;
// avoid direct equality comparisons and ideally only use this in tests.
var FlavorMySQL57 = Flavor{VendorMySQL, 5, 7, 0}
// FlavorMySQL80 represents MySQL 8.0.x. This constant omits a patch number;
// avoid direct equality comparisons and ideally only use this in tests.
// Patch number is especially relevant in MySQL 8.0.x as functionality now
// changes in patch releases.
var FlavorMySQL80 = Flavor{VendorMySQL, 8, 0, 0}
// FlavorPercona55 represents Percona Server 5.5.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorPercona55 = Flavor{VendorPercona, 5, 5, 0}
// FlavorPercona56 represents Percona Server 5.6.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorPercona56 = Flavor{VendorPercona, 5, 6, 0}
// FlavorPercona57 represents Percona Server 5.7.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorPercona57 = Flavor{VendorPercona, 5, 7, 0}
// FlavorPercona80 represents Percona Server 8.0.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
// Patch number is especially relevant in Percona Server 8.0.x as functionality
// now changes in patch releases.
var FlavorPercona80 = Flavor{VendorPercona, 8, 0, 0}
// FlavorMariaDB101 represents MariaDB 10.1.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB101 = Flavor{VendorMariaDB, 10, 1, 0}
// FlavorMariaDB102 represents MariaDB 10.2.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB102 = Flavor{VendorMariaDB, 10, 2, 0}
// FlavorMariaDB103 represents MariaDB 10.3.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB103 = Flavor{VendorMariaDB, 10, 3, 0}
// FlavorMariaDB104 represents MariaDB 10.4.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB104 = Flavor{VendorMariaDB, 10, 4, 0}
// FlavorMariaDB105 represents MariaDB 10.5.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB105 = Flavor{VendorMariaDB, 10, 5, 0}
// FlavorMariaDB106 represents MariaDB 10.6.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB106 = Flavor{VendorMariaDB, 10, 6, 0}
// NewFlavor returns a Flavor value based on its inputs, which should be
// supplied in one of these forms:
// NewFlavor("vendor", major, minor)
// NewFlavor("vendor", major, minor, patch)
// NewFlavor("vendor:major.minor")
// NewFlavor("vendor:major.minor.patch")
func NewFlavor(base string, versionParts ...int) Flavor {
if len(versionParts) == 0 {
versionParts = []int{0, 0, 0}
tokens := strings.Split(base, ":")
base = tokens[0]
if len(tokens) > 1 {
tokens = strings.Split(tokens[1], ".")
for n := 0; n < 3 && n < len(tokens); n++ {
versionParts[n], _ = strconv.Atoi(tokens[n]) // no need to check error, 0 value is fine
}
}
} else if len(versionParts) < 3 {
// Append enough zeroes for length to be 3
versionParts = append(versionParts, make([]int, 3-len(versionParts))...)
}
return Flavor{ParseVendor(base), versionParts[0], versionParts[1], versionParts[2]}
}
// ParseFlavor returns a Flavor value based on inputs obtained from server vars
// @@global.version and @@global.version_comment. It accounts for how some
// distributions and/or cloud platforms manipulate those values.
func ParseFlavor(versionString, versionComment string) Flavor {
version := ParseVersion(versionString)
vendor := VendorUnknown
versionString = strings.ToLower(versionString)
versionComment = strings.ToLower(versionComment)
for _, attempt := range []Vendor{VendorMariaDB, VendorPercona, VendorMySQL} {
if strings.Contains(versionComment, attempt.String()) || strings.Contains(versionString, attempt.String()) {
vendor = attempt
break
}
}
// If the vendor is still unknown after the above checks, it may be because
// various distribution methods adjust one or both of those strings. Fall
// back to sane defaults for known major versions.
// This logic will need to change whenever MySQL 9+ or MariaDB 11+ exists.
if vendor == VendorUnknown {
if version[0] == 10 {
vendor = VendorMariaDB
} else if version[0] == 5 || version[0] == 8 {
vendor = VendorMySQL
}
}
return Flavor{
Vendor: vendor,
Major: version[0],
Minor: version[1],
Patch: version[2],
}
}
func (fl Flavor) String() string {
if fl.Patch > 0 {
return fmt.Sprintf("%s:%d.%d.%d", fl.Vendor, fl.Major, fl.Minor, fl.Patch)
}
return fmt.Sprintf("%s:%d.%d", fl.Vendor, fl.Major, fl.Minor)
}
// Family returns a copy of the receiver with a zeroed-out patch version.
func (fl Flavor) Family() Flavor {
fl.Patch = 0 // receiver is passed by value, so mutation is fine here
return fl
}
// VendorMinVersion returns true if this flavor matches the supplied vendor,
// and has a version equal to or newer than the specified version.
func (fl Flavor) VendorMinVersion(vendor Vendor, versionParts ...int) bool {
if fl.Vendor != vendor {
return false
}
if len(versionParts) < 3 {
// Append enough zeroes for length to be 3
versionParts = append(versionParts, make([]int, 3-len(versionParts))...)
}
other := Flavor{vendor, versionParts[0], versionParts[1], versionParts[2]}
if fl.Major != other.Major {
return fl.Major > other.Major
}
if fl.Minor != other.Minor {
return fl.Minor > other.Minor
}
return fl.Patch >= other.Patch
}
// MySQLishMinVersion returns true if the vendor isn't VendorMariaDB, and this
// flavor has a version equal to or newer than the specified version. Note that
// this intentionally DOES consider VendorUnknown to be MySQLish.
func (fl Flavor) MySQLishMinVersion(versionParts ...int) bool {
if fl.Vendor == VendorMariaDB {
return false
}
return fl.VendorMinVersion(fl.Vendor, versionParts...)
}
// Supported returns true if package tengo officially supports this flavor
func (fl Flavor) Supported() bool {
switch fl.Vendor {
case VendorMySQL, VendorPercona:
// Currently support 5.5.0 through 8.0.x
return fl.MySQLishMinVersion(5, 5) && !fl.MySQLishMinVersion(8, 1)
case VendorMariaDB:
// Currently support 10.1.0 through 10.6.x
return fl.Major == 10 && fl.Minor >= 1 && fl.Minor <= 6
}
return false
}
// Known returns true if both the vendor and major version of this flavor were
// parsed properly
func (fl Flavor) Known() bool {
return fl.Vendor != VendorUnknown && fl.Major > 0
}
// AllowBlobDefaults returns true if the flavor permits blob and text types
// to have literal default values. (Note that MySQL may permit these types to
// have default *expressions* anyway.)
func (fl Flavor) AllowBlobDefaults() bool {
return fl.VendorMinVersion(VendorMariaDB, 10, 2)
}
// FractionalTimestamps returns true if the flavor supports fractional
// seconds in timestamp and datetime values. Note that this returns true for
// FlavorUnknown as a special-case, since all recent flavors do support this.
func (fl Flavor) FractionalTimestamps() bool {
if fl == FlavorUnknown {
return true
}
return fl.Major > 5 || (fl.Major == 5 && fl.Minor > 5)
}
// HasDataDictionary returns true if the flavor has a global transactional
// data dictionary instead of using traditional frm files.
func (fl Flavor) HasDataDictionary() bool {
return fl.MySQLishMinVersion(8, 0)
}
// DefaultUtf8mb4Collation returns the name of the default collation of the
// utf8mb4 character set in this flavor.
func (fl Flavor) DefaultUtf8mb4Collation() string {
if fl.MySQLishMinVersion(8, 0) {
return "utf8mb4_0900_ai_ci"
}
return "utf8mb4_general_ci"
}
// AlwaysShowTableCollation returns true if this flavor always emits a collation
// clause for the supplied character set, even if the collation is the default
// for the character set
func (fl Flavor) AlwaysShowTableCollation(charSet string) bool {
if charSet == "utf8mb4" {
return fl.DefaultUtf8mb4Collation() != "utf8mb4_general_ci"
}
return false
}
// GeneratedColumns returns true if the flavor supports generated columns
// using MySQL's native syntax. (Although MariaDB 10.1 has support for generated
// columns, its syntax is borrowed from other DBMS, so false is returned.)
func (fl Flavor) GeneratedColumns() bool {
return fl.MySQLishMinVersion(5, 7) || fl.VendorMinVersion(VendorMariaDB, 10, 2)
}
// SortedForeignKeys returns true if the flavor sorts foreign keys
// lexicographically in SHOW CREATE TABLE.
func (fl Flavor) SortedForeignKeys() bool {
// MySQL/Percona 8.0.19+ no longer sort lexicographically
if fl.MySQLishMinVersion(8, 0, 19) {
return false
}
// 5.5 did not sort lexicographically; other versions do
return fl.Major > 5 || (fl.Major == 5 && fl.Minor > 5)
}
// OmitIntDisplayWidth returns true if the flavor omits inclusion of display
// widths from column types in the int family, aside from special cases like
// tinyint(1).
func (fl Flavor) OmitIntDisplayWidth() bool {
return fl.MySQLishMinVersion(8, 0, 19)
}
// HasCheckConstraints returns true if the flavor supports check constraints
// and exposes them in information_schema.
func (fl Flavor) HasCheckConstraints() bool {
if fl.MySQLishMinVersion(8, 0, 16) || fl.VendorMinVersion(VendorMariaDB, 10, 3, 10) {
return true
}
return fl.Family() == FlavorMariaDB102 && fl.VendorMinVersion(VendorMariaDB, 10, 2, 22)
}