Skip to content

Commit 0d936dd

Browse files
committed
WIP: Implement multiline query output
This is a first pass at adding support for matching query output line by line without attempting to parse column types and values. This just relies on the database driver to return DBOutput::MultiLine variants. Ideally we could inform the driver on every SQL run, but I think that's part of a bigger refactor where we refactor the DB and AsyncDB traits so that they're aware of whether we're expecting statement or results output. Signed-off-by: Paul J. Davis <[email protected]>
1 parent 9857485 commit 0d936dd

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

sqllogictest/src/parser.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ pub enum QueryExpect<T: ColumnType> {
8888
label: Option<String>,
8989
results: Vec<String>,
9090
},
91+
/// Query output can be returned as a multiline output to accommodate
92+
/// matching tabular output directly.
93+
MultiLine { data: Vec<String> },
9194
/// Query should fail with the given error message.
9295
Error(ExpectedError),
9396
}
@@ -245,6 +248,9 @@ impl<T: ColumnType> std::fmt::Display for Record<T> {
245248
write!(f, " {label}")?;
246249
}
247250
}
251+
QueryExpect::MultiLine { .. } => {
252+
write!(f, "multiline")?;
253+
}
248254
QueryExpect::Error(err) => err.fmt_inline(f)?,
249255
}
250256
writeln!(f)?;
@@ -260,6 +266,12 @@ impl<T: ColumnType> std::fmt::Display for Record<T> {
260266
// query always ends with a blank line
261267
writeln!(f)?
262268
}
269+
QueryExpect::MultiLine { data } => {
270+
writeln!(f, "{}", RESULTS_DELIMITER)?;
271+
for line in data {
272+
writeln!(f, "{}", line)?;
273+
}
274+
}
263275
QueryExpect::Error(err) => err.fmt_multiline(f)?,
264276
}
265277
Ok(())
@@ -737,6 +749,7 @@ fn parse_inner<T: ColumnType>(loc: &Location, script: &str) -> Result<Vec<Record
737749
.map_err(|e| e.at(loc.clone()))?;
738750
QueryExpect::Error(error)
739751
}
752+
["multiline"] => QueryExpect::MultiLine { data: Vec::new() },
740753
[type_str, res @ ..] => {
741754
let types = type_str
742755
.chars()
@@ -776,6 +789,14 @@ fn parse_inner<T: ColumnType>(loc: &Location, script: &str) -> Result<Vec<Record
776789
results.push(line.to_string());
777790
}
778791
}
792+
QueryExpect::MultiLine { data } => {
793+
for (_, line) in &mut lines {
794+
if line.is_empty() {
795+
break;
796+
}
797+
data.push(line.to_string())
798+
}
799+
}
779800
// If no inline error message is specified, it might be a multiline error.
780801
QueryExpect::Error(e) => {
781802
if e.is_empty() {
@@ -988,6 +1009,11 @@ mod tests {
9881009
parse_roundtrip::<DefaultColumnType>("../tests/slt/rowsort.slt")
9891010
}
9901011

1012+
#[test]
1013+
fn test_multiline_query() {
1014+
parse_roundtrip::<DefaultColumnType>("../tests/slt/multiline_query.slt")
1015+
}
1016+
9911017
#[test]
9921018
fn test_substitution() {
9931019
parse_roundtrip::<DefaultColumnType>("../tests/substitution/basic.slt")

sqllogictest/src/runner.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ pub enum RecordOutput<T: ColumnType> {
3636
rows: Vec<Vec<String>>,
3737
error: Option<AnyError>,
3838
},
39+
MultiLineQuery {
40+
lines: Vec<String>,
41+
error: Option<AnyError>,
42+
},
3943
/// The output of a `statement`.
4044
Statement { count: u64, error: Option<AnyError> },
4145
/// The output of a `system` command.
@@ -52,6 +56,9 @@ pub enum DBOutput<T: ColumnType> {
5256
types: Vec<T>,
5357
rows: Vec<Vec<String>>,
5458
},
59+
MultiLine {
60+
lines: Vec<String>,
61+
},
5562
/// A statement in the query has completed.
5663
///
5764
/// The number of rows modified or selected is returned.
@@ -293,6 +300,8 @@ pub enum TestErrorKind {
293300
expected: String,
294301
actual: String,
295302
},
303+
#[error("query result type mismatch:\n[SQL] {sql}\nThe query expected {expected} output")]
304+
QueryResultTypeMismatch { sql: String, expected: String },
296305
#[error(
297306
"query columns mismatch:\n[SQL] {sql}\n{}",
298307
format_column_diff(expected, actual, false)
@@ -607,6 +616,9 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
607616
rows,
608617
error: None,
609618
},
619+
DBOutput::MultiLine { lines } => {
620+
RecordOutput::MultiLineQuery { lines, error: None }
621+
}
610622
DBOutput::StatementComplete(count) => {
611623
RecordOutput::Statement { count, error: None }
612624
}
@@ -751,6 +763,9 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
751763
let (types, mut rows) = match conn.run(&sql).await {
752764
Ok(out) => match out {
753765
DBOutput::Rows { types, rows } => (types, rows),
766+
DBOutput::MultiLine { lines } => {
767+
return RecordOutput::MultiLineQuery { lines, error: None }
768+
}
754769
DBOutput::StatementComplete(count) => {
755770
return RecordOutput::Statement { count, error: None };
756771
}
@@ -766,6 +781,7 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
766781

767782
let sort_mode = match expected {
768783
QueryExpect::Results { sort_mode, .. } => sort_mode,
784+
QueryExpect::MultiLine { .. } => None,
769785
QueryExpect::Error(_) => None,
770786
}
771787
.or(self.sort_mode);
@@ -881,6 +897,14 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
881897
.at(loc))
882898
}
883899
QueryExpect::Results { .. } => {}
900+
QueryExpect::MultiLine { data } => {
901+
return Err(TestErrorKind::QueryResultMismatch {
902+
sql,
903+
expected: data.join("\n"),
904+
actual: "".to_string(),
905+
}
906+
.at(loc));
907+
}
884908
},
885909
(
886910
Record::Statement {
@@ -959,6 +983,14 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
959983
.at(loc));
960984
}
961985
}
986+
(Some(e), QueryExpect::MultiLine { .. }) => {
987+
return Err(TestErrorKind::Fail {
988+
sql,
989+
err: Arc::clone(e),
990+
kind: RecordKind::Query,
991+
}
992+
.at(loc))
993+
}
962994
(Some(e), QueryExpect::Results { .. }) => {
963995
return Err(TestErrorKind::Fail {
964996
sql,
@@ -967,6 +999,13 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
967999
}
9681000
.at(loc));
9691001
}
1002+
(None, QueryExpect::MultiLine { .. }) => {
1003+
return Err(TestErrorKind::QueryResultTypeMismatch {
1004+
sql,
1005+
expected: "multiline".to_string(),
1006+
}
1007+
.at(loc));
1008+
}
9701009
(
9711010
None,
9721011
QueryExpect::Results {
@@ -997,6 +1036,72 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
9971036
}
9981037
};
9991038
}
1039+
(
1040+
Record::Query {
1041+
loc,
1042+
conditions: _,
1043+
connection: _,
1044+
sql,
1045+
expected,
1046+
},
1047+
RecordOutput::MultiLineQuery { lines, error },
1048+
) => {
1049+
match (error, expected) {
1050+
(None, QueryExpect::Error(_)) => {
1051+
return Err(TestErrorKind::Ok {
1052+
sql,
1053+
kind: RecordKind::Query,
1054+
}
1055+
.at(loc));
1056+
}
1057+
(Some(e), QueryExpect::Error(expected_error)) => {
1058+
if !expected_error.is_match(&e.to_string()) {
1059+
return Err(TestErrorKind::ErrorMismatch {
1060+
sql,
1061+
err: Arc::clone(e),
1062+
expected_err: expected_error.to_string(),
1063+
kind: RecordKind::Query,
1064+
}
1065+
.at(loc));
1066+
}
1067+
}
1068+
(Some(e), QueryExpect::MultiLine { .. }) => {
1069+
return Err(TestErrorKind::Fail {
1070+
sql,
1071+
err: Arc::clone(e),
1072+
kind: RecordKind::Query,
1073+
}
1074+
.at(loc))
1075+
}
1076+
(Some(e), QueryExpect::Results { .. }) => {
1077+
return Err(TestErrorKind::Fail {
1078+
sql,
1079+
err: Arc::clone(e),
1080+
kind: RecordKind::Query,
1081+
}
1082+
.at(loc));
1083+
}
1084+
(None, QueryExpect::MultiLine { data }) => {
1085+
for (actual, expected) in lines.iter().zip(data.iter()) {
1086+
if actual != expected {
1087+
return Err(TestErrorKind::QueryResultMismatch {
1088+
sql,
1089+
expected: data.join("\n"),
1090+
actual: lines.join("\n"),
1091+
}
1092+
.at(loc));
1093+
}
1094+
}
1095+
}
1096+
(None, QueryExpect::Results { .. }) => {
1097+
return Err(TestErrorKind::QueryResultTypeMismatch {
1098+
sql,
1099+
expected: "result".to_string(),
1100+
}
1101+
.at(loc));
1102+
}
1103+
};
1104+
}
10001105
(
10011106
Record::System {
10021107
loc,
@@ -1481,6 +1586,7 @@ pub fn update_record_with_output<T: ColumnType>(
14811586
(Some(e), r) => {
14821587
let reference = match &r {
14831588
QueryExpect::Error(e) => Some(e),
1589+
QueryExpect::MultiLine { .. } => None,
14841590
QueryExpect::Results { .. } => None,
14851591
};
14861592
Some(Record::Query {
@@ -1525,6 +1631,12 @@ pub fn update_record_with_output<T: ColumnType>(
15251631
sort_mode,
15261632
label,
15271633
},
1634+
QueryExpect::MultiLine { .. } => QueryExpect::Results {
1635+
results,
1636+
types,
1637+
sort_mode: None,
1638+
label: None,
1639+
},
15281640
QueryExpect::Error(_) => QueryExpect::Results {
15291641
results,
15301642
types,
@@ -1535,6 +1647,59 @@ pub fn update_record_with_output<T: ColumnType>(
15351647
})
15361648
}
15371649
},
1650+
// query, multiline query
1651+
(
1652+
Record::Query {
1653+
loc,
1654+
conditions,
1655+
connection,
1656+
sql,
1657+
expected,
1658+
},
1659+
RecordOutput::MultiLineQuery { lines, error },
1660+
) => match (error, expected) {
1661+
// Error match
1662+
(Some(e), QueryExpect::Error(expected_error))
1663+
if expected_error.is_match(&e.to_string()) =>
1664+
{
1665+
None
1666+
}
1667+
// Error mismatch
1668+
(Some(e), r) => {
1669+
let reference = match &r {
1670+
QueryExpect::Error(e) => Some(e),
1671+
QueryExpect::MultiLine { .. } => None,
1672+
QueryExpect::Results { .. } => None,
1673+
};
1674+
Some(Record::Query {
1675+
sql,
1676+
expected: QueryExpect::Error(ExpectedError::from_actual_error(
1677+
reference,
1678+
&e.to_string(),
1679+
)),
1680+
loc,
1681+
conditions,
1682+
connection,
1683+
})
1684+
}
1685+
(None, expected) => Some(Record::Query {
1686+
sql,
1687+
loc,
1688+
conditions,
1689+
connection,
1690+
expected: match expected {
1691+
QueryExpect::Results { .. } => QueryExpect::MultiLine {
1692+
data: lines.clone(),
1693+
},
1694+
QueryExpect::MultiLine { .. } => QueryExpect::MultiLine {
1695+
data: lines.clone(),
1696+
},
1697+
QueryExpect::Error(_) => QueryExpect::MultiLine {
1698+
data: lines.clone(),
1699+
},
1700+
},
1701+
}),
1702+
},
15381703
(
15391704
Record::System {
15401705
loc,

tests/harness.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ impl sqllogictest::DB for FakeDB {
5454
],
5555
});
5656
}
57+
if sql == "select * from example_multiline" {
58+
// Check multi-line output return values
59+
return Ok(DBOutput::MultiLine {
60+
lines: vec![
61+
"+---+---+---+".to_string(),
62+
"| a | b | c |".to_string(),
63+
"+---+---+---+".to_string(),
64+
"| 1 | 2 | 3 |".to_string(),
65+
"| 4 | 5 | 6 |".to_string(),
66+
"| 7 | 8 | 9 |".to_string(),
67+
"+---+---+---+".to_string(),
68+
],
69+
});
70+
}
5771
if sql == "select counter()" {
5872
self.counter += 1;
5973
return Ok(DBOutput::Rows {

tests/slt/multiline_query.slt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
query multiline
2+
select * from example_multiline
3+
----
4+
+---+---+---+
5+
| a | b | c |
6+
+---+---+---+
7+
| 1 | 2 | 3 |
8+
| 4 | 5 | 6 |
9+
| 7 | 8 | 9 |
10+
+---+---+---+

0 commit comments

Comments
 (0)