Read, write, clear, and atomically mutate key-value data with support for range queries, streaming results, and various atomic operations.
Set and clear individual keys and key ranges.
/**
* Set a key to a value. Overwrites any existing value.
*
* Parameters:
* - key: byte[] - Key to set (max 10KB)
* - value: byte[] - Value to store (max 100KB)
*/
void Transaction.set(byte[] key, byte[] value);
/**
* Remove a key from the database.
* No effect if key does not exist.
*
* Parameters:
* - key: byte[] - Key to clear
*/
void Transaction.clear(byte[] key);
/**
* Remove all keys in the range [beginKey, endKey).
* Range is half-open: beginKey is inclusive, endKey is exclusive.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
*/
void Transaction.clear(byte[] beginKey, byte[] endKey);
/**
* Remove all keys in the specified range.
*
* Parameters:
* - range: Range - Range object specifying keys to clear
*/
void Transaction.clear(Range range);
/**
* Remove all keys starting with the specified prefix.
*
* @deprecated Use Transaction.clear(Range.startsWith(prefix)) instead.
*
* Parameters:
* - prefix: byte[] - Prefix of keys to clear
*
* Throws:
* FDBException - If the clear-range operation fails
*/
@Deprecated
void Transaction.clearRangeStartsWith(byte[] prefix);Usage examples:
import com.apple.foundationdb.*;
// Set a key
db.run(tr -> {
tr.set("user:1001".getBytes(), "Alice".getBytes());
return null;
});
// Clear a key
db.run(tr -> {
tr.clear("user:1001".getBytes());
return null;
});
// Clear a range
db.run(tr -> {
// Remove all keys starting with "temp:"
tr.clear("temp:".getBytes(), "temp;".getBytes());
return null;
});
// Clear using Range object
db.run(tr -> {
Range range = Range.startsWith("cache:".getBytes());
tr.clear(range);
return null;
});Retrieve individual values by key.
/**
* Get the value for a key.
*
* Parameters:
* - key: byte[] - Key to retrieve
*
* Returns:
* CompletableFuture<byte[]> - Value, or null if key does not exist
*/
CompletableFuture<byte[]> ReadTransaction.get(byte[] key);Usage examples:
// Blocking read
byte[] value = db.run(tr -> {
return tr.get("user:1001".getBytes()).join();
});
if (value != null) {
System.out.println("User: " + new String(value));
}
// Async read
db.runAsync(tr -> {
return tr.get("user:1001".getBytes())
.thenAccept(value -> {
if (value != null) {
System.out.println("User: " + new String(value));
}
});
});
// Multiple reads
db.run(tr -> {
CompletableFuture<byte[]> f1 = tr.get("key1".getBytes());
CompletableFuture<byte[]> f2 = tr.get("key2".getBytes());
CompletableFuture<byte[]> f3 = tr.get("key3".getBytes());
// Wait for all
CompletableFuture.allOf(f1, f2, f3).join();
// Process results
byte[] v1 = f1.join();
byte[] v2 = f2.join();
byte[] v3 = f3.join();
return null;
});Query ranges of keys with streaming results and various options.
/**
* Get all key-value pairs in range [begin, end).
* Returns iterator that fetches results in batches.
*
* Parameters:
* - begin: byte[] - Start key (inclusive)
* - end: byte[] - End key (exclusive)
*
* Returns:
* AsyncIterable<KeyValue> - Iterable over key-value pairs in range
*/
AsyncIterable<KeyValue> ReadTransaction.getRange(byte[] begin, byte[] end);
/**
* Get key-value pairs in range with options.
*
* Parameters:
* - begin: byte[] - Start key (inclusive)
* - end: byte[] - End key (exclusive)
* - limit: int - Maximum results (ReadTransaction.ROW_LIMIT_UNLIMITED for no limit)
* - reverse: boolean - If true, return results in reverse order
* - mode: StreamingMode - Hint for result fetching strategy
*
* Returns:
* AsyncIterable<KeyValue> - Iterable over key-value pairs
*/
AsyncIterable<KeyValue> ReadTransaction.getRange(
byte[] begin, byte[] end, int limit, boolean reverse, StreamingMode mode
);
/**
* Get key-value pairs using Range object.
*
* Parameters:
* - range: Range - Range to query
*
* Returns:
* AsyncIterable<KeyValue> - Iterable over key-value pairs
*/
AsyncIterable<KeyValue> ReadTransaction.getRange(Range range);
/**
* Get key-value pairs using Range object with options.
*
* Parameters:
* - range: Range - Range to query
* - limit: int - Maximum results
* - reverse: boolean - If true, return in reverse order
* - mode: StreamingMode - Hint for fetching strategy
*
* Returns:
* AsyncIterable<KeyValue> - Iterable over key-value pairs
*/
AsyncIterable<KeyValue> ReadTransaction.getRange(
Range range, int limit, boolean reverse, StreamingMode mode
);
/**
* Get key-value pairs using KeySelector bounds.
* KeySelectors enable relative key positioning.
*
* Parameters:
* - begin: KeySelector - Start position
* - end: KeySelector - End position
*
* Returns:
* AsyncIterable<KeyValue> - Iterable over key-value pairs
*/
AsyncIterable<KeyValue> ReadTransaction.getRange(KeySelector begin, KeySelector end);
/**
* Get key-value pairs using KeySelector bounds with options.
*
* Parameters:
* - begin: KeySelector - Start position
* - end: KeySelector - End position
* - limit: int - Maximum results
* - reverse: boolean - If true, return in reverse order
* - mode: StreamingMode - Hint for fetching strategy
*
* Returns:
* AsyncIterable<KeyValue> - Iterable over key-value pairs
*/
AsyncIterable<KeyValue> ReadTransaction.getRange(
KeySelector begin, KeySelector end, int limit, boolean reverse, StreamingMode mode
);Usage examples:
import com.apple.foundationdb.*;
// Simple range query
db.read(tr -> {
for (KeyValue kv : tr.getRange("user:".getBytes(), "user;".getBytes())) {
String key = new String(kv.getKey());
String value = new String(kv.getValue());
System.out.println(key + " = " + value);
}
return null;
});
// Range query with limit
db.read(tr -> {
AsyncIterable<KeyValue> range = tr.getRange(
"item:".getBytes(),
"item;".getBytes(),
10, // Limit to 10 results
false, // Forward order
StreamingMode.ITERATOR
);
for (KeyValue kv : range) {
System.out.println(new String(kv.getKey()));
}
return null;
});
// Reverse range query
db.read(tr -> {
AsyncIterable<KeyValue> range = tr.getRange(
"log:".getBytes(),
"log;".getBytes(),
100,
true, // Reverse order (newest first)
StreamingMode.WANT_ALL
);
for (KeyValue kv : range) {
System.out.println("Log entry: " + new String(kv.getValue()));
}
return null;
});
// Range query using Range.startsWith
db.read(tr -> {
Range range = Range.startsWith("config:".getBytes());
for (KeyValue kv : tr.getRange(range)) {
System.out.println(new String(kv.getKey()));
}
return null;
});
// Using KeySelector for pagination
db.read(tr -> {
byte[] lastKey = "user:1000".getBytes();
// Get next page after lastKey
KeySelector begin = KeySelector.firstGreaterThan(lastKey);
KeySelector end = KeySelector.firstGreaterOrEqual("user;".getBytes());
AsyncIterable<KeyValue> range = tr.getRange(begin, end, 50, false,
StreamingMode.WANT_ALL);
for (KeyValue kv : range) {
System.out.println(new String(kv.getKey()));
}
return null;
});
// Convert range to list
db.read(tr -> {
AsyncIterable<KeyValue> range = tr.getRange("data:".getBytes(), "data;".getBytes());
List<KeyValue> list = range.asList().join();
System.out.println("Found " + list.size() + " items");
return null;
});Select keys by relative position using KeySelector.
/**
* Get the key selected by a KeySelector.
*
* Parameters:
* - selector: KeySelector - Selector specifying which key to retrieve
*
* Returns:
* CompletableFuture<byte[]> - Selected key
*/
CompletableFuture<byte[]> ReadTransaction.getKey(KeySelector selector);Usage example:
import com.apple.foundationdb.*;
// Get first key >= "user:1000"
db.read(tr -> {
KeySelector selector = KeySelector.firstGreaterOrEqual("user:1000".getBytes());
byte[] key = tr.getKey(selector).join();
System.out.println("First key: " + new String(key));
return null;
});
// Get last key < "user:2000"
db.read(tr -> {
KeySelector selector = KeySelector.lastLessThan("user:2000".getBytes());
byte[] key = tr.getKey(selector).join();
System.out.println("Last key: " + new String(key));
return null;
});Perform atomic mutations without reading current value.
/**
* Perform atomic operation on a key.
* Modifies value without reading it first.
*
* Parameters:
* - optype: MutationType - Type of atomic operation
* - key: byte[] - Key to mutate
* - param: byte[] - Operation parameter (interpretation depends on optype)
*/
void Transaction.mutate(MutationType optype, byte[] key, byte[] param);Usage examples:
import com.apple.foundationdb.*;
import java.nio.ByteBuffer;
// Atomic increment (ADD)
db.run(tr -> {
byte[] key = "counter".getBytes();
byte[] one = ByteBuffer.allocate(8).putLong(1).array();
tr.mutate(MutationType.ADD, key, one);
return null;
});
// Atomic append
db.run(tr -> {
byte[] key = "log".getBytes();
byte[] entry = "New entry\n".getBytes();
tr.mutate(MutationType.APPEND_IF_FITS, key, entry);
return null;
});
// Bitwise OR
db.run(tr -> {
byte[] key = "flags".getBytes();
byte[] mask = new byte[]{0x04}; // Set bit 2
tr.mutate(MutationType.BIT_OR, key, mask);
return null;
});
// Atomic MAX (keep larger value)
db.run(tr -> {
byte[] key = "high_score".getBytes();
byte[] score = ByteBuffer.allocate(8).putLong(1000).array();
tr.mutate(MutationType.MAX, key, score);
return null;
});
// Atomic MIN (keep smaller value)
db.run(tr -> {
byte[] key = "low_temp".getBytes();
byte[] temp = ByteBuffer.allocate(8).putLong(-5).array();
tr.mutate(MutationType.MIN, key, temp);
return null;
});
// Versionstamped key
db.run(tr -> {
// Key with incomplete versionstamp placeholder
Tuple keyTuple = Tuple.from("events", Versionstamp.incomplete());
byte[] key = keyTuple.packWithVersionstamp();
tr.mutate(MutationType.SET_VERSIONSTAMPED_KEY, key, "event data".getBytes());
return null;
});
// Versionstamped value
db.run(tr -> {
byte[] key = "versioned_data".getBytes();
// Value with incomplete versionstamp placeholder
Tuple valueTuple = Tuple.from("data", Versionstamp.incomplete());
byte[] value = valueTuple.packWithVersionstamp();
tr.mutate(MutationType.SET_VERSIONSTAMPED_VALUE, key, value);
return null;
});Query metadata about key ranges.
/**
* Estimate size in bytes of a key range.
* Useful for determining if range fits in transaction size limit.
*
* Parameters:
* - begin: byte[] - Start of range (inclusive)
* - end: byte[] - End of range (exclusive)
*
* Returns:
* CompletableFuture<Long> - Estimated size in bytes
*/
CompletableFuture<Long> ReadTransaction.getEstimatedRangeSizeBytes(byte[] begin, byte[] end);
/**
* Estimate size in bytes of a key range using Range object.
*
* Parameters:
* - range: Range - Range to estimate
*
* Returns:
* CompletableFuture<Long> - Estimated size in bytes
*/
CompletableFuture<Long> ReadTransaction.getEstimatedRangeSizeBytes(Range range);
/**
* Get keys that split a range into chunks of approximately chunkSize bytes.
* Useful for parallel processing of large ranges.
*
* Parameters:
* - begin: byte[] - Start of range (inclusive)
* - end: byte[] - End of range (exclusive)
* - chunkSize: long - Desired chunk size in bytes
*
* Returns:
* CompletableFuture<KeyArrayResult> - Array of split point keys
*/
CompletableFuture<KeyArrayResult> ReadTransaction.getRangeSplitPoints(
byte[] begin, byte[] end, long chunkSize
);
/**
* Get keys that split a range into chunks using Range object.
*
* Parameters:
* - range: Range - Range to split
* - chunkSize: long - Desired chunk size in bytes
*
* Returns:
* CompletableFuture<KeyArrayResult> - Array of split point keys
*/
CompletableFuture<KeyArrayResult> ReadTransaction.getRangeSplitPoints(Range range, long chunkSize);Usage examples:
// Check if range fits in transaction
db.read(tr -> {
long size = tr.getEstimatedRangeSizeBytes(
"data:".getBytes(),
"data;".getBytes()
).join();
if (size > 9_000_000) { // Near 10MB limit
System.out.println("Range too large for single transaction");
}
return null;
});
// Split range for parallel processing
db.read(tr -> {
KeyArrayResult splits = tr.getRangeSplitPoints(
"data:".getBytes(),
"data;".getBytes(),
1_000_000 // ~1MB chunks
).join();
byte[][] splitKeys = splits.getKeys();
System.out.println("Range can be split into " + (splitKeys.length + 1) + " chunks");
// Process each chunk in parallel
byte[] begin = "data:".getBytes();
for (byte[] splitKey : splitKeys) {
processChunk(begin, splitKey);
begin = splitKey;
}
processChunk(begin, "data;".getBytes());
return null;
});Query ranges with mapped results from secondary indexes.
/**
* Get mapped range results (experimental feature).
* Retrieves primary data and associated secondary index data in single query.
*
* Parameters:
* - begin: KeySelector - Start position
* - end: KeySelector - End position
* - mapper: byte[] - Mapper specification for secondary lookups
* - limit: int - Maximum results
* - reverse: boolean - If true, return in reverse order
* - mode: StreamingMode - Hint for fetching strategy
*
* Returns:
* AsyncIterable<MappedKeyValue> - Iterable over mapped results
*/
AsyncIterable<MappedKeyValue> ReadTransaction.getMappedRange(
KeySelector begin, KeySelector end, byte[] mapper,
int limit, boolean reverse, StreamingMode mode
);Query blob granule boundaries for ranges stored in blob storage.
/**
* Get blob granule ranges within specified bounds.
* Used for querying data stored in blob storage.
*
* Parameters:
* - begin: byte[] - Start of query range (inclusive)
* - end: byte[] - End of query range (exclusive)
* - rowLimit: int - Maximum number of granule ranges to return
*
* Returns:
* CompletableFuture<KeyRangeArrayResult> - Array of blob granule ranges
*/
CompletableFuture<KeyRangeArrayResult> ReadTransaction.getBlobGranuleRanges(
byte[] begin, byte[] end, int rowLimit
);class KeyValue {
KeyValue(byte[] key, byte[] value);
byte[] getKey();
byte[] getValue();
boolean equals(Object obj);
int hashCode();
String toString();
}
class Range {
byte[] begin; // Inclusive
byte[] end; // Exclusive
Range(byte[] begin, byte[] end);
static Range startsWith(byte[] prefix);
boolean equals(Object o);
int hashCode();
String toString();
}
class KeySelector {
KeySelector(byte[] key, boolean orEqual, int offset);
static KeySelector lastLessThan(byte[] key);
static KeySelector lastLessOrEqual(byte[] key);
static KeySelector firstGreaterThan(byte[] key);
static KeySelector firstGreaterOrEqual(byte[] key);
KeySelector add(int offset);
byte[] getKey();
boolean orEqual();
int getOffset();
String toString();
}
class MappedKeyValue {
byte[] getKey();
byte[] getValue();
byte[] getParent();
int getIndex();
List<KeyValue> getRangeResults();
}
class KeyArrayResult {
byte[][] getKeys();
}
class KeyRangeArrayResult {
Range[] getRanges();
}
enum StreamingMode {
WANT_ALL, // Client wants all results
ITERATOR, // Iterator mode (default, adaptive)
EXACT, // Exact row limit
SMALL, // Small result set expected
MEDIUM, // Medium result set expected
LARGE, // Large result set expected
SERIAL // Serial mode
}
enum MutationType {
ADD, // Atomic addition (little-endian)
BIT_AND, // Bitwise AND
BIT_OR, // Bitwise OR
BIT_XOR, // Bitwise XOR
APPEND_IF_FITS, // Append if under value size limit
MAX, // Keep maximum value (little-endian)
MIN, // Keep minimum value (little-endian)
SET_VERSIONSTAMPED_KEY, // Set key with versionstamp
SET_VERSIONSTAMPED_VALUE, // Set value with versionstamp
BYTE_MIN, // Byte-wise minimum
BYTE_MAX, // Byte-wise maximum
COMPARE_AND_CLEAR // Clear if value matches param
}
interface ReadTransaction extends ReadTransactionContext {
int ROW_LIMIT_UNLIMITED = 0;
}