Skip to content

Commit e7c8b51

Browse files
Omega359fuyufjh
authored andcommitted
Add valuesort, new control result mode and value normalizer (risinglightdb#237)
* Merged in PR 123 from parent project Signed-off-by: Bruce Ritchie <[email protected]> * Adding resultmode control and custom value normalizer. Bumped version. Signed-off-by: Bruce Ritchie <[email protected]> * Added test to verify resultmode can be updated Signed-off-by: Bruce Ritchie <[email protected]> * Cargo fmt. Signed-off-by: Bruce Ritchie <[email protected]> * Cargo clippy. Signed-off-by: Bruce Ritchie <[email protected]> * elide lifetimes to get ci check to pass. Signed-off-by: Bruce Ritchie <[email protected]> * Updates after merge with upstream Signed-off-by: Bruce Ritchie <[email protected]> * Added valuesort test, updated changelog. Signed-off-by: Bruce Ritchie <[email protected]> --------- Signed-off-by: Bruce Ritchie <[email protected]>
1 parent c03a71a commit e7c8b51

File tree

12 files changed

+434
-229
lines changed

12 files changed

+434
-229
lines changed

CHANGELOG.md

Lines changed: 103 additions & 52 deletions
Large diffs are not rendered by default.

Cargo.lock

Lines changed: 149 additions & 156 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ resolver = "2"
33
members = ["sqllogictest", "sqllogictest-bin", "sqllogictest-engines", "tests"]
44

55
[workspace.package]
6-
version = "0.23.1"
6+
version = "0.24.0"
77
edition = "2021"
88
homepage = "https://github.com/risinglightdb/sqllogictest-rs"
99
keywords = ["sql", "database", "parser", "cli"]

sqllogictest-bin/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ glob = "0.3"
2323
itertools = "0.13"
2424
quick-junit = { version = "0.5" }
2525
rand = "0.8"
26-
sqllogictest = { path = "../sqllogictest", version = "0.23" }
27-
sqllogictest-engines = { path = "../sqllogictest-engines", version = "0.23" }
26+
sqllogictest = { path = "../sqllogictest", version = "0.24" }
27+
sqllogictest-engines = { path = "../sqllogictest-engines", version = "0.24" }
2828
tokio = { version = "1", features = [
2929
"rt",
3030
"rt-multi-thread",

sqllogictest-bin/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
1717
use rand::distributions::DistString;
1818
use rand::seq::SliceRandom;
1919
use sqllogictest::{
20-
default_column_validator, default_validator, update_record_with_output, AsyncDB, Injected,
21-
MakeConnection, Record, Runner,
20+
default_column_validator, default_normalizer, default_validator, update_record_with_output,
21+
AsyncDB, Injected, MakeConnection, Record, Runner,
2222
};
2323

2424
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
@@ -770,6 +770,7 @@ async fn update_record<M: MakeConnection>(
770770
&record_output,
771771
"\t",
772772
default_validator,
773+
default_normalizer,
773774
default_column_validator,
774775
) {
775776
Some(new_record) => {

sqllogictest-engines/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ postgres-types = { version = "0.2.8", features = ["derive", "with-chrono-0_4"] }
2020
rust_decimal = { version = "1.36.0", features = ["tokio-pg"] }
2121
serde = { version = "1", features = ["derive"] }
2222
serde_json = "1"
23-
sqllogictest = { path = "../sqllogictest", version = "0.23" }
23+
sqllogictest = { path = "../sqllogictest", version = "0.24" }
2424
thiserror = "2"
2525
tokio = { version = "1", features = [
2626
"rt",

sqllogictest/src/parser.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pub enum QueryExpect<T: ColumnType> {
9494
Results {
9595
types: Vec<T>,
9696
sort_mode: Option<SortMode>,
97+
result_mode: Option<ResultMode>,
9798
results: Vec<String>,
9899
},
99100
/// Query should fail with the given error message.
@@ -106,6 +107,7 @@ impl<T: ColumnType> QueryExpect<T> {
106107
Self::Results {
107108
types: Vec::new(),
108109
sort_mode: None,
110+
result_mode: None,
109111
results: Vec::new(),
110112
}
111113
}
@@ -308,6 +310,7 @@ impl<T: ColumnType> std::fmt::Display for Record<T> {
308310
}
309311
Record::Control(c) => match c {
310312
Control::SortMode(m) => write!(f, "control sortmode {}", m.as_str()),
313+
Control::ResultMode(m) => write!(f, "control resultmode {}", m.as_str()),
311314
Control::Substitution(s) => write!(f, "control substitution {}", s.as_str()),
312315
},
313316
Record::Condition(cond) => match cond {
@@ -456,6 +459,8 @@ impl PartialEq for ExpectedError {
456459
pub enum Control {
457460
/// Control sort mode.
458461
SortMode(SortMode),
462+
/// control result mode.
463+
ResultMode(ResultMode),
459464
/// Control whether or not to substitute variables in the SQL.
460465
Substitution(bool),
461466
}
@@ -566,6 +571,38 @@ impl ControlItem for SortMode {
566571
}
567572
}
568573

574+
/// Whether the results should be parsed as value-wise or row-wise
575+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
576+
pub enum ResultMode {
577+
/// Results are in a single column
578+
ValueWise,
579+
/// The default option where results are in columns separated by spaces
580+
RowWise,
581+
}
582+
583+
impl ControlItem for ResultMode {
584+
fn try_from_str(s: &str) -> Result<Self, ParseErrorKind> {
585+
match s {
586+
"rowwise" => Ok(Self::RowWise),
587+
"valuewise" => Ok(Self::ValueWise),
588+
_ => Err(ParseErrorKind::InvalidSortMode(s.to_string())),
589+
}
590+
}
591+
592+
fn as_str(&self) -> &'static str {
593+
match self {
594+
Self::RowWise => "rowwise",
595+
Self::ValueWise => "valuewise",
596+
}
597+
}
598+
}
599+
600+
impl fmt::Display for ResultMode {
601+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
602+
write!(f, "{self:?}")
603+
}
604+
}
605+
569606
/// The error type for parsing sqllogictest.
570607
#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)]
571608
#[error("parse error at {loc}: {kind}")]
@@ -785,6 +822,7 @@ fn parse_inner<T: ColumnType>(loc: &Location, script: &str) -> Result<Vec<Record
785822
QueryExpect::Results {
786823
types,
787824
sort_mode,
825+
result_mode: None,
788826
results: Vec::new(),
789827
},
790828
&res[retry_start..],
@@ -847,6 +885,12 @@ fn parse_inner<T: ColumnType>(loc: &Location, script: &str) -> Result<Vec<Record
847885
});
848886
}
849887
["control", res @ ..] => match res {
888+
["resultmode", result_mode] => match ResultMode::try_from_str(result_mode) {
889+
Ok(result_mode) => {
890+
records.push(Record::Control(Control::ResultMode(result_mode)))
891+
}
892+
Err(k) => return Err(k.at(loc)),
893+
},
850894
["sortmode", sort_mode] => match SortMode::try_from_str(sort_mode) {
851895
Ok(sort_mode) => records.push(Record::Control(Control::SortMode(sort_mode))),
852896
Err(k) => return Err(k.at(loc)),
@@ -1076,6 +1120,11 @@ mod tests {
10761120
parse_roundtrip::<DefaultColumnType>("../tests/slt/rowsort.slt")
10771121
}
10781122

1123+
#[test]
1124+
fn test_valuesort() {
1125+
parse_roundtrip::<DefaultColumnType>("../tests/slt/valuesort.slt")
1126+
}
1127+
10791128
#[test]
10801129
fn test_substitution() {
10811130
parse_roundtrip::<DefaultColumnType>("../tests/substitution/basic.slt")

sqllogictest/src/runner.rs

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -449,26 +449,39 @@ fn format_column_diff(expected: &str, actual: &str, colorize: bool) -> String {
449449
format!("[Expected] {expected}\n[Actual ] {actual}")
450450
}
451451

452+
/// Normalizer will be used by [`Runner`] to normalize the result values
453+
///
454+
/// # Default
455+
///
456+
/// By default, the ([`default_normalizer`]) will be used to normalize values.
457+
pub type Normalizer = fn(s: &String) -> String;
458+
452459
/// Trim and replace multiple whitespaces with one.
453460
#[allow(clippy::ptr_arg)]
454-
fn normalize_string(s: &String) -> String {
461+
pub fn default_normalizer(s: &String) -> String {
455462
s.trim().split_ascii_whitespace().join(" ")
456463
}
457464

458465
/// Validator will be used by [`Runner`] to validate the output.
459466
///
460467
/// # Default
461468
///
462-
/// By default ([`default_validator`]), we will use compare normalized results.
463-
pub type Validator = fn(actual: &[Vec<String>], expected: &[String]) -> bool;
464-
465-
pub fn default_validator(actual: &[Vec<String>], expected: &[String]) -> bool {
466-
let expected_results = expected.iter().map(normalize_string).collect_vec();
469+
/// By default, the ([`default_validator`]) will be used compare normalized results.
470+
pub type Validator =
471+
fn(normalizer: Normalizer, actual: &[Vec<String>], expected: &[String]) -> bool;
472+
473+
pub fn default_validator(
474+
normalizer: Normalizer,
475+
actual: &[Vec<String>],
476+
expected: &[String],
477+
) -> bool {
478+
let expected_results = expected.iter().map(normalizer).collect_vec();
467479
// Default, we compare normalized results. Whitespace characters are ignored.
468480
let normalized_rows = actual
469481
.iter()
470-
.map(|strs| strs.iter().map(normalize_string).join(" "))
482+
.map(|strs| strs.iter().map(normalizer).join(" "))
471483
.collect_vec();
484+
472485
normalized_rows == expected_results
473486
}
474487

@@ -502,9 +515,12 @@ pub struct Runner<D: AsyncDB, M: MakeConnection> {
502515
conn: Connections<D, M>,
503516
// validator is used for validate if the result of query equals to expected.
504517
validator: Validator,
518+
// normalizer is used to normalize the result text
519+
normalizer: Normalizer,
505520
column_type_validator: ColumnTypeValidator<D::ColumnType>,
506521
substitution: Option<Substitution>,
507522
sort_mode: Option<SortMode>,
523+
result_mode: Option<ResultMode>,
508524
/// 0 means never hashing
509525
hash_threshold: usize,
510526
/// Labels for condition `skipif` and `onlyif`.
@@ -518,9 +534,11 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
518534
pub fn new(make_conn: M) -> Self {
519535
Runner {
520536
validator: default_validator,
537+
normalizer: default_normalizer,
521538
column_type_validator: default_column_validator,
522539
substitution: None,
523540
sort_mode: None,
541+
result_mode: None,
524542
hash_threshold: 0,
525543
labels: HashSet::new(),
526544
conn: Connections::new(make_conn),
@@ -532,6 +550,9 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
532550
self.labels.insert(label.to_string());
533551
}
534552

553+
pub fn with_normalizer(&mut self, normalizer: Normalizer) {
554+
self.normalizer = normalizer;
555+
}
535556
pub fn with_validator(&mut self, validator: Validator) {
536557
self.validator = validator;
537558
}
@@ -771,15 +792,31 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
771792
QueryExpect::Error(_) => None,
772793
}
773794
.or(self.sort_mode);
795+
796+
let mut value_sort = false;
774797
match sort_mode {
775798
None | Some(SortMode::NoSort) => {}
776799
Some(SortMode::RowSort) => {
777800
rows.sort_unstable();
778801
}
779-
Some(SortMode::ValueSort) => todo!("value sort"),
802+
Some(SortMode::ValueSort) => {
803+
rows = rows
804+
.iter()
805+
.flat_map(|row| row.iter())
806+
.map(|s| vec![s.to_owned()])
807+
.collect();
808+
rows.sort_unstable();
809+
value_sort = true;
810+
}
780811
};
781812

782-
if self.hash_threshold > 0 && rows.len() * types.len() > self.hash_threshold {
813+
let num_values = if value_sort {
814+
rows.len()
815+
} else {
816+
rows.len() * types.len()
817+
};
818+
819+
if self.hash_threshold > 0 && num_values > self.hash_threshold {
783820
let mut md5 = md5::Md5::new();
784821
for line in &rows {
785822
for value in line {
@@ -810,6 +847,9 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
810847
Control::SortMode(sort_mode) => {
811848
self.sort_mode = Some(sort_mode);
812849
}
850+
Control::ResultMode(result_mode) => {
851+
self.result_mode = Some(result_mode);
852+
}
813853
Control::Substitution(on_off) => match (&mut self.substitution, on_off) {
814854
(s @ None, true) => *s = Some(Substitution::default()),
815855
(s @ Some(_), false) => *s = None,
@@ -1029,7 +1069,17 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
10291069
.at(loc));
10301070
}
10311071

1032-
if !(self.validator)(rows, &expected_results) {
1072+
let actual_results = match self.result_mode {
1073+
Some(ResultMode::ValueWise) => rows
1074+
.iter()
1075+
.flat_map(|strs| strs.iter())
1076+
.map(|str| vec![str.to_string()])
1077+
.collect_vec(),
1078+
// default to rowwise
1079+
_ => rows.clone(),
1080+
};
1081+
1082+
if !(self.validator)(self.normalizer, &actual_results, &expected_results) {
10331083
let output_rows =
10341084
rows.iter().map(|strs| strs.iter().join(" ")).collect_vec();
10351085
return Err(TestErrorKind::QueryResultMismatch {
@@ -1200,9 +1250,11 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
12001250
conn_builder(target.clone(), db_name.clone()).map(Ok)
12011251
}),
12021252
validator: self.validator,
1253+
normalizer: self.normalizer,
12031254
column_type_validator: self.column_type_validator,
12041255
substitution: self.substitution.clone(),
12051256
sort_mode: self.sort_mode,
1257+
result_mode: self.result_mode,
12061258
hash_threshold: self.hash_threshold,
12071259
labels: self.labels.clone(),
12081260
};
@@ -1273,6 +1325,7 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
12731325
filename: impl AsRef<Path>,
12741326
col_separator: &str,
12751327
validator: Validator,
1328+
normalizer: Normalizer,
12761329
column_type_validator: ColumnTypeValidator<D::ColumnType>,
12771330
) -> Result<(), Box<dyn std::error::Error>> {
12781331
use std::io::{Read, Seek, SeekFrom, Write};
@@ -1388,6 +1441,7 @@ impl<D: AsyncDB, M: MakeConnection<Conn = D>> Runner<D, M> {
13881441
&record_output,
13891442
col_separator,
13901443
validator,
1444+
normalizer,
13911445
column_type_validator,
13921446
)
13931447
.unwrap_or(record);
@@ -1417,6 +1471,7 @@ pub fn update_record_with_output<T: ColumnType>(
14171471
record_output: &RecordOutput<T>,
14181472
col_separator: &str,
14191473
validator: Validator,
1474+
normalizer: Normalizer,
14201475
column_type_validator: ColumnTypeValidator<T>,
14211476
) -> Option<Record<T>> {
14221477
match (record.clone(), record_output) {
@@ -1565,7 +1620,7 @@ pub fn update_record_with_output<T: ColumnType>(
15651620
QueryExpect::Results {
15661621
results: expected_results,
15671622
..
1568-
} if validator(rows, expected_results) => expected_results.clone(),
1623+
} if validator(normalizer, rows, expected_results) => expected_results.clone(),
15691624
_ => rows.iter().map(|cols| cols.join(col_separator)).collect(),
15701625
};
15711626
let types = match &expected {
@@ -1582,15 +1637,22 @@ pub fn update_record_with_output<T: ColumnType>(
15821637
conditions,
15831638
connection,
15841639
expected: match expected {
1585-
QueryExpect::Results { sort_mode, .. } => QueryExpect::Results {
1640+
QueryExpect::Results {
1641+
sort_mode,
1642+
1643+
result_mode,
1644+
..
1645+
} => QueryExpect::Results {
15861646
results,
15871647
types,
15881648
sort_mode,
1649+
result_mode,
15891650
},
15901651
QueryExpect::Error(_) => QueryExpect::Results {
15911652
results,
15921653
types,
15931654
sort_mode: None,
1655+
result_mode: None,
15941656
},
15951657
},
15961658
retry,
@@ -2048,6 +2110,7 @@ Caused by:
20482110
&record_output,
20492111
" ",
20502112
default_validator,
2113+
default_normalizer,
20512114
strict_column_validator,
20522115
);
20532116

tests/custom_type/custom_type.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,8 @@ fn test() {
6969
let mut tester = sqllogictest::Runner::new(|| async { Ok(FakeDB) });
7070
tester.with_column_validator(strict_column_validator);
7171

72-
tester.run_file("./custom_type/custom_type.slt").unwrap();
72+
let r = tester.run_file("./custom_type/custom_type.slt");
73+
if let Err(err) = r {
74+
eprintln!("{:?}", err);
75+
}
7376
}

0 commit comments

Comments
 (0)