use foundationdb::tuple::{Subspace, TuplePack, unpack};
use foundationdb::{Database, FdbBindingError, FdbResult, RangeOption, Transaction};
use futures::TryStreamExt;
use std::fmt::{Display, Formatter};
#[derive(Default)]
struct User {
id: String,
name: String,
zipcode: String,
}
impl Display for User {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"id => '{}', zipcode => '{}', name => '{}'",
self.id, self.zipcode, self.name
)
}
}
async fn clear_subspaces(db: &Database, subspaces: &[Subspace]) {
db.run(|trx, _maybe_committed| async move {
for subspace in subspaces {
trx.clear_subspace_range(subspace);
}
Ok(())
})
.await
.expect("Unable to commit transaction");
}
async fn create_user(
transaction: &Transaction,
id: &str,
zipcode: &str,
name: &str,
user_subspace: &Subspace,
zipcode_index_subspace: &Subspace,
) {
let key = user_subspace.pack(&(id, zipcode));
let index = zipcode_index_subspace.pack(&(zipcode, id));
transaction.set(&key, &name.pack_to_vec());
transaction.set(&index, &[]);
}
async fn get_user(
user_subspace: &Subspace,
transaction: &Transaction,
user_id: &str,
zipcode: &str,
) -> FdbResult<User> {
let key = user_subspace.pack(&(user_id, zipcode));
let user = transaction
.get(&key, false)
.await?
.expect("Could not found a row");
let name: String = unpack(&user).expect("Could not unpack");
Ok(User {
id: user_id.to_string(),
zipcode: zipcode.to_string(),
name,
})
}
async fn search_user_by_zipcode(
db: &Database,
user_subspace: &Subspace,
zipcode_index: &Subspace,
zipcode: &str,
) -> Result<Vec<User>, FdbBindingError> {
db.run(|trx, _maybe_committed| async move {
let begin = zipcode_index.pack(&(zipcode));
let mut end = zipcode_index.pack(&(zipcode));
end.pop();
end.push(255_u8);
let range = RangeOption::from((begin, end));
let mut users = vec![];
let mut stream = trx.get_ranges_keyvalues(range, false);
while let Some(result) = stream.try_next().await? {
let (zipcode, id): (String, String) = zipcode_index
.unpack(result.key())
.expect("Unable to unpack value from index");
let result_user = get_user(user_subspace, &trx, &id, &zipcode).await;
if let Ok(user) = result_user {
users.push(user);
}
}
Ok(users)
})
.await
}
#[tokio::main]
async fn main() {
let _guard = unsafe { foundationdb::boot() };
let db = Database::new_compat(None)
.await
.expect("failed to get database");
db.set_option(foundationdb::options::DatabaseOption::TransactionTimeout(
5000,
))
.expect("failed to set transaction timeout");
db.set_option(foundationdb::options::DatabaseOption::TransactionRetryLimit(3))
.expect("failed to set transaction retry limit");
let user_subspace = Subspace::from_bytes("user");
let zipcode_index_subspace = Subspace::from_bytes("zipcode_index");
clear_subspaces(
&db,
&[user_subspace.clone(), zipcode_index_subspace.clone()],
)
.await;
populate_users(&db, &user_subspace, &zipcode_index_subspace)
.await
.expect("Unable to populate database");
let users = search_user_by_zipcode(&db, &user_subspace, &zipcode_index_subspace, "205").await;
match users {
Ok(users) => users.iter().for_each(|user| println!("{user}")),
Err(e) => eprintln!("Error searching users: {e}"),
}
}
async fn populate_users(
db: &Database,
user_subspace: &Subspace,
zipcode_index_subspace: &Subspace,
) -> Result<(), FdbBindingError> {
db.run(|trx, _maybe_committed| async move {
create_user(
&trx,
"001",
"20500",
"Barack",
user_subspace,
zipcode_index_subspace,
)
.await;
create_user(
&trx,
"002",
"20500",
"Michelle",
user_subspace,
zipcode_index_subspace,
)
.await;
create_user(
&trx,
"003",
"20500",
"Sasha",
user_subspace,
zipcode_index_subspace,
)
.await;
create_user(
&trx,
"004",
"20500",
"Malia",
user_subspace,
zipcode_index_subspace,
)
.await;
create_user(
&trx,
"005",
"20500",
"Bo",
user_subspace,
zipcode_index_subspace,
)
.await;
create_user(
&trx,
"101",
"SW1A 1AA",
"Elizabeth",
user_subspace,
zipcode_index_subspace,
)
.await;
create_user(
&trx,
"102",
"SW1A 1AA",
"Philip",
user_subspace,
zipcode_index_subspace,
)
.await;
Ok(())
})
.await
}