Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into bz/multiline-error
Browse files Browse the repository at this point in the history
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
  • Loading branch information
BugenZhao committed Nov 9, 2023
2 parents bd0127d + 52d5122 commit d7a652e
Showing 1 changed file with 61 additions and 77 deletions.
138 changes: 61 additions & 77 deletions sqllogictest/src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Sqllogictest parser.

use std::fmt;
use std::iter::Peekable;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
Expand All @@ -12,6 +13,8 @@ use regex::Regex;
use crate::ColumnType;
use crate::ParseErrorKind::InvalidIncludeFile;

const RESULTS_DELIMITER: &str = "----";

/// The location in source file.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Location {
Expand Down Expand Up @@ -224,7 +227,7 @@ impl<T: ColumnType> std::fmt::Display for Record<T> {
if let Some(err) = expected_error {
err.fmt_multiline(f)
} else {
write!(f, "----")?;
write!(f, "{}", RESULTS_DELIMITER)?;

for result in expected_results {
write!(f, "\n{result}")?;
Expand Down Expand Up @@ -334,7 +337,7 @@ impl ExpectedError {
/// Unparses the expected message with `----`, if it's multiline.
fn fmt_multiline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Self::Multiline(results) = self {
writeln!(f, "----")?;
writeln!(f, "{}", RESULTS_DELIMITER)?;
writeln!(f, "{}", results.trim())?;
writeln!(f)?; // another empty line to indicate the end of multiline message
}
Expand Down Expand Up @@ -592,12 +595,12 @@ fn parse_inner<T: ColumnType>(loc: &Location, script: &str) -> Result<Vec<Record
if let Some(text) = line.strip_prefix('#') {
comments.push(text.to_string());
if lines.peek().is_none() {
// Special handling for the case where the last line is a comment.
records.push(Record::Comment(comments));
comments = vec![];
break;
}
continue;
}

if !comments.is_empty() {
records.push(Record::Comment(comments));
comments = vec![];
Expand Down Expand Up @@ -672,41 +675,12 @@ fn parse_inner<T: ColumnType>(loc: &Location, script: &str) -> Result<Vec<Record
}
_ => return Err(ParseErrorKind::InvalidLine(line.into()).at(loc)),
};
let mut sql = match lines.next() {
Some((_, line)) => line.into(),
None => return Err(ParseErrorKind::UnexpectedEOF.at(loc.next_line())),
};
let mut has_multiline_error = false;
for (_, line) in &mut lines {
if line.is_empty() {
break;
}
if line == "----" {
has_multiline_error = true;
break;
}
sql += "\n";
sql += line;
}
if has_multiline_error {
let (sql, has_results) = parse_lines(&mut lines, &loc, Some(RESULTS_DELIMITER))?;
if has_results {
if let Some(e) = &expected_error {
// If no inline error message is specified, it might be a multiline error.
if e.is_empty() {
let mut results = String::new();

while let Some((_, line)) = lines.next() {
// 2 consecutive empty lines
if line.is_empty()
&& lines.peek().map(|(_, l)| l.is_empty()).unwrap_or(true)
{
lines.next();
break;
}
results += line;
results.push('\n');
}
expected_error =
Some(ExpectedError::Multiline(results.trim().to_string()));
expected_error = Some(parse_multiline_error(&mut lines));
} else {
return Err(ParseErrorKind::DuplicatedErrorMessage.at(loc.clone()));
}
Expand Down Expand Up @@ -756,42 +730,14 @@ fn parse_inner<T: ColumnType>(loc: &Location, script: &str) -> Result<Vec<Record

// The SQL for the query is found on second an subsequent lines of the record
// up to first line of the form "----" or until the end of the record.
let mut sql = match lines.next() {
Some((_, line)) => line.into(),
None => return Err(ParseErrorKind::UnexpectedEOF.at(loc.next_line())),
};
let mut has_result = false;
for (_, line) in &mut lines {
if line.is_empty() {
break;
}
if line == "----" {
has_result = true;
break;
}
sql += "\n";
sql += line;
}
let (sql, has_result) = parse_lines(&mut lines, &loc, Some(RESULTS_DELIMITER))?;
// Lines following the "----" are expected results of the query, one value per line.
let mut expected_results = vec![];
if has_result {
if let Some(e) = &expected_error {
// If no inline error message is specified, it might be a multiline error.
if e.is_empty() {
let mut results = String::new();
while let Some((_, line)) = lines.next() {
// 2 consecutive empty lines
if line.is_empty()
&& lines.peek().map(|(_, l)| l.is_empty()).unwrap_or(true)
{
lines.next();
break;
}
results += line;
results.push('\n');
}
expected_error =
Some(ExpectedError::Multiline(results.trim().to_string()));
expected_error = Some(parse_multiline_error(&mut lines));
} else {
return Err(ParseErrorKind::DuplicatedErrorMessage.at(loc.clone()));
}
Expand All @@ -818,17 +764,7 @@ fn parse_inner<T: ColumnType>(loc: &Location, script: &str) -> Result<Vec<Record
}
["system", "ok"] => {
// TODO: we don't support asserting error message for system command
let mut command = match lines.next() {
Some((_, line)) => line.into(),
None => return Err(ParseErrorKind::UnexpectedEOF.at(loc.next_line())),
};
for (_, line) in &mut lines {
if line.is_empty() {
break;
}
command += "\n";
command += line;
}
let (command, _) = parse_lines(&mut lines, &loc, None)?;
records.push(Record::System {
loc,
conditions: std::mem::take(&mut conditions),
Expand Down Expand Up @@ -901,6 +837,54 @@ fn parse_file_inner<T: ColumnType>(loc: Location) -> Result<Vec<Record<T>>, Pars
Ok(records)
}

/// Parse one or more lines until empty line or a delimiter.
fn parse_lines<'a>(
lines: &mut impl Iterator<Item = (usize, &'a str)>,
loc: &Location,
delimiter: Option<&str>,
) -> Result<(String, bool), ParseError> {
let mut found_delimiter = false;
let mut out = match lines.next() {
Some((_, line)) => Ok(line.into()),
None => Err(ParseErrorKind::UnexpectedEOF.at(loc.clone().next_line())),
}?;

for (_, line) in lines {
if line.is_empty() {
break;
}
if let Some(delimiter) = delimiter {
if line == delimiter {
found_delimiter = true;
break;
}
}
out += "\n";
out += line;
}

Ok((out, found_delimiter))
}

/// Parse multiline error message under `----`.
fn parse_multiline_error<'a>(
lines: &mut Peekable<impl Iterator<Item = (usize, &'a str)>>,
) -> ExpectedError {
let mut results = String::new();

while let Some((_, line)) = lines.next() {
// 2 consecutive empty lines
if line.is_empty() && lines.peek().map(|(_, l)| l.is_empty()).unwrap_or(true) {
lines.next();
break;
}
results += line;
results.push('\n');
}

ExpectedError::Multiline(results.trim().to_string())
}

#[cfg(test)]
mod tests {
use std::io::Write;
Expand Down

0 comments on commit d7a652e

Please sign in to comment.