use std::future::Future;
use std::pin::Pin;
use std::str;
use std::sync::Arc;
#[cfg(feature = "rt-tokio")]
use std::sync::LazyLock;
use std::vec::Vec;
use crate::expressions::Expression;
use aerospike_core::errors::Result;
use aerospike_core::operations::{CdtContext, Operation};
use aerospike_core::query::PartitionFilter;
use aerospike_core::txn::{AbortStatus, CommitStatus, Txn};
use aerospike_core::DropIndexTask;
use aerospike_core::UdfRemoveTask;
use aerospike_core::{
AdminPolicy, BatchOperation, BatchPolicy, BatchRecord, Bin, Bins, ClientPolicy,
CollectionIndexType, ExecuteTask, IndexTask, IndexType, Key, Node, Privilege, QueryPolicy,
ReadPolicy, Record, Recordset, RegisterTask, Role, Statement, ToHosts, TxnRollPolicy,
TxnVerifyPolicy, UDFLang, User, Value, WritePolicy,
};
use futures::Stream;
#[cfg(feature = "rt-tokio")]
static SYNC_RT: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(
std::thread::available_parallelism()
.map(|n| n.get().min(4))
.unwrap_or(1),
)
.enable_all()
.thread_name("aerospike-sync-rt")
.build()
.expect("aerospike: failed to build sync runtime")
});
#[cfg(feature = "rt-tokio")]
fn block_on<F>(f: F) -> F::Output
where
F: Future + Send,
F::Output: Send,
{
use tokio::runtime::{Handle, RuntimeFlavor};
match Handle::try_current() {
Ok(h) if h.runtime_flavor() == RuntimeFlavor::MultiThread => {
tokio::task::block_in_place(|| SYNC_RT.block_on(f))
}
Ok(_) => std::thread::scope(|s| {
s.spawn(|| SYNC_RT.block_on(f))
.join()
.expect("aerospike sync bridge thread panicked")
}),
Err(_) => SYNC_RT.block_on(f),
}
}
#[cfg(feature = "rt-async-std")]
fn block_on<F: Future>(f: F) -> F::Output {
async_std::task::block_on(f)
}
pub struct BatchStream {
inner: Pin<Box<dyn Stream<Item = (usize, BatchRecord)> + Send>>,
}
impl Iterator for BatchStream {
type Item = (usize, BatchRecord);
fn next(&mut self) -> Option<Self::Item> {
use futures::StreamExt;
block_on(self.inner.next())
}
}
pub struct Client {
async_client: aerospike_core::Client,
}
unsafe impl Send for Client {}
unsafe impl Sync for Client {}
impl Client {
pub fn new(policy: &ClientPolicy, hosts: &(dyn ToHosts + Send + Sync)) -> Result<Self> {
let client = block_on(aerospike_core::Client::new(policy, hosts))?;
Ok(Client {
async_client: client,
})
}
#[cfg(feature = "dynamic-config")]
pub fn new_with_config(
policy: &ClientPolicy,
hosts: &(dyn ToHosts + Send + Sync),
provider: Arc<dyn aerospike_core::config::ConfigProvider>,
) -> Result<Self> {
let client = block_on(aerospike_core::Client::new_with_config(
policy, hosts, provider,
))?;
Ok(Client {
async_client: client,
})
}
pub fn close(&self) -> Result<()> {
block_on(self.async_client.close())?;
Ok(())
}
pub fn is_connected(&self) -> bool {
self.async_client.is_connected()
}
#[must_use]
pub const fn client_version() -> &'static str {
aerospike_core::Client::client_version()
}
pub fn node_names(&self) -> Vec<String> {
self.async_client.node_names()
}
pub fn get_node(&self, name: &str) -> Result<Arc<Node>> {
self.async_client.get_node(name)
}
pub fn info(
&self,
policy: &AdminPolicy,
commands: &[&str],
) -> Result<aerospike_core::IndexMap<String, String>> {
block_on(self.async_client.info(policy, commands))
}
pub fn nodes(&self) -> Vec<Arc<Node>> {
self.async_client.nodes()
}
pub fn get<T>(&self, policy: &ReadPolicy, key: &Key, bins: T) -> Result<Record>
where
T: Into<Bins> + Send + Sync + 'static,
{
block_on(self.async_client.get(policy, key, bins))
}
pub fn batch(
&self,
policy: &BatchPolicy,
batch_records: &[BatchOperation],
) -> Result<Vec<BatchRecord>> {
block_on(self.async_client.batch(policy, batch_records))
}
pub fn batch_stream(
&self,
policy: &BatchPolicy,
ops: Vec<BatchOperation>,
) -> Result<BatchStream> {
let stream = block_on(self.async_client.batch_stream(policy, ops))?;
Ok(BatchStream {
inner: Box::pin(stream),
})
}
pub fn put<'a>(&self, policy: &'a WritePolicy, key: &'a Key, bins: &'a [Bin]) -> Result<()> {
block_on(self.async_client.put(policy, key, bins))
}
pub fn add<'a>(&self, policy: &'a WritePolicy, key: &'a Key, bins: &'a [Bin]) -> Result<()> {
block_on(self.async_client.add(policy, key, bins))
}
pub fn append<'a>(&self, policy: &'a WritePolicy, key: &'a Key, bins: &'a [Bin]) -> Result<()> {
block_on(self.async_client.append(policy, key, bins))
}
pub fn prepend<'a>(
&self,
policy: &'a WritePolicy,
key: &'a Key,
bins: &'a [Bin],
) -> Result<()> {
block_on(self.async_client.prepend(policy, key, bins))
}
pub fn delete(&self, policy: &WritePolicy, key: &Key) -> Result<bool> {
block_on(self.async_client.delete(policy, key))
}
pub fn touch(&self, policy: &WritePolicy, key: &Key) -> Result<()> {
block_on(self.async_client.touch(policy, key))
}
pub fn exists(&self, policy: &ReadPolicy, key: &Key) -> Result<bool> {
block_on(self.async_client.exists(policy, key))
}
pub fn operate(&self, policy: &WritePolicy, key: &Key, ops: &[Operation]) -> Result<Record> {
block_on(self.async_client.operate(policy, key, ops))
}
pub fn register_udf(
&self,
policy: &AdminPolicy,
udf_body: &[u8],
server_path: &str,
language: UDFLang,
) -> Result<RegisterTask> {
block_on(
self.async_client
.register_udf(policy, udf_body, server_path, language),
)
}
pub fn register_udf_from_file(
&self,
policy: &AdminPolicy,
client_path: &str,
server_path: &str,
language: UDFLang,
) -> Result<RegisterTask> {
block_on(self.async_client.register_udf_from_file(
policy,
client_path,
server_path,
language,
))
}
pub fn remove_udf(&self, policy: &AdminPolicy, server_path: &str) -> Result<UdfRemoveTask> {
block_on(self.async_client.remove_udf(policy, server_path))
}
pub fn execute_udf(
&self,
policy: &WritePolicy,
key: &Key,
server_path: &str,
function_name: &str,
args: Option<&[Value]>,
) -> Result<Option<Value>> {
block_on(
self.async_client
.execute_udf(policy, key, server_path, function_name, args),
)
}
pub fn query(
&self,
policy: &QueryPolicy,
partition_filter: PartitionFilter,
statement: Statement,
) -> Result<Arc<Recordset>> {
block_on(self.async_client.query(policy, partition_filter, statement))
}
#[cfg(feature = "lua")]
pub fn query_aggregate(
&self,
policy: &QueryPolicy,
statement: Statement,
package_name: &str,
function_name: &str,
function_args: Option<&[Value]>,
) -> Result<Arc<aerospike_core::query::ResultSet>> {
block_on(self.async_client.query_aggregate(
policy,
statement,
package_name,
function_name,
function_args,
))
}
pub fn query_operate(
&self,
write_policy: &WritePolicy,
statement: Statement,
operations: &[Operation],
) -> Result<ExecuteTask> {
block_on(
self.async_client
.query_operate(write_policy, statement, operations),
)
}
pub fn query_execute_udf(
&self,
write_policy: &WritePolicy,
statement: Statement,
package_name: &str,
function_name: &str,
args: Option<&[Value]>,
) -> Result<ExecuteTask> {
block_on(self.async_client.query_execute_udf(
write_policy,
statement,
package_name,
function_name,
args,
))
}
pub fn set_xdr_filter(
&self,
policy: &AdminPolicy,
datacenter: &str,
namespace: &str,
filter_expression: Option<&Expression>,
) -> Result<()> {
block_on(
self.async_client
.set_xdr_filter(policy, datacenter, namespace, filter_expression),
)
}
pub fn truncate(
&self,
policy: &AdminPolicy,
namespace: &str,
set_name: &str,
before_nanos: i64,
) -> Result<()> {
block_on(
self.async_client
.truncate(policy, namespace, set_name, before_nanos),
)
}
pub fn create_index_on_bin(
&self,
policy: &AdminPolicy,
namespace: &str,
set_name: &str,
bin_name: &str,
index_name: &str,
index_type: IndexType,
collection_index_type: CollectionIndexType,
ctx: Option<&[CdtContext]>,
) -> Result<IndexTask> {
block_on(self.async_client.create_index_on_bin(
policy,
namespace,
set_name,
bin_name,
index_name,
index_type,
collection_index_type,
ctx,
))
}
pub fn create_index_using_expression(
&self,
policy: &AdminPolicy,
namespace: &str,
set_name: &str,
index_name: &str,
index_type: IndexType,
collection_index_type: CollectionIndexType,
expression: &Expression,
) -> Result<IndexTask> {
block_on(self.async_client.create_index_using_expression(
policy,
namespace,
set_name,
index_name,
index_type,
collection_index_type,
expression,
))
}
pub fn drop_index(
&self,
policy: &AdminPolicy,
namespace: &str,
set_name: &str,
index_name: &str,
) -> Result<DropIndexTask> {
block_on(
self.async_client
.drop_index(policy, namespace, set_name, index_name),
)
}
pub fn create_user(
&self,
policy: &AdminPolicy,
user: &str,
password: &str,
roles: &[&str],
) -> Result<()> {
block_on(self.async_client.create_user(policy, user, password, roles))
}
pub fn drop_user(&self, policy: &AdminPolicy, user: &str) -> Result<()> {
block_on(self.async_client.drop_user(policy, user))
}
pub fn change_password(&self, policy: &AdminPolicy, user: &str, password: &str) -> Result<()> {
block_on(self.async_client.change_password(policy, user, password))
}
pub fn grant_roles(&self, policy: &AdminPolicy, user: &str, roles: &[&str]) -> Result<()> {
block_on(self.async_client.grant_roles(policy, user, roles))
}
pub fn revoke_roles(&self, policy: &AdminPolicy, user: &str, roles: &[&str]) -> Result<()> {
block_on(self.async_client.revoke_roles(policy, user, roles))
}
pub fn query_users(&self, policy: &AdminPolicy, user: Option<&str>) -> Result<Vec<User>> {
block_on(self.async_client.query_users(policy, user))
}
pub fn create_role(
&self,
policy: &AdminPolicy,
role_name: &str,
privileges: &[Privilege],
allowlist: &[&str],
read_quota: u32,
write_quota: u32,
) -> Result<()> {
block_on(self.async_client.create_role(
policy,
role_name,
privileges,
allowlist,
read_quota,
write_quota,
))
}
pub fn query_roles(&self, policy: &AdminPolicy, role: Option<&str>) -> Result<Vec<Role>> {
block_on(self.async_client.query_roles(policy, role))
}
pub fn drop_role(&self, policy: &AdminPolicy, role_name: &str) -> Result<()> {
block_on(self.async_client.drop_role(policy, role_name))
}
pub fn grant_privileges(
&self,
policy: &AdminPolicy,
role_name: &str,
privileges: &[Privilege],
) -> Result<()> {
block_on(
self.async_client
.grant_privileges(policy, role_name, privileges),
)
}
pub fn revoke_privileges(
&self,
policy: &AdminPolicy,
role_name: &str,
privileges: &[Privilege],
) -> Result<()> {
block_on(
self.async_client
.revoke_privileges(policy, role_name, privileges),
)
}
pub fn set_allowlist(
&self,
policy: &AdminPolicy,
role_name: &str,
allowlist: &[&str],
) -> Result<()> {
block_on(
self.async_client
.set_allowlist(policy, role_name, allowlist),
)
}
pub fn set_quotas(
&self,
policy: &AdminPolicy,
role_name: &str,
read_quota: u32,
write_quota: u32,
) -> Result<()> {
block_on(
self.async_client
.set_quotas(policy, role_name, read_quota, write_quota),
)
}
pub fn create_pki_user(&self, policy: &AdminPolicy, user: &str, roles: &[&str]) -> Result<()> {
block_on(self.async_client.create_pki_user(policy, user, roles))
}
pub fn commit(&self, txn: &Arc<Txn>) -> Result<CommitStatus> {
block_on(self.async_client.commit(txn))
}
pub fn commit_with_policies(
&self,
verify_policy: &TxnVerifyPolicy,
roll_policy: &TxnRollPolicy,
txn: &Arc<Txn>,
) -> Result<CommitStatus> {
block_on(
self.async_client
.commit_with_policies(verify_policy, roll_policy, txn),
)
}
pub fn abort(&self, txn: &Arc<Txn>) -> Result<AbortStatus> {
block_on(self.async_client.abort(txn))
}
pub fn abort_with_policy(
&self,
roll_policy: &TxnRollPolicy,
txn: &Arc<Txn>,
) -> Result<AbortStatus> {
block_on(self.async_client.abort_with_policy(roll_policy, txn))
}
pub fn enable_metrics(&self, policy: aerospike_core::MetricsPolicy) {
self.async_client.enable_metrics(policy);
}
pub fn disable_metrics(&self) {
self.async_client.disable_metrics();
}
pub fn metrics_enabled(&self) -> bool {
self.async_client.metrics_enabled()
}
pub fn metrics(&self) -> aerospike_core::ClusterMetrics {
self.async_client.metrics()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn batch_stream_over_empty_stream_ends_immediately() {
let mut bs = BatchStream {
inner: Box::pin(futures::stream::iter(Vec::<(usize, BatchRecord)>::new())),
};
assert!(bs.next().is_none());
assert!(bs.next().is_none());
}
#[test]
fn batch_stream_parked_next_wakes_when_producer_closes() {
let (tx, rx) = async_channel::bounded::<(usize, BatchRecord)>(4);
let mut bs = BatchStream { inner: Box::pin(rx) };
let (done_tx, done_rx) = std::sync::mpsc::channel();
let consumer = std::thread::spawn(move || {
let item = bs.next(); let _ = done_tx.send(item.is_none());
});
std::thread::sleep(Duration::from_millis(100));
drop(tx); let ended_clean = done_rx
.recv_timeout(Duration::from_secs(5))
.expect("parked BatchStream::next was not woken by channel close");
assert!(ended_clean, "expected None once the producer closed");
consumer.join().unwrap();
}
}