/
SQLSplitter.java
218 lines (194 loc) · 5.02 KB
/
SQLSplitter.java
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package play.db;
import java.util.*;
public class SQLSplitter implements Iterable<CharSequence> {
/**
* Skips the index past the quote.
*
* @param s The string
* @param start The starting character of the quote.
*
* @return The index that skips past the quote starting at start. If the quote does not start at that point, it simply returns start.
*/
static int consumeQuote(final CharSequence s, final int start) {
if ( start >= s.length() ) return start;
char ender;
switch ( s.charAt(start) ) {
case '\'':
ender = '\'';
break;
case '"':
ender = '"';
break;
case '[':
ender = ']';
break;
case '`':
ender = '`';
break;
case '$': {
int quoteEnd = start + 1;
for ( ; s.charAt(quoteEnd) != '$'; ++quoteEnd )
if ( quoteEnd >= s.length() )
return quoteEnd;
int i = quoteEnd + 1;
while ( i < s.length() ) {
if ( s.charAt(i) == '$' ) {
boolean match = true;
for ( int j = start; j <= quoteEnd && i < s.length(); ++j, ++i ) {
if ( s.charAt(i) != s.charAt(j) ) {
match = false;
break;
}
}
if ( match )
return i;
} else
++i;
}
return i;
}
default:
return start;
}
boolean escaped = false;
for ( int i = start + 1; i < s.length(); ++i ) {
if ( escaped ) {
escaped = false;
continue;
}
char c = s.charAt(i);
if ( c == '\\' )
escaped = true;
else if ( c == ender )
return i + 1;
}
return s.length();
}
static boolean isNewLine(char c) {
return c == '\n' || c == '\r';
}
/**
* Returns the index of the next line from a start location.
*/
static int consumeTillNextLine(final CharSequence s, int start) {
while ( start < s.length() && !isNewLine(s.charAt(start)) )
++start;
while ( start < s.length() && isNewLine(s.charAt(start)) )
++start;
return start;
}
static boolean isNext(final CharSequence s, final int start, final char c) {
if ( start + 1 < s.length() )
return s.charAt(start + 1) == c;
return false;
}
/**
* Skips the index past the comment.
*
* @param s The string
* @param start The starting character of the comment
*
* @return The index that skips past the comment starting at start. If the comment does not start at that point, it simply returns start.
*/
static int consumeComment(final CharSequence s, int start) {
if ( start >= s.length() ) return start;
switch ( s.charAt(start) ) {
case '-':
if ( isNext(s, start, '-') )
return consumeTillNextLine(s, start + 2);
else
return start;
case '#':
return consumeTillNextLine(s, start);
case '/':
if ( isNext(s, start, '*') ) {
start += 2;
while ( start < s.length() ) {
if ( s.charAt(start) == '*' ) {
++start;
if ( start < s.length() && s.charAt(start) == '/' )
return start + 1;
} else
++start;
}
}
return start;
case '{':
while ( start < s.length() && s.charAt(start) != '}' )
++start;
return start + 1;
default:
return start;
}
}
static int consumeParentheses(final CharSequence s, int start) {
if ( start >= s.length() ) return start;
switch ( s.charAt(start) ) {
case '(':
++start;
while ( start < s.length() ) {
if ( s.charAt(start) == ')' )
return start + 1;
start = nextChar(s, start);
}
break;
default:
break;
}
return start;
}
static int nextChar(final CharSequence sql, final int start) {
int i = consumeParentheses(sql, consumeComment(sql, consumeQuote(sql, start)));
if ( i == start ) return Math.min(start + 1, sql.length());
do {
final int j = consumeParentheses(sql, consumeComment(sql, consumeQuote(sql, i)));
if ( j == i )
return i;
i = j;
} while ( true );
}
/**
* Splits the SQL "properly" based on semicolons. Respecting quotes and comments.
*/
public static ArrayList<CharSequence> splitSQL(final CharSequence sql) {
final ArrayList<CharSequence> ret = new ArrayList<CharSequence>();
for ( CharSequence c : new SQLSplitter(sql) )
ret.add(c);
return ret;
}
final CharSequence sql;
public SQLSplitter(final CharSequence sql) {
this.sql = sql;
}
public Iterator<CharSequence> iterator() {
return new Iterator<CharSequence>() {
int i = 0, prev = 0;
public boolean hasNext() {
return prev < sql.length();
}
public CharSequence next() {
while ( i < sql.length() ) {
if ( sql.charAt(i) == ';' ) {
++i;
// check "double semicolon" -> used to escape a semicolon and avoid splitting
if ((i < sql.length() && sql.charAt(i) == ';')) {
++i;
} else {
CharSequence ret = sql.subSequence(prev, i).toString().replace(";;", ";");
prev = i;
return ret;
}
}
i = nextChar(sql, i);
}
if ( prev != i ) {
CharSequence ret = sql.subSequence(prev, i).toString().replace(";;", ";");
prev = i;
return ret;
}
throw new NoSuchElementException();
}
public void remove() { throw new UnsupportedOperationException(); }
};
}
}