Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions supabase-wrappers/src/upper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,22 @@ pub(super) extern "C-unwind" fn get_foreign_upper_paths<

let mut state = PgBox::<FdwState<E, W>>::from_pg(fdw_private as _);

// Every WHERE clause on the foreign table must have been extracted as a
// qual for the FDW to push down. A clause we couldn't extract (e.g.
// `lower(name) = 'x'` or `a > b`) is normally checked locally on each
// row, but the upper path has no local filter, so the remote aggregate
// would silently run without it.
let restrictinfo = (*input_rel).baserestrictinfo;
let n_clauses = if restrictinfo.is_null() {
0
} else {
(*restrictinfo).length as usize
};
if state.quals.len() != n_clauses {
debug2!("WHERE clause cannot be pushed down, skipping aggregate pushdown");
return;
}

// Check if FDW supports any aggregates
let supported = W::supported_aggregates();
if supported.is_empty() {
Expand Down
50 changes: 50 additions & 0 deletions wrappers/src/fdw/mysql_fdw/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,56 @@ mod tests {
"SELECT SUM(amount) FROM mysql_agg.orders WHERE id IN (1, 2, 3)"
);

// --- Negative: WHERE clause that can't be extracted as a qual ---
// `lower(name) = ...` stays a local filter, so the aggregate must not
// be pushed down, otherwise MySQL aggregates over rows the filter
// would have removed.
let cnt: i64 = c
.select(
"SELECT COUNT(*) FROM mysql_agg.orders WHERE lower(name) = 'carol'",
None,
&[],
)
.unwrap()
.first()
.get_one::<i64>()
.unwrap()
.unwrap();
assert_eq!(
cnt, 1,
"COUNT(*) WHERE lower(name) = 'carol' expected 1, got {cnt}"
);
assert_not_pushed_down!(
c,
"SELECT COUNT(*) FROM mysql_agg.orders WHERE lower(name) = 'carol'"
);

// mixed: `status` is pushable, `lower(name)` is not → Eve only = 150
let s: f64 = c
.select(
"SELECT SUM(amount) FROM mysql_agg.orders
WHERE status = 'inactive' AND lower(name) = 'eve'",
None,
&[],
)
.unwrap()
.first()
.get_one::<pgrx::AnyNumeric>()
.unwrap()
.unwrap()
.to_string()
.parse()
.unwrap();
assert!(
(s - 150.0).abs() < 0.01,
"SUM WHERE status = 'inactive' AND lower(name) = 'eve' expected 150.0, got {s}"
);
assert_not_pushed_down!(
c,
"SELECT SUM(amount) FROM mysql_agg.orders
WHERE status = 'inactive' AND lower(name) = 'eve'"
);

// --- Subquery table: verify aggregate pushdown through starts_with('(') branch ---
// COUNT(*) over the subquery — same 5 rows
let cnt: i64 = c
Expand Down