Skip to content

Commit 3b1d60f

Browse files
committed
readyset-server: Allow function predicates
Handle non-aggregate function calls in WHERE predicates by lowering them to filter expressions instead of raising an internal error. I believe the previous error message was inaccurate and misleading, perhaps copy and pasted from the aggregates case, since I can't find functions being "handled earlier in projection" or anything, and we see this error consistently whenever a function is the top-level expression in a predicate. Fixes: REA-5941 Release-Note-Core: Allow function as top-level predicates in WHERE, e.g. `WHERE COALESCE(col, TRUE)` or `WHERE JSON_OVERLAPS(col, '[42]')`. AI-Use: level:3 Change-Id: Iee455de1c509999f76d391ddcf4e4a7e6a6a6964 Reviewed-on: https://gerrit.readyset.name/c/readyset/+/11232 Tested-by: Buildkite CI Reviewed-by: Vassili Zarouba <vassili@readyset.io>
1 parent 1313bbb commit 3b1d60f

File tree

3 files changed

+278
-3
lines changed

3 files changed

+278
-3
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
statement ok
2+
CREATE TABLE json_overlaps_test (id INT PRIMARY KEY, payload JSON);
3+
4+
statement ok
5+
INSERT INTO json_overlaps_test VALUES
6+
(1, '[1,2,3]'),
7+
(2, '[3,4]'),
8+
(3, '[4,5]'),
9+
(4, '[1,2,3,4]'),
10+
(5, '[]');
11+
12+
query I
13+
SELECT json_overlaps(payload, '[1,2,3]') FROM json_overlaps_test ORDER BY id;
14+
----
15+
1
16+
1
17+
0
18+
1
19+
0
20+
21+
query I
22+
SELECT id FROM json_overlaps_test
23+
WHERE json_overlaps(payload, '[1,2,3]')
24+
ORDER BY id;
25+
----
26+
1
27+
2
28+
4
29+
30+
query I
31+
SELECT id FROM json_overlaps_test
32+
WHERE id > ? AND json_overlaps(payload, '[1,2,3]')
33+
ORDER BY id;
34+
? = 1
35+
----
36+
2
37+
4
38+
39+
# Test COALESCE and IFNULL as predicates
40+
statement ok
41+
CREATE TABLE bool_nulls (id INT PRIMARY KEY, b BOOLEAN);
42+
43+
statement ok
44+
INSERT INTO bool_nulls VALUES
45+
(1, TRUE),
46+
(2, FALSE),
47+
(3, NULL),
48+
(4, TRUE),
49+
(5, NULL);
50+
51+
# Test COALESCE with boolean, defaulting NULL to TRUE
52+
query I
53+
SELECT id FROM bool_nulls WHERE COALESCE(b, TRUE) ORDER BY id;
54+
----
55+
1
56+
3
57+
4
58+
5
59+
60+
# Test COALESCE with boolean, defaulting NULL to FALSE
61+
query I
62+
SELECT id FROM bool_nulls WHERE COALESCE(b, FALSE) ORDER BY id;
63+
----
64+
1
65+
4
66+
67+
# Test IFNULL with boolean, defaulting NULL to TRUE
68+
query I
69+
SELECT id FROM bool_nulls WHERE IFNULL(b, TRUE) ORDER BY id;
70+
----
71+
1
72+
3
73+
4
74+
5
75+
76+
# Test IFNULL with boolean, defaulting NULL to FALSE
77+
query I
78+
SELECT id FROM bool_nulls WHERE IFNULL(b, FALSE) ORDER BY id;
79+
----
80+
1
81+
4
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Test function calls as WHERE predicates in PostgreSQL
2+
3+
statement ok
4+
CREATE TABLE bool_nulls (id INT PRIMARY KEY, b BOOLEAN);
5+
6+
statement ok
7+
INSERT INTO bool_nulls VALUES
8+
(1, TRUE),
9+
(2, FALSE),
10+
(3, NULL),
11+
(4, TRUE),
12+
(5, NULL);
13+
14+
# Test COALESCE with boolean, defaulting NULL to TRUE
15+
query I
16+
SELECT id FROM bool_nulls WHERE COALESCE(b, TRUE) ORDER BY id;
17+
----
18+
1
19+
3
20+
4
21+
5
22+
23+
# Test COALESCE with boolean, defaulting NULL to FALSE
24+
query I
25+
SELECT id FROM bool_nulls WHERE COALESCE(b, FALSE) ORDER BY id;
26+
----
27+
1
28+
4
29+
30+
# Test COALESCE with three arguments
31+
query I
32+
SELECT id FROM bool_nulls WHERE COALESCE(NULL, b, TRUE) ORDER BY id;
33+
----
34+
1
35+
3
36+
4
37+
5

readyset-server/src/controller/sql/mir/mod.rs

