use crate::prelude::*;
#[derive(Debug, toasty::Model)]
#[key(partition = partition_id, local = sort_key)]
struct Item {
partition_id: i64,
sort_key: String,
name: String,
}
async fn setup(test: &mut Test) -> toasty::Db {
let mut db = test.setup_db(models!(Item)).await;
toasty::create!(Item::[
{ partition_id: 1_i64, sort_key: "alpha-1", name: "Alice" },
{ partition_id: 1_i64, sort_key: "alpha-2", name: "Alicia" },
{ partition_id: 1_i64, sort_key: "beta-1", name: "Bob" },
{ partition_id: 1_i64, sort_key: "beta-2", name: "Barry" },
{ partition_id: 2_i64, sort_key: "alpha-1", name: "Carol" },
])
.exec(&mut db)
.await
.unwrap();
db
}
#[driver_test]
pub async fn starts_with_sort_key(test: &mut Test) -> Result<()> {
let mut db = setup(test).await;
let mut items: Vec<Item> = Item::filter(
Item::fields()
.partition_id()
.eq(1_i64)
.and(Item::fields().sort_key().starts_with("alpha".to_string())),
)
.exec(&mut db)
.await?;
items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
assert_eq!(items.len(), 2);
assert_eq!(items[0].sort_key, "alpha-1");
assert_eq!(items[1].sort_key, "alpha-2");
Ok(())
}
#[driver_test]
pub async fn starts_with_non_key_attr(test: &mut Test) -> Result<()> {
let mut db = setup(test).await;
let mut items: Vec<Item> = Item::filter(
Item::fields()
.partition_id()
.eq(1_i64)
.and(Item::fields().name().starts_with("Al".to_string())),
)
.exec(&mut db)
.await?;
items.sort_by(|a, b| a.name.cmp(&b.name));
assert_eq!(items.len(), 2);
assert_eq!(items[0].name, "Alice");
assert_eq!(items[1].name, "Alicia");
Ok(())
}
#[driver_test]
pub async fn starts_with_no_match(test: &mut Test) -> Result<()> {
let mut db = setup(test).await;
let items: Vec<Item> = Item::filter(
Item::fields()
.partition_id()
.eq(1_i64)
.and(Item::fields().sort_key().starts_with("gamma".to_string())),
)
.exec(&mut db)
.await?;
assert_eq!(items.len(), 0);
Ok(())
}
#[driver_test(requires(not(sql)))]
pub async fn starts_with_empty_prefix(test: &mut Test) -> Result<()> {
let mut db = setup(test).await;
let result: toasty::Result<Vec<Item>> = Item::filter(
Item::fields()
.partition_id()
.eq(1_i64)
.and(Item::fields().sort_key().starts_with("".to_string())),
)
.exec(&mut db)
.await;
assert!(
result.is_err(),
"expected error when using starts_with with empty prefix on DynamoDB"
);
Ok(())
}
#[driver_test(requires(sql))]
pub async fn starts_with_empty_prefix_sql(test: &mut Test) -> Result<()> {
let mut db = setup(test).await;
let items: Vec<Item> = Item::filter(
Item::fields()
.partition_id()
.eq(1_i64)
.and(Item::fields().sort_key().starts_with("".to_string())),
)
.exec(&mut db)
.await?;
assert_eq!(items.len(), 4, "empty prefix should match all rows on SQL");
Ok(())
}
#[driver_test]
pub async fn starts_with_special_chars(test: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(partition = partition_id, local = sort_key)]
struct StringItem {
partition_id: i64,
sort_key: String,
}
let mut db = test.setup_db(models!(StringItem)).await;
toasty::create!(StringItem::[
{ partition_id: 1_i64, sort_key: "100%-discount" },
{ partition_id: 1_i64, sort_key: "100xdiscount" },
{ partition_id: 1_i64, sort_key: "1009" },
{ partition_id: 1_i64, sort_key: "a_b-literal" },
{ partition_id: 1_i64, sort_key: "axb-wildcard" },
{ partition_id: 1_i64, sort_key: "!bang-literal" },
{ partition_id: 1_i64, sort_key: "x!bang" },
])
.exec(&mut db)
.await
.unwrap();
let mut items: Vec<StringItem> = StringItem::filter(
StringItem::fields().partition_id().eq(1_i64).and(
StringItem::fields()
.sort_key()
.starts_with("100%".to_string()),
),
)
.exec(&mut db)
.await?;
items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
assert_eq!(items.len(), 1);
assert_eq!(items[0].sort_key, "100%-discount");
let mut items: Vec<StringItem> = StringItem::filter(
StringItem::fields().partition_id().eq(1_i64).and(
StringItem::fields()
.sort_key()
.starts_with("a_b".to_string()),
),
)
.exec(&mut db)
.await?;
items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
assert_eq!(items.len(), 1);
assert_eq!(items[0].sort_key, "a_b-literal");
let mut items: Vec<StringItem> = StringItem::filter(
StringItem::fields().partition_id().eq(1_i64).and(
StringItem::fields()
.sort_key()
.starts_with("!bang".to_string()),
),
)
.exec(&mut db)
.await?;
items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
assert_eq!(items.len(), 1);
assert_eq!(items[0].sort_key, "!bang-literal");
Ok(())
}
#[driver_test]
pub async fn starts_with_optional_field(test: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(partition = partition_id, local = id)]
struct OptItem {
partition_id: i64,
id: i64,
nickname: Option<String>,
}
let mut db = test.setup_db(models!(OptItem)).await;
toasty::create!(OptItem::[
{ partition_id: 1_i64, id: 1_i64, nickname: Some("Ali".to_string()) },
{ partition_id: 1_i64, id: 2_i64, nickname: Some("Alicia".to_string()) },
{ partition_id: 1_i64, id: 3_i64, nickname: Some("Bob".to_string()) },
{ partition_id: 1_i64, id: 4_i64, nickname: None },
])
.exec(&mut db)
.await?;
let mut items: Vec<OptItem> = OptItem::filter(
OptItem::fields()
.partition_id()
.eq(1_i64)
.and(OptItem::fields().nickname().starts_with("Al".to_string())),
)
.exec(&mut db)
.await?;
items.sort_by_key(|i| i.id);
assert_eq!(items.len(), 2);
assert_eq!(items[0].nickname.as_deref(), Some("Ali"));
assert_eq!(items[1].nickname.as_deref(), Some("Alicia"));
Ok(())
}
#[driver_test(requires(not(sql)))]
pub async fn starts_with_partition_key_error(test: &mut Test) -> Result<()> {
#[derive(Debug, toasty::Model)]
#[key(partition = partition_id, local = sort_key)]
struct StringKeyItem {
partition_id: String,
sort_key: String,
}
let mut db = test.setup_db(models!(StringKeyItem)).await;
StringKeyItem::create()
.partition_id("hello")
.sort_key("world")
.exec(&mut db)
.await?;
let result = StringKeyItem::filter(
StringKeyItem::fields()
.partition_id()
.starts_with("hel".to_string()),
)
.exec(&mut db)
.await;
assert!(
result.is_err(),
"expected error when using starts_with on partition key"
);
Ok(())
}