diff --git a/sbroad-cartridge/test_app/test/integration/trim_test.lua b/sbroad-cartridge/test_app/test/integration/trim_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..bd88e166208864af92ab4d9be8f40a4d1b0de128 --- /dev/null +++ b/sbroad-cartridge/test_app/test/integration/trim_test.lua @@ -0,0 +1,78 @@ + +local t = require('luatest') +local g = t.group('integration_api.trim') + +local helper = require('test.helper.cluster_no_replication') + +g.before_all( + function() + helper.start_test_cluster(helper.cluster_config) + local api = helper.cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", + { [[ INSERT INTO "t"("id", "a") VALUES (112211, 2211) ]], } + ) + t.assert_equals(err, nil) + t.assert_equals(r, {row_count = 1}) + end +) + +g.after_all(function() + helper.stop_test_cluster() +end) + +g.test_trim = function () + local api = helper.cluster:server("api-1").net_box + + -- basic trim test + local r, err = api:call("sbroad.execute", + { [[SELECT trim(' aabb ') as "a" from "t"]], } + ) + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { {name = "a", type = "string"}, }, + rows = { { 'aabb' } }, + }) + + -- trim inside trim + r, err = api:call("sbroad.execute", + { [[SELECT trim(trim(' aabb ')) from "t"]], } + ) + t.assert_equals(err, nil) + t.assert_equals(r["rows"], { { 'aabb' } }) + + -- trim with literal in pattern + r, err = api:call("sbroad.execute", + { [[SELECT trim('a' from trim(' aabb ')) from "t"]], } + ) + t.assert_equals(err, nil) + t.assert_equals(r["rows"], { { 'bb' } }) + + -- trim with expression in pattern + r, err = api:call("sbroad.execute", + { [[SELECT trim(trim(' aabb ') from trim(' aabb ')) from "t"]], } + ) + t.assert_equals(err, nil) + t.assert_equals(r["rows"], { { '' } }) + + -- trim with leading modifier + r, err = api:call("sbroad.execute", + { [[SELECT trim(leading 'a' from trim('aabb ')) from "t"]], } + ) + t.assert_equals(err, nil) + t.assert_equals(r["rows"], { { 'bb' } }) + + -- trim with trailing modifier + r, err = api:call("sbroad.execute", + { [[SELECT trim(trailing 'b' from trim('aabb')) from "t"]], } + ) + t.assert_equals(err, nil) + t.assert_equals(r["rows"], { { 'aa' } }) + + -- trim with both modifier + r, err = api:call("sbroad.execute", + { [[SELECT trim(both 'ab' from 'aabb') from "t"]], } + ) + t.assert_equals(err, nil) + t.assert_equals(r["rows"], { { '' } }) +end diff --git a/sbroad-core/src/backend/sql/ir.rs b/sbroad-core/src/backend/sql/ir.rs index 6c5b85b7c6227195088127977e8f4e7b1e255d51..3b50577b7755d3e1c1e9c331439d8a5122253cdb 100644 --- a/sbroad-core/src/backend/sql/ir.rs +++ b/sbroad-core/src/backend/sql/ir.rs @@ -296,6 +296,7 @@ impl ExecutionPlan { SyntaxData::Trailing => sql.push_str("TRAILING"), SyntaxData::Operator(s) => sql.push_str(s.as_str()), SyntaxData::OpenParenthesis => sql.push('('), + SyntaxData::Trim => sql.push_str("TRIM"), SyntaxData::PlanId(id) => { let node = ir_plan.get_node(*id)?; match node { @@ -370,6 +371,7 @@ impl ExecutionPlan { | Expression::Cast { .. } | Expression::Concat { .. } | Expression::Row { .. } + | Expression::Trim { .. } | Expression::Unary { .. } => {} Expression::Constant { value, .. } => { write!(sql, "{value}").map_err(|e| { diff --git a/sbroad-core/src/backend/sql/tree.rs b/sbroad-core/src/backend/sql/tree.rs index 0b09f69a88190fc91d11aaa26e1b46f3618eb888..ef8941e74d1170eb0e175335424f4752f0a9ae5f 100644 --- a/sbroad-core/src/backend/sql/tree.rs +++ b/sbroad-core/src/backend/sql/tree.rs @@ -42,6 +42,8 @@ pub enum SyntaxData { Both, /// "trailing" Trailing, + /// "trim" + Trim, /// "(" OpenParenthesis, /// "=, >, <, and, or, ..." @@ -179,6 +181,14 @@ impl SyntaxNode { } } + fn new_trim() -> Self { + SyntaxNode { + data: SyntaxData::Trim, + left: None, + right: Vec::new(), + } + } + fn new_operator(value: &str) -> Self { SyntaxNode { data: SyntaxData::Operator(value.into()), @@ -1001,45 +1011,53 @@ impl<'p> SyntaxPlan<'p> { } => { let mut nodes: Vec<usize> = vec![self.nodes.push_syntax_node(SyntaxNode::new_open())]; - if let Some(FunctionFeature::Trim(kind)) = feature { - // `trim` function has a special format. For instance, here how we can - // call it: trim(leading 'a' from 'ab'). - match kind { - TrimKind::Leading => { - nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_leading())); - } - TrimKind::Trailing => { - nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_trailing())); - } - TrimKind::Both => { - nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_both())); - } - } - - if let Some((string, removal_chars)) = children.split_last() { - for child in removal_chars { - nodes.push(self.nodes.get_syntax_node_id(*child)?); - } - nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_from())); - nodes.push(self.nodes.get_syntax_node_id(*string)?); - } - } else { - if let Some(FunctionFeature::Distinct) = feature { - nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_distinct())); - } - if let Some((last, others)) = children.split_last() { - for child in others { - nodes.push(self.nodes.get_syntax_node_id(*child)?); - nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_comma())); - } - nodes.push(self.nodes.get_syntax_node_id(*last)?); + if let Some(FunctionFeature::Distinct) = feature { + nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_distinct())); + } + if let Some((last, others)) = children.split_last() { + for child in others { + nodes.push(self.nodes.get_syntax_node_id(*child)?); + nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_comma())); } + nodes.push(self.nodes.get_syntax_node_id(*last)?); } nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_close())); let sn = SyntaxNode::new_pointer(id, None, nodes); Ok(self.nodes.push_syntax_node(sn)) } + Expression::Trim { + kind, + pattern, + target, + } => { + let syn_kind = match kind { + Some(TrimKind::Leading) => Some(SyntaxNode::new_leading()), + Some(TrimKind::Trailing) => Some(SyntaxNode::new_trailing()), + Some(TrimKind::Both) => Some(SyntaxNode::new_both()), + None => None, + }; + let mut nodes = Vec::with_capacity(6); + nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_open())); + let mut need_from = false; + if let Some(kind) = syn_kind { + nodes.push(self.nodes.push_syntax_node(kind)); + need_from = true; + } + if let Some(pattern) = pattern { + nodes.push(self.nodes.get_syntax_node_id(*pattern)?); + need_from = true; + } + if need_from { + nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_from())); + } + nodes.push(self.nodes.get_syntax_node_id(*target)?); + nodes.push(self.nodes.push_syntax_node(SyntaxNode::new_close())); + + let trim_id = self.nodes.push_syntax_node(SyntaxNode::new_trim()); + let sn = SyntaxNode::new_pointer(id, Some(trim_id), nodes); + Ok(self.nodes.push_syntax_node(sn)) + } }, } } diff --git a/sbroad-core/src/executor/ir.rs b/sbroad-core/src/executor/ir.rs index 452eb0a1448b14e864803a9fffae562dba74e689..be9325eae222879f6c71938d0919439248469668 100644 --- a/sbroad-core/src/executor/ir.rs +++ b/sbroad-core/src/executor/ir.rs @@ -595,6 +595,32 @@ impl ExecutionPlan { ) })?; } + Expression::Trim { + ref mut pattern, + ref mut target, + .. + } => { + if let Some(pattern) = pattern { + *pattern = *translation.get(pattern).ok_or_else(|| { + SbroadError::FailedTo( + Action::Build, + Some(Entity::SubTree), + format_smolstr!( + "could not find pattern node id {pattern} in the map" + ), + ) + })?; + } + *target = *translation.get(target).ok_or_else(|| { + SbroadError::FailedTo( + Action::Build, + Some(Entity::SubTree), + format_smolstr!( + "could not find target node id {target} in the map" + ), + ) + })?; + } Expression::Reference { ref mut parent, .. } => { // The new parent node id MUST be set while processing the relational nodes. *parent = None; diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index bead363580f779c62b5adbd10c755443ca8a2cf0..1fa642d8920aecd9e79b36b4a63eb63714018194 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -884,72 +884,63 @@ fn parse_grant_revoke( Ok((grant_revoke_type, grantee_name, timeout)) } -fn parse_trim_function_args<M: Metadata>( - function_name: String, - function_args: Pair<'_, Rule>, +fn parse_trim<M: Metadata>( + pair: Pair<Rule>, referred_relation_ids: &[usize], worker: &mut ExpressionsWorker<M>, plan: &mut Plan, ) -> Result<ParseExpression, SbroadError> { - let normalized_name = function_name.to_lowercase(); - if "trim" != normalized_name.as_str() { - return Err(SbroadError::Invalid( - Entity::Query, - Some(format_smolstr!( - "Trim function artuments format is allowed only inside \"trim\" function. Got: {normalized_name}", - )) - )); - } - let mut parse_exprs_args = Vec::new(); + assert_eq!(pair.as_rule(), Rule::Trim); let mut kind = None; - let mut removal_chars = None; - let mut string = None; + let mut pattern = None; + let mut target = None; - let args_inner = function_args.into_inner(); - for arg_pair in args_inner { - match arg_pair.as_rule() { + let inner_pairs = pair.into_inner(); + for child_pair in &mut inner_pairs.into_iter() { + match child_pair.as_rule() { Rule::TrimKind => { - for kind_pair in arg_pair.into_inner() { - match kind_pair.as_rule() { - Rule::TrimKindBoth => kind = Some(TrimKind::Both), - Rule::TrimKindLeading => kind = Some(TrimKind::Leading), - Rule::TrimKindTrailing => kind = Some(TrimKind::Trailing), - rule => unreachable!("Expected TrimKind variant. Got: {rule:?}"), + let kind_pair = child_pair + .into_inner() + .next() + .expect("Expected child of TrimKind"); + match kind_pair.as_rule() { + Rule::TrimKindBoth => kind = Some(TrimKind::Both), + Rule::TrimKindLeading => kind = Some(TrimKind::Leading), + Rule::TrimKindTrailing => kind = Some(TrimKind::Trailing), + _ => { + panic!("Unexpected node: {kind_pair:?}"); } } } - Rule::TrimChars => { - removal_chars = Some(parse_expr_pratt( - arg_pair.into_inner(), + Rule::TrimPattern => { + let inner_pattern = child_pair.into_inner(); + pattern = Some(Box::new(parse_expr_pratt( + inner_pattern, referred_relation_ids, worker, plan, - )?); + )?)); } - Rule::TrimString => { - string = Some(parse_expr_pratt( - arg_pair.into_inner(), + Rule::TrimTarget => { + let inner_target = child_pair.into_inner(); + target = Some(Box::new(parse_expr_pratt( + inner_target, referred_relation_ids, worker, plan, - )?); + )?)); + } + _ => { + panic!("Unexpected node: {child_pair:?}"); } - rule => unreachable!("Unexpected rule under TrimFunctionArgs: {rule:?}"), } } - let string = string.expect("string is required by grammar"); - - if let Some(removal_chars) = removal_chars { - parse_exprs_args.push(removal_chars); - } - parse_exprs_args.push(string); - let trim_kind = kind.unwrap_or_default(); - - Ok(ParseExpression::Function { - name: function_name, - args: parse_exprs_args, - feature: Some(FunctionFeature::Trim(trim_kind)), - }) + let trim = ParseExpression::Trim { + kind, + pattern, + target: target.expect("Trim target must be specified"), + }; + Ok(trim) } /// Common logic for `SqlVdbeMaxSteps` and `VTableMaxRows` parsing. @@ -1260,6 +1251,11 @@ enum ParseExpression { cast_type: CastType, child: Box<ParseExpression>, }, + Trim { + kind: Option<TrimKind>, + pattern: Option<Box<ParseExpression>>, + target: Box<ParseExpression>, + }, Between { is_not: bool, left: Box<ParseExpression>, @@ -1288,6 +1284,7 @@ impl Plan { | Expression::Reference { .. } | Expression::Row { .. } | Expression::StableFunction { .. } + | Expression::Trim { .. } | Expression::Unary { .. } | Expression::CountAsterisk => Err(SbroadError::Invalid( Entity::Expression, @@ -1363,6 +1360,22 @@ impl ParseExpression { let child_plan_id = child.populate_plan(plan, worker)?; plan.add_cast(child_plan_id, cast_type.clone())? } + ParseExpression::Trim { + kind, + pattern, + target, + } => { + let pattern = match pattern { + Some(p) => Some(p.populate_plan(plan, worker)?), + None => None, + }; + let trim_expr = Expression::Trim { + kind: kind.clone(), + pattern, + target: target.populate_plan(plan, worker)?, + }; + plan.nodes.push(Node::Expression(trim_expr)) + } ParseExpression::Between { is_not, left, @@ -1648,15 +1661,6 @@ where parse_exprs_args.push(arg_expr); } } - Rule::TrimFunctionArgs => { - return parse_trim_function_args( - function_name, - function_args, - referred_relation_ids, - worker, - plan - ); - } rule => unreachable!("{}", format!("Unexpected rule under FunctionInvocation: {rule:?}")) } } @@ -1795,6 +1799,7 @@ where )?; ParseExpression::Exists { is_not: first_is_not, child: Box::new(child_parse_expr)} } + Rule::Trim => parse_trim(primary, referred_relation_ids, worker, plan)?, Rule::Cast => { let mut inner_pairs = primary.into_inner(); let expr_pair = inner_pairs.next().expect("Cast has no expr child."); diff --git a/sbroad-core/src/frontend/sql/ir.rs b/sbroad-core/src/frontend/sql/ir.rs index 8bc901b6033c0049f153e20c64ce8701e1754369..a13d8a016e1d31dc05d037175a760dbb6f6e0b0d 100644 --- a/sbroad-core/src/frontend/sql/ir.rs +++ b/sbroad-core/src/frontend/sql/ir.rs @@ -333,6 +333,20 @@ impl Plan { SbroadError::NotFound(Entity::SubTree, format_smolstr!("(id {id})")) })?; } + Expression::Trim { + ref mut pattern, + ref mut target, + .. + } => { + if let Some(pattern) = pattern { + *pattern = *map.get(pattern).ok_or_else(|| { + SbroadError::NotFound(Entity::SubTree, format_smolstr!("(id {id})")) + })?; + } + *target = *map.get(target).ok_or_else(|| { + SbroadError::NotFound(Entity::SubTree, format_smolstr!("(id {id})")) + })?; + } Expression::Row { list: ref mut children, .. @@ -436,6 +450,16 @@ impl SubtreeCloner { *left = self.get_new_id(*left)?; *right = self.get_new_id(*right)?; } + Expression::Trim { + ref mut pattern, + ref mut target, + .. + } => { + if let Some(pattern) = pattern { + *pattern = self.get_new_id(*pattern)?; + } + *target = self.get_new_id(*target)?; + } Expression::Row { list: ref mut children, distribution: _, diff --git a/sbroad-core/src/frontend/sql/ir/tests/trim.rs b/sbroad-core/src/frontend/sql/ir/tests/trim.rs index d5df6085d5f191cec0f56c9101f4e04f40455e35..1a0f4df0532ddc4f12deb91d835e0d734e47ffb0 100644 --- a/sbroad-core/src/frontend/sql/ir/tests/trim.rs +++ b/sbroad-core/src/frontend/sql/ir/tests/trim.rs @@ -7,7 +7,7 @@ fn trim() { let plan = sql_to_optimized_ir(sql, vec![]); let expected_explain = String::from( - r#"projection ("TRIM"(("test_space"."FIRST_NAME"::string))::string -> "COL_1") + r#"projection (TRIM("test_space"."FIRST_NAME"::string) -> "COL_1") scan "test_space" execution options: sql_vdbe_max_steps = 45000 @@ -24,7 +24,7 @@ fn trim_leading_from() { let plan = sql_to_optimized_ir(sql, vec![]); let expected_explain = String::from( - r#"projection ("TRIM"(leading from "test_space"."FIRST_NAME"::string)::string -> "COL_1") + r#"projection (TRIM(leading from "test_space"."FIRST_NAME"::string) -> "COL_1") scan "test_space" execution options: sql_vdbe_max_steps = 45000 @@ -41,7 +41,7 @@ fn trim_both_space_from() { let plan = sql_to_optimized_ir(sql, vec![]); let expected_explain = String::from( - r#"projection ("TRIM"(both ' '::string from "test_space"."FIRST_NAME"::string)::string -> "COL_1") + r#"projection (TRIM(both ' '::string from "test_space"."FIRST_NAME"::string) -> "COL_1") scan "test_space" execution options: sql_vdbe_max_steps = 45000 diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index adc104cf578c45de48063852da166f326b8d45e9..bf471acdef31d6cece5b8b10bf30722d3c07b40e 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -227,7 +227,7 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* } ExprAtomValue = _{ UnaryNot* ~ AtomicExpr ~ IsNullPostfix? } UnaryNot = @{ NotFlag } IsNullPostfix = { ^"is" ~ NotFlag? ~ ^"null" } - AtomicExpr = _{ Literal | Parameter | Cast | IdentifierWithOptionalContinuation | ExpressionInParentheses | UnaryOperator | SubQuery | Row } + AtomicExpr = _{ Literal | Parameter | Cast | Trim | IdentifierWithOptionalContinuation | ExpressionInParentheses | UnaryOperator | SubQuery | Row } Literal = { True | False | Null | Double | Decimal | Unsigned | Integer | SingleQuotedString } True = { ^"true" } False = { ^"false" } @@ -242,17 +242,21 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* } PgParameter = { "$" ~ Unsigned } IdentifierWithOptionalContinuation = { Identifier ~ (ReferenceContinuation | FunctionInvocationContinuation)? } ReferenceContinuation = { "." ~ Identifier } - FunctionInvocationContinuation = { "(" ~ (CountAsterisk | TrimFunctionArgs | FunctionArgs)? ~ ")" } + FunctionInvocationContinuation = { "(" ~ (CountAsterisk | FunctionArgs)? ~ ")" } FunctionArgs = { Distinct? ~ (Expr ~ ("," ~ Expr)*)? } CountAsterisk = { "*" } - TrimFunctionArgs = { ((TrimKind? ~ TrimChars) | TrimKind) ~ ^"from" ~ TrimString } - TrimKind = { (TrimKindLeading | TrimKindTrailing | TrimKindBoth) } - TrimKindLeading = { ^"leading" } - TrimKindTrailing = { ^"trailing" } - TrimKindBoth = { ^"both" } - TrimChars = { Expr } - TrimString = { Expr } ExpressionInParentheses = { "(" ~ Expr ~ ")" } + Trim = { + ^"trim" ~ "(" + ~ (((TrimKind? ~ TrimPattern) | TrimKind) ~ ^"from")? ~ TrimTarget + ~ ")" + } + TrimKind = { (TrimKindLeading | TrimKindTrailing | TrimKindBoth) } + TrimKindLeading = { ^"leading" } + TrimKindTrailing = { ^"trailing" } + TrimKindBoth = { ^"both" } + TrimPattern = { Expr } + TrimTarget = { Expr } Cast = { ^"cast" ~ "(" ~ Expr ~ ^"as" ~ TypeCast ~ ")" } TypeCast = _{ TypeAny | ColumnDefType } ColumnDefType = { TypeBool | TypeDecimal | TypeDouble | TypeInt | TypeNumber diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs index dd6fd90fcc018d820cbf49e3b615400f0ec3b09d..e2b26238352fdf524b9c4eb9880db97d3fd31456 100644 --- a/sbroad-core/src/ir.rs +++ b/sbroad-core/src/ir.rs @@ -1024,6 +1024,20 @@ impl Plan { return Ok(()); } } + Expression::Trim { + pattern, target, .. + } => { + if let Some(pattern_id) = pattern { + if *pattern_id == old_id { + *pattern_id = new_id; + return Ok(()); + } + } + if *target == old_id { + *target = new_id; + return Ok(()); + } + } Expression::Row { list: arr, .. } | StableFunction { children: arr, .. } => { for child in arr.iter_mut() { if *child == old_id { diff --git a/sbroad-core/src/ir/api/parameter.rs b/sbroad-core/src/ir/api/parameter.rs index e95c1509f23db86f603ab48b0f13ada481fb7a12..74e3dd5ec5490dcfb4ab37ecd1b9bfbb215cdaa0 100644 --- a/sbroad-core/src/ir/api/parameter.rs +++ b/sbroad-core/src/ir/api/parameter.rs @@ -240,6 +240,23 @@ impl Plan { } } } + Expression::Trim { + ref pattern, + ref target, + .. + } => { + let params = match pattern { + Some(p) => [Some(*p), Some(*target)], + None => [None, Some(*target)], + }; + for param_id in params.into_iter().flatten() { + if param_node_ids.take(¶m_id).is_some() { + idx = idx.saturating_sub(1); + let val_id = get_value(param_id, idx)?; + row_ids.insert(param_id, self.nodes.add_row(vec![val_id], None)); + } + } + } Expression::Row { ref list, .. } | Expression::StableFunction { children: ref list, .. @@ -369,6 +386,23 @@ impl Plan { } } } + Expression::Trim { + ref mut pattern, + ref mut target, + .. + } => { + let params = match pattern { + Some(p) => [Some(p), Some(target)], + None => [None, Some(target)], + }; + for param_id in params.into_iter().flatten() { + if param_node_ids_cloned.take(param_id).is_some() { + idx = idx.saturating_sub(1); + let row_id = get_row(*param_id)?; + *param_id = row_id; + } + } + } Expression::Row { ref mut list, .. } | Expression::StableFunction { children: ref mut list, diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs index 06f67eeba2e8b1269787de583e39a975385ae3c6..85e5b45b927cd6e41738105c941d8bd1439b4454 100644 --- a/sbroad-core/src/ir/explain.rs +++ b/sbroad-core/src/ir/explain.rs @@ -8,7 +8,7 @@ use smol_str::{format_smolstr, SmolStr, ToSmolStr}; use crate::errors::{Entity, SbroadError}; use crate::ir::expression::cast::Type as CastType; -use crate::ir::expression::Expression; +use crate::ir::expression::{Expression, TrimKind}; use crate::ir::operator::{ConflictStrategy, JoinKind, Relational}; use crate::ir::relation::Type; use crate::ir::transformation::redistribution::{ @@ -32,6 +32,7 @@ enum ColExpr { Cast(Box<ColExpr>, CastType), Concat(Box<ColExpr>, Box<ColExpr>), StableFunction(SmolStr, Vec<ColExpr>, Option<FunctionFeature>, Type), + Trim(Option<TrimKind>, Option<Box<ColExpr>>, Box<ColExpr>), Row(Row), None, } @@ -59,24 +60,18 @@ impl Display for ColExpr { ColExpr::Concat(l, r) => format!("{l} || {r}"), ColExpr::StableFunction(name, args, feature, func_type) => { let is_distinct = matches!(feature, Some(FunctionFeature::Distinct)); - let formatted_args = if let Some(FunctionFeature::Trim(kind)) = feature { - let (string, removal_chars) = args - .split_last() - .expect("string is required by the grammar"); - format!( - "{} {} from {}", - kind.as_str(), - removal_chars.iter().format(""), - string - ) - } else { - format!("({})", args.iter().format(", ")) - }; + let formatted_args = format!("({})", args.iter().format(", ")); format!( "{name}({}{formatted_args})::{func_type}", if is_distinct { "distinct " } else { "" } ) } + ColExpr::Trim(kind, pattern, target) => match (kind, pattern) { + (Some(k), Some(p)) => format!("TRIM({} {p} from {target})", k.as_str()), + (Some(k), None) => format!("TRIM({} from {target})", k.as_str()), + (None, Some(p)) => format!("TRIM({p} from {target})"), + (None, None) => format!("TRIM({target})"), + }, ColExpr::Row(row) => row.to_string(), ColExpr::None => String::new(), }; @@ -162,6 +157,14 @@ impl ColExpr { ColExpr::Column(value.to_string(), current_node.calculate_type(plan)?); stack.push((expr, id)); } + Expression::Trim { kind, .. } => { + let (target, _) = stack + .pop() + .expect("stack is empty while processing TRIM expression"); + let pattern = stack.pop().map(|(pattern, _)| Box::new(pattern)); + let trim_expr = ColExpr::Trim(kind.clone(), pattern, Box::new(target)); + stack.push((trim_expr, id)); + } Expression::StableFunction { name, children, diff --git a/sbroad-core/src/ir/expression.rs b/sbroad-core/src/ir/expression.rs index 9cded1277353d9e0ffc7494e6e520809f93a2d61..548f5ed6d571242452f8e721d070f192b6f435fc 100644 --- a/sbroad-core/src/ir/expression.rs +++ b/sbroad-core/src/ir/expression.rs @@ -150,6 +150,15 @@ pub enum Expression { /// Function return type. func_type: Type, }, + /// Trim expression. + Trim { + /// Trim kind. + kind: Option<TrimKind>, + /// Trim string pattern to remove (it can be an expression). + pattern: Option<usize>, + /// Target expression to trim. + target: usize, + }, /// Unary expression returning boolean result. Unary { /// Unary operator. @@ -168,8 +177,6 @@ pub enum Expression { pub enum FunctionFeature { /// Current function is an aggregate function and is marked as DISTINCT. Distinct, - /// Current function is `trim` function. - Trim(TrimKind), } /// This is the kind of `trim` function that can be set @@ -608,6 +615,33 @@ impl<'plan> Comparator<'plan> { && self.are_subtrees_equal(*right_left, *right_right)?); } } + Expression::Trim { + kind: kind_left, + pattern: pattern_left, + target: target_left, + } => { + if let Expression::Trim { + kind: kind_right, + pattern: pattern_right, + target: target_right, + } = right + { + match (pattern_left, pattern_right) { + (Some(p_left), Some(p_right)) => { + return Ok(*kind_left == *kind_right + && self.are_subtrees_equal(*p_left, *p_right)? + && self + .are_subtrees_equal(*target_left, *target_right)?); + } + (None, None) => { + return Ok(*kind_left == *kind_right + && self + .are_subtrees_equal(*target_left, *target_right)?); + } + _ => return Ok(false), + } + } + } Expression::Constant { value: value_left } => { if let Expression::Constant { value: value_right } = right { return Ok(*value_left == *value_right); @@ -713,6 +747,17 @@ impl<'plan> Comparator<'plan> { self.hash_for_expr(*left, state, depth - 1); self.hash_for_expr(*right, state, depth - 1); } + Expression::Trim { + kind, + pattern, + target, + } => { + kind.hash(state); + if let Some(pattern) = pattern { + self.hash_for_expr(*pattern, state, depth - 1); + } + self.hash_for_expr(*target, state, depth - 1); + } Expression::Constant { value } => { value.hash(state); } diff --git a/sbroad-core/src/ir/expression/types.rs b/sbroad-core/src/ir/expression/types.rs index 23669fbbb719dd39279474ad5627129b2185abde..574d87f5454fd85c80226a9db79d4ea2874d1287 100644 --- a/sbroad-core/src/ir/expression/types.rs +++ b/sbroad-core/src/ir/expression/types.rs @@ -68,7 +68,7 @@ impl Expression { } } Expression::Cast { to, .. } => Ok(to.as_relation_type()), - Expression::Concat { .. } => Ok(Type::String), + Expression::Trim { .. } | Expression::Concat { .. } => Ok(Type::String), Expression::Constant { value, .. } => Ok(value.get_type()), Expression::Reference { col_type, .. } => Ok(col_type.clone()), Expression::Row { list, .. } => { diff --git a/sbroad-core/src/ir/helpers.rs b/sbroad-core/src/ir/helpers.rs index 3f0dc9fd9041682f3ac96543c3058154db6d5160..d0f0a31e96888bf9946b640f7b870e88e3634d5b 100644 --- a/sbroad-core/src/ir/helpers.rs +++ b/sbroad-core/src/ir/helpers.rs @@ -153,6 +153,7 @@ impl Plan { } } Expression::Cast { .. } => writeln!(buf, "Cast")?, + Expression::Trim { .. } => writeln!(buf, "Trim")?, Expression::Concat { .. } => writeln!(buf, "Concat")?, Expression::StableFunction { .. } => writeln!(buf, "StableFunction")?, Expression::Unary { op, child } => { diff --git a/sbroad-core/src/ir/transformation.rs b/sbroad-core/src/ir/transformation.rs index 10c563a749dcb758d324b8ecf720e5591b214457..fcdbb5af98c6dc41ce4d1e818dbf040adb8048c7 100644 --- a/sbroad-core/src/ir/transformation.rs +++ b/sbroad-core/src/ir/transformation.rs @@ -219,6 +219,18 @@ impl Plan { *right = *new_id; } } + Expression::Trim { + pattern, target, .. + } => { + if let Some(pattern) = pattern { + if let Some(new_id) = map.get(pattern) { + *pattern = *new_id; + } + } + if let Some(new_id) = map.get(target) { + *target = *new_id; + } + } Expression::Row { list, .. } | Expression::StableFunction { children: list, .. } => { for id in list { diff --git a/sbroad-core/src/ir/transformation/redistribution/eq_cols.rs b/sbroad-core/src/ir/transformation/redistribution/eq_cols.rs index d97489542713ed87e14ce9483e91dc34f9e9f9de..8f0fd969e121bb8af7f88dc511080574302158d3 100644 --- a/sbroad-core/src/ir/transformation/redistribution/eq_cols.rs +++ b/sbroad-core/src/ir/transformation/redistribution/eq_cols.rs @@ -82,6 +82,17 @@ impl ReferredMap { .add(referred.get_or_none(*right)); referred.insert(node_id, res); } + Expression::Trim { + pattern, target, .. + } => { + let res = match pattern { + Some(pattern) => referred + .get_or_none(*pattern) + .add(referred.get_or_none(*target)), + None => referred.get_or_none(*target).clone(), + }; + referred.insert(node_id, res); + } Expression::Constant { .. } | Expression::CountAsterisk => { referred.insert(node_id, Referred::None); } diff --git a/sbroad-core/src/ir/transformation/redistribution/groupby.rs b/sbroad-core/src/ir/transformation/redistribution/groupby.rs index a6718934f5e18feb6383dc5bc8c95c9c5aea9234..fd3d9905903b4c19bc2fee72666172ea67c1d03f 100644 --- a/sbroad-core/src/ir/transformation/redistribution/groupby.rs +++ b/sbroad-core/src/ir/transformation/redistribution/groupby.rs @@ -430,6 +430,33 @@ impl Plan { && self.are_subtrees_equal(*child_left, *child_right)?); } } + Expression::Trim { + kind: kind_left, + pattern: pattern_left, + target: target_left, + } => { + if let Expression::Trim { + kind: kind_right, + pattern: pattern_right, + target: target_right, + } = right + { + match (pattern_left, pattern_right) { + (Some(p_left), Some(p_right)) => { + return Ok(*kind_left == *kind_right + && self.are_subtrees_equal(*p_left, *p_right)? + && self + .are_subtrees_equal(*target_left, *target_right)?); + } + (None, None) => { + return Ok(*kind_left == *kind_right + && self + .are_subtrees_equal(*target_left, *target_right)?); + } + _ => return Ok(false), + } + } + } Expression::Concat { left: left_left, right: right_left, diff --git a/sbroad-core/src/ir/tree/expression.rs b/sbroad-core/src/ir/tree/expression.rs index 1bbf6984f4c86759f65d6fef7512fb5c4aa4687c..616aa85f1bade2e6bb0d6ac9e9ab366960dd2beb 100644 --- a/sbroad-core/src/ir/tree/expression.rs +++ b/sbroad-core/src/ir/tree/expression.rs @@ -97,6 +97,7 @@ impl<'n> Iterator for AggregateIterator<'n> { } } +#[allow(clippy::too_many_lines)] fn expression_next<'nodes>( iter: &mut impl ExpressionTreeIterator<'nodes>, ) -> Option<&'nodes usize> { @@ -129,6 +130,28 @@ fn expression_next<'nodes>( } None } + Some(Node::Expression(Expression::Trim { + pattern, target, .. + })) => { + let child_step = *iter.get_child().borrow(); + match child_step { + 0 => { + *iter.get_child().borrow_mut() += 1; + match pattern { + Some(_) => pattern.as_ref(), + None => Some(target), + } + } + 1 => { + *iter.get_child().borrow_mut() += 1; + match pattern { + Some(_) => Some(target), + None => None, + } + } + _ => None, + } + } Some(Node::Expression(Expression::Row { list, .. })) => { let child_step = *iter.get_child().borrow(); let mut is_leaf = false; diff --git a/sbroad-core/src/ir/tree/subtree.rs b/sbroad-core/src/ir/tree/subtree.rs index 2fc9ed0421eca937d2c614f97cd022e73ad92b3b..fcca41a23a2ff32a4493c45b88211a8934093707 100644 --- a/sbroad-core/src/ir/tree/subtree.rs +++ b/sbroad-core/src/ir/tree/subtree.rs @@ -221,6 +221,28 @@ fn subtree_next<'plan>( } None } + Expression::Trim { + pattern, target, .. + } => { + let child_step = *iter.get_child().borrow(); + match child_step { + 0 => { + *iter.get_child().borrow_mut() += 1; + match pattern { + Some(_) => pattern.as_ref(), + None => Some(target), + } + } + 1 => { + *iter.get_child().borrow_mut() += 1; + match pattern { + Some(_) => Some(target), + None => None, + } + } + _ => None, + } + } Expression::Row { list, .. } | Expression::StableFunction { children: list, .. } => { let child_step = *iter.get_child().borrow();