sqlite_sql/
lib.rs

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        // Cleanup a bit and remove unnecessary whitespace after the seperator.
18        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            // Check if we didn't encourter a keywords
53            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    // Flush what the buffer contained before the EOF
95    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        // Taken from https://github.com/jvasile/sql_split/blob/main/src/lib.rs
158
159        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        ); // brackets are ok for identifiers in sqlite
218    }
219}