1#[derive(PartialEq)]
2enum State {
3 Normal,
4 AtSeperator,
5 InBegin,
6 InString,
7 InIdent,
8}
9
10pub fn split_statements(input: &str) -> Vec<String> {
11 let mut out = Vec::new();
12 let mut state = State::Normal;
13
14 let mut buffer = "".to_owned();
15
16 for b in input.chars() {
17 if state == State::AtSeperator {
19 if b == ' ' || b == '\n' {
20 continue;
21 }
22
23 state = State::Normal;
24 buffer.push(b);
25
26 continue;
27 }
28
29 if state == State::InIdent {
30 buffer.push(b);
31
32 if b == ']' {
33 state = State::Normal;
34 }
35
36 continue;
37 }
38
39 if state == State::InString {
40 buffer.push(b);
41
42 if b == '\'' || b == '"' || b == '`' {
43 state = State::Normal;
44 }
45
46 continue;
47 }
48
49 if state == State::Normal {
50 buffer.push(b);
51
52 if b == ' ' {
54 if buffer.ends_with("BEGIN ") {
55 state = State::InBegin;
56 }
57 }
58 if b == '\n' {
59 if buffer.ends_with("BEGIN\n") {
60 state = State::InBegin;
61 }
62 }
63
64 if b == '\'' || b == '"' || b == '`' {
65 state = State::InString;
66 }
67
68 if b == '[' {
69 state = State::InIdent;
70 }
71
72 if b == ';' {
73 state = State::AtSeperator;
74 out.push(buffer.clone());
75 buffer.clear();
76 }
77
78 continue;
79 }
80
81 if state == State::InBegin {
82 buffer.push(b);
83
84 if b == ';' && buffer.ends_with("END;") {
85 state = State::AtSeperator;
86 out.push(buffer.clone());
87 buffer.clear();
88 }
89
90 continue;
91 }
92 }
93
94 if !buffer.is_empty() {
96 out.push(buffer);
97 }
98
99 out
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn it_split_statements() {
108 let stmts = split_statements("SELECT 1; SELECT 2");
109 assert_eq!(stmts, vec!["SELECT 1;", "SELECT 2"]);
110 }
111
112 #[test]
113 fn it_keep_statement_with_begin() {
114 let stmts =
115 split_statements("CREATE TRIGGER trigger AFTER INSERT ON t BEGIN SELECT 1; END;");
116 assert_eq!(
117 stmts,
118 vec!["CREATE TRIGGER trigger AFTER INSERT ON t BEGIN SELECT 1; END;"]
119 );
120
121 let stmts = split_statements(
122 "CREATE TRIGGER trigger AFTER INSERT ON t BEGIN SELECT 1; END; SELECT 1",
123 );
124 assert_eq!(
125 stmts,
126 vec![
127 "CREATE TRIGGER trigger AFTER INSERT ON t BEGIN SELECT 1; END;",
128 "SELECT 1"
129 ]
130 );
131 }
132
133 #[test]
134 fn it_split_statements_multiline() {
135 let stmts = split_statements(
136 r#"SELECT 1;
137 SELECT 2;
138 "#,
139 );
140 assert_eq!(stmts, vec!["SELECT 1;", "SELECT 2;"]);
141
142 let stmts = split_statements(
143 r#"CREATE TRIGGER trigger AFTER INSERT ON t
144BEGIN
145 SELECT 1;
146END;
147 "#,
148 );
149 assert_eq!(
150 stmts,
151 vec!["CREATE TRIGGER trigger AFTER INSERT ON t\nBEGIN\n SELECT 1;\nEND;"]
152 );
153 }
154
155 #[test]
156 fn it_against_sql_split() {
157 assert_eq!(
160 split_statements("CREATE TABLE foo (bar text)"),
161 vec!["CREATE TABLE foo (bar text)"],
162 "Trailing semi-colon is optional"
163 );
164 assert_eq!(
165 split_statements("CREATE TABLE foo (bar text);"),
166 vec!["CREATE TABLE foo (bar text);"],
167 "We preserve the semi-colons"
168 );
169 assert_eq!(
170 split_statements("CREATE TABLE foo (bar text); INSERT into foo (bar) VALUES ('hi')"),
171 vec![
172 "CREATE TABLE foo (bar text);",
173 "INSERT into foo (bar) VALUES ('hi')"
174 ]
175 );
176 assert_eq!(
177 split_statements("invalid sql; but we don't care because we don't really parse it;"),
178 vec![
179 "invalid sql;",
180 "but we don't care because we don't really parse it;"
181 ]
182 );
183 assert_eq!(
184 split_statements("INSERT INTO foo (bar) VALUES ('semicolon in string: ;')"),
185 vec!["INSERT INTO foo (bar) VALUES ('semicolon in string: ;')"]
186 );
187 assert_eq!(
188 split_statements(
189 "INSERT INTO foo (bar) VALUES (\"semicolon in double-quoted string: ;\")"
190 ),
191 vec!["INSERT INTO foo (bar) VALUES (\"semicolon in double-quoted string: ;\")"]
192 );
193 assert_eq!(
194 split_statements("INSERT INTO foo (bar) VALUES (`semicolon in backtick string: ;`)"),
195 vec!["INSERT INTO foo (bar) VALUES (`semicolon in backtick string: ;`)"]
196 );
197 assert_eq!(
198 split_statements(
199 "INSERT INTO foo (bar) VALUES ('interior quote and semicolon in string: ;''')"
200 ),
201 vec!["INSERT INTO foo (bar) VALUES ('interior quote and semicolon in string: ;''')"]
202 );
203 assert_eq!(split_statements("INSERT INTO foo (bar) VALUES (\"interior quote and semicolon in double-quoted string: ;\"\"\")"), vec!["INSERT INTO foo (bar) VALUES (\"interior quote and semicolon in double-quoted string: ;\"\"\")"]);
204 assert_eq!(split_statements("INSERT INTO foo (bar) VALUES (`interior quote and semicolon in backtick string: ;```)"), vec!["INSERT INTO foo (bar) VALUES (`interior quote and semicolon in backtick string: ;```)"]);
205 assert_eq!(
206 split_statements("INSERT INTO foo (bar) VALUES (`semicolon after interior quote ``;`)"),
207 vec!["INSERT INTO foo (bar) VALUES (`semicolon after interior quote ``;`)"]
208 );
209 assert_eq!(
210 split_statements(
211 "CREATE TABLE [foo;bar] (bar: text); INSERT into foo (bar) VALUES ('hi')"
212 ),
213 vec![
214 "CREATE TABLE [foo;bar] (bar: text);",
215 "INSERT into foo (bar) VALUES ('hi')"
216 ]
217 ); }
219}