fix: contain generated files and assets and harden SQL rendering
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// 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 func(string) string, 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()
|
||||
}
|
||||
Reference in New Issue
Block a user