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
102 lines
2.5 KiB
Go
102 lines
2.5 KiB
Go
// Package sqllex recognizes PostgreSQL lexical boundaries without parsing SQL.
|
|
package sqllex
|
|
|
|
import "strings"
|
|
|
|
func start(c byte) bool { return c >= 128 || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' }
|
|
func ident(c byte) bool { return start(c) || c >= '0' && c <= '9' || c >= 128 }
|
|
|
|
// Walk visits named parameters and standalone line comments in executable SQL. Quoted
|
|
// strings/identifiers, dollar bodies, casts and nested block comments are opaque.
|
|
// Returning replacement text is literal: it is never scanned again.
|
|
func Walk(sql string, parameter, lineComment func(string) string) string {
|
|
var out strings.Builder
|
|
for i := 0; i < len(sql); {
|
|
begin := i
|
|
switch {
|
|
case strings.HasPrefix(sql[i:], "--"):
|
|
i += 2
|
|
for i < len(sql) && sql[i] != '\n' && sql[i] != '\r' {
|
|
i++
|
|
}
|
|
text := sql[begin:i]
|
|
if lineComment != nil && strings.TrimSpace(sql[strings.LastIndexAny(sql[:begin], "\r\n")+1:begin]) == "" {
|
|
text = lineComment(text)
|
|
}
|
|
out.WriteString(text)
|
|
continue
|
|
case strings.HasPrefix(sql[i:], "/*"):
|
|
i += 2
|
|
depth := 1
|
|
for i < len(sql) && depth > 0 {
|
|
if strings.HasPrefix(sql[i:], "/*") {
|
|
depth++
|
|
i += 2
|
|
} else if strings.HasPrefix(sql[i:], "*/") {
|
|
depth--
|
|
i += 2
|
|
} else {
|
|
i++
|
|
}
|
|
}
|
|
case sql[i] == '\'' || sql[i] == '"':
|
|
q := sql[i]
|
|
escapes := q == '\'' && i > 0 && (sql[i-1] == 'E' || sql[i-1] == 'e') && (i == 1 || !ident(sql[i-2]))
|
|
i++
|
|
for i < len(sql) {
|
|
if escapes && sql[i] == '\\' {
|
|
i++
|
|
if i < len(sql) {
|
|
i++
|
|
}
|
|
continue
|
|
}
|
|
if sql[i] == q {
|
|
i++
|
|
if i < len(sql) && sql[i] == q {
|
|
i++
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
case sql[i] == '$' && (i == 0 || !ident(sql[i-1]) && sql[i-1] != '$'):
|
|
j := i + 1
|
|
if j < len(sql) && start(sql[j]) {
|
|
for j < len(sql) && ident(sql[j]) {
|
|
j++
|
|
}
|
|
}
|
|
if j < len(sql) && sql[j] == '$' {
|
|
tag := sql[i : j+1]
|
|
end := strings.Index(sql[j+1:], tag)
|
|
if end < 0 {
|
|
i = len(sql)
|
|
} else {
|
|
i = j + 1 + end + len(tag)
|
|
}
|
|
} else {
|
|
i++
|
|
}
|
|
case strings.HasPrefix(sql[i:], "::"):
|
|
i += 2
|
|
case sql[i] == ':' && i+1 < len(sql) && start(sql[i+1]) && (i == 0 || !ident(sql[i-1]) && sql[i-1] != ':'):
|
|
i += 2
|
|
for i < len(sql) && ident(sql[i]) {
|
|
i++
|
|
}
|
|
text := sql[begin:i]
|
|
if parameter != nil {
|
|
text = parameter(text)
|
|
}
|
|
out.WriteString(text)
|
|
continue
|
|
default:
|
|
i++
|
|
}
|
|
out.WriteString(sql[begin:i])
|
|
}
|
|
return out.String()
|
|
}
|