From e2f18ca26cc77fae6f4cdbdc269cb1041a793ca6 Mon Sep 17 00:00:00 2001 From: Ruihang Xia Date: Thu, 29 Jun 2023 19:42:39 +0800 Subject: [PATCH 1/3] impl order interceptor Signed-off-by: Ruihang Xia --- sqlness/src/interceptor.rs | 1 + sqlness/src/interceptor/sort_result.rs | 188 +++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 sqlness/src/interceptor/sort_result.rs diff --git a/sqlness/src/interceptor.rs b/sqlness/src/interceptor.rs index 315599b..e119659 100644 --- a/sqlness/src/interceptor.rs +++ b/sqlness/src/interceptor.rs @@ -11,6 +11,7 @@ use crate::{ pub mod arg; pub mod replace; +pub mod sort_result; pub type InterceptorRef = Box; diff --git a/sqlness/src/interceptor/sort_result.rs b/sqlness/src/interceptor/sort_result.rs new file mode 100644 index 00000000..c70879a --- /dev/null +++ b/sqlness/src/interceptor/sort_result.rs @@ -0,0 +1,188 @@ +// Copyright 2022 CeresDB Project Authors. Licensed under Apache-2.0. + +use std::collections::VecDeque; + +use crate::interceptor::{Interceptor, InterceptorFactory, InterceptorRef}; + +const PREFIX: &str = "SORT_RESULT"; + +/// Sort the query result in lexicographical order. +/// +/// Grammar: +/// ``` text +/// -- SQLNESS SORT_RESULT +/// ``` +/// +/// Both `ignore-head` and `ignore-tail` are optional. Default value is 0 (no lines will be ignored). +/// +/// # Example +/// `.sql` file: +/// ``` sql +/// -- SQLNESS SORT_RESULT +/// SELECT * from values (3), (2), (1); +/// ``` +/// +/// `.result` file: +/// ``` sql +/// -- SQLNESS SORT_RESULT +/// SELECT * from values (3), (2), (1); +/// +/// 1 +/// 2 +/// 3 +/// ``` +#[derive(Debug)] +pub struct SortResultInterceptor { + /// How much lines to ignore from the head + ignore_head: usize, + /// How much lines to ignore from the tail + ignore_tail: usize, +} + +impl Interceptor for SortResultInterceptor { + fn after_execute(&self, result: &mut String) { + let mut lines = result.lines().collect::>(); + let mut head = Vec::with_capacity(self.ignore_head); + let mut tail = Vec::with_capacity(self.ignore_tail); + + // ignore head and tail + for _ in 0..self.ignore_head { + if let Some(l) = lines.pop_front() { + head.push(l); + } + } + for _ in 0..self.ignore_tail { + if let Some(l) = lines.pop_back() { + tail.push(l); + } + } + tail.reverse(); + + // sort remaining lines + lines.make_contiguous().sort(); + + let new_lines = head + .into_iter() + .chain(lines.into_iter()) + .chain(tail.into_iter()) + .collect::>(); + *result = new_lines.join("\n"); + } +} + +pub struct SortResultInterceptorFactory; + +impl InterceptorFactory for SortResultInterceptorFactory { + fn try_new(&self, interceptor: &str) -> Option { + Self::try_new_from_str(interceptor).map(|i| Box::new(i) as _) + } +} + +impl SortResultInterceptorFactory { + fn try_new_from_str(interceptor: &str) -> Option { + if interceptor.starts_with(PREFIX) { + let args = interceptor + .trim_start_matches(PREFIX) + .trim_start() + .trim_end(); + let mut args = args.splitn(2, ' ').filter(|s| !s.is_empty()); + let ignore_head = args.next().unwrap_or("0").parse().ok()?; + let ignore_tail = args.next().unwrap_or("0").parse().ok()?; + + Some(SortResultInterceptor { + ignore_head, + ignore_tail, + }) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn construct_with_empty_string() { + let input = "SORT_RESULT"; + let sort_result = SortResultInterceptorFactory::try_new_from_str(input).unwrap(); + assert_eq!(sort_result.ignore_head, 0); + assert_eq!(sort_result.ignore_tail, 0); + } + + #[test] + fn construct_with_negative() { + let input = "SORT_RESULT -1"; + let interceptor = SortResultInterceptorFactory.try_new(input); + assert!(interceptor.is_none()); + } + + #[test] + fn sort_result_full() { + let input = "SORT_RESULT"; + let interceptor = SortResultInterceptorFactory.try_new(input).unwrap(); + + let cases = [ + ( + String::from( + "abc\ + \ncde\ + \nefg", + ), + String::from( + "abc\ + \ncde\ + \nefg", + ), + ), + ( + String::from( + "efg\ + \ncde\ + \nabc", + ), + String::from( + "abc\ + \ncde\ + \nefg", + ), + ), + ]; + + for (mut input, expected) in cases { + interceptor.after_execute(&mut input); + assert_eq!(input, expected); + } + } + + #[test] + fn ignore_head_exceeds_length() { + let input = "SORT_RESULT 10000"; + let interceptor = SortResultInterceptorFactory.try_new(input).unwrap(); + + let mut exec_result = String::from( + "3\ + \n2\ + \n1", + ); + let expected = exec_result.clone(); + interceptor.after_execute(&mut exec_result); + assert_eq!(exec_result, expected); + } + + #[test] + fn ignore_tail_exceeds_length() { + let input = "SORT_RESULT 0 10000"; + let interceptor = SortResultInterceptorFactory.try_new(input).unwrap(); + + let mut exec_result = String::from( + "3\ + \n2\ + \n1", + ); + let expected = exec_result.clone(); + interceptor.after_execute(&mut exec_result); + assert_eq!(exec_result, expected); + } +} From 6992ef70028149e872ba3f13eb4c886e6219d2d3 Mon Sep 17 00:00:00 2001 From: Ruihang Xia Date: Thu, 29 Jun 2023 20:04:44 +0800 Subject: [PATCH 2/3] add example Signed-off-by: Ruihang Xia --- Makefile | 5 +- .../simple/sort_result.result | 33 ++++++++++++ .../simple/sort_result.sql | 17 ++++++ sqlness/examples/interceptor_sort_result.rs | 53 +++++++++++++++++++ sqlness/src/case.rs | 2 +- sqlness/src/interceptor/sort_result.rs | 2 +- 6 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 sqlness/examples/interceptor-sort-result/simple/sort_result.result create mode 100644 sqlness/examples/interceptor-sort-result/simple/sort_result.sql create mode 100644 sqlness/examples/interceptor_sort_result.rs diff --git a/Makefile b/Makefile index 8b9b4cf..cdb4e47 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ cli-test: example: good-example bad-example -good-example: basic-example interceptor-arg-example interceptor-replace-example +good-example: basic-example interceptor-arg-example interceptor-replace-example interceptor-sort-result-example basic-example: cd $(DIR)/sqlness; cargo run --example basic @@ -39,3 +39,6 @@ interceptor-arg-example: interceptor-replace-example: cd $(DIR)/sqlness; cargo run --example interceptor_replace + +interceptor-sort-result-example: + cd $(DIR)/sqlness; cargo run --example interceptor_sort_result diff --git a/sqlness/examples/interceptor-sort-result/simple/sort_result.result b/sqlness/examples/interceptor-sort-result/simple/sort_result.result new file mode 100644 index 00000000..86c175d --- /dev/null +++ b/sqlness/examples/interceptor-sort-result/simple/sort_result.result @@ -0,0 +1,33 @@ +-- SQLNESS SORT_RESULT +4 +3 +6 +1; + +4 +3 +6 +1; + +-- SQLNESS SORT_RESULT 1 1 +7 +1 +4 +2 +2; + +7 +1 +4 +2 +2; + +-- SQLNESS SORT_RESULT -1 +6 +2 +4; + +6 +2 +4; + diff --git a/sqlness/examples/interceptor-sort-result/simple/sort_result.sql b/sqlness/examples/interceptor-sort-result/simple/sort_result.sql new file mode 100644 index 00000000..8d1c977 --- /dev/null +++ b/sqlness/examples/interceptor-sort-result/simple/sort_result.sql @@ -0,0 +1,17 @@ +-- SQLNESS SORT_RESULT +4 +3 +6 +1; + +-- SQLNESS SORT_RESULT 1 1 +7 +1 +4 +2 +2; + +-- SQLNESS SORT_RESULT -1 +6 +2 +4; diff --git a/sqlness/examples/interceptor_sort_result.rs b/sqlness/examples/interceptor_sort_result.rs new file mode 100644 index 00000000..aeeec19 --- /dev/null +++ b/sqlness/examples/interceptor_sort_result.rs @@ -0,0 +1,53 @@ +// Copyright 2023 CeresDB Project Authors. Licensed under Apache-2.0. + +//! Shows how an SORT_RESULT interceptor works. + +use std::{fmt::Display, path::Path}; + +use async_trait::async_trait; +use sqlness::{ConfigBuilder, Database, EnvController, QueryContext, Runner}; + +struct MyController; +struct MyDB; + +#[async_trait] +impl Database for MyDB { + async fn query(&self, _: QueryContext, query: String) -> Box { + return Box::new(query); + } +} + +impl MyDB { + fn new(_env: &str, _config: Option<&Path>) -> Self { + MyDB + } + + fn stop(self) {} +} + +#[async_trait] +impl EnvController for MyController { + type DB = MyDB; + + async fn start(&self, env: &str, config: Option<&Path>) -> Self::DB { + MyDB::new(env, config) + } + + async fn stop(&self, _env: &str, database: Self::DB) { + database.stop(); + } +} + +#[tokio::main] +async fn main() { + let env = MyController; + let config = ConfigBuilder::default() + .case_dir("examples/interceptor-sort-result".to_string()) + .build() + .unwrap(); + let runner = Runner::new(config, env); + + println!("Run testcase..."); + + runner.run().await.unwrap(); +} diff --git a/sqlness/src/case.rs b/sqlness/src/case.rs index db5e659..5577b86 100644 --- a/sqlness/src/case.rs +++ b/sqlness/src/case.rs @@ -155,7 +155,7 @@ impl Query { fn concat_query_lines(&self) -> String { self.query_lines .iter() - .fold(String::new(), |query, str| query + " " + str) + .fold(String::new(), |query, str| query + str) .trim_start() .to_string() } diff --git a/sqlness/src/interceptor/sort_result.rs b/sqlness/src/interceptor/sort_result.rs index c70879a..d728355 100644 --- a/sqlness/src/interceptor/sort_result.rs +++ b/sqlness/src/interceptor/sort_result.rs @@ -1,4 +1,4 @@ -// Copyright 2022 CeresDB Project Authors. Licensed under Apache-2.0. +// Copyright 2023 CeresDB Project Authors. Licensed under Apache-2.0. use std::collections::VecDeque; From a4663365795d2067eb53966c383e1bb0c89c7627 Mon Sep 17 00:00:00 2001 From: Ruihang Xia Date: Fri, 30 Jun 2023 11:32:05 +0800 Subject: [PATCH 3/3] construct and update result Signed-off-by: Ruihang Xia --- .../interceptor-sort-result/simple/sort_result.result | 6 +++--- sqlness/src/interceptor.rs | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/sqlness/examples/interceptor-sort-result/simple/sort_result.result b/sqlness/examples/interceptor-sort-result/simple/sort_result.result index 86c175d..43920fc 100644 --- a/sqlness/examples/interceptor-sort-result/simple/sort_result.result +++ b/sqlness/examples/interceptor-sort-result/simple/sort_result.result @@ -4,10 +4,10 @@ 6 1; -4 +1; 3 +4 6 -1; -- SQLNESS SORT_RESULT 1 1 7 @@ -18,8 +18,8 @@ 7 1 -4 2 +4 2; -- SQLNESS SORT_RESULT -1 diff --git a/sqlness/src/interceptor.rs b/sqlness/src/interceptor.rs index e119659..2aea588 100644 --- a/sqlness/src/interceptor.rs +++ b/sqlness/src/interceptor.rs @@ -6,7 +6,10 @@ use std::sync::Arc; use crate::{ case::QueryContext, - interceptor::{arg::ArgInterceptorFactory, replace::ReplaceInterceptorFactory}, + interceptor::{ + arg::ArgInterceptorFactory, replace::ReplaceInterceptorFactory, + sort_result::SortResultInterceptorFactory, + }, }; pub mod arg; @@ -34,5 +37,6 @@ pub fn builtin_interceptors() -> Vec { vec![ Arc::new(ArgInterceptorFactory {}), Arc::new(ReplaceInterceptorFactory {}), + Arc::new(SortResultInterceptorFactory {}), ] }