// Copyright (c) 2023-2025 ParadeDB, Inc. // // This file is part of ParadeDB - Postgres for Search and Analytics // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // Tests for ParadeDB's Aggregate Custom Scan implementation mod fixtures; use fixtures::*; use pretty_assertions::assert_eq; use rstest::*; use serde_json::Value; use sqlx::PgConnection; fn assert_uses_custom_scan(conn: &mut PgConnection, enabled: bool, query: impl AsRef) { let (plan,) = format!(" EXPLAIN (FORMAT JSON) {}", query.as_ref()).fetch_one::<(Value,)>(conn); eprintln!("{plan:#?}"); assert_eq!( enabled, plan.to_string().contains("ParadeDB Aggregate Scan") ); } #[rstest] fn test_count(mut conn: PgConnection) { SimpleProductsTable::setup().execute(&mut conn); // Use the aggregate custom scan only if it is enabled. for enabled in [true, false] { format!("SET paradedb.enable_aggregate_custom_scan TO {enabled};").execute(&mut conn); let query = "SELECT COUNT(*) FROM paradedb.bm25_search WHERE description @@@ 'keyboard'"; assert_uses_custom_scan(&mut conn, enabled, query); let (count,) = query.fetch_one::<(i64,)>(&mut conn); assert_eq!(count, 2, "With custom scan: {enabled}"); } } #[rstest] fn test_group_by(mut conn: PgConnection) { SimpleProductsTable::setup().execute(&mut conn); "SET paradedb.enable_aggregate_custom_scan TO on;".execute(&mut conn); // Cannot use aggregate scan with GROUP BY yet. assert_uses_custom_scan( &mut conn, false, r#" SELECT rating, COUNT(*) FROM paradedb.bm25_search WHERE description @@@ 'keyboard' GROUP BY rating ORDER BY rating "#, ); } #[rstest] fn test_no_bm25_index(mut conn: PgConnection) { "CALL paradedb.create_bm25_test_table(table_name => 'no_bm25', schema_name => 'paradedb');" .execute(&mut conn); "SET paradedb.enable_aggregate_custom_scan TO on;".execute(&mut conn); // Do not use the aggregate custom scan on non-bm25 indexed tables. assert_uses_custom_scan(&mut conn, false, "SELECT COUNT(*) FROM paradedb.no_bm25"); } #[rstest] fn test_other_aggregates(mut conn: PgConnection) { SimpleProductsTable::setup().execute(&mut conn); "SET paradedb.enable_aggregate_custom_scan TO on;".execute(&mut conn); // Do not use the aggregate custom scan for aggregates that we do not support yet. for aggregate_func in ["SUM(rating)", "AVG(rating)", "MIN(rating)", "MAX(rating)"] { assert_uses_custom_scan( &mut conn, false, format!( r#" SELECT {aggregate_func} FROM paradedb.bm25_search WHERE description @@@ 'keyboard' "# ), ); } }