use crate::prelude::*;
use toasty_core::driver::Operation;
#[driver_test]
pub async fn composite_index_basic(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(user_id, game_title)]
#[index(game_title, top_score)]
struct GameScore {
user_id: String,
game_title: String,
top_score: i64,
}
let mut db = t.setup_db(models!(GameScore)).await;
toasty::create!(GameScore::[
{ user_id: "u1", game_title: "chess", top_score: 100_i64 },
{ user_id: "u2", game_title: "chess", top_score: 200_i64 },
{ user_id: "u1", game_title: "go", top_score: 50_i64 },
])
.exec(&mut db)
.await?;
let mut scores: Vec<GameScore> = GameScore::filter_by_game_title("chess")
.exec(&mut db)
.await?;
scores.sort_by_key(|s| s.top_score);
assert_eq!(scores.len(), 2);
assert_eq!(scores[0].top_score, 100);
assert_eq!(scores[1].top_score, 200);
Ok(())
}
#[driver_test]
pub async fn composite_index_struct_level(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(id, name)]
#[index(user_id)]
struct Post {
id: String,
name: String,
user_id: String,
title: String,
}
let mut db = t.setup_db(models!(Post)).await;
toasty::create!(Post::[
{ id: "p1", name: "first", user_id: "alice", title: "Hello World" },
{ id: "p2", name: "second", user_id: "alice", title: "Another Post" },
{ id: "p3", name: "third", user_id: "bob", title: "Bob's Post" },
])
.exec(&mut db)
.await?;
t.log().clear();
let mut posts: Vec<Post> = Post::filter_by_user_id("alice").exec(&mut db).await?;
posts.sort_by(|a, b| a.id.cmp(&b.id));
assert_eq!(posts.len(), 2);
assert_eq!(posts[0].title, "Hello World");
assert_eq!(posts[1].title, "Another Post");
let op = t.log().pop_op();
if t.capability().sql {
assert_struct!(op, Operation::QuerySql(_));
} else {
assert_struct!(op, Operation::QueryPk(_));
}
Ok(())
}
#[driver_test]
pub async fn composite_index_prefix_queries(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(user_id, game_title)]
#[index(game_title, top_score)]
struct GameScore {
user_id: String,
game_title: String,
top_score: i64,
}
let mut db = t.setup_db(models!(GameScore)).await;
toasty::create!(GameScore::[
{ user_id: "u1", game_title: "chess", top_score: 100_i64 },
{ user_id: "u2", game_title: "chess", top_score: 200_i64 },
{ user_id: "u3", game_title: "chess", top_score: 200_i64 },
{ user_id: "u1", game_title: "go", top_score: 50_i64 },
])
.exec(&mut db)
.await?;
t.log().clear();
let scores: Vec<GameScore> = GameScore::filter_by_game_title("chess")
.exec(&mut db)
.await?;
assert_eq!(scores.len(), 3);
let op = t.log().pop_op();
if t.capability().sql {
assert_struct!(op, Operation::QuerySql(_));
} else {
assert_struct!(op, Operation::QueryPk(_));
}
t.log().clear();
let scores: Vec<GameScore> = GameScore::filter_by_game_title_and_top_score("chess", 100)
.exec(&mut db)
.await?;
assert_eq!(scores.len(), 1);
assert_eq!(scores[0].user_id, "u1");
let op = t.log().pop_op();
if t.capability().sql {
assert_struct!(op, Operation::QuerySql(_));
} else {
assert_struct!(op, Operation::QueryPk(_));
}
Ok(())
}
#[driver_test(requires(not(sql)))]
pub async fn composite_index_multi_hash(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(id)]
#[index(partition = [tournament_id, region], local = [round])]
struct Match {
id: String,
tournament_id: String,
region: String,
round: String,
player1_id: String,
player2_id: String,
}
let mut db = t.setup_db(models!(Match)).await;
toasty::create!(Match::[
{ id: "m1", tournament_id: "WINTER2024", region: "NA-EAST", round: "SEMIFINALS", player1_id: "alice", player2_id: "bob" },
{ id: "m2", tournament_id: "WINTER2024", region: "NA-EAST", round: "FINALS", player1_id: "charlie", player2_id: "dave" },
{ id: "m3", tournament_id: "WINTER2024", region: "EU-WEST", round: "SEMIFINALS", player1_id: "eve", player2_id: "frank" },
])
.exec(&mut db)
.await?;
t.log().clear();
let mut matches: Vec<Match> =
Match::filter_by_tournament_id_and_region("WINTER2024", "NA-EAST")
.exec(&mut db)
.await?;
matches.sort_by(|a, b| a.id.cmp(&b.id));
assert_eq!(matches.len(), 2);
assert_eq!(matches[0].round, "SEMIFINALS");
assert_eq!(matches[1].round, "FINALS");
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
t.log().clear();
let matches: Vec<Match> =
Match::filter_by_tournament_id_and_region_and_round("WINTER2024", "NA-EAST", "SEMIFINALS")
.exec(&mut db)
.await?;
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].player1_id, "alice");
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
Ok(())
}
#[driver_test(requires(not(sql)))]
pub async fn composite_index_multi_range(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(id)]
#[index(partition = [player_id], local = [match_date, round])]
struct PlayerMatch {
id: String,
player_id: String,
match_date: String,
round: String,
opponent_id: String,
score: String,
}
let mut db = t.setup_db(models!(PlayerMatch)).await;
toasty::create!(PlayerMatch::[
{ id: "pm1", player_id: "101", match_date: "2024-01-18", round: "SEMIFINALS", opponent_id: "102", score: "3-1" },
{ id: "pm2", player_id: "101", match_date: "2024-01-18", round: "FINALS", opponent_id: "103", score: "2-1" },
{ id: "pm3", player_id: "101", match_date: "2024-01-25", round: "SEMIFINALS", opponent_id: "104", score: "3-0" },
{ id: "pm4", player_id: "999", match_date: "2024-01-18", round: "QUARTERFINALS", opponent_id: "101", score: "1-3" },
])
.exec(&mut db)
.await?;
t.log().clear();
let matches: Vec<PlayerMatch> = PlayerMatch::filter_by_player_id("101")
.exec(&mut db)
.await?;
assert_eq!(matches.len(), 3);
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
t.log().clear();
let matches: Vec<PlayerMatch> =
PlayerMatch::filter_by_player_id_and_match_date("101", "2024-01-18")
.exec(&mut db)
.await?;
assert_eq!(matches.len(), 2);
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
t.log().clear();
let matches: Vec<PlayerMatch> = PlayerMatch::filter_by_player_id_and_match_date_and_round(
"101",
"2024-01-18",
"SEMIFINALS",
)
.exec(&mut db)
.await?;
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].opponent_id, "102");
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
Ok(())
}
#[driver_test(requires(sql))]
pub async fn composite_index_three_columns(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(id)]
#[index(country, city, zip_code)]
struct Address {
#[auto]
id: u64,
country: String,
city: String,
zip_code: String,
street: String,
}
let mut db = t.setup_db(models!(Address)).await;
toasty::create!(Address::[
{ country: "US", city: "Seattle", zip_code: "98101", street: "1st Ave" },
{ country: "US", city: "Seattle", zip_code: "98102", street: "2nd Ave" },
{ country: "US", city: "Portland", zip_code: "97201", street: "Oak St" },
{ country: "CA", city: "Toronto", zip_code: "M5V", street: "King St" },
])
.exec(&mut db)
.await?;
t.log().clear();
let addrs: Vec<Address> = Address::filter_by_country("US").exec(&mut db).await?;
assert_eq!(addrs.len(), 3);
let op = t.log().pop_op();
assert_struct!(op, Operation::QuerySql(_));
t.log().clear();
let addrs: Vec<Address> = Address::filter_by_country_and_city("US", "Seattle")
.exec(&mut db)
.await?;
assert_eq!(addrs.len(), 2);
let op = t.log().pop_op();
assert_struct!(op, Operation::QuerySql(_));
t.log().clear();
let addrs: Vec<Address> =
Address::filter_by_country_and_city_and_zip_code("US", "Seattle", "98101")
.exec(&mut db)
.await?;
assert_eq!(addrs.len(), 1);
assert_eq!(addrs[0].street, "1st Ave");
let op = t.log().pop_op();
assert_struct!(op, Operation::QuerySql(_));
Ok(())
}
#[driver_test(requires(not(sql)))]
pub async fn composite_index_simple_three_column_ddb(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(id)]
#[index(country, city, zip_code)]
struct Address {
id: String,
country: String,
city: String,
zip_code: String,
street: String,
}
let mut db = t.setup_db(models!(Address)).await;
toasty::create!(Address::[
{ id: "a1", country: "US", city: "Seattle", zip_code: "98101", street: "1st Ave" },
{ id: "a2", country: "US", city: "Seattle", zip_code: "98102", street: "2nd Ave" },
{ id: "a3", country: "US", city: "Portland", zip_code: "97201", street: "Oak St" },
{ id: "a4", country: "CA", city: "Toronto", zip_code: "M5V", street: "King St" },
])
.exec(&mut db)
.await?;
t.log().clear();
let addrs: Vec<Address> = Address::filter_by_country("US").exec(&mut db).await?;
assert_eq!(addrs.len(), 3);
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
t.log().clear();
let addrs: Vec<Address> = Address::filter_by_country_and_city("US", "Seattle")
.exec(&mut db)
.await?;
assert_eq!(addrs.len(), 2);
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
t.log().clear();
let addrs: Vec<Address> =
Address::filter_by_country_and_city_and_zip_code("US", "Seattle", "98101")
.exec(&mut db)
.await?;
assert_eq!(addrs.len(), 1);
assert_eq!(addrs[0].street, "1st Ave");
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
Ok(())
}
#[driver_test]
pub async fn composite_index_multiple_indexes(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(id)]
#[index(category)]
#[index(brand)]
struct Product {
id: String,
category: String,
brand: String,
name: String,
}
let mut db = t.setup_db(models!(Product)).await;
toasty::create!(Product::[
{ id: "p1", category: "electronics", brand: "acme", name: "Widget A" },
{ id: "p2", category: "electronics", brand: "globex", name: "Widget B" },
{ id: "p3", category: "clothing", brand: "acme", name: "Shirt C" },
{ id: "p4", category: "clothing", brand: "initech", name: "Pants D" },
])
.exec(&mut db)
.await?;
t.log().clear();
let mut products: Vec<Product> = Product::filter_by_category("electronics")
.exec(&mut db)
.await?;
products.sort_by(|a, b| a.id.cmp(&b.id));
assert_eq!(products.len(), 2);
assert_eq!(products[0].name, "Widget A");
assert_eq!(products[1].name, "Widget B");
let op = t.log().pop_op();
if t.capability().sql {
assert_struct!(op, Operation::QuerySql(_));
} else {
assert_struct!(op, Operation::QueryPk(_));
}
t.log().clear();
let mut products: Vec<Product> = Product::filter_by_brand("acme").exec(&mut db).await?;
products.sort_by(|a, b| a.id.cmp(&b.id));
assert_eq!(products.len(), 2);
assert_eq!(products[0].name, "Widget A");
assert_eq!(products[1].name, "Shirt C");
let op = t.log().pop_op();
if t.capability().sql {
assert_struct!(op, Operation::QuerySql(_));
} else {
assert_struct!(op, Operation::QueryPk(_));
}
Ok(())
}
#[driver_test(requires(not(sql)))]
#[allow(clippy::too_many_arguments)]
pub async fn composite_index_max_attributes(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(id)]
#[index(partition = [f1, f2, f3, f4], local = [f5, f6, f7, f8])]
struct MaxIndex {
id: String,
f1: String,
f2: String,
f3: String,
f4: String,
f5: String,
f6: String,
f7: String,
f8: String,
value: String,
}
let mut db = t.setup_db(models!(MaxIndex)).await;
toasty::create!(MaxIndex::[
{ id: "r1", f1: "a1", f2: "b1", f3: "c1", f4: "d1", f5: "e1", f6: "g1", f7: "h1", f8: "i1", value: "found" },
{ id: "r2", f1: "a1", f2: "b1", f3: "c1", f4: "d2", f5: "e1", f6: "g1", f7: "h1", f8: "i1", value: "other" },
])
.exec(&mut db)
.await?;
t.log().clear();
let records: Vec<MaxIndex> =
MaxIndex::filter_by_f1_and_f2_and_f3_and_f4("a1", "b1", "c1", "d1")
.exec(&mut db)
.await?;
assert_eq!(records.len(), 1);
assert_eq!(records[0].value, "found");
let op = t.log().pop_op();
assert_struct!(op, Operation::QueryPk(_));
Ok(())
}
#[driver_test(requires(not(sql)))]
#[allow(clippy::too_many_arguments)]
pub async fn composite_index_too_many_range_columns(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(id)]
#[index(a, b, c, d, e, f)]
struct TooManyRange {
id: String,
a: String,
b: String,
c: String,
d: String,
e: String,
f: String,
}
let result = t.try_setup_db(models!(TooManyRange)).await;
assert!(
result.is_err(),
"expected setup_db to fail for 1 HASH + 5 RANGE index"
);
let err = result.unwrap_err();
assert!(
err.is_invalid_schema(),
"expected invalid_schema error, got: {err}"
);
Ok(())
}
#[driver_test]
pub async fn composite_index_sort_key_range_filter(t: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(user_id, game_title)]
#[index(game_title, top_score)]
struct GameScore {
user_id: String,
game_title: String,
top_score: i64,
}
let mut db = t.setup_db(models!(GameScore)).await;
toasty::create!(GameScore::[
{ user_id: "u1", game_title: "chess", top_score: 100_i64 },
{ user_id: "u2", game_title: "chess", top_score: 200_i64 },
{ user_id: "u3", game_title: "chess", top_score: 1500_i64 },
{ user_id: "u4", game_title: "chess", top_score: 50_i64 },
{ user_id: "u1", game_title: "go", top_score: 9999_i64 },
])
.exec(&mut db)
.await?;
let mut scores: Vec<GameScore> = GameScore::filter_by_game_title("chess")
.filter(GameScore::fields().top_score().gt(150))
.exec(&mut db)
.await?;
scores.sort_by_key(|s| s.top_score);
assert_eq!(scores.len(), 2);
assert_eq!(scores[0].top_score, 200);
assert_eq!(scores[1].top_score, 1500);
assert!(scores.iter().all(|s| s.game_title == "chess"));
Ok(())
}