0
# Directory Layer
1
2
Hierarchical directory abstraction for organizing keys into namespaces with automatic prefix management and metadata. The Directory Layer provides a file system-like interface on top of FoundationDB's key-value store.
3
4
## Capabilities
5
6
### Directory Management
7
8
Create, open, and manage hierarchical directories with automatic prefix allocation.
9
10
```python { .api }
11
class DirectoryLayer:
12
def __init__(self, node_subspace: Subspace = None, content_subspace: Subspace = None):
13
"""
14
Create a DirectoryLayer instance
15
16
Args:
17
node_subspace: Subspace for directory metadata (default: system subspace)
18
content_subspace: Subspace for directory contents (default: empty prefix)
19
"""
20
21
def create_or_open(self, tr: Transaction, path: List[str], layer: bytes = b'') -> DirectorySubspace:
22
"""
23
Create directory if it doesn't exist, otherwise open it
24
25
Args:
26
tr: Transaction to use
27
path: Directory path as list of strings
28
layer: Optional layer identifier for the directory
29
30
Returns:
31
DirectorySubspace for the directory
32
33
Raises:
34
DirectoryLayerMismatchError: If directory exists with different layer
35
"""
36
37
def open(self, tr: Transaction, path: List[str], layer: bytes = b'') -> DirectorySubspace:
38
"""
39
Open an existing directory
40
41
Args:
42
tr: Transaction to use
43
path: Directory path as list of strings
44
layer: Expected layer identifier (must match existing)
45
46
Returns:
47
DirectorySubspace for the directory
48
49
Raises:
50
DirectoryNotFoundError: If directory doesn't exist
51
DirectoryLayerMismatchError: If layer doesn't match
52
"""
53
54
def create(self, tr: Transaction, path: List[str], layer: bytes = b'', prefix: bytes = None) -> DirectorySubspace:
55
"""
56
Create a new directory
57
58
Args:
59
tr: Transaction to use
60
path: Directory path as list of strings
61
layer: Optional layer identifier
62
prefix: Optional specific prefix (auto-allocated if None)
63
64
Returns:
65
DirectorySubspace for the new directory
66
67
Raises:
68
DirectoryAlreadyExistsError: If directory already exists
69
"""
70
71
def move(self, tr: Transaction, old_path: List[str], new_path: List[str]) -> DirectorySubspace:
72
"""
73
Move/rename a directory
74
75
Args:
76
tr: Transaction to use
77
old_path: Current directory path
78
new_path: New directory path
79
80
Returns:
81
DirectorySubspace for the moved directory
82
83
Raises:
84
DirectoryNotFoundError: If source directory doesn't exist
85
DirectoryAlreadyExistsError: If destination already exists
86
"""
87
88
def remove(self, tr: Transaction, path: List[str]) -> bool:
89
"""
90
Remove a directory and all its contents
91
92
Args:
93
tr: Transaction to use
94
path: Directory path to remove
95
96
Returns:
97
True if directory was removed, False if it didn't exist
98
"""
99
100
def list(self, tr: Transaction, path: List[str] = []) -> List[str]:
101
"""
102
List subdirectories at the given path
103
104
Args:
105
tr: Transaction to use
106
path: Directory path to list (empty for root)
107
108
Returns:
109
List of subdirectory names
110
"""
111
112
def exists(self, tr: Transaction, path: List[str]) -> bool:
113
"""
114
Check if a directory exists
115
116
Args:
117
tr: Transaction to use
118
path: Directory path to check
119
120
Returns:
121
True if directory exists, False otherwise
122
"""
123
124
# Default directory layer instance
125
directory: DirectoryLayer
126
```
127
128
```java { .api }
129
// Java API
130
public class DirectoryLayer {
131
public DirectoryLayer():
132
/**
133
* Create DirectoryLayer with default subspaces
134
*/
135
136
public DirectoryLayer(Subspace nodeSubspace, Subspace contentSubspace):
137
/**
138
* Create DirectoryLayer with custom subspaces
139
* @param nodeSubspace Subspace for directory metadata
140
* @param contentSubspace Subspace for directory contents
141
*/
142
143
public CompletableFuture<DirectorySubspace> createOrOpen(TransactionContext tcx, List<String> path):
144
/**
145
* Create directory if it doesn't exist, otherwise open it
146
* @param tcx Transaction context
147
* @param path Directory path
148
* @return CompletableFuture with DirectorySubspace
149
*/
150
151
public CompletableFuture<DirectorySubspace> open(TransactionContext tcx, List<String> path):
152
/**
153
* Open an existing directory
154
* @param tcx Transaction context
155
* @param path Directory path
156
* @return CompletableFuture with DirectorySubspace
157
*/
158
159
public CompletableFuture<DirectorySubspace> create(TransactionContext tcx, List<String> path):
160
/**
161
* Create a new directory
162
* @param tcx Transaction context
163
* @param path Directory path
164
* @return CompletableFuture with DirectorySubspace
165
*/
166
167
public CompletableFuture<DirectorySubspace> move(TransactionContext tcx, List<String> oldPath, List<String> newPath):
168
/**
169
* Move/rename a directory
170
* @param tcx Transaction context
171
* @param oldPath Current directory path
172
* @param newPath New directory path
173
* @return CompletableFuture with DirectorySubspace
174
*/
175
176
public CompletableFuture<Boolean> remove(TransactionContext tcx, List<String> path):
177
/**
178
* Remove a directory and all its contents
179
* @param tcx Transaction context
180
* @param path Directory path to remove
181
* @return CompletableFuture with success boolean
182
*/
183
184
public CompletableFuture<List<String>> list(TransactionContext tcx, List<String> path):
185
/**
186
* List subdirectories at the given path
187
* @param tcx Transaction context
188
* @param path Directory path to list
189
* @return CompletableFuture with list of subdirectory names
190
*/
191
}
192
193
// Default directory layer instance
194
public static final DirectoryLayer DEFAULT = new DirectoryLayer();
195
```
196
197
```go { .api }
198
// Go API
199
type DirectoryLayer struct{}
200
201
func NewDirectoryLayer() DirectoryLayer: ...
202
203
func (dl DirectoryLayer) CreateOrOpen(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error): ...
204
func (dl DirectoryLayer) Open(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error): ...
205
func (dl DirectoryLayer) Create(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error): ...
206
func (dl DirectoryLayer) Move(t fdb.Transactor, oldPath, newPath []string) (DirectorySubspace, error): ...
207
func (dl DirectoryLayer) Remove(t fdb.Transactor, path []string) (bool, error): ...
208
func (dl DirectoryLayer) List(t fdb.Transactor, path []string) ([]string, error): ...
209
func (dl DirectoryLayer) Exists(t fdb.Transactor, path []string) (bool, error): ...
210
211
// Default directory layer instance
212
var DefaultDirectoryLayer DirectoryLayer
213
```
214
215
### Directory Subspaces
216
217
DirectorySubspace combines the functionality of a Subspace with directory metadata.
218
219
```python { .api }
220
class DirectorySubspace(Subspace):
221
def get_path(self) -> List[str]:
222
"""
223
Get the path of this directory
224
225
Returns:
226
Directory path as list of strings
227
"""
228
229
def get_layer(self) -> bytes:
230
"""
231
Get the layer identifier for this directory
232
233
Returns:
234
Layer identifier bytes
235
"""
236
237
def create_or_open(self, tr: Transaction, name: str, layer: bytes = b'') -> 'DirectorySubspace':
238
"""
239
Create or open a subdirectory
240
241
Args:
242
tr: Transaction to use
243
name: Subdirectory name
244
layer: Optional layer identifier
245
246
Returns:
247
DirectorySubspace for the subdirectory
248
"""
249
250
def open(self, tr: Transaction, name: str, layer: bytes = b'') -> 'DirectorySubspace':
251
"""
252
Open a subdirectory
253
254
Args:
255
tr: Transaction to use
256
name: Subdirectory name
257
layer: Expected layer identifier
258
259
Returns:
260
DirectorySubspace for the subdirectory
261
"""
262
263
def create(self, tr: Transaction, name: str, layer: bytes = b'', prefix: bytes = None) -> 'DirectorySubspace':
264
"""
265
Create a subdirectory
266
267
Args:
268
tr: Transaction to use
269
name: Subdirectory name
270
layer: Optional layer identifier
271
prefix: Optional specific prefix
272
273
Returns:
274
DirectorySubspace for the new subdirectory
275
"""
276
277
def move(self, tr: Transaction, new_path: List[str]) -> 'DirectorySubspace':
278
"""
279
Move this directory to a new path
280
281
Args:
282
tr: Transaction to use
283
new_path: New directory path
284
285
Returns:
286
DirectorySubspace for the moved directory
287
"""
288
289
def remove(self, tr: Transaction) -> bool:
290
"""
291
Remove this directory and all its contents
292
293
Args:
294
tr: Transaction to use
295
296
Returns:
297
True if directory was removed
298
"""
299
300
def list(self, tr: Transaction) -> List[str]:
301
"""
302
List subdirectories of this directory
303
304
Args:
305
tr: Transaction to use
306
307
Returns:
308
List of subdirectory names
309
"""
310
```
311
312
```java { .api }
313
// Java API
314
public class DirectorySubspace extends Subspace {
315
public List<String> getPath():
316
/**
317
* Get the directory path
318
* @return Directory path as list of strings
319
*/
320
321
public byte[] getLayer():
322
/**
323
* Get the layer identifier
324
* @return Layer identifier bytes
325
*/
326
327
public CompletableFuture<DirectorySubspace> createOrOpen(TransactionContext tcx, String name):
328
/**
329
* Create or open a subdirectory
330
* @param tcx Transaction context
331
* @param name Subdirectory name
332
* @return CompletableFuture with DirectorySubspace
333
*/
334
335
public CompletableFuture<DirectorySubspace> open(TransactionContext tcx, String name):
336
/**
337
* Open a subdirectory
338
* @param tcx Transaction context
339
* @param name Subdirectory name
340
* @return CompletableFuture with DirectorySubspace
341
*/
342
343
public CompletableFuture<DirectorySubspace> create(TransactionContext tcx, String name):
344
/**
345
* Create a subdirectory
346
* @param tcx Transaction context
347
* @param name Subdirectory name
348
* @return CompletableFuture with DirectorySubspace
349
*/
350
351
public CompletableFuture<Boolean> remove(TransactionContext tcx):
352
/**
353
* Remove this directory
354
* @param tcx Transaction context
355
* @return CompletableFuture with success boolean
356
*/
357
358
public CompletableFuture<List<String>> list(TransactionContext tcx):
359
/**
360
* List subdirectories
361
* @param tcx Transaction context
362
* @return CompletableFuture with list of subdirectory names
363
*/
364
}
365
```
366
367
```go { .api }
368
// Go API
369
type DirectorySubspace interface {
370
fdb.Subspace
371
372
GetPath() []string
373
GetLayer() []byte
374
CreateOrOpen(t fdb.Transactor, name string, layer []byte) (DirectorySubspace, error)
375
Open(t fdb.Transactor, name string, layer []byte) (DirectorySubspace, error)
376
Create(t fdb.Transactor, name string, layer []byte) (DirectorySubspace, error)
377
Remove(t fdb.Transactor) (bool, error)
378
List(t fdb.Transactor) ([]string, error)
379
}
380
```
381
382
### Directory Partitions
383
384
Special directories that act as independent directory layers, providing complete isolation.
385
386
```python { .api }
387
class DirectoryPartition(DirectorySubspace):
388
"""
389
A directory partition acts as an independent directory layer
390
"""
391
392
def __init__(self, path: List[str], prefix: bytes, parent_dir_layer: DirectoryLayer):
393
"""
394
Create a DirectoryPartition
395
396
Args:
397
path: Directory path
398
prefix: Prefix for this partition
399
parent_dir_layer: Parent directory layer
400
"""
401
402
# Inherits all DirectorySubspace methods but operations are isolated to this partition
403
```
404
405
### Exception Types
406
407
Exceptions raised by directory layer operations.
408
409
```python { .api }
410
class DirectoryError(Exception):
411
"""Base class for directory layer errors"""
412
413
class DirectoryNotFoundError(DirectoryError):
414
"""Directory does not exist"""
415
416
class DirectoryAlreadyExistsError(DirectoryError):
417
"""Directory already exists"""
418
419
class DirectoryLayerMismatchError(DirectoryError):
420
"""Directory layer does not match expected"""
421
422
class DirectoryVersionError(DirectoryError):
423
"""Directory version is not supported"""
424
```
425
426
**Usage Examples:**
427
428
**Basic Directory Operations (Python):**
429
```python
430
import fdb
431
432
fdb.api_version(630)
433
db = fdb.open()
434
435
@fdb.transactional
436
def directory_example(tr):
437
# Create application directory structure
438
app_dir = fdb.directory.create_or_open(tr, ['myapp'])
439
users_dir = app_dir.create_or_open(tr, 'users')
440
config_dir = app_dir.create_or_open(tr, 'config')
441
442
# Create user-specific directories
443
alice_dir = users_dir.create_or_open(tr, 'alice')
444
bob_dir = users_dir.create_or_open(tr, 'bob')
445
446
# Store data in user directories
447
alice_dir[tr][b'profile'] = b'{"name": "Alice", "email": "alice@example.com"}'
448
bob_dir[tr][b'profile'] = b'{"name": "Bob", "email": "bob@example.com"}'
449
450
# Store configuration
451
config_dir[tr][b'database_url'] = b'fdb://cluster'
452
config_dir[tr][b'api_key'] = b'secret123'
453
454
# List users
455
user_list = users_dir.list(tr)
456
print(f"Users: {user_list}") # Users: ['alice', 'bob']
457
458
return app_dir.get_path()
459
460
app_path = directory_example(db)
461
print(f"App directory path: {app_path}")
462
```
463
464
**Directory Management (Java):**
465
```java
466
import com.apple.foundationdb.*;
467
import com.apple.foundationdb.directory.*;
468
469
FDB fdb = FDB.selectAPIVersion(630);
470
471
try (Database db = fdb.open()) {
472
DirectoryLayer directory = DirectoryLayer.getDefault();
473
474
db.run(tr -> {
475
// Create organizational structure
476
DirectorySubspace company = directory.createOrOpen(tr, Arrays.asList("company")).join();
477
DirectorySubspace departments = company.createOrOpen(tr, "departments").join();
478
479
// Create department directories
480
DirectorySubspace engineering = departments.createOrOpen(tr, "engineering").join();
481
DirectorySubspace marketing = departments.createOrOpen(tr, "marketing").join();
482
DirectorySubspace sales = departments.createOrOpen(tr, "sales").join();
483
484
// Store department data
485
engineering.pack(Tuple.from("head_count")).set(tr, Tuple.from(25).pack());
486
marketing.pack(Tuple.from("head_count")).set(tr, Tuple.from(12).pack());
487
sales.pack(Tuple.from("head_count")).set(tr, Tuple.from(18).pack());
488
489
// List all departments
490
List<String> deptList = departments.list(tr).join();
491
System.out.println("Departments: " + deptList);
492
493
return null;
494
});
495
}
496
```
497
498
**Hierarchical Data Organization (Go):**
499
```go
500
package main
501
502
import (
503
"fmt"
504
"github.com/apple/foundationdb/bindings/go/src/fdb"
505
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
506
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
507
)
508
509
func main() {
510
fdb.MustAPIVersion(630)
511
db := fdb.MustOpenDefault()
512
513
db.Transact(func(tr fdb.Transaction) (interface{}, error) {
514
// Create e-commerce directory structure
515
ecommerce, err := directory.CreateOrOpen(tr, []string{"ecommerce"}, nil)
516
if err != nil {
517
return nil, err
518
}
519
520
// Create product catalog
521
products, err := ecommerce.CreateOrOpen(tr, "products", nil)
522
if err != nil {
523
return nil, err
524
}
525
526
// Create categories
527
electronics, err := products.CreateOrOpen(tr, "electronics", nil)
528
if err != nil {
529
return nil, err
530
}
531
532
clothing, err := products.CreateOrOpen(tr, "clothing", nil)
533
if err != nil {
534
return nil, err
535
}
536
537
// Store product data
538
laptop_key := electronics.Pack(tuple.Tuple{"laptop", "12345"})
539
tr.Set(laptop_key, []byte(`{"name": "Gaming Laptop", "price": 1299}`))
540
541
shirt_key := clothing.Pack(tuple.Tuple{"shirt", "67890"})
542
tr.Set(shirt_key, []byte(`{"name": "Cotton T-Shirt", "price": 19}`))
543
544
// List categories
545
categories, err := products.List(tr)
546
if err != nil {
547
return nil, err
548
}
549
550
fmt.Printf("Product categories: %v\n", categories)
551
552
return nil, nil
553
})
554
}
555
```
556
557
**Directory Layers and Partitions (Python):**
558
```python
559
import fdb
560
561
fdb.api_version(630)
562
db = fdb.open()
563
564
@fdb.transactional
565
def multi_tenant_example(tr):
566
# Create tenant directories with layers
567
tenant1_dir = fdb.directory.create_or_open(tr, ['tenant1'], layer=b'application')
568
tenant2_dir = fdb.directory.create_or_open(tr, ['tenant2'], layer=b'application')
569
570
# Each tenant has isolated data
571
tenant1_users = tenant1_dir.create_or_open(tr, 'users')
572
tenant1_orders = tenant1_dir.create_or_open(tr, 'orders')
573
574
tenant2_users = tenant2_dir.create_or_open(tr, 'users')
575
tenant2_orders = tenant2_dir.create_or_open(tr, 'orders')
576
577
# Store tenant-specific data
578
tenant1_users[tr][b'user:123'] = b'{"name": "Alice", "tenant": "1"}'
579
tenant1_orders[tr][b'order:456'] = b'{"user": "123", "total": 99.99}'
580
581
tenant2_users[tr][b'user:123'] = b'{"name": "Bob", "tenant": "2"}' # Same ID, different tenant
582
tenant2_orders[tr][b'order:456'] = b'{"user": "123", "total": 149.99}'
583
584
# Verify isolation - each tenant sees only their data
585
t1_users = list(tenant1_users.get_range(tr, b'', b'\xFF'))
586
t2_users = list(tenant2_users.get_range(tr, b'', b'\xFF'))
587
588
print(f"Tenant 1 users: {len(t1_users)}") # 1
589
print(f"Tenant 2 users: {len(t2_users)}") # 1
590
591
# Directory information
592
print(f"Tenant 1 path: {tenant1_dir.get_path()}")
593
print(f"Tenant 1 layer: {tenant1_dir.get_layer()}")
594
595
multi_tenant_example(db)
596
```
597
598
**Directory Cleanup and Management (Python):**
599
```python
600
import fdb
601
602
fdb.api_version(630)
603
db = fdb.open()
604
605
@fdb.transactional
606
def cleanup_example(tr):
607
# Create temporary directories
608
temp_dir = fdb.directory.create_or_open(tr, ['temp'])
609
job1_dir = temp_dir.create_or_open(tr, 'job1')
610
job2_dir = temp_dir.create_or_open(tr, 'job2')
611
612
# Add some data
613
job1_dir[tr][b'status'] = b'completed'
614
job2_dir[tr][b'status'] = b'failed'
615
616
# List temporary jobs
617
jobs = temp_dir.list(tr)
618
print(f"Temporary jobs: {jobs}")
619
620
# Remove completed job
621
job1_dir.remove(tr)
622
623
# Move failed job to retry queue
624
retry_dir = fdb.directory.create_or_open(tr, ['retry'])
625
job2_dir.move(tr, ['retry', 'job2'])
626
627
# Clean up empty temp directory if needed
628
remaining_jobs = temp_dir.list(tr)
629
if not remaining_jobs:
630
temp_dir.remove(tr)
631
print("Cleaned up empty temp directory")
632
633
# Verify final state
634
try:
635
retry_job = fdb.directory.open(tr, ['retry', 'job2'])
636
print(f"Retry job exists at: {retry_job.get_path()}")
637
except fdb.DirectoryNotFoundError:
638
print("Retry job not found")
639
640
cleanup_example(db)
641
```
642
643
**Error Handling (Python):**
644
```python
645
import fdb
646
647
fdb.api_version(630)
648
db = fdb.open()
649
650
@fdb.transactional
651
def error_handling_example(tr):
652
try:
653
# Try to open non-existent directory
654
missing_dir = fdb.directory.open(tr, ['does', 'not', 'exist'])
655
except fdb.DirectoryNotFoundError as e:
656
print(f"Directory not found: {e}")
657
658
try:
659
# Create directory that already exists
660
existing_dir = fdb.directory.create_or_open(tr, ['existing'])
661
fdb.directory.create(tr, ['existing']) # This will fail
662
except fdb.DirectoryAlreadyExistsError as e:
663
print(f"Directory already exists: {e}")
664
665
try:
666
# Layer mismatch
667
layer_dir = fdb.directory.create_or_open(tr, ['layered'], layer=b'app')
668
fdb.directory.open(tr, ['layered'], layer=b'wrong_layer')
669
except fdb.DirectoryLayerMismatchError as e:
670
print(f"Layer mismatch: {e}")
671
672
error_handling_example(db)
673
```