Add ends-with operator and fix dataframe operator behavior (#5395)

* add ends-with operator

* escape needles in dataframe operator regex patterns
This commit is contained in:
panicbit 2022-05-02 10:02:38 +02:00 committed by GitHub
parent 07a7bb14bf
commit 49cbc30974
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 68 additions and 2 deletions

View file

@ -273,7 +273,7 @@ pub(super) fn compute_series_single_value(
compare_series_decimal(&lhs, *val, ChunkedArray::equal, lhs_span) compare_series_decimal(&lhs, *val, ChunkedArray::equal, lhs_span)
} }
Value::String { val, .. } => { Value::String { val, .. } => {
let equal_pattern = format!("^{}$", val); let equal_pattern = format!("^{}$", regex::escape(val));
contains_series_pat(&lhs, &equal_pattern, lhs_span) contains_series_pat(&lhs, &equal_pattern, lhs_span)
} }
Value::Date { val, .. } => { Value::Date { val, .. } => {
@ -385,7 +385,7 @@ pub(super) fn compute_series_single_value(
}, },
Operator::StartsWith => match &right { Operator::StartsWith => match &right {
Value::String { val, .. } => { Value::String { val, .. } => {
let starts_with_pattern = format!("^{}", val); let starts_with_pattern = format!("^{}", regex::escape(val));
contains_series_pat(&lhs, &starts_with_pattern, lhs_span) contains_series_pat(&lhs, &starts_with_pattern, lhs_span)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
@ -396,6 +396,19 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::EndsWith => match &right {
Value::String { val, .. } => {
let ends_with_pattern = format!("{}$", regex::escape(val));
contains_series_pat(&lhs, &ends_with_pattern, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
op_span: operator.span,
lhs_ty: left.get_type(),
lhs_span: left.span()?,
rhs_ty: right.get_type(),
rhs_span: right.span()?,
}),
},
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: operator.span, op_span: operator.span,
lhs_ty: left.get_type(), lhs_ty: left.get_type(),

View file

@ -427,6 +427,10 @@ pub fn eval_expression(
let rhs = eval_expression(engine_state, stack, rhs)?; let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.starts_with(op_span, &rhs, expr.span) lhs.starts_with(op_span, &rhs, expr.span)
} }
Operator::EndsWith => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.ends_with(op_span, &rhs, expr.span)
}
} }
} }
Expr::Subexpression(block_id) => { Expr::Subexpression(block_id) => {

View file

@ -4049,6 +4049,7 @@ pub fn parse_operator(
b"not-in" => Operator::NotIn, b"not-in" => Operator::NotIn,
b"mod" => Operator::Modulo, b"mod" => Operator::Modulo,
b"starts-with" => Operator::StartsWith, b"starts-with" => Operator::StartsWith,
b"ends-with" => Operator::EndsWith,
b"&&" | b"and" => Operator::And, b"&&" | b"and" => Operator::And,
b"||" | b"or" => Operator::Or, b"||" | b"or" => Operator::Or,
b"**" => Operator::Pow, b"**" => Operator::Pow,

View file

@ -337,6 +337,24 @@ pub fn math_result_type(
) )
} }
}, },
Operator::EndsWith => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None),
_ => {
*op = Expression::garbage(op.span);
(
Type::Any,
Some(ParseError::UnsupportedOperation(
op.span,
lhs.span,
lhs.ty.clone(),
rhs.span,
rhs.ty.clone(),
)),
)
}
},
Operator::In => match (&lhs.ty, &rhs.ty) { Operator::In => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None), (t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None), (Type::Int | Type::Float, Type::Range) => (Type::Bool, None),

View file

@ -35,6 +35,7 @@ impl Expression {
Operator::NotRegexMatch Operator::NotRegexMatch
| Operator::RegexMatch | Operator::RegexMatch
| Operator::StartsWith | Operator::StartsWith
| Operator::EndsWith
| Operator::LessThan | Operator::LessThan
| Operator::LessThanOrEqual | Operator::LessThanOrEqual
| Operator::GreaterThan | Operator::GreaterThan

View file

@ -24,6 +24,7 @@ pub enum Operator {
Or, Or,
Pow, Pow,
StartsWith, StartsWith,
EndsWith,
} }
impl Display for Operator { impl Display for Operator {
@ -48,6 +49,7 @@ impl Display for Operator {
Operator::LessThanOrEqual => write!(f, "<="), Operator::LessThanOrEqual => write!(f, "<="),
Operator::GreaterThanOrEqual => write!(f, ">="), Operator::GreaterThanOrEqual => write!(f, ">="),
Operator::StartsWith => write!(f, "starts-with"), Operator::StartsWith => write!(f, "starts-with"),
Operator::EndsWith => write!(f, "ends-with"),
} }
} }
} }

View file

@ -2080,6 +2080,25 @@ impl Value {
} }
} }
pub fn ends_with(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool {
val: lhs.ends_with(rhs),
span,
}),
(Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::EndsWith, op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type(),
lhs_span: self.span()?,
rhs_ty: rhs.get_type(),
rhs_span: rhs.span()?,
}),
}
}
pub fn modulo(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn modulo(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) { match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {

View file

@ -326,6 +326,14 @@ fn starts_with_operator_succeeds() -> TestResult {
) )
} }
#[test]
fn ends_with_operator_succeeds() -> TestResult {
run_test(
r#"[Moe Larry Curly] | where $it ends-with ly | str collect"#,
"Curly",
)
}
#[test] #[test]
fn proper_missing_param() -> TestResult { fn proper_missing_param() -> TestResult {
fail_test(r#"def foo [x y z w] { }; foo a b c"#, "missing w") fail_test(r#"def foo [x y z w] { }; foo a b c"#, "missing w")