#[derive(PartialEq)]
enum State {
Normal,
AtSeperator,
InBegin,
InString,
InIdent,
}
pub fn split_statements(input: &str) -> Vec<String> {
let mut out = Vec::new();
let mut state = State::Normal;
let mut buffer = "".to_owned();
for b in input.chars() {
if state == State::AtSeperator {
if b == ' ' || b == '\n' {
continue;
}
state = State::Normal;
buffer.push(b);
continue;
}
if state == State::InIdent {
buffer.push(b);
if b == ']' {
state = State::Normal;
}
continue;
}
if state == State::InString {
buffer.push(b);
if b == '\'' || b == '"' || b == '`' {
state = State::Normal;
}
continue;
}
if state == State::Normal {
buffer.push(b);
if b == ' ' {
if buffer.ends_with("BEGIN ") {
state = State::InBegin;
}
}
if b == '\n' {
if buffer.ends_with("BEGIN\n") {
state = State::InBegin;
}
}
if b == '\'' || b == '"' || b == '`' {
state = State::InString;
}
if b == '[' {
state = State::InIdent;
}
if b == ';' {
state = State::AtSeperator;
out.push(buffer.clone());
buffer.clear();
}
continue;
}
if state == State::InBegin {
buffer.push(b);
if b == ';' && buffer.ends_with("END;") {
state = State::AtSeperator;
out.push(buffer.clone());
buffer.clear();
}
continue;
}
}
if !buffer.is_empty() {
out.push(buffer);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_split_statements() {
let stmts = split_statements("SELECT 1; SELECT 2");
assert_eq!(stmts, vec!["SELECT 1;", "SELECT 2"]);
}
#[test]
fn it_keep_statement_with_begin() {
let stmts =
split_statements("CREATE TRIGGER trigger AFTER INSERT ON t BEGIN SELECT 1; END;");
assert_eq!(
stmts,
vec!["CREATE TRIGGER trigger AFTER INSERT ON t BEGIN SELECT 1; END;"]
);
let stmts = split_statements(
"CREATE TRIGGER trigger AFTER INSERT ON t BEGIN SELECT 1; END; SELECT 1",
);
assert_eq!(
stmts,
vec![
"CREATE TRIGGER trigger AFTER INSERT ON t BEGIN SELECT 1; END;",
"SELECT 1"
]
);
}
#[test]
fn it_split_statements_multiline() {
let stmts = split_statements(
r#"SELECT 1;
SELECT 2;
"#,
);
assert_eq!(stmts, vec!["SELECT 1;", "SELECT 2;"]);
let stmts = split_statements(
r#"CREATE TRIGGER trigger AFTER INSERT ON t
BEGIN
SELECT 1;
END;
"#,
);
assert_eq!(
stmts,
vec!["CREATE TRIGGER trigger AFTER INSERT ON t\nBEGIN\n SELECT 1;\nEND;"]
);
}
#[test]
fn it_against_sql_split() {
assert_eq!(
split_statements("CREATE TABLE foo (bar text)"),
vec!["CREATE TABLE foo (bar text)"],
"Trailing semi-colon is optional"
);
assert_eq!(
split_statements("CREATE TABLE foo (bar text);"),
vec!["CREATE TABLE foo (bar text);"],
"We preserve the semi-colons"
);
assert_eq!(
split_statements("CREATE TABLE foo (bar text); INSERT into foo (bar) VALUES ('hi')"),
vec![
"CREATE TABLE foo (bar text);",
"INSERT into foo (bar) VALUES ('hi')"
]
);
assert_eq!(
split_statements("invalid sql; but we don't care because we don't really parse it;"),
vec![
"invalid sql;",
"but we don't care because we don't really parse it;"
]
);
assert_eq!(
split_statements("INSERT INTO foo (bar) VALUES ('semicolon in string: ;')"),
vec!["INSERT INTO foo (bar) VALUES ('semicolon in string: ;')"]
);
assert_eq!(
split_statements(
"INSERT INTO foo (bar) VALUES (\"semicolon in double-quoted string: ;\")"
),
vec!["INSERT INTO foo (bar) VALUES (\"semicolon in double-quoted string: ;\")"]
);
assert_eq!(
split_statements("INSERT INTO foo (bar) VALUES (`semicolon in backtick string: ;`)"),
vec!["INSERT INTO foo (bar) VALUES (`semicolon in backtick string: ;`)"]
);
assert_eq!(
split_statements(
"INSERT INTO foo (bar) VALUES ('interior quote and semicolon in string: ;''')"
),
vec!["INSERT INTO foo (bar) VALUES ('interior quote and semicolon in string: ;''')"]
);
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: ;\"\"\")"]);
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: ;```)"]);
assert_eq!(
split_statements("INSERT INTO foo (bar) VALUES (`semicolon after interior quote ``;`)"),
vec!["INSERT INTO foo (bar) VALUES (`semicolon after interior quote ``;`)"]
);
assert_eq!(
split_statements(
"CREATE TABLE [foo;bar] (bar: text); INSERT into foo (bar) VALUES ('hi')"
),
vec![
"CREATE TABLE [foo;bar] (bar: text);",
"INSERT into foo (bar) VALUES ('hi')"
]
); }
}