Skip to content

Commit e36e4e1

Browse files
alancai98jpschorr
andauthored
Implement LIKE for non-string, non-literals (#287)
Co-authored-by: Josh Pschorr <[email protected]>
1 parent 747f41a commit e36e4e1

File tree

7 files changed

+113
-39
lines changed

7 files changed

+113
-39
lines changed

partiql-conformance-tests/src/bin/generate_comparison_report.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ Number failing in main but now pass: {}
135135
).expect("write passing_orig_failing_new heading");
136136
for test_name in &passing_orig_failing_new {
137137
comparison_report_file
138-
.write_all(format!("- {}\n", test_name).as_bytes())
138+
.write_all(format!("- {test_name}\n").as_bytes())
139139
.expect("write passing_orig_failing_new test case");
140140
}
141141
comparison_report_file
@@ -149,7 +149,7 @@ Number failing in main but now pass: {}
149149
).expect("write failure_orig_passing_new heading");
150150
for test_name in &failure_orig_passing_new {
151151
comparison_report_file
152-
.write_all(format!("- {}\n", test_name).as_bytes())
152+
.write_all(format!("- {test_name}\n").as_bytes())
153153
.expect("write failure_orig_passing_new test case");
154154
}
155155
comparison_report_file

partiql-conformance-tests/src/bin/generate_cts_report.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn main() {
4747
}
4848
}
4949
}
50-
Err(e) => panic!("Error reading line: {}", e),
50+
Err(e) => panic!("Error reading line: {e}"),
5151
}
5252
}
5353

partiql-eval/src/eval.rs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use partiql_logical::Type;
2323

2424
use petgraph::graph::NodeIndex;
2525

26+
use crate::pattern_match::like_to_re_pattern;
2627
use petgraph::visit::EdgeRef;
2728
use regex::{Regex, RegexBuilder};
2829
use std::borrow::Borrow;
@@ -1151,10 +1152,60 @@ impl EvalExpr for EvalLikeMatch {
11511152
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
11521153
let value = self.value.evaluate(bindings, ctx);
11531154
match value {
1154-
Null => Value::Null,
1155-
Missing => Value::Missing,
1156-
Value::String(s) => Value::Boolean(self.pattern.is_match(s.as_ref())),
1157-
_ => Value::Boolean(false),
1155+
Null => Null,
1156+
Missing => Missing,
1157+
Value::String(s) => Boolean(self.pattern.is_match(s.as_ref())),
1158+
_ => Missing,
1159+
}
1160+
}
1161+
}
1162+
1163+
/// Represents an evaluation `LIKE` operator without string literals in the match and/or escape
1164+
/// pattern, e.g. in `s LIKE match_str ESCAPE escape_char`.
1165+
#[derive(Debug)]
1166+
pub struct EvalLikeNonStringNonLiteralMatch {
1167+
pub value: Box<dyn EvalExpr>,
1168+
pub pattern: Box<dyn EvalExpr>,
1169+
pub escape: Box<dyn EvalExpr>,
1170+
}
1171+
1172+
impl EvalLikeNonStringNonLiteralMatch {
1173+
pub fn new(
1174+
value: Box<dyn EvalExpr>,
1175+
pattern: Box<dyn EvalExpr>,
1176+
escape: Box<dyn EvalExpr>,
1177+
) -> Self {
1178+
EvalLikeNonStringNonLiteralMatch {
1179+
value,
1180+
pattern,
1181+
escape,
1182+
}
1183+
}
1184+
}
1185+
1186+
impl EvalExpr for EvalLikeNonStringNonLiteralMatch {
1187+
fn evaluate(&self, bindings: &Tuple, ctx: &dyn EvalContext) -> Value {
1188+
let value = self.value.evaluate(bindings, ctx);
1189+
let pattern = self.pattern.evaluate(bindings, ctx);
1190+
let escape = self.escape.evaluate(bindings, ctx);
1191+
1192+
match (value, pattern, escape) {
1193+
(Missing, _, _) => Missing,
1194+
(_, Missing, _) => Missing,
1195+
(_, _, Missing) => Missing,
1196+
(Null, _, _) => Null,
1197+
(_, Null, _) => Null,
1198+
(_, _, Null) => Null,
1199+
(Value::String(v), Value::String(p), Value::String(e)) => {
1200+
assert!(e.chars().count() <= 1);
1201+
let escape = e.chars().next();
1202+
let regex_pattern = RegexBuilder::new(&like_to_re_pattern(&p, escape))
1203+
.size_limit(RE_SIZE_LIMIT)
1204+
.build()
1205+
.expect("Like Pattern");
1206+
Boolean(regex_pattern.is_match(v.as_ref()))
1207+
}
1208+
_ => Missing,
11581209
}
11591210
}
11601211
}

