oxia/lib.rs
1//! Rust client SDK for [Oxia](https://github.com/oxia-db/oxia), a scalable
2//! metadata store and coordination system.
3//!
4//! The entry point is [`OxiaClient`]. Every operation returns a request builder:
5//! chain options onto it and `.await` it directly.
6//!
7//! ```no_run
8//! use oxia::{ComparisonType, OxiaClient};
9//!
10//! # async fn example() -> Result<(), oxia::OxiaError> {
11//! let client = OxiaClient::connect("localhost:6648").await?;
12//!
13//! // Plain put/get.
14//! let res = client.put("greeting", "hello").await?;
15//! let rec = client.get("greeting").await?;
16//! assert_eq!(rec.value.as_deref(), Some(b"hello".as_ref()));
17//!
18//! // Options chain onto the builder.
19//! client
20//! .put("config/a", "1")
21//! .expected_version_id(res.version.version_id)
22//! .await?;
23//! client.put("locks/w1", "").ephemeral().await?;
24//! let floor = client.get("config/z").comparison(ComparisonType::Floor).await?;
25//!
26//! // Range operations, as a whole result or as an ordered stream.
27//! let keys = client.list("config/", "config/~").await?;
28//! let mut scan = client.range_scan("config/", "config/~").stream().await?;
29//! # let _ = (floor, keys, scan);
30//! client.close().await?;
31//! # Ok(())
32//! # }
33//! ```
34//!
35//! # Concepts
36//!
37//! - **Records** are key/value pairs with a [`Version`] carrying the record's
38//! version id and timestamps; conditional operations
39//! ([`expected_version_id`](PutBuilder::expected_version_id)) implement
40//! compare-and-swap.
41//! - **Ephemeral records** ([`PutBuilder::ephemeral`]) live as long as the
42//! client's session and are removed automatically when it closes or expires.
43//! - **Partition keys** ([`PutBuilder::partition_key`]) co-locate related
44//! records on one shard, enabling atomic multi-record batches and
45//! server-generated **sequential keys**
46//! ([`PutBuilder::sequence_key_deltas`], [`OxiaClient::sequence_updates`]).
47//! - **Secondary indexes** ([`PutBuilder::secondary_index`]) provide alternate
48//! query paths for gets, lists and scans
49//! ([`GetBuilder::use_index`], [`ListBuilder::use_index`]).
50//! - **Notifications** ([`OxiaClient::notifications`]) stream every change
51//! applied to the database.
52//!
53//! Keys are sorted with Oxia's slash-aware order (`/`-separated path segments),
54//! not plain lexicographic order; range boundaries follow it.
55//!
56//! # Error handling
57//!
58//! Every operation returns [`OxiaError`]. Semantic outcomes callers commonly
59//! match on are dedicated variants ([`OxiaError::KeyNotFound`],
60//! [`OxiaError::UnexpectedVersionId`], …); transient infrastructure failures
61//! answer `true` to [`OxiaError::is_retryable`] and are safe to retry —
62//! idempotent operations unconditionally, non-idempotent ones (puts without a
63//! version condition) with application-level judgment. Each operation on
64//! [`OxiaClient`] documents the errors it can produce.
65//!
66//! The client retries retryable failures internally — re-routing via the
67//! leader hints the server attaches to routing errors, re-hashing operations
68//! onto the new shard when a shard is split or merged, with exponential
69//! backoff, bounded by the request timeout — so the errors you observe are
70//! post-retry. One consequence, shared with the reference clients: a write
71//! whose batch failed *after* reaching the wire may be retried even though the
72//! server already applied it, so unconditional writes have at-least-once
73//! semantics under retries; version-conditioned writes are exactly-once.
74//!
75//! # Cancellation
76//!
77//! Dropping an operation's future stops waiting but does not recall an
78//! operation already submitted to a batch; it may still execute on the
79//! server. The `recv` methods on [`Notifications`] and [`SequenceUpdates`]
80//! are cancel-safe.
81
82#![forbid(unsafe_code)]
83#![warn(missing_docs)]
84#![allow(clippy::result_large_err)]
85// A panic on wire data must never take down the caller's process: forbid
86// `.unwrap()` in library code so every fallible result is handled. Unit tests
87// (compiled with `cfg(test)`) are exempt, where unwrapping is the idiom.
88#![cfg_attr(not(test), deny(clippy::unwrap_used))]
89
90mod address;
91mod batcher;
92mod client;
93mod client_builder;
94mod client_options;
95mod errors;
96mod hash;
97mod key;
98mod notification_manager;
99mod operations;
100mod provider_manager;
101mod requests;
102mod retry;
103mod sequence_updates_manager;
104mod server_error;
105mod session_manager;
106mod shard_manager;
107mod streams;
108mod types;
109
110#[allow(
111 clippy::derive_partial_eq_without_eq,
112 clippy::enum_variant_names,
113 missing_docs
114)]
115pub(crate) mod proto {
116 include!(concat!(env!("OUT_DIR"), "/io.oxia.proto.v1.rs"));
117}
118
119pub use bytes::Bytes;
120
121pub use client::OxiaClient;
122pub use client_builder::OxiaClientBuilder;
123pub use errors::OxiaError;
124pub use requests::{
125 DeleteBuilder, DeleteRangeBuilder, GetBuilder, ListBuilder, NotificationsBuilder, PutBuilder,
126 RangeScanBuilder, SequenceUpdatesBuilder,
127};
128pub use streams::{ListStream, Notifications, RangeScanStream, SequenceUpdates};
129pub use types::{ComparisonType, GetResult, Notification, PutResult, Version};