fix(embed): update embedDirectives to return result directly fix(executor): clean up import order and formatting fix(syntax_test): change file permission syntax to octal fix(mysql): update file permission syntax to octal fix(pgsql): update file permission syntax to octal fix(sqlex): simplify Walk function parameter handling fix(transform): adjust identifier comparison logic fix(template): clean up function formatting and error handling
195 lines
4.2 KiB
Go
195 lines
4.2 KiB
Go
package mysql
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// columnDefaults extracts native DEFAULT expressions from SHOW CREATE TABLE.
|
|
// MySQL can truncate binary defaults in information_schema (for example X'00FF'
|
|
// appears as just "0x"). SHOW CREATE is authoritative for their SQL spelling.
|
|
// Separators and whitespace inside quoted strings, identifiers and nested
|
|
// expressions are never treated as definition/default boundaries.
|
|
func columnDefaults(create string) map[string]string {
|
|
out := map[string]string{}
|
|
depth, start := 0, 0
|
|
var quoted byte
|
|
definitions := []string{}
|
|
for i := 0; i < len(create); i++ {
|
|
ch := create[i]
|
|
if quoted != 0 {
|
|
if ch == '\\' && quoted != '`' && i+1 < len(create) {
|
|
i++
|
|
continue
|
|
}
|
|
if ch == quoted {
|
|
if i+1 < len(create) && create[i+1] == quoted {
|
|
i++
|
|
} else {
|
|
quoted = 0
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if ch == '\'' || ch == '"' || ch == '`' {
|
|
quoted = ch
|
|
continue
|
|
}
|
|
switch ch {
|
|
case '(':
|
|
depth++
|
|
if depth == 1 {
|
|
start = i + 1
|
|
}
|
|
case ',':
|
|
if depth == 1 {
|
|
definitions = append(definitions, create[start:i])
|
|
start = i + 1
|
|
}
|
|
case ')':
|
|
depth--
|
|
if depth == 0 {
|
|
definitions = append(definitions, create[start:i])
|
|
}
|
|
}
|
|
if ch == ')' && depth == 0 {
|
|
break
|
|
}
|
|
}
|
|
for _, def := range definitions {
|
|
def = strings.TrimSpace(def)
|
|
if len(def) == 0 || def[0] != '`' {
|
|
continue
|
|
}
|
|
var name strings.Builder
|
|
end := 1
|
|
for end < len(def) {
|
|
if def[end] == '`' {
|
|
if end+1 < len(def) && def[end+1] == '`' {
|
|
name.WriteByte('`')
|
|
end += 2
|
|
continue
|
|
}
|
|
end++
|
|
break
|
|
}
|
|
name.WriteByte(def[end])
|
|
end++
|
|
}
|
|
// Consume full tokens so DEFAULT inside a generated expression or literal
|
|
// cannot masquerade as a column default.
|
|
for end < len(def) {
|
|
for end < len(def) && isSpace(def[end]) {
|
|
end++
|
|
}
|
|
begin := end
|
|
end = tokenEnd(def, end)
|
|
if end == begin {
|
|
break
|
|
}
|
|
if strings.EqualFold(def[begin:end], "DEFAULT") {
|
|
for end < len(def) && isSpace(def[end]) {
|
|
end++
|
|
}
|
|
finish := tokenEnd(def, end)
|
|
out[name.String()] = def[end:finish]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
func isSpace(b byte) bool { return b == ' ' || b == '\n' || b == '\r' || b == '\t' }
|
|
func tokenEnd(s string, start int) int {
|
|
depth := 0
|
|
var quoted byte
|
|
for i := start; i < len(s); i++ {
|
|
ch := s[i]
|
|
if quoted != 0 {
|
|
if ch == '\\' && quoted != '`' && i+1 < len(s) {
|
|
i++
|
|
continue
|
|
}
|
|
if ch == quoted {
|
|
if i+1 < len(s) && s[i+1] == quoted {
|
|
i++
|
|
} else {
|
|
quoted = 0
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if ch == '\'' || ch == '"' || ch == '`' {
|
|
quoted = ch
|
|
continue
|
|
}
|
|
if ch == '(' {
|
|
depth++
|
|
}
|
|
if ch == ')' {
|
|
depth--
|
|
}
|
|
if depth == 0 && isSpace(ch) {
|
|
return i
|
|
}
|
|
}
|
|
return len(s)
|
|
}
|
|
|
|
// SHOW CREATE has native options not exposed by STATISTICS/COLUMNS. Ignore
|
|
// quoted identifiers and values when detecting options outside our contract.
|
|
func unquotedSQL(create string) string {
|
|
var code strings.Builder
|
|
var quoted byte
|
|
for i := 0; i < len(create); i++ {
|
|
ch := create[i]
|
|
if quoted != 0 {
|
|
if ch == '\\' && quoted != '`' && i+1 < len(create) {
|
|
i++
|
|
continue
|
|
}
|
|
if ch == quoted {
|
|
if i+1 < len(create) && create[i+1] == quoted {
|
|
i++
|
|
} else {
|
|
quoted = 0
|
|
code.WriteByte(' ')
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if ch == '\'' || ch == '"' || ch == '`' {
|
|
quoted = ch
|
|
code.WriteByte(' ')
|
|
continue
|
|
}
|
|
code.WriteByte(ch)
|
|
}
|
|
return code.String()
|
|
}
|
|
|
|
var autoIncrementOption = regexp.MustCompile(`(?i)\bAUTO_INCREMENT\s*=\s*([0-9]+)`)
|
|
|
|
func autoIncrementValue(create string) string {
|
|
if match := autoIncrementOption.FindStringSubmatch(unquotedSQL(create)); len(match) > 1 {
|
|
return match[1]
|
|
}
|
|
return "1"
|
|
}
|
|
|
|
func unsupportedNativeOptions(create string) []string {
|
|
words := strings.FieldsFunc(strings.ToUpper(unquotedSQL(create)), func(r rune) bool { return (r < 'A' || r > 'Z') && r != '_' })
|
|
var out []string
|
|
for i, w := range words {
|
|
switch w {
|
|
case "ENGINE_ATTRIBUTE", "SECONDARY_ENGINE_ATTRIBUTE", "KEY_BLOCK_SIZE", "COLUMN_FORMAT", "STORAGE":
|
|
out = append(out, w)
|
|
case "PARSER":
|
|
if i > 0 && words[i-1] == "WITH" {
|
|
out = append(out, "WITH PARSER")
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|