partiql-eval/src/plan.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use crate::eval::{
1313
EvalBagExpr, EvalBetweenExpr, EvalBinOp, EvalBinOpExpr, EvalDynamicLookup, EvalExpr,
1414
EvalFnBitLength, EvalFnBtrim, EvalFnCharLength, EvalFnExists, EvalFnLower, EvalFnLtrim,
1515
EvalFnOctetLength, EvalFnPosition, EvalFnRtrim, EvalFnSubstring, EvalFnUpper, EvalIsTypeExpr,
16-
EvalJoinKind, EvalLikeMatch, EvalListExpr, EvalLitExpr, EvalPath, EvalPlan,
17-
EvalSearchedCaseExpr, EvalSubQueryExpr, EvalTupleExpr, EvalUnaryOp, EvalUnaryOpExpr,
18-
EvalVarRef, Evaluable,
16+
EvalJoinKind, EvalLikeMatch, EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr,
17+
EvalPath, EvalPlan, EvalSearchedCaseExpr, EvalSubQueryExpr, EvalTupleExpr, EvalUnaryOp,
18+
EvalUnaryOpExpr, EvalVarRef, Evaluable,
1919
};
2020
use crate::pattern_match::like_to_re_pattern;
2121
use partiql_value::Value::Null;
@@ -226,13 +226,23 @@ impl EvaluatorPlanner {
226226
ValueExpr::PatternMatchExpr(PatternMatchExpr { value, pattern }) => {
227227
let value = self.plan_values(value);
228228
match pattern {
229-
Pattern::LIKE(logical::LikeMatch { pattern, escape }) => {
229+
Pattern::Like(logical::LikeMatch { pattern, escape }) => {
230230
// TODO statically assert escape length
231231
assert!(escape.chars().count() <= 1);
232232
let escape = escape.chars().next();
233233
let regex = like_to_re_pattern(pattern, escape);
234234
Box::new(EvalLikeMatch::new(value, &regex))
235235
}
236+
Pattern::LikeNonStringNonLiteral(logical::LikeNonStringNonLiteralMatch {
237+
pattern,
238+
escape,
239+
}) => {
240+
let pattern = self.plan_values(pattern);
241+
let escape = self.plan_values(escape);
242+
Box::new(EvalLikeNonStringNonLiteralMatch::new(
243+
value, pattern, escape,
244+
))
245+
}
236246
}
237247
}
238248
ValueExpr::SubQueryExpr(expr) => {

partiql-logical-planner/src/lower.rs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use partiql_ast::ast::{
1414
use partiql_ast::visit::{Visit, Visitor};
1515
use partiql_logical as logical;
1616
use partiql_logical::{
17-
BagExpr, BetweenExpr, BindingsOp, IsTypeExpr, LikeMatch, ListExpr, LogicalPlan, OpId,
18-
PathComponent, Pattern, PatternMatchExpr, TupleExpr, ValueExpr,
17+
BagExpr, BetweenExpr, BindingsOp, IsTypeExpr, LikeMatch, LikeNonStringNonLiteralMatch,
18+
ListExpr, LogicalPlan, OpId, PathComponent, Pattern, PatternMatchExpr, TupleExpr, ValueExpr,
1919
};
2020

2121
use partiql_value::{BindingsName, Value};
@@ -155,21 +155,6 @@ fn infer_id(expr: &ValueExpr) -> Option<SymbolPrimitive> {
155155
}
156156
}
157157

158-
// TODO Error/Result
159-
fn require_lit(expr: &ValueExpr) -> &Value {
160-
match expr {
161-
ValueExpr::Lit(lit) => lit.as_ref(),
162-
_ => todo!("Error on not-literal"),
163-
}
164-
}
165-
166-
fn require_str(lit: &Value) -> &str {
167-
match lit {
168-
Value::String(s) => s.as_ref(),
169-
_ => todo!("Error on not-string"),
170-
}
171-
}
172-
173158
impl AstToLogical {
174159
pub fn new(registry: name_resolver::KeyRegistry) -> Self {
175160
AstToLogical {
@@ -737,18 +722,35 @@ impl<'ast> Visitor<'ast> for AstToLogical {
737722
fn exit_like(&mut self, _like: &'ast Like) {
738723
let mut env = self.exit_env();
739724
assert!((2..=3).contains(&env.len()));
740-
let escape = if env.len() == 3 {
741-
require_str(require_lit(&env.pop().unwrap())).to_string()
725+
let escape_ve = if env.len() == 3 {
726+
env.pop().unwrap()
742727
} else {
743-
"".to_string()
728+
ValueExpr::Lit(Box::new(Value::String(Box::new("".to_string()))))
744729
};
745-
let pattern = require_str(require_lit(&env.pop().unwrap())).to_string();
730+
let pattern_ve = env.pop().unwrap();
746731
let value = Box::new(env.pop().unwrap());
747-
let pattern = Pattern::LIKE(LikeMatch { pattern, escape });
748-
self.push_vexpr(ValueExpr::PatternMatchExpr(PatternMatchExpr {
749-
value,
750-
pattern,
751-
}));
732+
733+
let pattern = match (&pattern_ve, &escape_ve) {
734+
(ValueExpr::Lit(pattern_lit), ValueExpr::Lit(escape_lit)) => {
735+
match (pattern_lit.as_ref(), escape_lit.as_ref()) {
736+
(Value::String(pattern), Value::String(escape)) => Pattern::Like(LikeMatch {
737+
pattern: pattern.to_string(),
738+
escape: escape.to_string(),
739+
}),
740+
_ => Pattern::LikeNonStringNonLiteral(LikeNonStringNonLiteralMatch {
741+
pattern: Box::new(pattern_ve),
742+
escape: Box::new(escape_ve),
743+
}),
744+
}
745+
}
746+
_ => Pattern::LikeNonStringNonLiteral(LikeNonStringNonLiteralMatch {
747+
pattern: Box::new(pattern_ve),
748+
escape: Box::new(escape_ve),
749+
}),
750+
};
751+
752+
let pattern = ValueExpr::PatternMatchExpr(PatternMatchExpr { value, pattern });
753+
self.push_vexpr(pattern);
752754
}
753755

754756
fn enter_call(&mut self, _call: &'ast Call) {

partiql-logical/src/lib.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,15 +377,26 @@ pub struct PatternMatchExpr {
377377

378378
#[derive(Debug, Clone, Eq, PartialEq)]
379379
pub enum Pattern {
380-
LIKE(LikeMatch), // TODO other e.g., SIMILAR_TO, or regex match
380+
Like(LikeMatch), // TODO other e.g., SIMILAR_TO, or regex match
381+
LikeNonStringNonLiteral(LikeNonStringNonLiteralMatch),
381382
}
382383

384+
/// Represents a LIKE expression where both the `pattern` and `escape` are string literals,
385+
/// e.g. `'foo%' ESCAPE '/'`
383386
#[derive(Debug, Clone, Eq, PartialEq)]
384387
pub struct LikeMatch {
385388
pub pattern: String,
386389
pub escape: String,
387390
}
388391

392+
/// Represents a LIKE expression where one of `pattern` and `escape` is not a string literal,
393+
/// e.g. `some_pattern ESCAPE '/'`
394+
#[derive(Debug, Clone, Eq, PartialEq)]
395+
pub struct LikeNonStringNonLiteralMatch {
396+
pub pattern: Box<ValueExpr>,
397+
pub escape: Box<ValueExpr>,
398+
}
399+
389400
/// Represents a sub-query expression, e.g. `SELECT v.a*2 AS u FROM t AS v` in
390401
/// `SELECT t.a, s FROM data AS t, (SELECT v.a*2 AS u FROM t AS v) AS s`
391402
#[derive(Debug, Clone, Eq, PartialEq)]

0 commit comments

Comments
 (0)