mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-06-09 18:32:43 +00:00
5468707289
* fix(util): ignore comment only sql input Problem: sqlutil.SplitStatements strips SQL comments while scanning, but when no statements remain it falls back to returning the original query. Inputs that contain only comments are therefore reported as executable SQL statements. Root cause: The no-statements fallback did not distinguish a real single statement from input that had been fully removed by comment filtering. Fix: Remove the original-query fallback and return an explicit empty slice when scanning produces no statements. Reproduction: env GOCACHE=/private/tmp/seaweedfs-go-cache go test ./weed/util/sqlutil -run TestSplitStatements -count=1 failed before the fix because comment-only inputs returned the comment text as a statement. Validation: gofmt -w weed/util/sqlutil/splitter.go weed/util/sqlutil/splitter_test.go; env GOCACHE=/private/tmp/seaweedfs-go-cache go test ./weed/util/sqlutil -run TestSplitStatements -count=1; env GOCACHE=/private/tmp/seaweedfs-go-cache go test ./weed/util/sqlutil -count=1; git diff --check; git diff --cached --check. Duplicate check: Searched /private/tmp/seaweedfs-codex0610-old-branch-index.tsv and existing tests for sqlutil, SplitStatements, comments, and comment-only. Old PostgreSQL query branches cover malformed wire frames and SQL engine numeric parsing, not comment-only statement splitting. Co-authored-by: Codex <noreply@openai.com> * Update weed/util/sqlutil/splitter.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
141 lines
3.0 KiB
Go
141 lines
3.0 KiB
Go
package sqlutil
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// SplitStatements splits a query string into individual SQL statements.
|
|
// This robust implementation handles SQL comments, quoted strings, and escaped characters.
|
|
//
|
|
// Features:
|
|
// - Handles single-line comments (-- comment)
|
|
// - Handles multi-line comments (/* comment */)
|
|
// - Properly escapes single quotes in strings ('don”t')
|
|
// - Properly escapes double quotes in identifiers ("column""name")
|
|
// - Ignores semicolons within quoted strings and comments
|
|
// - Returns clean, trimmed statements with empty statements filtered out
|
|
func SplitStatements(query string) []string {
|
|
var statements []string
|
|
var current strings.Builder
|
|
|
|
query = strings.TrimSpace(query)
|
|
if query == "" {
|
|
return []string{}
|
|
}
|
|
|
|
runes := []rune(query)
|
|
i := 0
|
|
|
|
for i < len(runes) {
|
|
char := runes[i]
|
|
|
|
// Handle single-line comments (-- comment)
|
|
if char == '-' && i+1 < len(runes) && runes[i+1] == '-' {
|
|
// Skip the entire comment without including it in any statement
|
|
for i < len(runes) && runes[i] != '\n' && runes[i] != '\r' {
|
|
i++
|
|
}
|
|
// Skip the newline if present
|
|
if i < len(runes) {
|
|
i++
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Handle multi-line comments (/* comment */)
|
|
if char == '/' && i+1 < len(runes) && runes[i+1] == '*' {
|
|
// Skip the /* opening
|
|
i++
|
|
i++
|
|
|
|
// Skip to end of comment or end of input without including content
|
|
for i < len(runes) {
|
|
if runes[i] == '*' && i+1 < len(runes) && runes[i+1] == '/' {
|
|
i++ // Skip the *
|
|
i++ // Skip the /
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Handle single-quoted strings
|
|
if char == '\'' {
|
|
current.WriteRune(char)
|
|
i++
|
|
|
|
for i < len(runes) {
|
|
char = runes[i]
|
|
current.WriteRune(char)
|
|
|
|
if char == '\'' {
|
|
// Check if it's an escaped quote
|
|
if i+1 < len(runes) && runes[i+1] == '\'' {
|
|
i++ // Skip the next quote (it's escaped)
|
|
if i < len(runes) {
|
|
current.WriteRune(runes[i])
|
|
}
|
|
} else {
|
|
break // End of string
|
|
}
|
|
}
|
|
i++
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
|
|
// Handle double-quoted identifiers
|
|
if char == '"' {
|
|
current.WriteRune(char)
|
|
i++
|
|
|
|
for i < len(runes) {
|
|
char = runes[i]
|
|
current.WriteRune(char)
|
|
|
|
if char == '"' {
|
|
// Check if it's an escaped quote
|
|
if i+1 < len(runes) && runes[i+1] == '"' {
|
|
i++ // Skip the next quote (it's escaped)
|
|
if i < len(runes) {
|
|
current.WriteRune(runes[i])
|
|
}
|
|
} else {
|
|
break // End of identifier
|
|
}
|
|
}
|
|
i++
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
|
|
// Handle semicolon (statement separator)
|
|
if char == ';' {
|
|
stmt := strings.TrimSpace(current.String())
|
|
if stmt != "" {
|
|
statements = append(statements, stmt)
|
|
}
|
|
current.Reset()
|
|
} else {
|
|
current.WriteRune(char)
|
|
}
|
|
i++
|
|
}
|
|
|
|
// Add any remaining statement
|
|
if current.Len() > 0 {
|
|
stmt := strings.TrimSpace(current.String())
|
|
if stmt != "" {
|
|
statements = append(statements, stmt)
|
|
}
|
|
}
|
|
|
|
if len(statements) == 0 {
|
|
return []string{}
|
|
}
|
|
return statements
|
|
}
|