use std::io::{BufRead, BufReader, Read};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(super) enum SqlDialect {
MySql,
#[default]
Postgres,
Sqlite,
}
pub(super) struct Parser<R: Read> {
reader: BufReader<R>,
stmt_buffer: Vec<u8>,
dialect: SqlDialect,
}
impl<R: Read> Parser<R> {
pub(super) fn with_dialect(
reader: R,
buffer_size: usize,
dialect: SqlDialect,
) -> Self {
Self {
reader: BufReader::with_capacity(buffer_size, reader),
stmt_buffer: Vec::with_capacity(32 * 1024),
dialect,
}
}
pub(super) fn read_statement(
&mut self,
) -> std::io::Result<Option<Vec<u8>>> {
self.stmt_buffer.clear();
let mut inside_single_quote = false;
let mut inside_double_quote = false;
let mut escaped = false;
let mut in_line_comment = false;
let mut in_block_comment = false;
let mut in_dollar_quote = false;
let mut dollar_tag: Vec<u8> = Vec::new();
loop {
let buf = self.reader.fill_buf()?;
if buf.is_empty() {
if self.stmt_buffer.is_empty() {
return Ok(None);
}
let result = std::mem::take(&mut self.stmt_buffer);
return Ok(Some(result));
}
let mut consumed = 0;
let mut found_terminator = false;
for (i, &b) in buf.iter().enumerate() {
let inside_string = inside_single_quote
|| inside_double_quote
|| in_dollar_quote;
if in_line_comment {
if b == b'\n' {
in_line_comment = false;
}
continue;
}
if in_block_comment {
if b == b'*' && i + 1 < buf.len() && buf[i + 1] == b'/' {
in_block_comment = false;
}
continue;
}
if escaped {
escaped = false;
continue;
}
if b == b'\\'
&& inside_string
&& self.dialect == SqlDialect::MySql
{
escaped = true;
continue;
}
if b == b'/'
&& !inside_string
&& i + 1 < buf.len()
&& buf[i + 1] == b'*'
{
in_block_comment = true;
continue;
}
if b == b'-'
&& !inside_string
&& i + 1 < buf.len()
&& buf[i + 1] == b'-'
{
in_line_comment = true;
continue;
}
if self.dialect == SqlDialect::Postgres
&& !inside_single_quote
&& !inside_double_quote
{
if b == b'$' && !in_dollar_quote {
if let Some(end) =
buf[i + 1..].iter().position(|&c| c == b'$')
{
let tag_bytes = &buf[i + 1..i + 1 + end];
let is_valid_tag = if tag_bytes.is_empty() {
true
} else {
let mut iter = tag_bytes.iter();
match iter.next() {
Some(&first)
if first.is_ascii_alphabetic()
|| first == b'_' =>
{
iter.all(|&c| {
c.is_ascii_alphanumeric()
|| c == b'_'
})
},
_ => false,
}
};
if is_valid_tag {
dollar_tag = tag_bytes.to_vec();
in_dollar_quote = true;
continue;
}
}
} else if b == b'$' && in_dollar_quote {
let tag_len = dollar_tag.len();
if i + 1 + tag_len < buf.len()
&& buf[i + 1..i + 1 + tag_len] == dollar_tag[..]
&& buf.get(i + 1 + tag_len) == Some(&b'$')
{
in_dollar_quote = false;
dollar_tag.clear();
continue;
}
}
}
if b == b'\'' && !inside_double_quote && !in_dollar_quote {
inside_single_quote = !inside_single_quote;
} else if b == b'"' && !inside_single_quote && !in_dollar_quote
{
inside_double_quote = !inside_double_quote;
} else if b == b';' && !inside_string {
self.stmt_buffer.extend_from_slice(&buf[..=i]);
consumed = i + 1;
found_terminator = true;
break;
}
}
if found_terminator {
self.reader.consume(consumed);
let result = std::mem::take(&mut self.stmt_buffer);
return Ok(Some(result));
}
self.stmt_buffer.extend_from_slice(buf);
let len = buf.len();
self.reader.consume(len);
}
}
}