aboutsummaryrefslogtreecommitdiff
path: root/weed/util/sqlutil/splitter.go
blob: 098a7ecb355618c2b42bcb254239e386c9323941 (plain)
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
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 no statements found, return the original query as a single statement
	if len(statements) == 0 {
		return []string{strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(query), ";"))}
	}

	return statements
}