Skip to content

Commit 0cfc0f5

Browse files
finchxxiafinchxia
andauthored
fix(iceberg): preserve supported predicate conjuncts (#20164)
Co-authored-by: finchxia <finch@aviagames.com>
1 parent 5876b2d commit 0cfc0f5

2 files changed

Lines changed: 165 additions & 29 deletions

File tree

src/query/storages/iceberg/src/predicate.rs

Lines changed: 142 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,7 @@ impl PredicateBuilder {
4747
generics: _,
4848
args,
4949
return_type: _,
50-
} if args.len() == 1 && id.name().as_ref() == "is_true" => {
51-
let (uncertain, predicate) = Self::build(&args[0]);
52-
if uncertain {
53-
return (uncertain, Predicate::AlwaysTrue);
54-
}
55-
match predicate {
56-
Predicate::AlwaysTrue => (false, Predicate::AlwaysTrue),
57-
Predicate::AlwaysFalse => (false, Predicate::AlwaysFalse),
58-
_ => (false, predicate),
59-
}
60-
}
50+
} if args.len() == 1 && id.name().as_ref() == "is_true" => Self::build(&args[0]),
6151

6252
// unary
6353
RemoteExpr::FunctionCall {
@@ -97,28 +87,39 @@ impl PredicateBuilder {
9787
(false, predicate)
9888
}
9989

100-
// binary {a op datum}
90+
// logical operators
10191
RemoteExpr::FunctionCall {
10292
span: _,
10393
id,
10494
generics: _,
10595
args,
10696
return_type: _,
107-
} if args.len() == 2
97+
} if args.len() >= 2
10898
&& ["and", "and_filters", "or", "or_filters"].contains(&id.name().as_ref()) =>
10999
{
110-
let (left_uncertain, left) = Self::build(&args[0]);
111-
let (right_uncertain, right) = Self::build(&args[1]);
112-
if left_uncertain || right_uncertain {
113-
return (true, Predicate::AlwaysTrue);
114-
}
115-
let predicate = match id.name().as_ref() {
116-
"and" | "and_filters" => left.and(right),
117-
"or" | "or_filters" => left.or(right),
118-
_ => unreachable!(),
100+
let is_and = matches!(id.name().as_ref(), "and" | "and_filters");
101+
let initial = if is_and {
102+
Predicate::AlwaysTrue
103+
} else {
104+
Predicate::AlwaysFalse
119105
};
120106

121-
(false, predicate)
107+
let mut uncertain = false;
108+
let mut predicate = initial;
109+
for arg in args {
110+
let (arg_uncertain, arg_predicate) = Self::build(arg);
111+
if !is_and && arg_uncertain {
112+
return (true, Predicate::AlwaysTrue);
113+
}
114+
uncertain |= arg_uncertain;
115+
predicate = if is_and {
116+
predicate.and(arg_predicate)
117+
} else {
118+
predicate.or(arg_predicate)
119+
};
120+
}
121+
122+
(uncertain, predicate)
122123
}
123124

124125
// binary {a op datum}
@@ -190,11 +191,11 @@ fn build_unary(r: Reference, op: &str) -> Option<Predicate> {
190191
fn build_binary(r: Reference, op: &str, datum: Datum) -> Option<Predicate> {
191192
let op = match op {
192193
"lt" | "<" => r.less_than(datum),
193-
"le" | "<=" => r.less_than_or_equal_to(datum),
194+
"le" | "lte" | "<=" => r.less_than_or_equal_to(datum),
194195
"gt" | ">" => r.greater_than(datum),
195-
"ge" | ">=" => r.greater_than_or_equal_to(datum),
196+
"ge" | "gte" | ">=" => r.greater_than_or_equal_to(datum),
196197
"eq" | "=" => r.equal_to(datum),
197-
"ne" | "!=" => r.not_equal_to(datum),
198+
"ne" | "noteq" | "!=" => r.not_equal_to(datum),
198199
_ => return None,
199200
};
200201
Some(op)
@@ -204,11 +205,11 @@ fn build_binary(r: Reference, op: &str, datum: Datum) -> Option<Predicate> {
204205
fn build_reverse_binary(r: Reference, op: &str, datum: Datum) -> Option<Predicate> {
205206
let op = match op {
206207
"lt" | "<" => r.greater_than(datum),
207-
"le" | "<=" => r.greater_than_or_equal_to(datum),
208+
"le" | "lte" | "<=" => r.greater_than_or_equal_to(datum),
208209
"gt" | ">" => r.less_than(datum),
209-
"ge" | ">=" => r.less_than_or_equal_to(datum),
210+
"ge" | "gte" | ">=" => r.less_than_or_equal_to(datum),
210211
"eq" | "=" => r.equal_to(datum),
211-
"ne" | "!=" => r.not_equal_to(datum),
212+
"ne" | "noteq" | "!=" => r.not_equal_to(datum),
212213
_ => return None,
213214
};
214215
Some(op)
@@ -238,3 +239,115 @@ fn scalar_to_datatum(scalar: &Scalar) -> Option<Datum> {
238239
};
239240
Some(val)
240241
}
242+
243+
#[cfg(test)]
244+
mod tests {
245+
use databend_common_expression::FunctionID;
246+
247+
use super::*;
248+
249+
fn column(name: &str, data_type: DataType) -> RemoteExpr<String> {
250+
RemoteExpr::ColumnRef {
251+
span: None,
252+
id: name.to_string(),
253+
data_type,
254+
display_name: name.to_string(),
255+
}
256+
}
257+
258+
fn constant(scalar: Scalar, data_type: DataType) -> RemoteExpr<String> {
259+
RemoteExpr::Constant {
260+
span: None,
261+
scalar,
262+
data_type,
263+
}
264+
}
265+
266+
fn function(name: &str, args: Vec<RemoteExpr<String>>) -> RemoteExpr<String> {
267+
RemoteExpr::FunctionCall {
268+
span: None,
269+
id: Box::new(FunctionID::Builtin {
270+
name: name.to_string(),
271+
id: 0,
272+
}),
273+
generics: vec![],
274+
args,
275+
return_type: DataType::Boolean,
276+
}
277+
}
278+
279+
fn string_eq(column_name: &str, value: &str) -> RemoteExpr<String> {
280+
function("eq", vec![
281+
column(column_name, DataType::String),
282+
constant(Scalar::String(value.to_string()), DataType::String),
283+
])
284+
}
285+
286+
#[test]
287+
fn test_build_databend_comparison_names() {
288+
let timestamp = 1_784_197_641_000_000;
289+
let expr = function("gte", vec![
290+
column("report_time", DataType::Timestamp),
291+
constant(Scalar::Timestamp(timestamp), DataType::Timestamp),
292+
]);
293+
294+
let (uncertain, predicate) = PredicateBuilder::build(&expr);
295+
296+
assert!(!uncertain);
297+
assert_eq!(
298+
predicate,
299+
Reference::new("report_time")
300+
.greater_than_or_equal_to(Datum::timestamp_micros(timestamp))
301+
);
302+
}
303+
304+
#[test]
305+
fn test_and_keeps_supported_conjuncts() {
306+
let unsupported = function("unsupported", vec![column(
307+
"report_time",
308+
DataType::Timestamp,
309+
)]);
310+
let event_name = string_eq("event_name", "game_end");
311+
let expr = function("is_true", vec![function("and_filters", vec![
312+
unsupported,
313+
event_name,
314+
])]);
315+
316+
let (uncertain, predicate) = PredicateBuilder::build(&expr);
317+
318+
assert!(uncertain);
319+
assert_eq!(
320+
predicate,
321+
Reference::new("event_name").equal_to(Datum::string("game_end"))
322+
);
323+
}
324+
325+
#[test]
326+
fn test_variadic_and_builds_all_conjuncts() {
327+
let expr = function("is_true", vec![function("and_filters", vec![
328+
string_eq("event_name", "game_end"),
329+
string_eq("source_flag", "p7"),
330+
string_eq("page", "home"),
331+
])]);
332+
333+
let (uncertain, predicate) = PredicateBuilder::build(&expr);
334+
335+
assert!(!uncertain);
336+
assert_ne!(predicate, Predicate::AlwaysTrue);
337+
}
338+
339+
#[test]
340+
fn test_or_drops_partial_predicate() {
341+
let unsupported = function("unsupported", vec![column(
342+
"report_time",
343+
DataType::Timestamp,
344+
)]);
345+
let event_name = string_eq("event_name", "game_end");
346+
let expr = function("or_filters", vec![unsupported, event_name]);
347+
348+
let (uncertain, predicate) = PredicateBuilder::build(&expr);
349+
350+
assert!(uncertain);
351+
assert_eq!(predicate, Predicate::AlwaysTrue);
352+
}
353+
}

tests/sqllogictests/suites/tpch_iceberg/prune.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,29 @@ EvalScalar
101101
├── push downs: [filters: [lineitem.l_orderkey (#0) > 1 and lineitem.l_commitdate (#11) = '1992-01-22'], limit: NONE]
102102
└── estimated rows: 600572.00
103103

104+
# Keep supported Iceberg conjuncts when another conjunct cannot be converted.
105+
query T
106+
explain select 1 from ctl.tpch.lineitem where l_orderkey < 1 and length(l_comment) > 0;
107+
----
108+
EvalScalar
109+
├── output columns: [1 (#16)]
110+
├── expressions: [1]
111+
├── estimated rows: 0.00
112+
└── Filter
113+
├── output columns: []
114+
├── filters: [is_true(lineitem.l_orderkey (#0) < 1), is_true(length(lineitem.l_comment (#15)) > 0)]
115+
├── estimated rows: 0.00
116+
└── TableScan
117+
├── table: ctl.tpch.lineitem
118+
├── scan id: 0
119+
├── output columns: [l_orderkey (#0), l_comment (#15)]
120+
├── read rows: 0
121+
├── read size: 0
122+
├── partitions total: 0
123+
├── partitions scanned: 0
124+
├── push downs: [filters: [lineitem.l_orderkey (#0) < 1 and length(lineitem.l_comment (#15)) > 0], limit: NONE]
125+
└── estimated rows: 600572.00
126+
104127

105128
query T
106129
explain select 1 from ctl.tpch.lineitem where l_orderkey is null;

0 commit comments

Comments
 (0)