ignite-client
An async Rust thin client for Apache Ignite 2.x, implementing the Ignite Binary Client Protocol over TCP.
Table of Contents
- Features
- Protocol Reference
- Project Structure
- Quick Start
- API Reference
- Architecture
- Codec Details
- Comparison with Existing Rust Clients
- Running Tests
- License
Features
| Capability | Status |
|---|---|
| Protocol 1.7.0 handshake | ✅ |
| Authentication (username/password) | ✅ |
OP_QUERY_SQL_FIELDS (SELECT) |
✅ |
| Automatic cursor pagination | ✅ |
| DML: INSERT / UPDATE / DELETE | ✅ |
Transactions (TX_START / TX_END) |
✅ |
| Drop-based rollback | ✅ |
| Async pipelining (multiple in-flight requests per connection) | ✅ |
| deadpool connection pool | ✅ |
| Full wire type coverage (Null, Bool, Byte…Long, Float, Double, String, UUID, Date, Time, Timestamp, Decimal, arrays) | ✅ |
| KV cache API (get, put, get_all, put_all, contains_key, remove, replace, …) | ✅ |
TLS (rustls + native system CA bundle) |
✅ |
Streaming QueryStream cursor |
✅ |
| TCP keepalive | ✅ |
| Client-side request timeouts | ✅ |
| Partition awareness / affinity routing | 🔲 Future |
Protocol Reference
This client implements the Apache Ignite 2.x Thin Client Binary Protocol.
| Document | URL |
|---|---|
| Thin Client Overview | https://ignite.apache.org/docs/latest/thin-clients/getting-started-with-thin-clients |
| Binary Client Protocol spec | https://ignite.apache.org/docs/latest/binary-client-protocol/binary-client-protocol |
| Data Format (type codes, wire encoding) | https://ignite.apache.org/docs/latest/binary-client-protocol/data-format |
| SQL and Scan Queries operations | https://ignite.apache.org/docs/latest/binary-client-protocol/sql-and-scan-queries |
| Cache operations | https://ignite.apache.org/docs/latest/binary-client-protocol/key-value-queries |
| Transaction operations | https://ignite.apache.org/docs/latest/binary-client-protocol/transactions |
| Error codes | https://ignite.apache.org/docs/latest/binary-client-protocol/error-codes |
Protocol version
The handshake negotiates protocol version 1.7.0 — the highest version supported by the Apache Ignite 2.x series. GridGain 8.x (the commercial fork) uses the same protocol version.
Port
Ignite thin client port defaults to 10800.
Project Structure
This is a single crate — no workspace members.
ignite-client/
├── Cargo.toml ← package manifest (ignite-v2-client, crate: ignite_client)
├── src/
│ ├── lib.rs ← public re-exports
│ ├── client.rs ← IgniteClient: query, execute, begin_transaction, cache, …
│ ├── transaction.rs ← Transaction: query, execute, commit, rollback, cache, drop
│ ├── cache.rs ← IgniteCache: get, put, get_all, put_all, remove, …
│ ├── stream.rs ← QueryStream: lazily-paged streaming cursor
│ ├── query.rs ← QueryResult, Row, Column, UpdateResult
│ ├── pool.rs ← IgniteClientConfig, deadpool manager
│ ├── error.rs ← IgniteError
│ ├── protocol/ ← pure codec layer: no I/O, no async
│ │ ├── mod.rs
│ │ ├── types.rs ← IgniteValue enum, op codes, type codes, tx enums
│ │ ├── codec.rs ← encode_value / decode_value roundtrip
│ │ ├── handshake.rs ← protocol 1.7.0 handshake encoding
│ │ ├── error.rs ← ProtocolError
│ │ └── messages.rs ← SqlFieldsRequest, TxStart/End, cache ops, cursor pagination
│ └── transport/ ← async TCP layer
│ ├── mod.rs
│ ├── connection.rs ← IgniteConnection (pipelined, multiplexed)
│ ├── error.rs ← TransportError
│ └── tls.rs ← build_tls_config (rustls + native-certs)
└── tests/
└── smoke.rs ← 35 end-to-end integration tests
The protocol module has no I/O dependency, so its codec tests run without a
live Ignite node.
Quick Start
Add to Cargo.toml:
[]
= { = "../ignite-client" }
= { = "1", = ["full"] }
SELECT query
use ;
async
DML
let updated = client
.execute
.await?;
println!;
Transaction
let mut tx = client.begin_transaction.await?;
tx.execute.await?;
tx.execute.await?;
tx.commit.await?;
// If commit() is not called, Drop triggers a fire-and-forget rollback.
Transaction helper (auto-commit/rollback)
let result = client
.with_transaction
.await?;
KV cache
let cache = client.get_or_create_cache.await?;
cache.put.await?;
let val = cache.get.await?;
println!; // String("hello")
Streaming cursor
use StreamExt;
let mut stream = client
.query_stream
.await?;
while let Some = stream.next.await
TLS
let config = new
.with_tls; // use system CA bundle
// .with_tls_accept_invalid_certs() // for self-signed / dev certs
let client = new;
Authentication
let config = new
.with_auth
.with_pool_size;
API Reference
IgniteClientConfig
connect_timeout is also used as the deadpool wait and create timeout.
request_timeout is applied per-request inside send_and_receive.
IgniteClient
IgniteClient is Clone — share a single instance across tasks; it wraps an
internal Arc'd pool.
Transaction
Transaction isolation levels map to Ignite protocol values:
TxIsolation |
Protocol value |
|---|---|
ReadCommitted |
0 |
RepeatableRead |
1 |
Serializable |
2 |
Transaction concurrency modes:
TxConcurrency |
Protocol value |
|---|---|
Optimistic |
0 |
Pessimistic |
1 |
Note: DML inside thin-client transactions requires the Ignite node to be started with
-DIGNITE_ALLOW_DML_INSIDE_TRANSACTION=trueand the table must useATOMICITY=TRANSACTIONAL.
IgniteCache
Obtained via client.cache(), client.get_or_create_cache(), or
transaction.cache(). Cheap to clone — holds an i32 cache ID and either a
pool reference or a transaction connection.
QueryResult / Row
QueryStream
A lazily-paged result stream returned by client.query_stream() and
transaction.query_stream(). Rows are yielded one at a time; subsequent pages
are fetched from the server only when the current page is exhausted. The
server-side cursor is closed automatically when the stream is exhausted or
dropped mid-iteration.
Use futures::StreamExt to drive the stream with .next().await.
IgniteValue type system
IgniteValue maps every Ignite wire type to a Rust variant:
Wire encoding follows the spec at https://ignite.apache.org/docs/latest/binary-client-protocol/data-format
Notable encoding details:
- UUID: 16 bytes big-endian (most-significant bytes first, matching Java's
UUID.getMostSignificantBits()/getLeastSignificantBits()) - Decimal:
[i32: scale][i32: byte_count][bytes: two's-complement big-endian magnitude] - Timestamp:
[i64: epoch_ms][i32: nanoseconds_fraction] - Null: type code 101 with no payload; any typed field may be null
Architecture
Request multiplexing
A single IgniteConnection supports many concurrent requests without
serialising them through a mutex on reads:
Caller A ──request(id=1)──┐ ┌──response(id=1)──▶ Caller A
│ TCP socket │
Caller B ──request(id=2)──┤ ─────────────▶│
│ │ background
Caller C ──request(id=3)──┘ │ reader task
└──response(id=3)──▶ Caller C
response(id=2)──▶ Caller B
Requests are written to a Mutex<SplitSink> (contended only on write, not on
read). Each caller registers a oneshot::Sender in a shared HashMap<i64, Sender> keyed by request_id. The background reader task peeks the first 8
bytes of each response frame, looks up the sender, and delivers the payload.
This is the same design used by tokio-postgres and redis-rs.
Connection pool
IgniteClient wraps a deadpool managed
pool of IgniteConnection objects. Pool behaviour:
max_pool_sizeconnections maximum (default 10)- Each connection is health-checked on recycle via
is_alive()(AtomicBool) - Connections are created on demand, not pre-warmed
connect_timeoutis applied as both the deadpoolwaitandcreatetimeout- TCP keepalive is applied to every socket (60 s idle, 15 s interval)
Transaction connections
Transactions use a dedicated TCP connection that is not drawn from the pool.
This avoids pool exhaustion when many concurrent long-running transactions are
in flight. The connection is closed when the Transaction is dropped.
Pagination
OP_QUERY_SQL_FIELDS returns a first page with a cursor_id and a has_more
flag.
client.query()/transaction.query()— automatically fetches all subsequent pages viaOP_QUERY_SQL_FIELDS_CURSOR_GET_PAGEand returns a fully materialisedQueryResult.client.query_stream()/transaction.query_stream()— returns aQueryStreamthat fetches pages lazily as the consumer polls the stream. The server-side cursor is closed when the stream is exhausted or dropped.
The page_size config field controls how many rows are returned per server
round-trip (default 1024).
Codec Details
The src/protocol/ module contains the codec with no I/O dependency, making
it independently testable.
Frame format
[i32 LE: payload_length] ← length prefix handled by LengthDelimitedCodec
[payload bytes] ← the codec layer strips the prefix before delivery
Request payload format
[i16 LE: op_code]
[i64 LE: request_id]
[operation-specific fields …]
Response payload format
[i64 LE: request_id]
[i32 LE: status] ← 0 = success; non-zero = server error
if status != 0:
[string: error_message]
if status == 0:
[operation-specific response …]
Java string hashing
Cache IDs and field IDs in the binary protocol are derived using Java's
String.hashCode() algorithm. The java_hash() function in types.rs
replicates this:
The cache_id() helper derives the cache ID from a cache name:
Comparison with Existing Rust Clients
| vkulichenko | ptupitsyn fork | this crate | |
|---|---|---|---|
| SQL queries | ✗ | ✗ | ✅ |
| Cursor pagination | ✗ | ✗ | ✅ |
| Streaming cursor | ✗ | ✗ | ✅ |
| Transactions | ✗ | ✗ | ✅ |
| Async I/O (tokio) | ✗ | ✗ | ✅ |
| Connection pool | ✗ | ✗ | ✅ |
| UUID / Date / Timestamp / Decimal | ✗ | ✗ | ✅ |
| Null handling | ✗ | ✗ | ✅ |
| Query parameters | ✗ | ✗ | ✅ |
| TLS | ✗ | ✗ | ✅ |
| KV get/put | ✅ | ✅ | ✅ |
| Last commit | 2020 | 2020 | 2026 |
| Intended use | learning exercise | abandoned fork | production |
The vkulichenko / ptupitsyn implementations are synchronous, blocking, cover
only get / put on primitives, and have not been updated since 2020. They
are not suitable as a dependency baseline for production work.
Running Tests
Unit tests (no live node required)
Covers:
IgniteValuecodec roundtrip for every type (Null, Bool, Byte, Short, Int, Long, Float, Double, Char, String, UUID, Date, Timestamp, Time, Decimal, ByteArray, RawObject)- Two's-complement Decimal encoding (positive, negative, zero, boundary)
java_hash()against known Java reference valuesSqlFieldsRequestencode/decode- Transaction start/end encoding
Smoke tests (requires a live Ignite 2.x node on localhost:10800)
Start an Ignite node. DML-in-transaction tests require the JVM flag and a
TRANSACTIONAL-atomicity table (see create_tx_table in the test file):
# Linux / macOS
# Windows
Run all 35 smoke tests sequentially (parallel DDL causes schema lock contention):
Windows note: If Windows Smart App Control blocks newly compiled binaries, build to AppData instead:
CARGO_TARGET_DIR="$APPDATA/ignite-test-target" cargo test --test smoke -- --nocapture --test-threads 1
Smoke test coverage:
- Basic connectivity (
SELECT 1) - SQL query and DML (
query,execute) - Multi-page cursor pagination
query_stream(lazy streaming) and early-drop cursor close- Configurable
page_size(forces multi-page fetch) begin_transaction,begin_transaction_with(explicit concurrency/isolation/timeout)with_transaction(auto-commit and rollback/Drop paths)Transaction::query,Transaction::execute,Transaction::query_streamTransaction::cache(KV ops inside a transaction — commit and rollback)IgniteCache: get, put, put_if_absent, get_all, put_all, contains_key, remove, replace, get_and_put, get_and_remove, get_and_replace, get_sizecache_names,get_or_create_cache,destroy_cacheRow::get,get_by_name,len,is_empty,columns,valuesQueryResult::columnsfield androw_countpool_status(),with_pool_size(),with_auth()builder fields- Type roundtrips: Bool, Int, Long, Double, String, Byte, Short, Float, ByteArray, Decimal, UUID, Null
- Date, Time, Timestamp as query parameters
- Server-side error propagation
- TLS config builder (no network), TLS graceful failure to plaintext server
- Concurrent queries on a shared client
Cargo.lock dependency pins
Due to MSRV constraints the following crates are pinned to exact versions.
If your project requires newer versions of these, the workspace root
Cargo.toml [workspace.dependencies] section is the single place to update
them:
| Crate | Pinned version | Reason |
|---|---|---|
tokio |
=1.35.1 |
system cargo 1.75 compatibility |
uuid |
=1.6.1 |
API stability |
deadpool |
=0.10.0 |
API stability |
License
Apache License 2.0. See LICENSE.
Apache Ignite is a registered trademark of The Apache Software Foundation. This project is not affiliated with or endorsed by the Apache Software Foundation or GridGain Systems.