0
# Key-Value Operations
1
2
Reading and writing key-value pairs with support for atomic operations, range queries, and watches for change notifications. FoundationDB stores data as ordered key-value pairs with binary keys and values.
3
4
## Capabilities
5
6
### Single Key Operations
7
8
Basic operations for reading and writing individual key-value pairs.
9
10
```c { .api }
11
/**
12
* Read a value from the database by key
13
* @param tr Transaction handle
14
* @param key_name Key to read
15
* @param key_name_length Length of key in bytes
16
* @param snapshot Whether to use snapshot read (non-conflicting)
17
* @return Future containing the value (or NULL if key doesn't exist)
18
*/
19
FDBFuture* fdb_transaction_get(FDBTransaction* tr, uint8_t const* key_name,
20
int key_name_length, fdb_bool_t snapshot);
21
22
/**
23
* Set a key-value pair in the database
24
* @param tr Transaction handle
25
* @param key_name Key to set
26
* @param key_name_length Length of key in bytes
27
* @param value Value to store
28
* @param value_length Length of value in bytes
29
*/
30
void fdb_transaction_set(FDBTransaction* tr, uint8_t const* key_name, int key_name_length,
31
uint8_t const* value, int value_length);
32
33
/**
34
* Remove a key from the database
35
* @param tr Transaction handle
36
* @param key_name Key to remove
37
* @param key_name_length Length of key in bytes
38
*/
39
void fdb_transaction_clear(FDBTransaction* tr, uint8_t const* key_name, int key_name_length);
40
```
41
42
```python { .api }
43
class Transaction:
44
def get(self, key: bytes, snapshot: bool = False) -> Future:
45
"""
46
Read a value from the database
47
48
Args:
49
key: Key to read
50
snapshot: Whether to use snapshot read (default False)
51
52
Returns:
53
Future that resolves to bytes value or None if key doesn't exist
54
"""
55
56
def set(self, key: bytes, value: bytes) -> None:
57
"""
58
Set a key-value pair
59
60
Args:
61
key: Key to set
62
value: Value to store
63
"""
64
65
def clear(self, key: bytes) -> None:
66
"""
67
Remove a key from the database
68
69
Args:
70
key: Key to remove
71
"""
72
73
def __getitem__(self, key: bytes) -> bytes:
74
"""Dictionary-style read (blocking)"""
75
76
def __setitem__(self, key: bytes, value: bytes) -> None:
77
"""Dictionary-style write"""
78
79
def __delitem__(self, key: bytes) -> None:
80
"""Dictionary-style delete"""
81
```
82
83
```java { .api }
84
// Java API
85
public interface ReadTransaction {
86
public CompletableFuture<byte[]> get(byte[] key):
87
/**
88
* Read a value from the database
89
* @param key Key to read
90
* @return CompletableFuture with value bytes (null if not found)
91
*/
92
}
93
94
public interface Transaction extends ReadTransaction {
95
public void set(byte[] key, byte[] value):
96
/**
97
* Set a key-value pair
98
* @param key Key to set
99
* @param value Value to store
100
*/
101
102
public void clear(byte[] key):
103
/**
104
* Remove a key from the database
105
* @param key Key to remove
106
*/
107
}
108
```
109
110
```go { .api }
111
// Go API
112
type ReadTransaction interface {
113
Get(key Key) FutureByteSlice:
114
/**
115
* Read a value from the database
116
* @param key Key to read
117
* @return Future with value bytes (nil if not found)
118
*/
119
120
Snapshot() ReadTransaction:
121
/**
122
* Get snapshot view for non-conflicting reads
123
* @return ReadTransaction for snapshot reads
124
*/
125
}
126
127
type Transaction interface {
128
ReadTransaction
129
130
Set(key Key, value []byte):
131
/**
132
* Set a key-value pair
133
* @param key Key to set
134
* @param value Value to store
135
*/
136
137
Clear(key Key):
138
/**
139
* Remove a key from the database
140
* @param key Key to remove
141
*/
142
}
143
```
144
145
### Range Operations
146
147
Operations for reading and clearing ranges of key-value pairs.
148
149
```c { .api }
150
/**
151
* Read a range of key-value pairs
152
* @param tr Transaction handle
153
* @param begin_key_name Start key
154
* @param begin_key_name_length Length of start key
155
* @param begin_or_equal Whether start is inclusive (1) or exclusive (0)
156
* @param begin_offset Offset from start key
157
* @param end_key_name End key
158
* @param end_key_name_length Length of end key
159
* @param end_or_equal Whether end is inclusive (1) or exclusive (0)
160
* @param end_offset Offset from end key
161
* @param limit Maximum number of results (0 for no limit)
162
* @param target_bytes Target bytes to read (0 for no target)
163
* @param mode Streaming mode for result delivery
164
* @param iteration Iteration number for continuation
165
* @param snapshot Whether to use snapshot read
166
* @param reverse Whether to read in reverse order
167
* @return Future containing array of key-value pairs
168
*/
169
FDBFuture* fdb_transaction_get_range(FDBTransaction* tr,
170
uint8_t const* begin_key_name, int begin_key_name_length,
171
fdb_bool_t begin_or_equal, int begin_offset,
172
uint8_t const* end_key_name, int end_key_name_length,
173
fdb_bool_t end_or_equal, int end_offset,
174
int limit, int target_bytes, FDBStreamingMode mode,
175
int iteration, fdb_bool_t snapshot, fdb_bool_t reverse);
176
177
/**
178
* Clear a range of keys
179
* @param tr Transaction handle
180
* @param begin_key_name Start of range (inclusive)
181
* @param begin_key_name_length Length of start key
182
* @param end_key_name End of range (exclusive)
183
* @param end_key_name_length Length of end key
184
*/
185
void fdb_transaction_clear_range(FDBTransaction* tr,
186
uint8_t const* begin_key_name, int begin_key_name_length,
187
uint8_t const* end_key_name, int end_key_name_length);
188
```
189
190
```python { .api }
191
class Transaction:
192
def get_range(self, begin: KeySelector, end: KeySelector, limit: int = 0,
193
reverse: bool = False, streaming_mode: int = StreamingMode.ITERATOR,
194
snapshot: bool = False) -> FDBRange:
195
"""
196
Read a range of key-value pairs
197
198
Args:
199
begin: Start key selector
200
end: End key selector
201
limit: Maximum number of results (0 for no limit)
202
reverse: Whether to read in reverse order
203
streaming_mode: How results are delivered
204
snapshot: Whether to use snapshot read
205
206
Returns:
207
FDBRange iterable of KeyValue pairs
208
"""
209
210
def get_range_startswith(self, prefix: bytes, **kwargs) -> FDBRange:
211
"""
212
Read all keys starting with a prefix
213
214
Args:
215
prefix: Key prefix to match
216
**kwargs: Additional arguments passed to get_range
217
218
Returns:
219
FDBRange iterable of KeyValue pairs
220
"""
221
222
def clear_range(self, begin: bytes, end: bytes) -> None:
223
"""
224
Clear a range of keys
225
226
Args:
227
begin: Start of range (inclusive)
228
end: End of range (exclusive)
229
"""
230
231
def clear_range_startswith(self, prefix: bytes) -> None:
232
"""
233
Clear all keys starting with a prefix
234
235
Args:
236
prefix: Key prefix to match
237
"""
238
239
class KeySelector:
240
@staticmethod
241
def first_greater_or_equal(key: bytes) -> 'KeySelector': ...
242
243
@staticmethod
244
def first_greater_than(key: bytes) -> 'KeySelector': ...
245
246
@staticmethod
247
def last_less_or_equal(key: bytes) -> 'KeySelector': ...
248
249
@staticmethod
250
def last_less_than(key: bytes) -> 'KeySelector': ...
251
252
class KeyValue:
253
key: bytes
254
value: bytes
255
256
class FDBRange:
257
"""Iterator over range query results"""
258
259
def __init__(self, tr: Transaction, begin: KeySelector, end: KeySelector,
260
limit: int, reverse: bool, streaming_mode: int): ...
261
262
def __iter__(self) -> Iterator[KeyValue]:
263
"""Iterate over KeyValue pairs in the range"""
264
265
def to_list(self) -> List[KeyValue]:
266
"""Convert range to list (forces evaluation)"""
267
```
268
269
```java { .api }
270
// Java API
271
public interface ReadTransaction {
272
public AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end):
273
/**
274
* Read a range of key-value pairs
275
* @param begin Start key selector
276
* @param end End key selector
277
* @return AsyncIterable of KeyValue pairs
278
*/
279
280
public AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end, int limit):
281
/**
282
* Read a range with limit
283
* @param begin Start key selector
284
* @param end End key selector
285
* @param limit Maximum number of results
286
* @return AsyncIterable of KeyValue pairs
287
*/
288
289
public AsyncIterable<KeyValue> getRange(Range range):
290
/**
291
* Read a range using Range object
292
* @param range Range specification
293
* @return AsyncIterable of KeyValue pairs
294
*/
295
}
296
297
public interface Transaction extends ReadTransaction {
298
public void clear(Range range):
299
/**
300
* Clear a range of keys
301
* @param range Range to clear
302
*/
303
}
304
305
public class KeySelector {
306
public static KeySelector firstGreaterOrEqual(byte[] key): ...
307
public static KeySelector firstGreaterThan(byte[] key): ...
308
public static KeySelector lastLessOrEqual(byte[] key): ...
309
public static KeySelector lastLessThan(byte[] key): ...
310
}
311
312
public class KeyValue {
313
public byte[] getKey(): ...
314
public byte[] getValue(): ...
315
}
316
317
public class Range {
318
public Range(byte[] begin, byte[] end): ...
319
public static Range startsWith(byte[] prefix): ...
320
}
321
```
322
323
```go { .api }
324
// Go API
325
type ReadTransaction interface {
326
GetRange(begin, end KeySelector, options RangeOptions) RangeResult:
327
/**
328
* Read a range of key-value pairs
329
* @param begin Start key selector
330
* @param end End key selector
331
* @param options Range query options
332
* @return RangeResult with key-value pairs
333
*/
334
}
335
336
type Transaction interface {
337
ReadTransaction
338
339
ClearRange(begin, end Key):
340
/**
341
* Clear a range of keys
342
* @param begin Start of range (inclusive)
343
* @param end End of range (exclusive)
344
*/
345
}
346
347
type KeySelector struct {
348
Key Key
349
OrEqual bool
350
Offset int
351
}
352
353
func FirstGreaterOrEqual(key Key) KeySelector: ...
354
func FirstGreaterThan(key Key) KeySelector: ...
355
func LastLessOrEqual(key Key) KeySelector: ...
356
func LastLessThan(key Key) KeySelector: ...
357
358
type KeyValue struct {
359
Key Key
360
Value []byte
361
}
362
363
type RangeOptions struct {
364
Limit int
365
Reverse bool
366
Mode StreamingMode
367
}
368
```
369
370
### Key Selection and Navigation
371
372
Advanced key selection for precise range boundaries and key navigation.
373
374
```c { .api }
375
/**
376
* Get a key based on a key selector
377
* @param tr Transaction handle
378
* @param key_name Base key for selector
379
* @param key_name_length Length of base key
380
* @param or_equal Whether selector includes the base key
381
* @param offset Offset from base key
382
* @param snapshot Whether to use snapshot read
383
* @return Future containing the selected key
384
*/
385
FDBFuture* fdb_transaction_get_key(FDBTransaction* tr, uint8_t const* key_name,
386
int key_name_length, fdb_bool_t or_equal,
387
int offset, fdb_bool_t snapshot);
388
389
// Key selector convenience macros
390
#define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0
391
#define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0
392
#define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1
393
#define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1
394
```
395
396
```python { .api }
397
class Transaction:
398
def get_key(self, selector: KeySelector, snapshot: bool = False) -> Future:
399
"""
400
Get a key based on a key selector
401
402
Args:
403
selector: Key selector specification
404
snapshot: Whether to use snapshot read
405
406
Returns:
407
Future containing the selected key bytes
408
"""
409
410
class KeySelector:
411
def __init__(self, key: bytes, or_equal: bool, offset: int): ...
412
413
def __add__(self, offset: int) -> 'KeySelector':
414
"""Add offset to selector"""
415
416
def __sub__(self, offset: int) -> 'KeySelector':
417
"""Subtract offset from selector"""
418
```
419
420
```java { .api }
421
// Java API
422
public interface ReadTransaction {
423
public CompletableFuture<byte[]> getKey(KeySelector selector):
424
/**
425
* Get a key based on a key selector
426
* @param selector Key selector specification
427
* @return CompletableFuture with selected key
428
*/
429
}
430
431
public class KeySelector {
432
public KeySelector add(int offset):
433
/**
434
* Create new selector with added offset
435
* @param offset Offset to add
436
* @return New KeySelector
437
*/
438
439
public KeySelector subtract(int offset):
440
/**
441
* Create new selector with subtracted offset
442
* @param offset Offset to subtract
443
* @return New KeySelector
444
*/
445
}
446
```
447
448
```go { .api }
449
// Go API
450
type ReadTransaction interface {
451
GetKey(selector KeySelector) FutureKey:
452
/**
453
* Get a key based on a key selector
454
* @param selector Key selector specification
455
* @return Future with selected key
456
*/
457
}
458
459
func (ks KeySelector) Add(offset int) KeySelector: ...
460
func (ks KeySelector) Sub(offset int) KeySelector: ...
461
```
462
463
### Watch Operations
464
465
Monitor keys for changes with efficient push notifications.
466
467
```c { .api }
468
/**
469
* Watch a key for changes
470
* @param tr Transaction handle
471
* @param key_name Key to watch
472
* @param key_name_length Length of key
473
* @return Future that completes when key changes
474
*/
475
FDBFuture* fdb_transaction_watch(FDBTransaction* tr, uint8_t const* key_name, int key_name_length);
476
```
477
478
```python { .api }
479
class Transaction:
480
def watch(self, key: bytes) -> Future:
481
"""
482
Watch a key for changes
483
484
Args:
485
key: Key to monitor for changes
486
487
Returns:
488
Future that completes when key changes from its current value
489
"""
490
```
491
492
```java { .api }
493
// Java API
494
public interface Transaction {
495
public CompletableFuture<Void> watch(byte[] key):
496
/**
497
* Watch a key for changes
498
* @param key Key to monitor
499
* @return CompletableFuture that completes when key changes
500
*/
501
}
502
```
503
504
```go { .api }
505
// Go API
506
type Transaction interface {
507
Watch(key Key) FutureNil:
508
/**
509
* Watch a key for changes
510
* @param key Key to monitor
511
* @return Future that completes when key changes
512
*/
513
}
514
```
515
516
### Size Estimation
517
518
Estimate the size of key ranges for performance planning.
519
520
```c { .api }
521
/**
522
* Get estimated size of a key range in bytes
523
* @param tr Transaction handle
524
* @param begin_key_name Start of range
525
* @param begin_key_name_length Length of start key
526
* @param end_key_name End of range
527
* @param end_key_name_length Length of end key
528
* @return Future containing estimated size in bytes
529
*/
530
FDBFuture* fdb_transaction_get_estimated_range_size_bytes(FDBTransaction* tr,
531
uint8_t const* begin_key_name, int begin_key_name_length,
532
uint8_t const* end_key_name, int end_key_name_length);
533
```
534
535
```python { .api }
536
class Transaction:
537
def get_estimated_range_size_bytes(self, begin: bytes, end: bytes) -> Future:
538
"""
539
Get estimated size of a key range
540
541
Args:
542
begin: Start of range (inclusive)
543
end: End of range (exclusive)
544
545
Returns:
546
Future containing estimated size in bytes
547
"""
548
```
549
550
```java { .api }
551
// Java API
552
public interface ReadTransaction {
553
public CompletableFuture<Long> getEstimatedRangeSizeBytes(byte[] begin, byte[] end):
554
/**
555
* Get estimated size of a key range
556
* @param begin Start of range
557
* @param end End of range
558
* @return CompletableFuture with estimated size in bytes
559
*/
560
}
561
```
562
563
```go { .api }
564
// Go API
565
type ReadTransaction interface {
566
GetEstimatedRangeSizeBytes(begin, end Key) FutureInt64:
567
/**
568
* Get estimated size of a key range
569
* @param begin Start of range
570
* @param end End of range
571
* @return Future with estimated size in bytes
572
*/
573
}
574
```
575
576
**Usage Examples:**
577
578
**Basic Key-Value Operations (Python):**
579
```python
580
import fdb
581
582
fdb.api_version(630)
583
db = fdb.open()
584
585
@fdb.transactional
586
def basic_operations(tr):
587
# Set some values
588
tr.set(b"user:123:name", b"Alice")
589
tr.set(b"user:123:email", b"alice@example.com")
590
tr.set(b"user:124:name", b"Bob")
591
tr.set(b"user:124:email", b"bob@example.com")
592
593
# Read a value
594
name = tr.get(b"user:123:name").wait()
595
print(f"Name: {name.decode()}")
596
597
# Delete a key
598
tr.clear(b"user:124:email")
599
600
return name
601
602
result = basic_operations(db)
603
```
604
605
**Range Queries (Java):**
606
```java
607
import com.apple.foundationdb.*;
608
import com.apple.foundationdb.tuple.Tuple;
609
610
FDB fdb = FDB.selectAPIVersion(630);
611
612
try (Database db = fdb.open()) {
613
db.run(tr -> {
614
// Set some data
615
tr.set(Tuple.from("user", 123, "name").pack(), "Alice".getBytes());
616
tr.set(Tuple.from("user", 123, "email").pack(), "alice@example.com".getBytes());
617
tr.set(Tuple.from("user", 124, "name").pack(), "Bob".getBytes());
618
tr.set(Tuple.from("user", 124, "email").pack(), "bob@example.com".getBytes());
619
620
// Range query for all user data
621
Range userRange = Range.startsWith(Tuple.from("user").pack());
622
623
for (KeyValue kv : tr.getRange(userRange)) {
624
Tuple key = Tuple.fromBytes(kv.getKey());
625
String value = new String(kv.getValue());
626
System.out.println(key + " = " + value);
627
}
628
629
return null;
630
});
631
}
632
```
633
634
**Key Selectors and Navigation (Go):**
635
```go
636
package main
637
638
import (
639
"fmt"
640
"github.com/apple/foundationdb/bindings/go/src/fdb"
641
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
642
)
643
644
func main() {
645
fdb.MustAPIVersion(630)
646
db := fdb.MustOpenDefault()
647
648
db.Transact(func(tr fdb.Transaction) (interface{}, error) {
649
// Set some ordered data
650
for i := 0; i < 10; i++ {
651
key := tuple.Tuple{"item", i}.Pack()
652
value := fmt.Sprintf("value-%d", i)
653
tr.Set(fdb.Key(key), []byte(value))
654
}
655
656
// Find the first key >= "item", 5
657
targetKey := tuple.Tuple{"item", 5}.Pack()
658
selector := fdb.FirstGreaterOrEqual(fdb.Key(targetKey))
659
foundKey := tr.GetKey(selector).MustGet()
660
661
foundTuple, _ := tuple.Unpack(foundKey)
662
fmt.Printf("Found key: %v\n", foundTuple)
663
664
// Get range from item 3 to item 7
665
beginKey := tuple.Tuple{"item", 3}.Pack()
666
endKey := tuple.Tuple{"item", 8}.Pack() // End is exclusive
667
668
beginSel := fdb.FirstGreaterOrEqual(fdb.Key(beginKey))
669
endSel := fdb.FirstGreaterOrEqual(fdb.Key(endKey))
670
671
rangeResult := tr.GetRange(beginSel, endSel, fdb.RangeOptions{})
672
kvs := rangeResult.GetSliceWithError()
673
674
for _, kv := range kvs {
675
keyTuple, _ := tuple.Unpack(kv.Key)
676
fmt.Printf("%v = %s\n", keyTuple, string(kv.Value))
677
}
678
679
return nil, nil
680
})
681
}
682
```
683
684
**Watch Operations (Python):**
685
```python
686
import fdb
687
import threading
688
import time
689
690
fdb.api_version(630)
691
db = fdb.open()
692
693
def watch_key():
694
@fdb.transactional
695
def setup_watch(tr, key):
696
# Get current value and set up watch
697
current_value = tr.get(key).wait()
698
watch_future = tr.watch(key)
699
return current_value, watch_future
700
701
key = b"watched_key"
702
current_value, watch_future = setup_watch(db, key)
703
print(f"Initial value: {current_value}")
704
705
# Wait for change (this will block until key changes)
706
watch_future.wait()
707
print("Key changed!")
708
709
# Get new value
710
@fdb.transactional
711
def get_new_value(tr, key):
712
return tr.get(key).wait()
713
714
new_value = get_new_value(db, key)
715
print(f"New value: {new_value}")
716
717
# Start watching in background
718
watch_thread = threading.Thread(target=watch_key)
719
watch_thread.start()
720
721
# Change the value after a delay
722
time.sleep(2)
723
724
@fdb.transactional
725
def change_value(tr):
726
tr.set(b"watched_key", b"new_value")
727
728
change_value(db)
729
watch_thread.join()
730
```