0
# Java API Reference
1
2
The FoundationDB Java API provides an object-oriented interface to the database with CompletableFuture-based asynchronous operations, functional transaction interfaces, and comprehensive type safety. The API follows Java conventions and integrates well with modern Java applications.
3
4
## Installation
5
6
### Maven
7
8
```xml
9
<dependency>
10
<groupId>org.foundationdb</groupId>
11
<artifactId>fdb-java</artifactId>
12
<version>7.4.3</version>
13
</dependency>
14
```
15
16
### Gradle
17
18
```gradle
19
implementation 'org.foundationdb:fdb-java:7.4.3'
20
```
21
22
## Core Imports
23
24
```java
25
import com.apple.foundationdb.*;
26
import com.apple.foundationdb.async.AsyncIterable;
27
import com.apple.foundationdb.tuple.Tuple;
28
import com.apple.foundationdb.subspace.Subspace;
29
import com.apple.foundationdb.directory.Directory;
30
import java.util.concurrent.CompletableFuture;
31
```
32
33
## Capabilities
34
35
### Initialization and Database Connection
36
37
API version selection and database connection management.
38
39
```java { .api }
40
public class FDB {
41
/**
42
* Select API version and get FDB instance (must be called first).
43
*
44
* @param version API version to use (typically 740 for v7.4.0+)
45
* @return FDB instance for opening databases
46
*/
47
public static FDB selectAPIVersion(int version);
48
49
/**
50
* Get the current FDB instance.
51
*
52
* @return Current FDB instance
53
*/
54
public static FDB instance();
55
56
/**
57
* Open database with default cluster file.
58
*
59
* @return Database handle for creating transactions
60
*/
61
public Database open();
62
63
/**
64
* Open database with specified cluster file.
65
*
66
* @param clusterFilePath Path to cluster file
67
* @return Database handle for creating transactions
68
*/
69
public Database open(String clusterFilePath);
70
71
/**
72
* Start network thread (called automatically by open()).
73
*/
74
public void startNetwork();
75
76
/**
77
* Stop network thread.
78
*/
79
public void stopNetwork();
80
81
/**
82
* Set network-level options.
83
*
84
* @param option Network option to set
85
* @param value Option value
86
*/
87
public void setNetworkOption(NetworkOptions option, byte[] value);
88
}
89
```
90
91
**Usage Example:**
92
93
```java
94
import com.apple.foundationdb.*;
95
96
// Initialize FoundationDB (required first step)
97
FDB fdb = FDB.selectAPIVersion(740);
98
99
// Open database connection
100
Database db = fdb.open(); // Uses default cluster file
101
// or specify cluster file path
102
Database db = fdb.open("/etc/foundationdb/fdb.cluster");
103
```
104
105
### Database Operations
106
107
Database interface for creating transactions and managing tenants.
108
109
```java { .api }
110
public interface Database extends Transactor, ReadTransactor {
111
/**
112
* Create a new transaction.
113
*
114
* @return Transaction handle for database operations
115
*/
116
Transaction createTransaction();
117
118
/**
119
* Execute function with automatic transaction retry.
120
*
121
* @param <T> Return type
122
* @param retryable Function to execute with retry logic
123
* @return Result of the function
124
*/
125
<T> T run(Function<Transaction, T> retryable);
126
127
/**
128
* Execute read-only function with automatic retry.
129
*
130
* @param <T> Return type
131
* @param retryable Read-only function to execute
132
* @return Result of the function
133
*/
134
<T> T read(Function<ReadTransaction, T> retryable);
135
136
/**
137
* Open tenant handle for multi-tenancy.
138
*
139
* @param tenantName Name of the tenant to open
140
* @return Tenant handle with isolated keyspace
141
*/
142
Tenant openTenant(byte[] tenantName);
143
144
/**
145
* Set database-level options.
146
*
147
* @param option Database option to set
148
* @param value Option value
149
*/
150
void setOption(DatabaseOptions option, byte[] value);
151
}
152
```
153
154
### Transactor Interfaces
155
156
Functional interfaces for executing transactional operations.
157
158
```java { .api }
159
public interface Transactor {
160
/**
161
* Execute function with automatic transaction retry.
162
*
163
* @param <T> Return type
164
* @param retryable Function to execute with retry logic
165
* @return Result of the function
166
*/
167
<T> T run(Function<Transaction, T> retryable);
168
}
169
170
public interface ReadTransactor {
171
/**
172
* Execute read-only function with automatic retry.
173
*
174
* @param <T> Return type
175
* @param retryable Read-only function to execute
176
* @return Result of the function
177
*/
178
<T> T read(Function<ReadTransaction, T> retryable);
179
}
180
```
181
182
**Usage Example:**
183
184
```java
185
Database db = fdb.open();
186
187
// Functional transaction handling
188
String result = db.run(tr -> {
189
byte[] value = tr.get("my_key".getBytes()).join();
190
if (value == null) {
191
tr.set("my_key".getBytes(), "initial_value".getBytes());
192
return "created";
193
} else {
194
return new String(value);
195
}
196
});
197
198
// Read-only transaction
199
String readResult = db.read(tr -> {
200
byte[] value = tr.get("my_key".getBytes()).join();
201
return value != null ? new String(value) : "not_found";
202
});
203
```
204
205
### Transaction Operations
206
207
Core transaction operations for reading and writing data.
208
209
```java { .api }
210
public interface Transaction extends ReadTransaction {
211
/**
212
* Write a key-value pair.
213
*
214
* @param key Key to write
215
* @param value Value to write
216
*/
217
void set(byte[] key, byte[] value);
218
219
/**
220
* Delete a single key.
221
*
222
* @param key Key to delete
223
*/
224
void clear(byte[] key);
225
226
/**
227
* Delete a range of keys.
228
*
229
* @param begin Start key (inclusive)
230
* @param end End key (exclusive)
231
*/
232
void clearRange(byte[] begin, byte[] end);
233
234
/**
235
* Commit the transaction.
236
*
237
* @return CompletableFuture that completes when commit finishes
238
*/
239
CompletableFuture<Void> commit();
240
241
/**
242
* Handle transaction errors and determine retry logic.
243
*
244
* @param error Exception that occurred
245
* @return CompletableFuture that completes when ready to retry
246
*/
247
CompletableFuture<Void> onError(Throwable error);
248
249
/**
250
* Get the read version for this transaction.
251
*
252
* @return CompletableFuture containing the read version
253
*/
254
CompletableFuture<Long> getReadVersion();
255
256
/**
257
* Set the read version for this transaction.
258
*
259
* @param version Read version to use
260
*/
261
void setReadVersion(long version);
262
263
/**
264
* Get the version at which this transaction was committed.
265
* Only valid after successful commit.
266
*
267
* @return Committed version number
268
*/
269
long getCommittedVersion();
270
271
/**
272
* Get the versionstamp for this transaction.
273
* Only valid after successful commit.
274
*
275
* @return CompletableFuture containing the 12-byte versionstamp
276
*/
277
CompletableFuture<byte[]> getVersionstamp();
278
279
/**
280
* Add a range to the transaction's read conflict ranges.
281
*
282
* @param begin Start key (inclusive)
283
* @param end End key (exclusive)
284
*/
285
void addReadConflictRange(byte[] begin, byte[] end);
286
287
/**
288
* Add a range to the transaction's write conflict ranges.
289
*
290
* @param begin Start key (inclusive)
291
* @param end End key (exclusive)
292
*/
293
void addWriteConflictRange(byte[] begin, byte[] end);
294
295
/**
296
* Set transaction-level options.
297
*
298
* @param option Transaction option to set
299
* @param value Option value
300
*/
301
void setOption(TransactionOptions option, byte[] value);
302
}
303
304
public interface ReadTransaction {
305
/**
306
* Read a single key.
307
*
308
* @param key Key to read
309
* @return CompletableFuture containing the value (null if key doesn't exist)
310
*/
311
CompletableFuture<byte[]> get(byte[] key);
312
313
/**
314
* Read a range of key-value pairs.
315
*
316
* @param begin Start key selector
317
* @param end End key selector
318
* @return AsyncIterable of KeyValue objects
319
*/
320
AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end);
321
322
/**
323
* Read a range of key-value pairs with options.
324
*
325
* @param begin Start key selector
326
* @param end End key selector
327
* @param limit Maximum number of pairs to return (0 for no limit)
328
* @param reverse Whether to read in reverse order
329
* @param mode Streaming mode for result batching
330
* @return AsyncIterable of KeyValue objects
331
*/
332
AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end,
333
int limit, boolean reverse, StreamingMode mode);
334
335
/**
336
* Read a range using raw key boundaries.
337
*
338
* @param begin Start key (inclusive)
339
* @param end End key (exclusive)
340
* @return AsyncIterable of KeyValue objects
341
*/
342
AsyncIterable<KeyValue> getRange(byte[] begin, byte[] end);
343
344
/**
345
* Read a range using Range object.
346
*
347
* @param range Range specification
348
* @return AsyncIterable of KeyValue objects
349
*/
350
AsyncIterable<KeyValue> getRange(Range range);
351
352
/**
353
* Get estimated byte size of a range.
354
*
355
* @param begin Start key
356
* @param end End key
357
* @return CompletableFuture containing estimated size in bytes
358
*/
359
CompletableFuture<Long> getEstimatedRangeSize(byte[] begin, byte[] end);
360
361
/**
362
* Get boundary keys for a range (used for parallel processing).
363
*
364
* @param begin Start key
365
* @param end End key
366
* @return AsyncIterable of boundary keys
367
*/
368
AsyncIterable<byte[]> getRangeSplitPoints(byte[] begin, byte[] end, long chunkSize);
369
370
/**
371
* Get the read version that this transaction will use.
372
*
373
* @return CompletableFuture containing the read version
374
*/
375
CompletableFuture<Long> getReadVersion();
376
377
/**
378
* Perform a snapshot read (no conflicts).
379
*
380
* @return ReadTransaction for snapshot operations
381
*/
382
ReadTransaction snapshot();
383
}
384
```
385
386
### Key Selectors and Range Operations
387
388
Advanced key selection and range query operations.
389
390
```java { .api }
391
public class KeySelector {
392
/**
393
* Create a key selector.
394
*
395
* @param key Reference key
396
* @param orEqual Whether to include the reference key
397
* @param offset Offset from the reference key
398
*/
399
public KeySelector(byte[] key, boolean orEqual, int offset);
400
401
/**
402
* Key selector for last key less than given key.
403
*/
404
public static KeySelector lastLessThan(byte[] key);
405
406
/**
407
* Key selector for last key less than or equal to given key.
408
*/
409
public static KeySelector lastLessOrEqual(byte[] key);
410
411
/**
412
* Key selector for first key greater than given key.
413
*/
414
public static KeySelector firstGreaterThan(byte[] key);
415
416
/**
417
* Key selector for first key greater than or equal to given key.
418
*/
419
public static KeySelector firstGreaterOrEqual(byte[] key);
420
421
public byte[] getKey();
422
public boolean getOrEqual();
423
public int getOffset();
424
}
425
426
public class KeyValue {
427
/**
428
* Key-value pair from range operations.
429
*/
430
public byte[] getKey();
431
public byte[] getValue();
432
}
433
434
public class Range {
435
/**
436
* Key range specification.
437
*/
438
public Range(byte[] begin, byte[] end);
439
440
public byte[] begin;
441
public byte[] end;
442
443
public static Range startsWith(byte[] prefix);
444
}
445
```
446
447
### Tenant Operations
448
449
Multi-tenancy support for isolated keyspaces.
450
451
```java { .api }
452
public interface Tenant extends Transactor, ReadTransactor {
453
/**
454
* Create a new transaction within this tenant's keyspace.
455
*
456
* @return Transaction handle scoped to this tenant
457
*/
458
Transaction createTransaction();
459
460
/**
461
* Get the tenant name.
462
*
463
* @return Tenant name as byte array
464
*/
465
byte[] getName();
466
}
467
```
468
469
### Exception Handling
470
471
Exception classes for error handling.
472
473
```java { .api }
474
public class FDBException extends RuntimeException {
475
/**
476
* Base exception class for FoundationDB errors.
477
*
478
* @param message Error message
479
* @param code FoundationDB error code
480
*/
481
public FDBException(String message, int code);
482
483
/**
484
* Get the FoundationDB error code.
485
*
486
* @return Error code
487
*/
488
public int getCode();
489
490
/**
491
* Check if error indicates transaction should be retried.
492
*
493
* @return true if error is retryable
494
*/
495
public boolean isRetryable();
496
497
/**
498
* Check if transaction may have been committed despite error.
499
*
500
* @return true if transaction may have committed
501
*/
502
public boolean isMaybeCommitted();
503
504
/**
505
* Check if error is retryable and transaction was not committed.
506
*
507
* @return true if safe to retry (not committed)
508
*/
509
public boolean isRetryableNotCommitted();
510
}
511
```
512
513
### Tuple Encoding
514
515
Structured key encoding for hierarchical data organization.
516
517
```java { .api }
518
public class Tuple {
519
/**
520
* Create empty tuple.
521
*/
522
public Tuple();
523
524
/**
525
* Create tuple from items.
526
*
527
* @param items Values to include in tuple
528
*/
529
public Tuple(Object... items);
530
531
/**
532
* Add item to tuple.
533
*
534
* @param item Item to add
535
* @return This tuple for method chaining
536
*/
537
public Tuple add(Object item);
538
539
/**
540
* Add all items to tuple.
541
*
542
* @param items Items to add
543
* @return This tuple for method chaining
544
*/
545
public Tuple addAll(Object... items);
546
547
/**
548
* Get item at index.
549
*
550
* @param index Index of item to get
551
* @return Item at index
552
*/
553
public Object get(int index);
554
555
/**
556
* Get number of items in tuple.
557
*
558
* @return Number of items
559
*/
560
public int size();
561
562
/**
563
* Convert tuple to list.
564
*
565
* @return List containing tuple items
566
*/
567
public List<Object> getItems();
568
569
/**
570
* Encode tuple to bytes for use as key.
571
*
572
* @return Encoded bytes suitable for use as FoundationDB key
573
*/
574
public byte[] pack();
575
576
/**
577
* Decode bytes back to tuple.
578
*
579
* @param data Encoded tuple bytes
580
* @return Decoded tuple
581
*/
582
public static Tuple fromBytes(byte[] data);
583
584
/**
585
* Create key range for all tuples with this tuple as prefix.
586
*
587
* @return Range for prefix queries
588
*/
589
public Range range();
590
591
/**
592
* Create key range for all tuples with given prefix.
593
*
594
* @param prefix Tuple prefix
595
* @return Range for prefix queries
596
*/
597
public static Range range(Tuple prefix);
598
}
599
```
600
601
**Usage Example:**
602
603
```java
604
import com.apple.foundationdb.tuple.Tuple;
605
606
// Encode structured keys
607
byte[] userKey = Tuple.from("users", userId, "profile").pack();
608
byte[] scoreKey = Tuple.from("scores", gameId, timestamp, playerId).pack();
609
610
// Create ranges for prefix queries
611
Range userRange = Tuple.from("users", userId).range(); // All keys for this user
612
Range scoreRange = Tuple.from("scores", gameId).range(); // All scores for this game
613
614
// Use in transactions
615
db.run(tr -> {
616
for (KeyValue kv : tr.getRange(userRange)) {
617
Tuple key = Tuple.fromBytes(kv.getKey());
618
// Process user data
619
}
620
return null;
621
});
622
```
623
624
### Subspace Operations
625
626
Key prefix management for organizing data hierarchies.
627
628
```java { .api }
629
public class Subspace {
630
/**
631
* Create subspace with tuple prefix.
632
*
633
* @param prefix Tuple to use as prefix
634
*/
635
public Subspace(Tuple prefix);
636
637
/**
638
* Create subspace with raw prefix.
639
*
640
* @param rawPrefix Raw bytes to use as prefix
641
*/
642
public Subspace(byte[] rawPrefix);
643
644
/**
645
* Pack tuple with subspace prefix.
646
*
647
* @param tuple Tuple to pack with prefix
648
* @return Packed key with subspace prefix
649
*/
650
public byte[] pack(Tuple tuple);
651
652
/**
653
* Pack items with subspace prefix.
654
*
655
* @param items Items to pack with prefix
656
* @return Packed key with subspace prefix
657
*/
658
public byte[] pack(Object... items);
659
660
/**
661
* Unpack key and remove subspace prefix.
662
*
663
* @param key Key to unpack
664
* @return Tuple without subspace prefix
665
*/
666
public Tuple unpack(byte[] key);
667
668
/**
669
* Create range within this subspace.
670
*
671
* @param tuple Tuple suffix for range
672
* @return Range for operations within this subspace
673
*/
674
public Range range(Tuple tuple);
675
676
/**
677
* Create range for all keys in this subspace.
678
*
679
* @return Range covering entire subspace
680
*/
681
public Range range();
682
683
/**
684
* Check if key belongs to this subspace.
685
*
686
* @param key Key to check
687
* @return true if key is in this subspace
688
*/
689
public boolean contains(byte[] key);
690
691
/**
692
* Create child subspace.
693
*
694
* @param tuple Additional tuple elements for child
695
* @return New subspace with extended prefix
696
*/
697
public Subspace subspace(Tuple tuple);
698
699
/**
700
* Get the raw prefix for this subspace.
701
*
702
* @return Raw prefix bytes
703
*/
704
public byte[] getKey();
705
}
706
```
707
708
### Directory Layer
709
710
Hierarchical directory management system built on subspaces.
711
712
```java { .api }
713
public interface DirectoryLayer {
714
/**
715
* Create or open directory at path.
716
*
717
* @param tr Transaction handle
718
* @param path Directory path as list of strings
719
* @return Directory handle
720
*/
721
CompletableFuture<Directory> createOrOpen(ReadTransaction tr, List<String> path);
722
723
/**
724
* Create or open directory at path with layer.
725
*
726
* @param tr Transaction handle
727
* @param path Directory path as list of strings
728
* @param layer Layer identifier for directory type
729
* @return Directory handle
730
*/
731
CompletableFuture<Directory> createOrOpen(ReadTransaction tr, List<String> path, byte[] layer);
732
733
/**
734
* Open existing directory at path.
735
*
736
* @param tr Transaction handle
737
* @param path Directory path as list of strings
738
* @return Directory handle
739
* @throws DirectoryException if directory doesn't exist
740
*/
741
CompletableFuture<Directory> open(ReadTransaction tr, List<String> path);
742
743
/**
744
* Create new directory at path.
745
*
746
* @param tr Transaction handle
747
* @param path Directory path as list of strings
748
* @return Directory handle
749
* @throws DirectoryException if directory already exists
750
*/
751
CompletableFuture<Directory> create(Transaction tr, List<String> path);
752
753
/**
754
* List subdirectories at path.
755
*
756
* @param tr Transaction handle
757
* @param path Directory path to list
758
* @return CompletableFuture containing list of subdirectory names
759
*/
760
CompletableFuture<List<String>> list(ReadTransaction tr, List<String> path);
761
762
/**
763
* Remove directory and all its data.
764
*
765
* @param tr Transaction handle
766
* @param path Directory path to remove
767
* @return CompletableFuture containing true if directory was removed
768
*/
769
CompletableFuture<Boolean> remove(Transaction tr, List<String> path);
770
771
/**
772
* Move directory to new path.
773
*
774
* @param tr Transaction handle
775
* @param oldPath Current directory path
776
* @param newPath New directory path
777
* @return Directory handle at new location
778
*/
779
CompletableFuture<Directory> move(Transaction tr, List<String> oldPath, List<String> newPath);
780
781
/**
782
* Check if directory exists at path.
783
*
784
* @param tr Transaction handle
785
* @param path Directory path to check
786
* @return CompletableFuture containing true if directory exists
787
*/
788
CompletableFuture<Boolean> exists(ReadTransaction tr, List<String> path);
789
}
790
791
public interface Directory extends Subspace {
792
/**
793
* Directory handle extending Subspace functionality.
794
*/
795
796
/**
797
* Get the layer identifier for this directory.
798
*
799
* @return Layer identifier
800
*/
801
byte[] getLayer();
802
803
/**
804
* Get the path for this directory.
805
*
806
* @return Directory path as list of strings
807
*/
808
List<String> getPath();
809
}
810
811
// Default directory layer instance
812
public static final DirectoryLayer DEFAULT = new DirectoryLayerImpl();
813
```
814
815
### Asynchronous Utilities
816
817
Helper classes for working with asynchronous operations.
818
819
```java { .api }
820
public interface AsyncIterable<T> {
821
/**
822
* Get async iterator for this iterable.
823
*
824
* @return AsyncIterator for traversing items
825
*/
826
AsyncIterator<T> iterator();
827
828
/**
829
* Convert to list (blocking operation).
830
*
831
* @return List containing all items
832
*/
833
CompletableFuture<List<T>> asList();
834
835
/**
836
* Process each item with given function.
837
*
838
* @param consumer Function to apply to each item
839
* @return CompletableFuture that completes when all items processed
840
*/
841
CompletableFuture<Void> forEach(Function<T, CompletableFuture<Void>> consumer);
842
}
843
844
public interface AsyncIterator<T> {
845
/**
846
* Check if more items are available.
847
*
848
* @return CompletableFuture containing true if more items available
849
*/
850
CompletableFuture<Boolean> hasNext();
851
852
/**
853
* Get next item.
854
*
855
* @return CompletableFuture containing next item
856
*/
857
CompletableFuture<T> next();
858
}
859
```
860
861
## Configuration Options
862
863
### Streaming Modes
864
865
```java { .api }
866
public enum StreamingMode {
867
WANT_ALL(-2),
868
ITERATOR(-1), // Default for iteration
869
EXACT(0),
870
SMALL(1),
871
MEDIUM(2),
872
LARGE(3),
873
SERIAL(4);
874
875
private final int code;
876
877
StreamingMode(int code) {
878
this.code = code;
879
}
880
881
public int getCode() {
882
return code;
883
}
884
}
885
```
886
887
### Option Classes
888
889
```java { .api }
890
// Network options
891
public enum NetworkOptions {
892
TLS_CERT_PATH(13),
893
TLS_KEY_PATH(14),
894
TLS_VERIFY_PEERS(15),
895
EXTERNAL_CLIENT_LIBRARY(16),
896
DISABLE_LOCAL_CLIENT(17);
897
898
private final int code;
899
public int getCode() { return code; }
900
}
901
902
// Database options
903
public enum DatabaseOptions {
904
LOCATION_CACHE_SIZE(10),
905
MAX_WATCHES(20),
906
MACHINE_ID(21),
907
TRANSACTION_TIMEOUT(30),
908
TRANSACTION_RETRY_LIMIT(31);
909
910
private final int code;
911
public int getCode() { return code; }
912
}
913
914
// Transaction options
915
public enum TransactionOptions {
916
CAUSAL_READ_RISKY(20),
917
READ_YOUR_WRITES_DISABLE(22),
918
PRIORITY_SYSTEM_IMMEDIATE(200),
919
PRIORITY_BATCH(201),
920
TIMEOUT(500),
921
RETRY_LIMIT(501),
922
SIZE_LIMIT(503);
923
924
private final int code;
925
public int getCode() { return code; }
926
}
927
```
928
929
## Complete Usage Example
930
931
```java
932
import com.apple.foundationdb.*;
933
import com.apple.foundationdb.tuple.Tuple;
934
import com.apple.foundationdb.subspace.Subspace;
935
import com.apple.foundationdb.directory.DirectoryLayer;
936
import java.util.concurrent.CompletableFuture;
937
import java.util.List;
938
import java.util.ArrayList;
939
940
public class FoundationDBExample {
941
private final Database db;
942
private final Subspace userSpace;
943
private final Subspace sessionSpace;
944
945
public FoundationDBExample() {
946
// Initialize FoundationDB
947
FDB fdb = FDB.selectAPIVersion(740);
948
this.db = fdb.open();
949
950
// Set up subspaces
951
this.userSpace = new Subspace(Tuple.from("users"));
952
this.sessionSpace = new Subspace(Tuple.from("sessions"));
953
}
954
955
public String createUser(String userId, String email, String name) {
956
return db.run(tr -> {
957
Subspace user = userSpace.subspace(Tuple.from(userId));
958
959
// Store user profile
960
tr.set(user.pack(Tuple.from("email")), email.getBytes());
961
tr.set(user.pack(Tuple.from("name")), name.getBytes());
962
tr.set(user.pack(Tuple.from("created")),
963
String.valueOf(System.currentTimeMillis()).getBytes());
964
965
// Add to email index
966
byte[] emailKey = Tuple.from("email_index", email, userId).pack();
967
tr.set(emailKey, new byte[0]);
968
969
return userId;
970
});
971
}
972
973
public CompletableFuture<User> getUserProfileAsync(String userId) {
974
return db.readAsync(tr -> {
975
Subspace user = userSpace.subspace(Tuple.from(userId));
976
977
CompletableFuture<List<KeyValue>> rangeResult =
978
tr.getRange(user.range()).asList();
979
980
return rangeResult.thenApply(kvs -> {
981
User userObj = new User(userId);
982
for (KeyValue kv : kvs) {
983
Tuple key = user.unpack(kv.getKey());
984
String field = (String) key.get(0);
985
String value = new String(kv.getValue());
986
987
switch (field) {
988
case "email":
989
userObj.setEmail(value);
990
break;
991
case "name":
992
userObj.setName(value);
993
break;
994
case "created":
995
userObj.setCreatedTime(Long.parseLong(value));
996
break;
997
}
998
}
999
return userObj;
1000
});
1001
}).thenCompose(future -> future);
1002
}
1003
1004
public String findUserByEmail(String email) {
1005
return db.read(tr -> {
1006
Range emailRange = Tuple.from("email_index", email).range();
1007
1008
for (KeyValue kv : tr.getRange(emailRange)) {
1009
Tuple key = Tuple.fromBytes(kv.getKey());
1010
// Extract user_id from key
1011
return (String) key.get(2); // email_index, email, user_id
1012
}
1013
1014
return null;
1015
});
1016
}
1017
1018
public List<String> getAllUsers() {
1019
return db.read(tr -> {
1020
List<String> userIds = new ArrayList<>();
1021
1022
for (KeyValue kv : tr.getRange(userSpace.range())) {
1023
Tuple key = userSpace.unpack(kv.getKey());
1024
if (key.size() >= 2) {
1025
String userId = (String) key.get(0);
1026
String field = (String) key.get(1);
1027
1028
// Only add user ID once (when we see any field)
1029
if ("email".equals(field) && !userIds.contains(userId)) {
1030
userIds.add(userId);
1031
}
1032
}
1033
}
1034
1035
return userIds;
1036
});
1037
}
1038
1039
public void close() {
1040
// FoundationDB handles cleanup automatically
1041
// No explicit close needed for database handles
1042
}
1043
1044
// Helper class
1045
public static class User {
1046
private String id;
1047
private String email;
1048
private String name;
1049
private long createdTime;
1050
1051
public User(String id) {
1052
this.id = id;
1053
}
1054
1055
// Getters and setters
1056
public String getId() { return id; }
1057
public String getEmail() { return email; }
1058
public void setEmail(String email) { this.email = email; }
1059
public String getName() { return name; }
1060
public void setName(String name) { this.name = name; }
1061
public long getCreatedTime() { return createdTime; }
1062
public void setCreatedTime(long createdTime) { this.createdTime = createdTime; }
1063
}
1064
1065
public static void main(String[] args) {
1066
FoundationDBExample example = new FoundationDBExample();
1067
1068
try {
1069
// Create user
1070
String userId = example.createUser("user123", "alice@example.com", "Alice Smith");
1071
System.out.println("Created user: " + userId);
1072
1073
// Get user profile (async)
1074
CompletableFuture<User> userFuture = example.getUserProfileAsync(userId);
1075
User user = userFuture.join();
1076
System.out.println("User profile: " + user.getName() + " (" + user.getEmail() + ")");
1077
1078
// Find user by email
1079
String foundUserId = example.findUserByEmail("alice@example.com");
1080
System.out.println("Found user by email: " + foundUserId);
1081
1082
// List all users
1083
List<String> allUsers = example.getAllUsers();
1084
System.out.println("All users: " + allUsers);
1085
1086
} finally {
1087
example.close();
1088
}
1089
}
1090
}
1091
```