// 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 . mod fixtures; use fixtures::*; use rstest::*; use sqlx::PgConnection; #[fixture] fn setup_test_table(mut conn: PgConnection) -> PgConnection { let sql = r#" CREATE TABLE test ( id SERIAL8 NOT NULL PRIMARY KEY, col_boolean boolean DEFAULT false, col_text text, col_int8 int8 ); "#; sql.execute(&mut conn); let sql = r#" CREATE INDEX idxtest ON test USING bm25 (id, col_boolean, col_text, col_int8) WITH (key_field='id', text_fields = '{"col_text": {"fast": true, "tokenizer": {"type":"raw"}}}'); "#; sql.execute(&mut conn); "INSERT INTO test (id) VALUES (1);".execute(&mut conn); "INSERT INTO test (id, col_text) VALUES (2, 'foo');".execute(&mut conn); "INSERT INTO test (id, col_text, col_int8) VALUES (3, 'bar', 333);".execute(&mut conn); "INSERT INTO test (id, col_int8) VALUES (4, 444);".execute(&mut conn); "SET enable_indexscan TO off;".execute(&mut conn); "SET enable_bitmapscan TO off;".execute(&mut conn); "SET max_parallel_workers TO 0;".execute(&mut conn); conn } mod string_fast_field_exec { use super::*; #[rstest] fn with_range(#[from(setup_test_table)] mut conn: PgConnection) { let res = r#" SELECT * FROM test WHERE id @@@ paradedb.range(field => 'id', range => int8range(1, 5, '[]')) ORDER BY id; "# .fetch::<(i64, bool, Option, Option)>(&mut conn); assert_eq!( res, vec![ (1, false, None, None), (2, false, Some(String::from("foo")), None), (3, false, Some(String::from("bar")), Some(333)), (4, false, None, Some(444)) ] ); } #[rstest] fn with_filter(#[from(setup_test_table)] mut conn: PgConnection) { let res = r#" SELECT * FROM test WHERE col_text IS NULL and id @@@ '>2' ORDER BY id; "# .fetch::<(i64, bool, Option, Option)>(&mut conn); assert_eq!(res, vec![(4, false, None, Some(444))]); } #[rstest] fn with_multiple_filters(#[from(setup_test_table)] mut conn: PgConnection) { let res = r#" SELECT * FROM test WHERE col_text IS NULL AND col_int8 IS NOT NULL AND id @@@ paradedb.range(field => 'id', range => int8range(1, 5, '[]')) ORDER BY id; "# .fetch::<(i64, bool, Option, Option)>(&mut conn); assert_eq!(res, vec![(4, false, None, Some(444))]); } #[rstest] fn with_not_null(#[from(setup_test_table)] mut conn: PgConnection) { let res = r#" SELECT * FROM test WHERE col_text IS NOT NULL and id @@@ '>2' ORDER BY id; "# .fetch::<(i64, bool, Option, Option)>(&mut conn); assert_eq!(res, vec![(3, false, Some(String::from("bar")), Some(333))]); } #[rstest] fn with_null(#[from(setup_test_table)] mut conn: PgConnection) { let res = r#" SELECT * FROM test WHERE col_text IS NULL and id @@@ '<=2' ORDER BY id; "# .fetch::<(i64, bool, Option, Option)>(&mut conn); assert_eq!(res, vec![(1, false, None, None)]); } #[rstest] fn with_count(#[from(setup_test_table)] mut conn: PgConnection) { let count = r#" SELECT count(*) FROM test WHERE col_text IS NOT NULL and id @@@ '>2'; "# .fetch::<(i64,)>(&mut conn); assert_eq!(count, vec![(1,)]); let count = r#" SELECT count(*) FROM test WHERE col_text IS NULL and id @@@ '>2'; "# .fetch::<(i64,)>(&mut conn); assert_eq!(count, vec![(1,)]); } #[rstest] fn with_empty_string(#[from(setup_test_table)] mut conn: PgConnection) { "INSERT INTO test (id, col_text) VALUES (5, '');".execute(&mut conn); let res = r#" SELECT * FROM test WHERE col_text = '' ORDER BY id; "# .fetch::<(i64, bool, Option, Option)>(&mut conn); assert_eq!(res, vec![(5, false, Some(String::from("")), None)]); } #[rstest] fn with_all_null_segment(mut conn: PgConnection) { let sql = r#" CREATE TABLE another_test ( id SERIAL8 NOT NULL PRIMARY KEY, col_boolean boolean DEFAULT false, col_text text, col_int8 int8 ); "#; sql.execute(&mut conn); let sql = r#" CREATE INDEX another_idxtest ON another_test USING bm25 (id, col_boolean, col_text, col_int8) WITH (key_field='id', text_fields = '{"col_text": {"fast": true, "tokenizer": {"type":"raw"}}}'); "#; sql.execute(&mut conn); "INSERT INTO another_test (id) VALUES (1);".execute(&mut conn); "INSERT INTO another_test (id, col_int8) VALUES (3, 333);".execute(&mut conn); "INSERT INTO another_test (id, col_int8) VALUES (4, 444);".execute(&mut conn); "INSERT INTO another_test (id, col_text) VALUES (6, NULL), (7, NULL), (8, NULL);" .execute(&mut conn); "SET enable_indexscan TO off;".execute(&mut conn); "SET enable_bitmapscan TO off;".execute(&mut conn); "SET max_parallel_workers TO 0;".execute(&mut conn); let count = r#" SELECT count(*) FROM another_test WHERE col_text IS NULL and id @@@ '>2'; "# .fetch::<(i64,)>(&mut conn); assert_eq!(count, vec![(5,)]); let res = r#" SELECT * FROM another_test WHERE id @@@ paradedb.range(field => 'id', range => int8range(1, 8, '[]')) ORDER BY id; "# .fetch::<(i64, bool, Option, Option)>(&mut conn); assert_eq!( res, vec![ (1, false, None, None), (3, false, None, Some(333)), (4, false, None, Some(444)), (6, false, None, None), (7, false, None, None), (8, false, None, None) ] ); let count = r#" SELECT count(*) FROM another_test WHERE id @@@ paradedb.range(field => 'id', range => int8range(1, 8, '[]')) AND col_text IS NOT NULL "# .fetch::<(i64,)>(&mut conn); assert_eq!(count, vec![(0,)]) } }