Skip to content

Incompatible API changes

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

Version 3.0.0

Upgrading from 2.2.x to 3.0.0 requires dependency, policy, batch, query, and error-handling updates. For the complete upstream checklist, see MIGRATION.md in the Aerospike Rust client repository.

Cargo.toml dependency changes

Old (2.2.0):

[dependencies]
aerospike = "2.2.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

New:

[dependencies]
aerospike = { version = "3.0.0", features = ["rt-tokio"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
futures = "0.3"

Minimum supported Rust version is 1.87. TLS is enabled in the default feature set. If you disable default features, also enable async and rt-tokio when connecting over TLS.

ClientPolicy default timeouts

ClientPolicy.timeout defaults to 1 second (was 30 seconds in 2.2.x). ClientPolicy.idle_timeout defaults to 0 (disabled). Review timeouts after upgrading, or set explicit values.

Read consistency policy fields

BasePolicy.consistency_level was replaced by separate modes:

  • read_mode_ap for AP namespaces
  • read_mode_sc for strong-consistency namespaces

Batch API returns per-row results

Client::batch now takes &mut [BatchOperation] (or &mut Vec<BatchOperation>) and returns Result<()>. Per-key outcomes are read from each BatchOperation (result_code(), record(), and related methods) or from the BatchRecord returned by batch_record(). Use BatchWritePolicy and BatchDeletePolicy for per-operation write defaults. See Batch operations.

Query filters

Statement::add_filter was renamed to Statement::set_filter. Predicate filter macros were removed; use Filter constructors instead.

Client::operate and MultiResult

When a single operate call writes to a bin and reads the same bin, the returned record can include Value::MultiResult instead of a single value. Prefer Record::results when mixing modify and read operations on one bin. See String operations and Bin operations.

Type and API renames

Several public types were renamed for Rust naming conventions. Key fields are crate-private; use accessor methods such as namespace() and set_name(). Policy flag fields use newtypes instead of raw integers in several places.

TLS configuration uses webpki_roots and PEM iterators

When building TLS for ClientPolicy.tls_policy, wrap a rustls::ClientConfig in TlsPolicy::new and assign policy.tls_policy = Some(...). Initialize RootCertStore with webpki_roots::TLS_SERVER_ROOTS and load custom CAs with CertificateDer::pem_file_iter. See Connecting for full examples.

[dependencies]
aerospike = { version = "3.0.0", default-features = false, features = ["async", "rt-tokio", "tls"] }

Error is now a struct; match ErrorKind (or use accessors)

In 2.2.x, aerospike::Error was a flat enum you could match directly (see the “Version 2.1.0” section below). In 3.0.0, Error is an opaque struct. Call Error::kind() or accessors such as server_result_code() and is_client_timeout().

Old (2.2.x, does not compile in 3.0.0):

match client.get(&rpolicy, &key, Bins::All).await {
Ok(record) => println!("{}", record),
Err(Error::ServerError(ResultCode::KeyNotFoundError, ..)) => {
println!("Not found");
}
Err(err) => println!("Error: {}", err),
}

New:

match client.get(&rpolicy, &key, Bins::All).await {
Ok(record) => println!("{}", record),
Err(e) if e.server_result_code() == Some(ResultCode::KeyNotFoundError) => {
println!("Not found");
}
Err(err) => println!("Error: {}", err),
}

ErrorKind adds variants for 3.0.0 features, such as MaxErrorRate, Commit, StreamTerminated, and BatchRow (internal to batch parsing; inspect per-row BatchRecord results instead). ErrorKind is #[non_exhaustive], so a match on it needs a wildcard _ arm.

📖 API reference: Error | ErrorKind | Error handling

New APIs in 3.0.0

APIDescription
Client::batch_foreach()Invoke a per-row callback as batch results arrive
aerospike::txnMulti-record transaction (MRT) support
aerospike::operations::stringString manipulation using Client::operate
compression_thresholdMinimum buffer size before wire compression applies

📖 API reference: Client::batch_foreach | txn | Transactions | Batch operations | String operations


Version 2.1.0: Async runtime, unified query, new batch and error APIs

Version 2.1.0 is a major rewrite of the Rust client. All database operations are now async, the scan and query APIs are unified, batch operations support writes and deletes, and the error model has been replaced with a flat enum.

Cargo.toml dependency changes

Depending on how your application is configured, v2.1.0 uses either tokio or rt-async runtime crates for its asynchronous operation, and the futures crate for stream iteration.

Old:

[dependencies]
aerospike = "1.3"

New:

[dependencies]
aerospike = "2.1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
futures = "0.3"

📖 API reference: Client

All Client methods are now async

When using Tokio, every Client method that performs I/O is now an async fn and must use .await inside a Tokio runtime. Standalone programs require #[tokio::main].

Old:

fn main() {
let client = Client::new(&ClientPolicy::default(), &hosts)
.expect("Failed to connect");
client.put(&wpolicy, &key, &bins).unwrap();
let record = client.get(&rpolicy, &key, Bins::All).unwrap();
client.close().unwrap();
}

New:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new(&ClientPolicy::default(), &hosts).await?;
client.put(&wpolicy, &key, &bins).await?;
let record = client.get(&rpolicy, &key, Bins::All).await?;
client.close().await?;
Ok(())
}

📖 API reference: Client::new() | Client::close()

With async-std, the usage is:

async pub fn my_async_main() -> Result<X, Y> {
// ...
}
pub fn main() -> Result<X, Y> {
task::block_on(my_async_main())
}

See the async-std documentation for more help.

Macro imports

Declare the crate once at the root of your binary so macros such as as_key! and as_bin! are in scope:

#[macro_use]
extern crate aerospike;

📖 API reference: as_key! | as_bin! | as_val!

Error model: error-chain replaced by Error enum

The v1.3.0 error-chain pattern Error(ErrorKind::..., _) is replaced by a flat aerospike::Error enum with named variants.

Old:

use aerospike::errors::{Error, ErrorKind};
match client.get(&rpolicy, &key, Bins::All) {
Ok(record) => println!("{}", record),
Err(Error(ErrorKind::ServerError(ResultCode::KeyNotFoundError), _)) => {
println!("Not found");
}
Err(err) => println!("Error: {}", err),
}

New:

use aerospike::{Error, ResultCode};
match client.get(&rpolicy, &key, Bins::All).await {
Ok(record) => println!("{}", record),
Err(Error::ServerError(ResultCode::KeyNotFoundError, ..)) => {
println!("Not found");
}
Err(err) => println!("Error: {}", err),
}

The following Error variants are available in v2.1.0:

VariantDescription
ServerError(ResultCode, bool, String)Server returned an error result code
Connection(String)Cluster connection failure
Timeout(String)Client-side timeout
InvalidArgument(String)Invalid method argument
InvalidNode(String)Node not found or unavailable
NoMoreConnectionsConnection pool exhausted
ClientError(String)General client-side error
Io(std::io::Error)Underlying I/O error

📖 API reference (2.1.0): The table lists the flat Error enum from 2.1.0 (removed in 3.0). On the current client, use Error handling and ResultCode for server result codes.

Scan and query unified into client.query()

client.scan() and ScanPolicy have been removed. Use client.query() with a Statement that has no secondary index filter and PartitionFilter::all().

Old:

use aerospike::ScanPolicy;
let policy = ScanPolicy::default();
match client.scan(&policy, "test", "demo", Bins::All) {
Ok(records) => {
for record in &*records {
match record {
Ok(rec) => println!("{}", rec),
Err(err) => eprintln!("Error: {}", err),
}
}
}
Err(err) => eprintln!("Scan failed: {}", err),
}

New:

use aerospike::{QueryPolicy, Statement, Bins, PartitionFilter};
use futures::stream::StreamExt;
let policy = QueryPolicy::default();
let stmt = Statement::new("test", "demo", Bins::All);
let rs = client.query(&policy, PartitionFilter::all(), stmt).await?;
let mut stream = rs.into_stream();
while let Some(result) = stream.next().await {
match result {
Ok(record) => println!("{}", record),
Err(err) => eprintln!("Error: {}", err),
}
}

The client.scan_node() and client.query_node() methods have also been removed. Use PartitionFilter to target specific partitions instead.

📖 API reference: Client::query() | Statement::new() | PartitionFilter

Query signature change: PartitionFilter parameter added

client.query() now requires a PartitionFilter argument between the policy and the statement. This enables partition-based pagination.

Old:

let rs = client.query(&QueryPolicy::default(), stmt)?;

New:

let rs = client.query(&QueryPolicy::default(), PartitionFilter::all(), stmt).await?;

📖 API reference: Client::query() | PartitionFilter::all()

Recordset iteration: for loop replaced by async stream

v1.3.0 returned Arc<Recordset> which implemented IntoIterator for synchronous for loops. v2.1.0 requires converting the Recordset into an async RecordStream and consuming it with StreamExt::next().

Old:

let records = client.query(&policy, stmt)?;
for record in &*records {
match record {
Ok(rec) => println!("{}", rec),
Err(err) => eprintln!("Error: {}", err),
}
}

New:

use futures::stream::StreamExt;
let rs = client.query(&policy, PartitionFilter::all(), stmt).await?;
let mut stream = rs.into_stream();
while let Some(result) = stream.next().await {
match result {
Ok(record) => println!("{}", record),
Err(err) => eprintln!("Error: {}", err),
}
}

📖 API reference: Recordset::into_stream()

Batch API: batch_get() replaced by batch()

The v1.3.0 read-only client.batch_get(Vec<BatchRead>) is replaced by the v2.1.0 client.batch(&BatchPolicy, &[BatchOperation]), which supports reads, writes, deletes, and UDF calls in a single batch.

Old:

use aerospike::BatchRead;
let bins = Bins::from(["name", "age"]);
let mut batch_reads = vec![];
for i in 0..10 {
let key = as_key!("test", "test", i);
batch_reads.push(BatchRead::new(key, &bins));
}
match client.batch_get(&BatchPolicy::default(), batch_reads) {
Ok(results) => {
for result in &results {
match &result.record {
Some(record) => println!("{:?} => {:?}", result.key, record.bins),
None => println!("Not found: {:?}", result.key),
}
}
}
Err(err) => eprintln!("Batch error: {}", err),
}

New:

use aerospike::{BatchPolicy, BatchOperation, BatchReadPolicy, Bins};
let bins = Bins::from(["name", "age"]);
let bpr = BatchReadPolicy::default();
let ops: Vec<BatchOperation> = (0..10)
.map(|i| BatchOperation::read(&bpr, as_key!("test", "test", i), bins.clone()))
.collect();
match client.batch(&BatchPolicy::default(), &ops).await {
Ok(results) => {
for result in &results {
match &result.record {
Some(record) => println!("{:?} => {:?}", result.key, record.bins),
None => println!("Not found: {:?}", result.key),
}
}
}
Err(err) => eprintln!("Batch error: {}", err),
}

New batch operation types:

v2.1.0 methodDescription
BatchOperation::read()Read bins from a record
BatchOperation::read_ops()Read with specific operations
BatchOperation::write()Write operations to a record
BatchOperation::delete()Delete a record
BatchOperation::udf()Execute a UDF on a record

Each operation type has its own policy: BatchReadPolicy, BatchWritePolicy, BatchDeletePolicy, BatchUDFPolicy.

📖 API reference: Client::batch() | BatchOperation | BatchRecord

Bin passing: reference wrappers removed from put(), add(), append(), prepend()

v1.3.0 accepted bins by reference wrapper (&[A] where A: AsRef<Bin<'b>>). v2.1.0 accepts &[Bin] directly, so bins no longer need to be wrapped in an extra &.

Old:

let bin = as_bin!("name", "Alice");
client.put(&wpolicy, &key, &vec![&bin])?;

New:

let bin = as_bin!("name", "Alice");
client.put(&wpolicy, &key, &[bin]).await?;

📖 API reference: Client::put()

exists() now takes ReadPolicy instead of WritePolicy

Old:

let exists = client.exists(&WritePolicy::default(), &key)?;

New:

let exists = client.exists(&ReadPolicy::default(), &key).await?;

📖 API reference: Client::exists()

Index creation: create_index() replaced by create_index_on_bin()

The v1.3.0 methods create_index() and create_complex_index() are replaced by create_index_on_bin(), which combines both and adds support for CDT context.

Old:

let task = client.create_index(
&WritePolicy::default(), "test", "demo", "age",
"idx_demo_age", IndexType::Numeric,
)?;

New:

use aerospike::{AdminPolicy, IndexType, CollectionIndexType};
let task = client.create_index_on_bin(
&AdminPolicy::default(),
"test", "demo", "age", "idx_demo_age",
IndexType::Numeric,
CollectionIndexType::Default,
None,
).await?;
task.wait_till_complete(None).await?;

v2.1.0 also adds create_index_using_expression() for expression-based secondary indices.

📖 API reference: Client::create_index_on_bin() | Client::create_index_using_expression()

drop_index() now returns DropIndexTask

Old:

client.drop_index(&WritePolicy::default(), "test", "demo", "idx_demo_age")?;

New:

let task = client.drop_index(
&AdminPolicy::default(), "test", "demo", "idx_demo_age",
).await?;

📖 API reference: Client::drop_index() | DropIndexTask

Admin operations now take AdminPolicy

register_udf(), remove_udf(), truncate(), create_index_on_bin(), and drop_index() now accept &AdminPolicy instead of &WritePolicy.

Old:

let task = client.register_udf(
&WritePolicy::default(), code.as_bytes(), "example.lua", UDFLang::Lua,
)?;

New:

let task = client.register_udf(
&AdminPolicy::default(), code.as_bytes(), "example.lua", UdfLang::Lua,
).await?;
task.wait_till_complete(None).await?;

📖 API reference: Client::register_udf() | AdminPolicy

remove_udf() simplified and returns UdfRemoveTask

The language parameter has been removed, and the method now returns a task that can be awaited.

Old:

client.remove_udf(&WritePolicy::default(), "example.lua", UDFLang::Lua)?;

New:

let task = client.remove_udf(&AdminPolicy::default(), "example.lua").await?;

📖 API reference: Client::remove_udf() | UdfRemoveTask

Thread-based concurrency replaced by async tasks

v1.3.0 used std::thread with Arc<Client>. v2.1.0 uses tokio::spawn with Arc<Client>, since Client implements Send + Sync.

Old:

use std::sync::Arc;
use std::thread;
let client = Arc::new(Client::new(&cpolicy, &hosts)?);
let mut threads = vec![];
for i in 0..4 {
let client = client.clone();
threads.push(thread::spawn(move || {
client.put(&wpolicy, &key, &bins).unwrap();
}));
}
for t in threads {
t.join().unwrap();
}

New:

use std::sync::Arc;
let client = Arc::new(Client::new(&cpolicy, &hosts).await?);
let mut handles = vec![];
for i in 0..4 {
let client = client.clone();
handles.push(tokio::spawn(async move {
client.put(&wpolicy, &key, &bins).await.unwrap();
}));
}
futures::future::join_all(handles).await;

📖 API reference: Client

TLS support added using rustls

v2.1.0 adds optional TLS support through the tls Cargo feature. Configure TLS by setting ClientPolicy.tls_config to a rustls::ClientConfig. For TLS in 3.0.0, use tls_policy and TlsPolicy::new. See Connecting.

[dependencies]
aerospike = { version = "2.1.0", features = ["tls"] }
rustls = "0.23"
let mut policy = ClientPolicy::default();
policy.tls_config = Some(rustls_client_config);
let client = Client::new(&policy, &"tls-host.example.com:4333").await?;

📖 API reference: ClientPolicy

Summary of removed and renamed types

v1.3.0v2.1.0Notes
ScanPolicyQueryPolicyScans use client.query() with no filter
client.scan()client.query()Pass PartitionFilter::all() for full-set scan
client.scan_node()RemovedUse PartitionFilter to scope by partition
client.query_node()RemovedUse PartitionFilter to scope by partition
client.batch_get()client.batch()Now accepts &[BatchOperation]
BatchReadBatchRecord (result) / BatchOperation (input)Supports read, write, delete, UDF
client.create_index()client.create_index_on_bin()Adds CollectionIndexType and ctx params
client.create_complex_index()client.create_index_on_bin()Merged into single method
errors::Error (struct)Error (enum)Flat enum, no error-chain
errors::ErrorKindRemovedVariants moved to Error enum directly
for record in &*recordsrs.into_stream() + stream.next().awaitRequires futures::stream::StreamExt

Summary of new types in v2.1.0

TypeDescription
PartitionFilterPartition-based cursor for query/scan pagination
BatchOperationBatch operation enum (read, write, delete, UDF)
BatchReadPolicyPer-operation policy for batch reads
BatchWritePolicyPer-operation policy for batch writes
BatchDeletePolicyPer-operation policy for batch deletes
BatchUDFPolicyPer-operation policy for batch UDF calls
BatchRecordResult record from a batch operation
DropIndexTaskAsync task for tracking index deletion
UdfRemoveTaskAsync task for tracking UDF removal
QueryDurationHint for expected query duration
ReadTouchTTLControls read-based TTL refresh behavior
Recordset::into_stream()Converts recordset to async RecordStream

Version 1.0.0

Replace predicate expressions with Aerospike Expressions

The old predicate expression functionality has been replaced by Aerospike Expressions. Aerospike Expressions include all previous predicate expression functionality (with a different, improved syntax) plus support for record metadata expressions and list/map/bit/hyperloglog expression methods analogous to those used in operations.

This version of the client requires server version 5.2.0.4+. Code using old PredExp must be converted to the new syntax. Here is an example:

OLD:

let mut statement = Statement::new(namespace, &set_name, Bins::All);
statement.add_predicate(as_predexp_integer_bin!("age".to_string()));
statement.add_predicate(as_predexp_integer_value!(35));
statement.add_predicate(as_predexp_integer_lesseq!());
let qpolicy = QueryPolicy::default();
let results = client.query(&qpolicy, statement)?;

📖 API reference: Statement::new | QueryPolicy::default | Client::query

NEW:

use aerospike::expressions as exp;
let mut qpolicy = QueryPolicy::default();
qpolicy.base_policy.filter_expression = Some(
exp::le(
exp::int_bin("age".to_string()),
exp::int_val(35)
)
);
let statement = Statement::new(namespace, &set_name, Bins::All);
let results = client.query(&qpolicy, PartitionFilter::all(), statement).await?;

📖 API reference: Statement::new | QueryPolicy::default | Client::query

Next steps

  • Install: Cargo features and minimum Rust version for each client release.
  • Connecting: Set up a Client after upgrading.
  • Error handling: Classify errors with accessors and ErrorKind.
  • Client matrix: Minimum Aerospike server version for each client release.