Lines changed: 160 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,9 +1947,6 @@ impl SqlToMirConverter {
19471947

19481948
self.handle_exists(ce, query_name, &name, parent, subquery)?
19491949
}
1950-
Expr::Call(_) => {
1951-
internal!("Function calls should have been handled by projection earlier")
1952-
}
19531950
Expr::In {
19541951
lhs,
19551952
rhs: InValue::Subquery(subquery),
@@ -3255,4 +3252,164 @@ mod tests {
32553252
// post-lookups not enabled
32563253
expect_success: false
32573254
}
3255+
3256+
#[test]
3257+
fn function_call_predicate_is_supported() {
3258+
let query = parse_select(
3259+
readyset_sql::Dialect::MySQL,
3260+
"SELECT a FROM test_table WHERE json_overlaps(test_table.c, '[1,2,3]')",
3261+
)
3262+
.unwrap();
3263+
3264+
let qg = to_query_graph(query, Dialect::DEFAULT_MYSQL).unwrap();
3265+
let columns = &[
3266+
ColumnSpecification {
3267+
column: Column::from("test_table.a"),
3268+
sql_type: SqlType::Int(None),
3269+
generated: None,
3270+
constraints: vec![],
3271+
comment: None,
3272+
},
3273+
ColumnSpecification {
3274+
column: Column::from("test_table.c"),
3275+
sql_type: SqlType::Json,
3276+
generated: None,
3277+
constraints: vec![],
3278+
comment: None,
3279+
},
3280+
];
3281+
3282+
let mut converter = SqlToMirConverter::new(Dialect::DEFAULT_MYSQL);
3283+
let result = converter
3284+
.make_base_node(&"test_table".into(), columns, None)
3285+
.and_then(|_| {
3286+
converter.named_query_to_mir(&"q1".into(), &qg, &HashMap::new(), LeafBehavior::Leaf)
3287+
});
3288+
3289+
assert!(
3290+
result.is_ok(),
3291+
"Expected MIR lowering to succeed, but it failed: {:?}",
3292+
result.err()
3293+
);
3294+
}
3295+
3296+
#[test]
3297+
fn coalesce_predicate_mysql() {
3298+
let query = parse_select(
3299+
readyset_sql::Dialect::MySQL,
3300+
"SELECT a FROM test_table WHERE COALESCE(test_table.b, TRUE)",
3301+
)
3302+
.unwrap();
3303+
3304+
let qg = to_query_graph(query, Dialect::DEFAULT_MYSQL).unwrap();
3305+
let columns = &[
3306+
ColumnSpecification {
3307+
column: Column::from("test_table.a"),
3308+
sql_type: SqlType::Int(None),
3309+
generated: None,
3310+
constraints: vec![],
3311+
comment: None,
3312+
},
3313+
ColumnSpecification {
3314+
column: Column::from("test_table.b"),
3315+
sql_type: SqlType::Bool,
3316+
generated: None,
3317+
constraints: vec![],
3318+
comment: None,
3319+
},
3320+
];
3321+
3322+
let mut converter = SqlToMirConverter::new(Dialect::DEFAULT_MYSQL);
3323+
let result = converter
3324+
.make_base_node(&"test_table".into(), columns, None)
3325+
.and_then(|_| {
3326+
converter.named_query_to_mir(&"q1".into(), &qg, &HashMap::new(), LeafBehavior::Leaf)
3327+
});
3328+
3329+
assert!(
3330+
result.is_ok(),
3331+
"Expected MIR lowering to succeed for COALESCE predicate (MySQL), but it failed: {:?}",
3332+
result.err()
3333+
);
3334+
}
3335+
3336+
#[test]
3337+
fn ifnull_predicate_mysql() {
3338+
let query = parse_select(
3339+
readyset_sql::Dialect::MySQL,
3340+
"SELECT a FROM test_table WHERE IFNULL(test_table.b, FALSE)",
3341+
)
3342+
.unwrap();
3343+
3344+
let qg = to_query_graph(query, Dialect::DEFAULT_MYSQL).unwrap();
3345+
let columns = &[
3346+
ColumnSpecification {
3347+
column: Column::from("test_table.a"),
3348+
sql_type: SqlType::Int(None),
3349+
generated: None,
3350+
constraints: vec![],
3351+
comment: None,
3352+
},
3353+
ColumnSpecification {
3354+
column: Column::from("test_table.b"),
3355+
sql_type: SqlType::Bool,
3356+
generated: None,
3357+
constraints: vec![],
3358+
comment: None,
3359+
},
3360+
];
3361+
3362+
let mut converter = SqlToMirConverter::new(Dialect::DEFAULT_MYSQL);
3363+
let result = converter
3364+
.make_base_node(&"test_table".into(), columns, None)
3365+
.and_then(|_| {
3366+
converter.named_query_to_mir(&"q1".into(), &qg, &HashMap::new(), LeafBehavior::Leaf)
3367+
});
3368+
3369+
assert!(
3370+
result.is_ok(),
3371+
"Expected MIR lowering to succeed for IFNULL predicate (MySQL), but it failed: {:?}",
3372+
result.err()
3373+
);
3374+
}
3375+
3376+
#[test]
3377+
fn coalesce_predicate_postgresql() {
3378+
let query = parse_select(
3379+
readyset_sql::Dialect::PostgreSQL,
3380+
"SELECT a FROM test_table WHERE COALESCE(test_table.b, TRUE)",
3381+
)
3382+
.unwrap();
3383+
3384+
let qg = to_query_graph(query, Dialect::DEFAULT_POSTGRESQL).unwrap();
3385+
let columns = &[
3386+
ColumnSpecification {
3387+
column: Column::from("test_table.a"),
3388+
sql_type: SqlType::Int(None),
3389+
generated: None,
3390+
constraints: vec![],
3391+
comment: None,
3392+
},
3393+
ColumnSpecification {
3394+
column: Column::from("test_table.b"),
3395+
sql_type: SqlType::Bool,
3396+
generated: None,
3397+
constraints: vec![],
3398+
comment: None,
3399+
},
3400+
];
3401+
3402+
let mut converter = SqlToMirConverter::new(Dialect::DEFAULT_POSTGRESQL);
3403+
let result = converter
3404+
.make_base_node(&"test_table".into(), columns, None)
3405+
.and_then(|_| {
3406+
converter.named_query_to_mir(&"q1".into(), &qg, &HashMap::new(), LeafBehavior::Leaf)
3407+
});
3408+
3409+
assert!(
3410+
result.is_ok(),
3411+
"Expected MIR lowering to succeed for COALESCE predicate (PostgreSQL), but it failed: {:?}",
3412+
result.err()
3413+
);
3414+
}
32583415
}

0 commit comments

Comments
 (0)