#![allow(clippy::doc_link_with_quotes)]
use std::collections::HashSet;
use serde_json::json;
use velesdb_core::{Database, Point};
use super::helpers::{create_test_db, execute_sql, result_ids};
fn setup_hotels_collection(db: &Database) {
execute_sql(
db,
"CREATE COLLECTION hotels (dimension = 4, metric = 'cosine');",
)
.expect("test: CREATE hotels");
let vc = db
.get_vector_collection("hotels")
.expect("test: get hotels collection");
vc.upsert(vec![
Point::new(
1,
vec![1.0, 0.0, 0.0, 0.0],
Some(json!({
"amenities": ["pool", "gym", "spa"],
"tags": ["luxury", "5*"],
"rating": 4.8,
"city": "Paris"
})),
),
Point::new(
2,
vec![0.0, 1.0, 0.0, 0.0],
Some(json!({
"amenities": ["wifi", "parking"],
"tags": ["budget"],
"rating": 3.2,
"city": "London"
})),
),
Point::new(
3,
vec![0.0, 0.0, 1.0, 0.0],
Some(json!({
"amenities": ["pool", "wifi", "bar"],
"tags": ["family"],
"rating": 4.1,
"city": "Paris"
})),
),
Point::new(
4,
vec![0.0, 0.0, 0.0, 1.0],
Some(json!({
"amenities": [],
"tags": ["budget", "new"],
"rating": 2.9,
"city": "Berlin"
})),
),
Point::new(
5,
vec![0.5, 0.5, 0.0, 0.0],
Some(json!({
"rating": 4.5,
"city": "Tokyo"
})),
),
Point::new(
6,
vec![0.5, 0.0, 0.5, 0.0],
Some(json!({
"amenities": ["gym"],
"tags": ["luxury"],
"rating": 3.8,
"city": "London"
})),
),
Point::new(
7,
vec![0.0, 0.5, 0.0, 0.5],
Some(json!({
"amenities": ["pool", "gym", "spa", "wifi"],
"tags": ["luxury", "5*", "new"],
"rating": 4.9,
"city": "Dubai"
})),
),
])
.expect("test: upsert hotels");
}
#[test]
fn test_contains_single_value_returns_matching_rows() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'pool' LIMIT 10;",
)
.expect("CONTAINS single query");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 3, 7]), "Hotels with pool: 1, 3, 7");
}
#[test]
fn test_contains_any_returns_rows_with_at_least_one_match() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS ANY ('spa', 'bar') LIMIT 10;",
)
.expect("CONTAINS ANY query");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 3, 7]), "Hotels with spa or bar");
}
#[test]
fn test_contains_all_returns_rows_with_every_value() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS ALL ('pool', 'gym') LIMIT 10;",
)
.expect("CONTAINS ALL query");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 7]), "Hotels with pool AND gym");
}
#[test]
fn test_contains_on_empty_array_returns_no_results() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'pool' LIMIT 10;",
)
.expect("CONTAINS on empty array");
let ids = result_ids(&results);
assert!(!ids.contains(&4), "Empty array hotel should not match");
}
#[test]
fn test_contains_on_null_array_excludes_row() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'wifi' LIMIT 10;",
)
.expect("CONTAINS on null array");
let ids = result_ids(&results);
assert!(
!ids.contains(&5),
"Null/missing array hotel should not match"
);
}
#[test]
fn test_contains_on_single_element_array() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'gym' LIMIT 10;",
)
.expect("CONTAINS on single-element array");
let ids = result_ids(&results);
assert!(ids.contains(&6), "Single-element array hotel should match");
assert_eq!(ids, HashSet::from([1, 6, 7]));
}
#[test]
fn test_contains_any_with_overlapping_values() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS ANY ('parking', 'gym') LIMIT 10;",
)
.expect("CONTAINS ANY overlapping");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 2, 6, 7]));
}
#[test]
fn test_contains_on_scalar_string_field_returns_empty() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE city CONTAINS 'Paris' LIMIT 10;",
)
.expect("CONTAINS on scalar string");
assert!(
results.is_empty(),
"CONTAINS on scalar string should return empty"
);
}
#[test]
fn test_contains_on_nonexistent_field_returns_empty() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE nonexistent CONTAINS 'value' LIMIT 10;",
)
.expect("CONTAINS on nonexistent field");
assert!(results.is_empty(), "Non-existent field should return empty");
}
#[test]
fn test_contains_combined_with_scalar_filter() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'pool' AND rating > 4.5 LIMIT 10;",
)
.expect("CONTAINS AND scalar filter");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 7]), "Pool + rating > 4.5");
}
#[test]
fn test_contains_combined_with_equality_filter() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'wifi' AND city = 'Paris' LIMIT 10;",
)
.expect("CONTAINS AND equality");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([3]), "wifi + Paris = hotel 3");
}
#[test]
fn test_contains_all_combined_with_equality() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS ALL ('pool', 'spa') AND city = 'Paris' LIMIT 10;",
)
.expect("CONTAINS ALL AND equality");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1]), "pool+spa+Paris = hotel 1");
}
#[test]
fn test_contains_any_on_different_array_field() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE tags CONTAINS ANY ('luxury', 'family') LIMIT 10;",
)
.expect("CONTAINS ANY on tags");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 3, 6, 7]), "luxury or family tags");
}
#[test]
fn test_contains_with_order_by() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'pool' ORDER BY rating DESC LIMIT 10;",
)
.expect("CONTAINS with ORDER BY");
let ids: Vec<u64> = results.iter().map(|r| r.point.id).collect();
assert_eq!(ids, vec![7, 1, 3], "Ordered by rating DESC: 4.9, 4.8, 4.1");
}
#[test]
fn test_parse_contains_single_value() {
use velesdb_core::velesql::{Condition, ContainsMode, Parser};
let query = Parser::parse("SELECT * FROM c WHERE tags CONTAINS 'pool' LIMIT 5;")
.expect("parse CONTAINS single");
let condition = query.select.where_clause.expect("WHERE clause");
if let Condition::Contains(cc) = condition {
assert_eq!(cc.column, "tags");
assert_eq!(cc.mode, ContainsMode::Single);
assert_eq!(cc.values.len(), 1);
} else {
panic!("Expected Condition::Contains, got {condition:?}");
}
}
#[test]
fn test_parse_contains_any_values() {
use velesdb_core::velesql::{Condition, ContainsMode, Parser};
let query = Parser::parse("SELECT * FROM c WHERE tags CONTAINS ANY ('pool', 'gym') LIMIT 5;")
.expect("parse CONTAINS ANY");
let condition = query.select.where_clause.expect("WHERE clause");
if let Condition::Contains(cc) = condition {
assert_eq!(cc.column, "tags");
assert_eq!(cc.mode, ContainsMode::Any);
assert_eq!(cc.values.len(), 2);
} else {
panic!("Expected Condition::Contains, got {condition:?}");
}
}
#[test]
fn test_parse_contains_all_values() {
use velesdb_core::velesql::{Condition, ContainsMode, Parser};
let query =
Parser::parse("SELECT * FROM c WHERE tags CONTAINS ALL ('pool', 'gym', 'spa') LIMIT 5;")
.expect("parse CONTAINS ALL");
let condition = query.select.where_clause.expect("WHERE clause");
if let Condition::Contains(cc) = condition {
assert_eq!(cc.column, "tags");
assert_eq!(cc.mode, ContainsMode::All);
assert_eq!(cc.values.len(), 3);
} else {
panic!("Expected Condition::Contains, got {condition:?}");
}
}
#[test]
fn test_parse_contains_and_comparison() {
use velesdb_core::velesql::{Condition, Parser};
let query =
Parser::parse("SELECT * FROM c WHERE tags CONTAINS 'pool' AND rating > 4.0 LIMIT 5;")
.expect("parse CONTAINS AND comparison");
let condition = query.select.where_clause.expect("WHERE clause");
assert!(
matches!(condition, Condition::And(_, _)),
"Expected And condition, got {condition:?}"
);
}
#[test]
fn test_parse_malformed_contains_returns_error() {
use velesdb_core::velesql::Parser;
let result = Parser::parse("SELECT * FROM c WHERE tags CONTAINS LIMIT 5;");
assert!(result.is_err(), "Malformed CONTAINS should fail to parse");
}
#[test]
fn test_contains_with_integer_array() {
let (_dir, db) = create_test_db();
execute_sql(
&db,
"CREATE COLLECTION items (dimension = 2, metric = 'cosine');",
)
.expect("CREATE items");
let vc = db.get_vector_collection("items").expect("get items");
vc.upsert(vec![
Point::new(1, vec![1.0, 0.0], Some(json!({"scores": [10, 20, 30]}))),
Point::new(2, vec![0.0, 1.0], Some(json!({"scores": [40, 50]}))),
Point::new(3, vec![0.5, 0.5], Some(json!({"scores": [10, 50]}))),
])
.expect("upsert items");
let results = execute_sql(
&db,
"SELECT * FROM items WHERE scores CONTAINS 10 LIMIT 10;",
)
.expect("CONTAINS integer");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 3]), "Items with score 10");
}
#[test]
fn test_contains_all_no_match_returns_empty() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS ALL ('pool', 'parking') LIMIT 10;",
)
.expect("CONTAINS ALL no match");
assert!(results.is_empty(), "No hotel has both pool and parking");
}
#[test]
fn test_contains_any_single_value_equivalent_to_contains() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results_any = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS ANY ('spa') LIMIT 10;",
)
.expect("CONTAINS ANY single");
let results_single = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'spa' LIMIT 10;",
)
.expect("CONTAINS single");
assert_eq!(
result_ids(&results_any),
result_ids(&results_single),
"CONTAINS ANY ('spa') == CONTAINS 'spa'"
);
}
#[test]
fn test_contains_on_array_with_duplicate_elements() {
let (_dir, db) = create_test_db();
execute_sql(
&db,
"CREATE COLLECTION dupes (dimension = 2, metric = 'cosine');",
)
.expect("CREATE dupes");
let vc = db.get_vector_collection("dupes").expect("get dupes");
vc.upsert(vec![
Point::new(
1,
vec![1.0, 0.0],
Some(json!({"tags": ["a", "b", "a", "c"]})),
),
Point::new(2, vec![0.0, 1.0], Some(json!({"tags": ["x", "y"]}))),
])
.expect("upsert dupes");
let results = execute_sql(&db, "SELECT * FROM dupes WHERE tags CONTAINS 'a' LIMIT 10;")
.expect("CONTAINS on duplicated element");
let ids = result_ids(&results);
assert_eq!(
ids,
HashSet::from([1]),
"Row with duplicated 'a' matches once"
);
let results_all = execute_sql(
&db,
"SELECT * FROM dupes WHERE tags CONTAINS ALL ('a', 'c') LIMIT 10;",
)
.expect("CONTAINS ALL with duplicated element");
let ids_all = result_ids(&results_all);
assert_eq!(ids_all, HashSet::from([1]), "Row has both 'a' and 'c'");
}
#[test]
fn test_contains_combined_with_vector_search() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let mut params = std::collections::HashMap::new();
params.insert("v".to_string(), json!([1.0_f32, 0.0, 0.0, 0.0]));
let results = super::helpers::execute_sql_with_params(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'spa' OR vector NEAR $v LIMIT 10;",
¶ms,
)
.expect("CONTAINS OR vector NEAR");
let ids = result_ids(&results);
assert!(ids.contains(&1), "Hotel 1 has spa");
assert!(ids.contains(&7), "Hotel 7 has spa");
assert!(!ids.is_empty(), "Should have results from OR combination");
}
#[test]
fn test_contains_with_not_negation() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE NOT (amenities CONTAINS 'pool') LIMIT 10;",
)
.expect("NOT CONTAINS query");
let ids = result_ids(&results);
assert!(!ids.contains(&1), "Hotel 1 has pool — should be excluded");
assert!(!ids.contains(&3), "Hotel 3 has pool — should be excluded");
assert!(!ids.contains(&7), "Hotel 7 has pool — should be excluded");
assert!(ids.contains(&2), "Hotel 2 has no pool");
assert!(ids.contains(&6), "Hotel 6 has no pool");
}
#[test]
fn test_contains_or_scalar_filter() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'spa' OR city = 'Tokyo' LIMIT 10;",
)
.expect("CONTAINS OR scalar");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 5, 7]), "spa (1,7) OR Tokyo (5)");
}
#[test]
fn test_contains_with_limit_truncates_results() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'pool' LIMIT 2;",
)
.expect("CONTAINS with LIMIT 2");
assert_eq!(results.len(), 2, "LIMIT 2 should cap at 2 results");
for r in &results {
let amenities = r
.point
.payload
.as_ref()
.and_then(|p| p.get("amenities"))
.and_then(|v| v.as_array());
assert!(
amenities.is_some_and(|arr| arr.iter().any(|v| v.as_str() == Some("pool"))),
"All results must have pool in amenities"
);
}
}
#[test]
fn test_contains_all_and_contains_any_combined() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS ALL ('pool', 'gym') AND tags CONTAINS ANY ('new', 'family') LIMIT 10;",
)
.expect("CONTAINS ALL + CONTAINS ANY");
let ids = result_ids(&results);
assert_eq!(
ids,
HashSet::from([7]),
"Only hotel 7 matches both conditions"
);
}
#[test]
fn test_contains_nested_and_or_grouping() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE (amenities CONTAINS 'pool' AND city = 'Paris') OR tags CONTAINS 'budget' LIMIT 10;",
)
.expect("nested AND/OR with CONTAINS");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 2, 3, 4]), "(pool+Paris) OR budget");
}
#[test]
fn test_contains_on_two_different_array_fields() {
let (_dir, db) = create_test_db();
setup_hotels_collection(&db);
let results = execute_sql(
&db,
"SELECT * FROM hotels WHERE amenities CONTAINS 'gym' AND tags CONTAINS 'luxury' LIMIT 10;",
)
.expect("CONTAINS on two array fields");
let ids = result_ids(&results);
assert_eq!(ids, HashSet::from([1, 6, 7]), "gym AND luxury");
}