The Directory Layer provides a hierarchical directory system with automatic prefix allocation, enabling clean namespace management and multi-application data isolation in FoundationDB.
The Directory Layer is a system for managing related subspaces with human-readable path names. Instead of manually managing byte prefixes for different parts of your application, the Directory Layer automatically allocates unique, non-overlapping prefixes for each directory. This provides several key benefits:
Benefits over raw subspaces:
When to use Directory Layer:
When to use raw Subspaces:
Package: fdb
Module: fdb.directory_impl
Import: import fdb (directory accessible via fdb.directory)
import fdb
fdb.api_version(740)
db = fdb.open()
# Use default directory instance
users = fdb.directory.create_or_open(db, ['users'])Package: github.com/apple/foundationdb/bindings/go/src/fdb/directory
Import:
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
)db := fdb.MustOpenDefault()
users, err := directory.Root().CreateOrOpen(db, []string{"users"}, nil)Package: com.apple.foundationdb.directory
Import:
import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;Database db = FDB.selectAPIVersion(740).open();
DirectoryLayer dir = DirectoryLayer.getDefault();
DirectorySubspace users = dir.createOrOpen(db, Arrays.asList("users")).join();Module: FDB
Class: FDB::DirectoryLayer
Import: require 'fdb'
require 'fdb'
FDB.api_version(740)
db = FDB.open
# Use default directory instance
users = FDB.directory.create_or_open(db, ['users'])Directories are identified by hierarchical paths, similar to filesystem paths. Paths are represented as arrays/lists of string components:
['users'] - Top-level directory['app', 'users'] - Nested directory['app', 'users', 'profiles'] - Deeply nested directoryEach path component is a string that becomes part of the directory hierarchy.
When you create a directory, the Directory Layer automatically allocates a unique byte prefix from a pool. This prefix:
This eliminates manual prefix coordination and collision risks.
A DirectorySubspace combines the functionality of:
This dual nature allows you to manage directories hierarchically while using them as subspaces for data storage.
Layer identifiers (layer tags) are application-specific byte strings that mark directory types. They enable:
Common patterns:
b'' - Default/untyped directoryb'user_data' - Application-specific typeb'app_v2' - Version-tagged directoryPartitions are special directories that create isolated directory namespaces. They allow:
The Directory Layer stores metadata in a system subspace (by default \xFE):
This metadata enables directory operations without scanning user data.
Create new directories with automatic prefix allocation.
DirectoryLayer.create_or_open(
db_or_tr: Union[Database, Tenant, Transaction],
path: Union[str, List[str]],
layer: bytes = b'',
prefix: Optional[bytes] = None,
allow_create: bool = True,
allow_open: bool = True
) -> DirectorySubspaceCreate or open a directory. The most commonly used directory operation.
Parameters:
db_or_tr: Database, Tenant, or Transaction to usepath: Directory path as string or list of stringslayer: Layer identifier for type checkingprefix: Manual prefix (requires allow_manual_prefixes=True on DirectoryLayer)allow_create: Allow creating if doesn't exist (default True)allow_open: Allow opening if exists (default True)Returns: DirectorySubspace object
Example:
import fdb
fdb.api_version(740)
db = fdb.open()
# Create or open directory (idempotent)
users = fdb.directory.create_or_open(db, ['users'])
# With layer tag
posts = fdb.directory.create_or_open(db, ['posts'], layer=b'post_data')
# Nested directory
profiles = fdb.directory.create_or_open(db, ['users', 'profiles'])
# Use as subspace
@fdb.transactional
def store_user(tr, user_id, name):
key = users.pack((user_id, 'name'))
tr[key] = name.encode()
store_user(db, 123, 'Alice')DirectoryLayer.create(
db_or_tr: Union[Database, Tenant, Transaction],
path: Union[str, List[str]],
layer: bytes = b'',
prefix: Optional[bytes] = None
) -> DirectorySubspaceCreate a new directory. Fails if directory already exists.
Parameters:
db_or_tr: Database, Tenant, or Transaction to usepath: Directory pathlayer: Layer identifierprefix: Manual prefix (requires allow_manual_prefixes=True)Returns: DirectorySubspace object
Raises: Error if directory already exists
Example:
# Create new directory (fails if exists)
try:
analytics = fdb.directory.create(db, ['analytics'])
except fdb.FDBError as e:
print(f"Directory already exists: {e}")func (d Directory) CreateOrOpen(
t Transactor,
path []string,
layer []byte
) (DirectorySubspace, error)Create or open a directory.
Parameters:
t: Database or Transaction implementing Transactor interfacepath: Directory path as slice of stringslayer: Layer identifier (pass nil for default)Returns: DirectorySubspace and error
Example:
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
)
fdb.MustAPIVersion(740)
db := fdb.MustOpenDefault()
// Create or open directory
users, err := directory.Root().CreateOrOpen(db, []string{"users"}, nil)
if err != nil {
log.Fatal(err)
}
// With layer tag
posts, err := directory.Root().CreateOrOpen(db, []string{"posts"}, []byte("post_data"))
// Nested directory
profiles, err := directory.Root().CreateOrOpen(db, []string{"users", "profiles"}, nil)
// Use as subspace
_, err = db.Transact(func(tr fdb.Transaction) (interface{}, error) {
key := users.Pack(tuple.Tuple{123, "name"})
tr.Set(key, []byte("Alice"))
return nil, nil
})func (d Directory) Create(
t Transactor,
path []string,
layer []byte
) (DirectorySubspace, error)Create a new directory. Returns error if directory already exists.
Parameters:
t: Transactor (Database or Transaction)path: Directory pathlayer: Layer identifierReturns: DirectorySubspace and error
CompletableFuture<DirectorySubspace> createOrOpen(
TransactionContext tc,
List<String> path,
byte[] layer
)Create or open a directory asynchronously.
Parameters:
tc: Transaction context (Database, Tenant, or Transaction)path: Directory path as list of stringslayer: Layer identifierReturns: CompletableFuture<DirectorySubspace>
Example:
import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;
FDB fdb = FDB.selectAPIVersion(740);
Database db = fdb.open();
// Create or open directory
DirectorySubspace users = DirectoryLayer.getDefault()
.createOrOpen(db, Arrays.asList("users"))
.join();
// With layer tag
DirectorySubspace posts = DirectoryLayer.getDefault()
.createOrOpen(db, Arrays.asList("posts"), "post_data".getBytes())
.join();
// Nested directory
DirectorySubspace profiles = DirectoryLayer.getDefault()
.createOrOpen(db, Arrays.asList("users", "profiles"))
.join();
// Use as subspace
db.run(tr -> {
byte[] key = users.pack(Tuple.from(123, "name"));
tr.set(key, "Alice".getBytes());
return null;
});CompletableFuture<DirectorySubspace> create(
TransactionContext tc,
List<String> path,
byte[] layer
)Create a new directory. Fails if directory already exists.
Parameters:
tc: Transaction contextpath: Directory pathlayer: Layer identifierReturns: CompletableFuture<DirectorySubspace>
create_or_open(
db_or_tr,
path,
options = {}
) -> DirectorySubspaceCreate or open a directory.
Parameters:
db_or_tr: Database, Tenant, or Transactionpath: Directory path as array of stringsoptions: Hash with :layer and :prefix keysReturns: DirectorySubspace
Example:
require 'fdb'
FDB.api_version(740)
db = FDB.open
# Create or open directory
users = FDB.directory.create_or_open(db, ['users'])
# With layer tag
posts = FDB.directory.create_or_open(db, ['posts'], layer: 'post_data')
# Nested directory
profiles = FDB.directory.create_or_open(db, ['users', 'profiles'])
# Use as subspace
db.transact do |tr|
key = users.pack([123, 'name'])
tr[key] = 'Alice'
endcreate(
db_or_tr,
path,
options = {}
) -> DirectorySubspaceCreate a new directory. Raises error if directory already exists.
Parameters:
db_or_tr: Database, Tenant, or Transactionpath: Directory pathoptions: Hash with :layer and :prefix keysReturns: DirectorySubspace
Open existing directories and check for their existence.
DirectoryLayer.open(
db_or_tr: Union[Database, Tenant, Transaction],
path: Union[str, List[str]],
layer: bytes = b''
) -> DirectorySubspaceOpen an existing directory. Fails if directory doesn't exist.
Parameters:
db_or_tr: Database, Tenant, or Transaction to usepath: Directory pathlayer: Expected layer identifier (validates if provided)Returns: DirectorySubspace object
Raises: Error if directory doesn't exist or layer mismatch
Example:
# Open existing directory
try:
users = fdb.directory.open(db, ['users'])
except fdb.FDBError:
print("Directory doesn't exist")
# Open with layer validation
try:
posts = fdb.directory.open(db, ['posts'], layer=b'post_data')
except fdb.FDBError as e:
print(f"Directory not found or layer mismatch: {e}")DirectoryLayer.exists(
db_or_tr: Union[Database, Tenant, Transaction],
path: Union[str, List[str]]
) -> boolCheck if a directory exists.
Parameters:
db_or_tr: Database, Tenant, or Transaction to usepath: Directory path to checkReturns: True if directory exists, False otherwise
Example:
# Check before opening
if fdb.directory.exists(db, ['users']):
users = fdb.directory.open(db, ['users'])
else:
users = fdb.directory.create(db, ['users'])
# Conditional operations
@fdb.transactional
def ensure_directory(tr, path):
if not fdb.directory.exists(tr, path):
fdb.directory.create(tr, path)func (d Directory) Open(
rt ReadTransactor,
path []string,
layer []byte
) (DirectorySubspace, error)Open an existing directory.
Parameters:
rt: ReadTransactor (Database or Transaction)path: Directory pathlayer: Expected layer identifierReturns: DirectorySubspace and error
Example:
// Open existing directory
users, err := directory.Root().Open(db, []string{"users"}, nil)
if err != nil {
log.Printf("Directory doesn't exist: %v", err)
}
// Open with layer validation
posts, err := directory.Root().Open(db, []string{"posts"}, []byte("post_data"))
if err != nil {
log.Printf("Directory not found or layer mismatch: %v", err)
}func (d Directory) Exists(
rt ReadTransactor,
path []string
) (bool, error)Check if a directory exists.
Parameters:
rt: ReadTransactorpath: Directory pathReturns: Boolean and error
Example:
exists, err := directory.Root().Exists(db, []string{"users"})
if err != nil {
log.Fatal(err)
}
if exists {
users, _ := directory.Root().Open(db, []string{"users"}, nil)
// Use directory
}CompletableFuture<DirectorySubspace> open(
ReadTransactionContext rtc,
List<String> path,
byte[] layer
)Open an existing directory asynchronously.
Parameters:
rtc: Read transaction contextpath: Directory pathlayer: Expected layer identifierReturns: CompletableFuture<DirectorySubspace>
Example:
// Open existing directory
DirectorySubspace users = DirectoryLayer.getDefault()
.open(db, Arrays.asList("users"))
.join();
// Open with layer validation
try {
DirectorySubspace posts = DirectoryLayer.getDefault()
.open(db, Arrays.asList("posts"), "post_data".getBytes())
.join();
} catch (Exception e) {
System.out.println("Directory not found or layer mismatch: " + e);
}CompletableFuture<Boolean> exists(
ReadTransactionContext rtc,
List<String> path
)Check if a directory exists.
Parameters:
rtc: Read transaction contextpath: Directory pathReturns: CompletableFuture<Boolean>
Example:
boolean exists = DirectoryLayer.getDefault()
.exists(db, Arrays.asList("users"))
.join();
if (exists) {
DirectorySubspace users = DirectoryLayer.getDefault()
.open(db, Arrays.asList("users"))
.join();
}open(
db_or_tr,
path,
options = {}
) -> DirectorySubspaceOpen an existing directory.
Parameters:
db_or_tr: Database, Tenant, or Transactionpath: Directory pathoptions: Hash with :layer key for validationReturns: DirectorySubspace
Raises: Error if directory doesn't exist or layer mismatch
Example:
# Open existing directory
begin
users = FDB.directory.open(db, ['users'])
rescue FDB::Error => e
puts "Directory doesn't exist"
end
# Open with layer validation
begin
posts = FDB.directory.open(db, ['posts'], layer: 'post_data')
rescue FDB::Error => e
puts "Directory not found or layer mismatch: #{e}"
endexists?(
db_or_tr,
path
) -> BooleanCheck if a directory exists.
Parameters:
db_or_tr: Database, Tenant, or Transactionpath: Directory pathReturns: true or false
Example:
# Check before opening
if FDB.directory.exists?(db, ['users'])
users = FDB.directory.open(db, ['users'])
else
users = FDB.directory.create(db, ['users'])
endList subdirectories within a directory.
DirectoryLayer.list(
db_or_tr: Union[Database, Tenant, Transaction],
path: Union[str, List[str]] = ()
) -> List[str]List subdirectories at the given path.
Parameters:
db_or_tr: Database, Tenant, or Transaction to usepath: Directory path to list (empty tuple/list for root)Returns: List of subdirectory name strings
Example:
import fdb
fdb.api_version(740)
db = fdb.open()
# Create directory structure
fdb.directory.create_or_open(db, ['app', 'users'])
fdb.directory.create_or_open(db, ['app', 'posts'])
fdb.directory.create_or_open(db, ['app', 'comments'])
# List subdirectories
subdirs = fdb.directory.list(db, ['app'])
# ['users', 'posts', 'comments']
# List root directories
root_dirs = fdb.directory.list(db)
# ['app']
# List in transaction
@fdb.transactional
def list_user_types(tr):
return fdb.directory.list(tr, ['users'])func (d Directory) List(
rt ReadTransactor,
path []string
) ([]string, error)List subdirectories at the given path.
Parameters:
rt: ReadTransactor (Database or Transaction)path: Directory path (empty slice for root)Returns: Slice of subdirectory names and error
Example:
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
)
// Create directory structure
directory.Root().CreateOrOpen(db, []string{"app", "users"}, nil)
directory.Root().CreateOrOpen(db, []string{"app", "posts"}, nil)
directory.Root().CreateOrOpen(db, []string{"app", "comments"}, nil)
// List subdirectories
subdirs, err := directory.Root().List(db, []string{"app"})
if err != nil {
log.Fatal(err)
}
// subdirs: ["users", "posts", "comments"]
// List root directories
rootDirs, err := directory.Root().List(db, []string{})
// rootDirs: ["app"]CompletableFuture<List<String>> list(
ReadTransactionContext rtc,
List<String> path
)List subdirectories at the given path asynchronously.
Parameters:
rtc: Read transaction contextpath: Directory path (empty list for root)Returns: CompletableFuture<List<String>> of subdirectory names
Example:
import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import java.util.*;
// Create directory structure
DirectoryLayer dir = DirectoryLayer.getDefault();
dir.createOrOpen(db, Arrays.asList("app", "users")).join();
dir.createOrOpen(db, Arrays.asList("app", "posts")).join();
dir.createOrOpen(db, Arrays.asList("app", "comments")).join();
// List subdirectories
List<String> subdirs = dir.list(db, Arrays.asList("app")).join();
// ["users", "posts", "comments"]
// List root directories
List<String> rootDirs = dir.list(db, Collections.emptyList()).join();
// ["app"]list(
db_or_tr,
path = []
) -> ArrayList subdirectories at the given path.
Parameters:
db_or_tr: Database, Tenant, or Transactionpath: Directory path as array (empty for root)Returns: Array of subdirectory name strings
Example:
require 'fdb'
FDB.api_version(740)
db = FDB.open
# Create directory structure
FDB.directory.create_or_open(db, ['app', 'users'])
FDB.directory.create_or_open(db, ['app', 'posts'])
FDB.directory.create_or_open(db, ['app', 'comments'])
# List subdirectories
subdirs = FDB.directory.list(db, ['app'])
# ['users', 'posts', 'comments']
# List root directories
root_dirs = FDB.directory.list(db)
# ['app']
# List in transaction
subdirs = db.transact do |tr|
FDB.directory.list(tr, ['users'])
endMove or rename directories while preserving their contents and allocated prefix.
DirectoryLayer.move(
db_or_tr: Union[Database, Tenant, Transaction],
old_path: Union[str, List[str]],
new_path: Union[str, List[str]]
) -> DirectorySubspaceMove a directory to a new path. The directory's allocated prefix remains unchanged, so all existing data keys remain valid. Only the directory metadata is updated.
Parameters:
db_or_tr: Database, Tenant, or Transaction to useold_path: Current directory pathnew_path: New directory pathReturns: DirectorySubspace object at new path
Raises: Error if old path doesn't exist or new path already exists
Example:
import fdb
fdb.api_version(740)
db = fdb.open()
# Create directory and add data
users = fdb.directory.create_or_open(db, ['users'])
with db.create_transaction() as tr:
tr[users.pack((123,))] = b'Alice'
tr.commit().wait()
# Rename directory
users_v2 = fdb.directory.move(db, ['users'], ['users_v2'])
# Data is still accessible with new directory object
with db.create_transaction() as tr:
value = tr[users_v2.pack((123,))]
print(value) # b'Alice'
# Move to nested path
fdb.directory.move(db, ['users_v2'], ['app', 'users'])
# Cannot move to existing directory
try:
fdb.directory.move(db, ['app', 'users'], ['posts'])
except fdb.FDBError:
print("Destination already exists")func (d Directory) Move(
t Transactor,
oldPath []string,
newPath []string
) (DirectorySubspace, error)Move a directory to a new path.
Parameters:
t: Transactor (Database or Transaction)oldPath: Current directory pathnewPath: New directory pathReturns: DirectorySubspace at new path and error
Example:
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
// Create directory and add data
users, _ := directory.Root().CreateOrOpen(db, []string{"users"}, nil)
db.Transact(func(tr fdb.Transaction) (interface{}, error) {
key := users.Pack(tuple.Tuple{123})
tr.Set(key, []byte("Alice"))
return nil, nil
})
// Rename directory
usersV2, err := directory.Root().Move(db, []string{"users"}, []string{"users_v2"})
if err != nil {
log.Fatal(err)
}
// Data is still accessible
db.ReadTransact(func(tr fdb.ReadTransaction) (interface{}, error) {
key := usersV2.Pack(tuple.Tuple{123})
value := tr.Get(key).MustGet()
fmt.Printf("Value: %s\n", value) // Value: Alice
return nil, nil
})
// Move to nested path
directory.Root().Move(db, []string{"users_v2"}, []string{"app", "users"})CompletableFuture<DirectorySubspace> move(
TransactionContext tc,
List<String> oldPath,
List<String> newPath
)Move a directory to a new path asynchronously.
Parameters:
tc: Transaction contextoldPath: Current directory pathnewPath: New directory pathReturns: CompletableFuture<DirectorySubspace> at new path
Example:
import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;
DirectoryLayer dir = DirectoryLayer.getDefault();
// Create directory and add data
DirectorySubspace users = dir.createOrOpen(db, Arrays.asList("users")).join();
db.run(tr -> {
byte[] key = users.pack(Tuple.from(123));
tr.set(key, "Alice".getBytes());
return null;
});
// Rename directory
DirectorySubspace usersV2 = dir.move(
db,
Arrays.asList("users"),
Arrays.asList("users_v2")
).join();
// Data is still accessible
db.read(tr -> {
byte[] key = usersV2.pack(Tuple.from(123));
byte[] value = tr.get(key).join();
System.out.println("Value: " + new String(value)); // Value: Alice
return null;
});
// Move to nested path
dir.move(db, Arrays.asList("users_v2"), Arrays.asList("app", "users")).join();move(
db_or_tr,
old_path,
new_path
) -> DirectorySubspaceMove a directory to a new path.
Parameters:
db_or_tr: Database, Tenant, or Transactionold_path: Current directory path as arraynew_path: New directory path as arrayReturns: DirectorySubspace at new path
Raises: Error if old path doesn't exist or new path already exists
Example:
require 'fdb'
FDB.api_version(740)
db = FDB.open
# Create directory and add data
users = FDB.directory.create_or_open(db, ['users'])
db.transact do |tr|
tr[users.pack([123])] = 'Alice'
end
# Rename directory
users_v2 = FDB.directory.move(db, ['users'], ['users_v2'])
# Data is still accessible
value = db.transact do |tr|
tr[users_v2.pack([123])]
end
puts value # Alice
# Move to nested path
FDB.directory.move(db, ['users_v2'], ['app', 'users'])Remove directories and all their contents.
DirectoryLayer.remove(
db_or_tr: Union[Database, Tenant, Transaction],
path: Union[str, List[str]]
) -> boolRemove a directory and all its contents. This operation:
Parameters:
db_or_tr: Database, Tenant, or Transaction to usepath: Directory path to removeReturns: True if removed
Raises: Error if directory doesn't exist
Warning: This deletes all data stored in the directory!
Example:
import fdb
fdb.api_version(740)
db = fdb.open()
# Create directory with data
temp = fdb.directory.create_or_open(db, ['temp'])
with db.create_transaction() as tr:
tr[temp.pack((1,))] = b'data1'
tr[temp.pack((2,))] = b'data2'
tr.commit().wait()
# Remove directory and all contents
fdb.directory.remove(db, ['temp'])
# Verify removal
exists = fdb.directory.exists(db, ['temp'])
# False
# Error if directory doesn't exist
try:
fdb.directory.remove(db, ['nonexistent'])
except fdb.FDBError:
print("Directory doesn't exist")DirectoryLayer.remove_if_exists(
db_or_tr: Union[Database, Tenant, Transaction],
path: Union[str, List[str]]
) -> boolRemove a directory if it exists. Safe version of remove() that doesn't raise an error for non-existent directories.
Parameters:
db_or_tr: Database, Tenant, or Transaction to usepath: Directory path to removeReturns: True if directory existed and was removed, False if didn't exist
Example:
# Safe removal - no error if doesn't exist
removed = fdb.directory.remove_if_exists(db, ['temp'])
if removed:
print("Directory removed")
else:
print("Directory didn't exist")
# Cleanup pattern
def cleanup_temp_directories(db):
temp_dirs = ['temp1', 'temp2', 'temp3']
for name in temp_dirs:
fdb.directory.remove_if_exists(db, ['temp', name])func (d Directory) Remove(
t Transactor,
path []string
) (bool, error)Remove a directory and all its contents.
Parameters:
t: Transactor (Database or Transaction)path: Directory path to removeReturns: Boolean indicating removal and error
Example:
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
// Create directory with data
temp, _ := directory.Root().CreateOrOpen(db, []string{"temp"}, nil)
db.Transact(func(tr fdb.Transaction) (interface{}, error) {
tr.Set(temp.Pack(tuple.Tuple{1}), []byte("data1"))
tr.Set(temp.Pack(tuple.Tuple{2}), []byte("data2"))
return nil, nil
})
// Remove directory and all contents
removed, err := directory.Root().Remove(db, []string{"temp"})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Removed: %v\n", removed)
// Verify removal
exists, _ := directory.Root().Exists(db, []string{"temp"})
// exists: falsefunc (d Directory) RemoveIfExists(
t Transactor,
path []string
) (bool, error)Remove a directory if it exists.
Parameters:
t: Transactorpath: Directory pathReturns: Boolean indicating if directory existed and was removed, and error
Example:
// Safe removal
removed, err := directory.Root().RemoveIfExists(db, []string{"temp"})
if err != nil {
log.Fatal(err)
}
if removed {
fmt.Println("Directory removed")
} else {
fmt.Println("Directory didn't exist")
}CompletableFuture<Boolean> remove(
TransactionContext tc,
List<String> path
)Remove a directory and all its contents asynchronously.
Parameters:
tc: Transaction contextpath: Directory path to removeReturns: CompletableFuture<Boolean>
Example:
import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;
DirectoryLayer dir = DirectoryLayer.getDefault();
// Create directory with data
DirectorySubspace temp = dir.createOrOpen(db, Arrays.asList("temp")).join();
db.run(tr -> {
tr.set(temp.pack(Tuple.from(1)), "data1".getBytes());
tr.set(temp.pack(Tuple.from(2)), "data2".getBytes());
return null;
});
// Remove directory and all contents
boolean removed = dir.remove(db, Arrays.asList("temp")).join();
System.out.println("Removed: " + removed);
// Verify removal
boolean exists = dir.exists(db, Arrays.asList("temp")).join();
// exists: falseCompletableFuture<Boolean> removeIfExists(
TransactionContext tc,
List<String> path
)Remove a directory if it exists.
Parameters:
tc: Transaction contextpath: Directory pathReturns: CompletableFuture<Boolean> indicating if removed
Example:
// Safe removal
boolean removed = dir.removeIfExists(db, Arrays.asList("temp")).join();
if (removed) {
System.out.println("Directory removed");
} else {
System.out.println("Directory didn't exist");
}remove(
db_or_tr,
path
) -> BooleanRemove a directory and all its contents.
Parameters:
db_or_tr: Database, Tenant, or Transactionpath: Directory path as arrayReturns: true
Raises: Error if directory doesn't exist
Example:
require 'fdb'
FDB.api_version(740)
db = FDB.open
# Create directory with data
temp = FDB.directory.create_or_open(db, ['temp'])
db.transact do |tr|
tr[temp.pack([1])] = 'data1'
tr[temp.pack([2])] = 'data2'
end
# Remove directory and all contents
FDB.directory.remove(db, ['temp'])
# Verify removal
exists = FDB.directory.exists?(db, ['temp'])
# falseThe DirectorySubspace class combines directory management with subspace functionality, allowing you to both manage the directory hierarchically and use it for data storage.
class DirectorySubspaceA directory that is also a Subspace. Inherits all methods from both Directory and Subspace.
Inherited from Subspace:
key() - Get raw prefix bytespack(items) - Encode tuple with directory prefixunpack(key) - Decode key to tuplerange(items) - Get key range for tuple prefixcontains(key) - Check if key is in directorysubspace(items) - Create nested subspaceDirectory-specific properties:
DirectorySubspace.get_layer() -> bytesGet the layer identifier for the directory.
Returns: Layer bytes
DirectorySubspace.get_path() -> List[str]Get the directory path.
Returns: List of path components
Example:
import fdb
fdb.api_version(740)
db = fdb.open()
# Create directory with layer tag
users = fdb.directory.create_or_open(db, ['users'], layer=b'user_data_v2')
# Use as subspace
@fdb.transactional
def store_user(tr, user_id, name, email):
# Pack data with tuple encoding
name_key = users.pack((user_id, 'name'))
email_key = users.pack((user_id, 'email'))
tr[name_key] = name.encode()
tr[email_key] = email.encode()
store_user(db, 123, 'Alice', 'alice@example.com')
# Read with range query
@fdb.transactional
def get_user_data(tr, user_id):
begin, end = users.range((user_id,))
data = {}
for kv in tr.get_range(begin, end):
field = users.unpack(kv.key)[-1]
data[field] = kv.value.decode()
return data
user_data = get_user_data(db, 123)
# {'name': 'Alice', 'email': 'alice@example.com'}
# Directory properties
print(f"Path: {users.get_path()}") # ['users']
print(f"Layer: {users.get_layer()}") # b'user_data_v2'
print(f"Prefix: {users.key().hex()}") # Allocated prefix bytes
# Create nested subspace
profiles = users.subspace(('profiles',))
profile_key = profiles.pack((123,))type DirectorySubspace interface {
Directory
subspace.Subspace
GetLayer() []byte
GetPath() []string
}A directory that is also a Subspace. Provides all methods from both interfaces.
Subspace methods:
Bytes() - Get raw prefix bytesPack(t tuple.Tuple) - Encode tuple with directory prefixUnpack(k fdb.KeyConvertible) - Decode key to tupleContains(k fdb.KeyConvertible) - Check if key is in directorySub(el ...tuple.TupleElement) - Create nested subspaceExample:
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
// Create directory with layer tag
users, err := directory.Root().CreateOrOpen(
db,
[]string{"users"},
[]byte("user_data_v2"),
)
if err != nil {
log.Fatal(err)
}
// Use as subspace
_, err = db.Transact(func(tr fdb.Transaction) (interface{}, error) {
// Pack data with tuple encoding
nameKey := users.Pack(tuple.Tuple{123, "name"})
emailKey := users.Pack(tuple.Tuple{123, "email"})
tr.Set(nameKey, []byte("Alice"))
tr.Set(emailKey, []byte("alice@example.com"))
return nil, nil
})
// Read with range query
userData, _ := db.ReadTransact(func(tr fdb.ReadTransaction) (interface{}, error) {
begin, end := users.FDBRangeKeys()
ri := tr.GetRange(fdb.KeyRange{Begin: begin, End: end}, fdb.RangeOptions{}).Iterator()
data := make(map[string]string)
for ri.Advance() {
kv, err := ri.Get()
if err != nil {
return nil, err
}
t, _ := users.Unpack(kv.Key)
field := t[len(t)-1].(string)
data[field] = string(kv.Value)
}
return data, nil
})
// Directory properties
fmt.Printf("Path: %v\n", users.GetPath()) // [users]
fmt.Printf("Layer: %s\n", users.GetLayer()) // user_data_v2
fmt.Printf("Prefix: %x\n", users.Bytes()) // Allocated prefix bytes
// Create nested subspace
profiles := users.Sub("profiles")
profileKey := profiles.Pack(tuple.Tuple{123})interface DirectorySubspace extends Directory, Subspace {
byte[] getLayer();
List<String> getPath();
}A directory that is also a Subspace. Implements both interfaces.
Subspace methods:
getKey() - Get raw prefix bytespack(Tuple tuple) - Encode tuple with directory prefixunpack(byte[] key) - Decode key to tuplecontains(byte[] key) - Check if key is in directoryget(Object... items) - Create nested subspaceExample:
import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;
// Create directory with layer tag
DirectorySubspace users = DirectoryLayer.getDefault()
.createOrOpen(db, Arrays.asList("users"), "user_data_v2".getBytes())
.join();
// Use as subspace
db.run(tr -> {
// Pack data with tuple encoding
byte[] nameKey = users.pack(Tuple.from(123, "name"));
byte[] emailKey = users.pack(Tuple.from(123, "email"));
tr.set(nameKey, "Alice".getBytes());
tr.set(emailKey, "alice@example.com".getBytes());
return null;
});
// Read with range query
db.read(tr -> {
Range range = users.range(Tuple.from(123));
Map<String, String> data = new HashMap<>();
for (KeyValue kv : tr.getRange(range)) {
Tuple t = users.unpack(kv.getKey());
String field = t.getString(t.size() - 1);
data.put(field, new String(kv.getValue()));
}
System.out.println(data);
// {name=Alice, email=alice@example.com}
return null;
});
// Directory properties
System.out.println("Path: " + users.getPath()); // [users]
System.out.println("Layer: " + new String(users.getLayer())); // user_data_v2
System.out.println("Prefix: " + bytesToHex(users.getKey())); // Allocated prefix
// Create nested subspace
Subspace profiles = users.get("profiles");
byte[] profileKey = profiles.pack(Tuple.from(123));class DirectorySubspace < SubspaceA directory that is also a Subspace. Inherits from Subspace and includes directory methods.
Subspace methods:
key() - Get raw prefix bytespack(items) - Encode tuple with directory prefixunpack(key) - Decode key to tuplerange(items) - Get key range for tuple prefixcontains?(key) - Check if key is in directorysubspace(items) - Create nested subspaceDirectory properties:
DirectorySubspace#layer -> StringGet the layer identifier for the directory.
Returns: Layer bytes
DirectorySubspace#path -> ArrayGet the directory path.
Returns: Array of path components
Example:
require 'fdb'
FDB.api_version(740)
db = FDB.open
# Create directory with layer tag
users = FDB.directory.create_or_open(db, ['users'], layer: 'user_data_v2')
# Use as subspace
db.transact do |tr|
# Pack data with tuple encoding
name_key = users.pack([123, 'name'])
email_key = users.pack([123, 'email'])
tr[name_key] = 'Alice'
tr[email_key] = 'alice@example.com'
end
# Read with range query
user_data = db.transact do |tr|
begin_key, end_key = users.range([123])
data = {}
tr.get_range(begin_key, end_key).each do |kv|
field = users.unpack(kv.key).last
data[field] = kv.value
end
data
end
# {'name' => 'Alice', 'email' => 'alice@example.com'}
# Directory properties
puts "Path: #{users.path.inspect}" # ["users"]
puts "Layer: #{users.layer}" # user_data_v2
puts "Prefix: #{users.key.unpack('H*')}" # Allocated prefix bytes
# Create nested subspace
profiles = users.subspace(['profiles'])
profile_key = profiles.pack([123])Layer tags are application-specific identifiers that mark directory types and enable type checking.
Layer tags allow you to:
Common patterns:
b'' - Default, untyped directoryb'myapp:' - Application namespaceb'users_v2' - Versioned schemab'timeseries' - Data type indicatorimport fdb
fdb.api_version(740)
db = fdb.open()
# Create directories with different layer tags
users_v1 = fdb.directory.create_or_open(db, ['users'], layer=b'user_schema_v1')
users_v2 = fdb.directory.create_or_open(db, ['users_new'], layer=b'user_schema_v2')
analytics = fdb.directory.create_or_open(db, ['analytics'], layer=b'timeseries')
# Layer validation on open
try:
# This succeeds - layer matches
users_dir = fdb.directory.open(db, ['users'], layer=b'user_schema_v1')
except fdb.FDBError:
print("Layer mismatch")
try:
# This fails - wrong layer
users_dir = fdb.directory.open(db, ['users'], layer=b'user_schema_v2')
except fdb.FDBError:
print("Layer mismatch detected!")
# Migration pattern
def migrate_users_v1_to_v2(db):
"""Migrate from v1 to v2 schema"""
old_dir = fdb.directory.open(db, ['users'], layer=b'user_schema_v1')
new_dir = fdb.directory.create_or_open(db, ['users_v2'], layer=b'user_schema_v2')
@fdb.transactional
def copy_and_transform(tr):
# Read from old directory
begin, end = old_dir.range()
for kv in tr.get_range(begin, end):
# Transform data for v2 schema
old_data = old_dir.unpack(kv.key)
new_data = transform_to_v2(old_data, kv.value)
# Write to new directory
new_key = new_dir.pack(new_data[0])
tr[new_key] = new_data[1]
copy_and_transform(db)
# After migration, remove old directory
fdb.directory.remove(db, ['users'])
fdb.directory.move(db, ['users_v2'], ['users'])
# Application-specific layers for coexistence
APP_A_LAYER = b'application_a:'
APP_B_LAYER = b'application_b:'
app_a_users = fdb.directory.create_or_open(
db, ['shared', 'users'],
layer=APP_A_LAYER
)
app_b_users = fdb.directory.create_or_open(
db, ['shared', 'users_b'],
layer=APP_B_LAYER
)import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
)
// Create directories with different layer tags
usersV1, _ := directory.Root().CreateOrOpen(
db,
[]string{"users"},
[]byte("user_schema_v1"),
)
usersV2, _ := directory.Root().CreateOrOpen(
db,
[]string{"users_new"},
[]byte("user_schema_v2"),
)
analytics, _ := directory.Root().CreateOrOpen(
db,
[]string{"analytics"},
[]byte("timeseries"),
)
// Layer validation on open
usersDir, err := directory.Root().Open(
db,
[]string{"users"},
[]byte("user_schema_v1"),
)
// Success - layer matches
_, err = directory.Root().Open(
db,
[]string{"users"},
[]byte("user_schema_v2"),
)
// Error - layer mismatch
// Application-specific layers
const (
AppALayer = "application_a:"
AppBLayer = "application_b:"
)
appAUsers, _ := directory.Root().CreateOrOpen(
db,
[]string{"shared", "users"},
[]byte(AppALayer),
)
appBUsers, _ := directory.Root().CreateOrOpen(
db,
[]string{"shared", "users_b"},
[]byte(AppBLayer),
)import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import java.util.*;
DirectoryLayer dir = DirectoryLayer.getDefault();
// Create directories with different layer tags
DirectorySubspace usersV1 = dir.createOrOpen(
db,
Arrays.asList("users"),
"user_schema_v1".getBytes()
).join();
DirectorySubspace usersV2 = dir.createOrOpen(
db,
Arrays.asList("users_new"),
"user_schema_v2".getBytes()
).join();
DirectorySubspace analytics = dir.createOrOpen(
db,
Arrays.asList("analytics"),
"timeseries".getBytes()
).join();
// Layer validation on open
try {
DirectorySubspace usersDir = dir.open(
db,
Arrays.asList("users"),
"user_schema_v1".getBytes()
).join();
// Success - layer matches
} catch (Exception e) {
System.out.println("Layer mismatch");
}
try {
DirectorySubspace usersDir = dir.open(
db,
Arrays.asList("users"),
"user_schema_v2".getBytes()
).join();
} catch (Exception e) {
System.out.println("Layer mismatch detected!");
}
// Application-specific layers
final byte[] APP_A_LAYER = "application_a:".getBytes();
final byte[] APP_B_LAYER = "application_b:".getBytes();
DirectorySubspace appAUsers = dir.createOrOpen(
db,
Arrays.asList("shared", "users"),
APP_A_LAYER
).join();
DirectorySubspace appBUsers = dir.createOrOpen(
db,
Arrays.asList("shared", "users_b"),
APP_B_LAYER
).join();require 'fdb'
FDB.api_version(740)
db = FDB.open
# Create directories with different layer tags
users_v1 = FDB.directory.create_or_open(db, ['users'], layer: 'user_schema_v1')
users_v2 = FDB.directory.create_or_open(db, ['users_new'], layer: 'user_schema_v2')
analytics = FDB.directory.create_or_open(db, ['analytics'], layer: 'timeseries')
# Layer validation on open
begin
# This succeeds - layer matches
users_dir = FDB.directory.open(db, ['users'], layer: 'user_schema_v1')
rescue FDB::Error
puts "Layer mismatch"
end
begin
# This fails - wrong layer
users_dir = FDB.directory.open(db, ['users'], layer: 'user_schema_v2')
rescue FDB::Error
puts "Layer mismatch detected!"
end
# Application-specific layers
APP_A_LAYER = 'application_a:'
APP_B_LAYER = 'application_b:'
app_a_users = FDB.directory.create_or_open(
db,
['shared', 'users'],
layer: APP_A_LAYER
)
app_b_users = FDB.directory.create_or_open(
db,
['shared', 'users_b'],
layer: APP_B_LAYER
)By default, the Directory Layer automatically allocates prefixes. For special cases, you can manually specify prefixes.
Manual prefix specification must be explicitly enabled when creating a DirectoryLayer instance.
import fdb
from fdb import directory_impl
fdb.api_version(740)
db = fdb.open()
# Create DirectoryLayer that allows manual prefixes
custom_dir_layer = directory_impl.DirectoryLayer(
allow_manual_prefixes=True
)
# Specify manual prefix
system_dir = custom_dir_layer.create_or_open(
db,
['system'],
prefix=b'\x00\x00'
)
# Use specified prefix
print(system_dir.key().hex()) # 0000
# Note: Be very careful with manual prefixes
# - Ensure no collisions with other manual prefixes
# - Don't overlap with automatically allocated prefixes
# - Manual prefixes cannot be movedimport (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
)
// Create DirectoryLayer that allows manual prefixes
nodeSubspace := subspace.FromBytes([]byte("\xFE"))
contentSubspace := subspace.FromBytes([]byte{})
customDirLayer := directory.NewDirectoryLayer(
nodeSubspace,
contentSubspace,
true, // allowManualPrefixes
)
// Note: Manual prefix specification in Go requires
// using the appropriate Create method variant
// (exact API may vary by version)import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.subspace.Subspace;
// Create DirectoryLayer that allows manual prefixes
Subspace nodeSubspace = new Subspace(new byte[]{(byte)0xFE});
Subspace contentSubspace = new Subspace();
DirectoryLayer customDirLayer = new DirectoryLayer(
nodeSubspace,
contentSubspace,
true // allowManualPrefixes
);
// Specify manual prefix
DirectorySubspace systemDir = customDirLayer.create(
db,
Arrays.asList("system"),
null, // layer
new byte[]{0x00, 0x00} // manual prefix
).join();
System.out.println(bytesToHex(systemDir.getKey())); // 0000require 'fdb'
FDB.api_version(740)
db = FDB.open
# Create DirectoryLayer that allows manual prefixes
custom_dir_layer = FDB::DirectoryLayer.new(
allow_manual_prefixes: true
)
# Specify manual prefix
system_dir = custom_dir_layer.create_or_open(
db,
['system'],
prefix: "\x00\x00"
)
puts system_dir.key.unpack('H*') # 0000Important considerations for manual prefixes:
move()\xFE, \xFF) to avoid conflictsFundamental patterns for working with directories.
import fdb
fdb.api_version(740)
db = fdb.open()
# Basic directory creation and usage
users = fdb.directory.create_or_open(db, ['users'])
posts = fdb.directory.create_or_open(db, ['posts'])
@fdb.transactional
def create_user(tr, user_id, name, email):
"""Store user data in users directory"""
tr[users.pack((user_id, 'name'))] = name.encode()
tr[users.pack((user_id, 'email'))] = email.encode()
@fdb.transactional
def get_user(tr, user_id):
"""Retrieve all data for a user"""
begin, end = users.range((user_id,))
user_data = {}
for kv in tr.get_range(begin, end):
field = users.unpack(kv.key)[-1]
user_data[field] = kv.value.decode()
return user_data
@fdb.transactional
def create_post(tr, post_id, author_id, content):
"""Store post data in posts directory"""
tr[posts.pack((post_id, 'author'))] = str(author_id).encode()
tr[posts.pack((post_id, 'content'))] = content.encode()
# Use the functions
create_user(db, 123, 'Alice', 'alice@example.com')
create_user(db, 456, 'Bob', 'bob@example.com')
create_post(db, 1, 123, 'Hello from Alice!')
create_post(db, 2, 456, 'Hello from Bob!')
# Retrieve data
user = get_user(db, 123)
print(f"User: {user}")
# User: {'name': 'Alice', 'email': 'alice@example.com'}
# List all users
@fdb.transactional
def list_all_users(tr):
begin, end = users.range()
user_ids = set()
for kv in tr.get_range(begin, end):
user_id = users.unpack(kv.key)[0]
user_ids.add(user_id)
return list(user_ids)
all_users = list_all_users(db)
print(f"All users: {all_users}")
# All users: [123, 456]import (
"fmt"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/directory"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
fdb.MustAPIVersion(740)
db := fdb.MustOpenDefault()
// Basic directory creation and usage
users, _ := directory.Root().CreateOrOpen(db, []string{"users"}, nil)
posts, _ := directory.Root().CreateOrOpen(db, []string{"posts"}, nil)
// Create user
createUser := func(userID int, name, email string) error {
_, err := db.Transact(func(tr fdb.Transaction) (interface{}, error) {
tr.Set(users.Pack(tuple.Tuple{userID, "name"}), []byte(name))
tr.Set(users.Pack(tuple.Tuple{userID, "email"}), []byte(email))
return nil, nil
})
return err
}
// Get user
getUser := func(userID int) (map[string]string, error) {
return db.ReadTransact(func(tr fdb.ReadTransaction) (interface{}, error) {
begin, end := users.FDBRangeKeys()
ri := tr.GetRange(fdb.KeyRange{Begin: begin, End: end}, fdb.RangeOptions{}).Iterator()
userData := make(map[string]string)
for ri.Advance() {
kv, err := ri.Get()
if err != nil {
return nil, err
}
t, _ := users.Unpack(kv.Key)
if t[0].(int64) == int64(userID) {
field := t[1].(string)
userData[field] = string(kv.Value)
}
}
return userData, nil
})
}
// Create post
createPost := func(postID, authorID int, content string) error {
_, err := db.Transact(func(tr fdb.Transaction) (interface{}, error) {
tr.Set(posts.Pack(tuple.Tuple{postID, "author"}), []byte(fmt.Sprintf("%d", authorID)))
tr.Set(posts.Pack(tuple.Tuple{postID, "content"}), []byte(content))
return nil, nil
})
return err
}
// Use the functions
createUser(123, "Alice", "alice@example.com")
createUser(456, "Bob", "bob@example.com")
createPost(1, 123, "Hello from Alice!")
createPost(2, 456, "Hello from Bob!")
// Retrieve data
userData, _ := getUser(123)
fmt.Printf("User: %v\n", userData)
// User: map[email:alice@example.com name:Alice]Organizing data with nested directory structures.
import fdb
fdb.api_version(740)
db = fdb.open()
# Create hierarchical directory structure
app_dir = fdb.directory.create_or_open(db, ['myapp'])
users_dir = fdb.directory.create_or_open(db, ['myapp', 'users'])
profiles_dir = fdb.directory.create_or_open(db, ['myapp', 'users', 'profiles'])
settings_dir = fdb.directory.create_or_open(db, ['myapp', 'users', 'settings'])
posts_dir = fdb.directory.create_or_open(db, ['myapp', 'posts'])
comments_dir = fdb.directory.create_or_open(db, ['myapp', 'posts', 'comments'])
# Store data in hierarchical structure
@fdb.transactional
def create_user_with_profile(tr, user_id, name, bio, theme):
# Basic user data
tr[users_dir.pack((user_id,))] = name.encode()
# Profile information
tr[profiles_dir.pack((user_id, 'bio'))] = bio.encode()
# User settings
tr[settings_dir.pack((user_id, 'theme'))] = theme.encode()
create_user_with_profile(db, 123, 'Alice', 'Software developer', 'dark')
# List directory structure
def show_directory_tree(db, path=(), indent=0):
"""Recursively display directory structure"""
subdirs = fdb.directory.list(db, path)
for name in subdirs:
print(' ' * indent + name)
subpath = list(path) + [name]
show_directory_tree(db, subpath, indent + 1)
print("Directory structure:")
show_directory_tree(db)
# Directory structure:
# myapp
# users
# profiles
# settings
# posts
# comments
# Retrieve data from hierarchy
@fdb.transactional
def get_complete_user_data(tr, user_id):
"""Get all user data from multiple directory levels"""
data = {}
# Basic user data
name = tr[users_dir.pack((user_id,))]
data['name'] = name.decode() if name else None
# Profile data
bio_key = profiles_dir.pack((user_id, 'bio'))
bio = tr[bio_key]
data['bio'] = bio.decode() if bio else None
# Settings data
theme_key = settings_dir.pack((user_id, 'theme'))
theme = tr[theme_key]
data['theme'] = theme.decode() if theme else None
return data
user_data = get_complete_user_data(db, 123)
print(user_data)
# {'name': 'Alice', 'bio': 'Software developer', 'theme': 'dark'}import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;
FDB fdb = FDB.selectAPIVersion(740);
Database db = fdb.open();
DirectoryLayer dir = DirectoryLayer.getDefault();
// Create hierarchical directory structure
DirectorySubspace appDir = dir.createOrOpen(
db, Arrays.asList("myapp")
).join();
DirectorySubspace usersDir = dir.createOrOpen(
db, Arrays.asList("myapp", "users")
).join();
DirectorySubspace profilesDir = dir.createOrOpen(
db, Arrays.asList("myapp", "users", "profiles")
).join();
DirectorySubspace settingsDir = dir.createOrOpen(
db, Arrays.asList("myapp", "users", "settings")
).join();
DirectorySubspace postsDir = dir.createOrOpen(
db, Arrays.asList("myapp", "posts")
).join();
// Store hierarchical data
db.run(tr -> {
int userId = 123;
// Basic user data
tr.set(usersDir.pack(Tuple.from(userId)), "Alice".getBytes());
// Profile information
tr.set(profilesDir.pack(Tuple.from(userId, "bio")),
"Software developer".getBytes());
// User settings
tr.set(settingsDir.pack(Tuple.from(userId, "theme")),
"dark".getBytes());
return null;
});
// List directory structure
void showDirectoryTree(Database db, List<String> path, int indent) {
List<String> subdirs = dir.list(db, path).join();
for (String name : subdirs) {
System.out.println(" ".repeat(indent) + name);
List<String> subpath = new ArrayList<>(path);
subpath.add(name);
showDirectoryTree(db, subpath, indent + 1);
}
}
System.out.println("Directory structure:");
showDirectoryTree(db, Collections.emptyList(), 0);Using layer tags to separate different data types and versions.
import fdb
fdb.api_version(740)
db = fdb.open()
# Define layer tags for different purposes
LAYER_USER_V1 = b'user_v1'
LAYER_USER_V2 = b'user_v2'
LAYER_TIMESERIES = b'timeseries'
LAYER_CACHE = b'cache'
# Create directories with specific layers
users_v1 = fdb.directory.create_or_open(
db, ['users_v1'],
layer=LAYER_USER_V1
)
users_v2 = fdb.directory.create_or_open(
db, ['users_v2'],
layer=LAYER_USER_V2
)
metrics = fdb.directory.create_or_open(
db, ['metrics'],
layer=LAYER_TIMESERIES
)
cache = fdb.directory.create_or_open(
db, ['cache'],
layer=LAYER_CACHE
)
# Type-safe directory access
class DirectoryManager:
"""Manage directories with layer validation"""
def __init__(self, db):
self.db = db
def get_user_v1_dir(self):
"""Get user v1 directory with layer validation"""
return fdb.directory.open(self.db, ['users_v1'], layer=LAYER_USER_V1)
def get_user_v2_dir(self):
"""Get user v2 directory with layer validation"""
return fdb.directory.open(self.db, ['users_v2'], layer=LAYER_USER_V2)
def get_metrics_dir(self):
"""Get timeseries metrics directory"""
return fdb.directory.open(self.db, ['metrics'], layer=LAYER_TIMESERIES)
def get_cache_dir(self):
"""Get cache directory"""
return fdb.directory.open(self.db, ['cache'], layer=LAYER_CACHE)
dm = DirectoryManager(db)
# Store data with different schemas
@fdb.transactional
def store_user_v1(tr, user_id, name):
"""Store user with v1 schema (simple)"""
dir = dm.get_user_v1_dir()
tr[dir.pack((user_id,))] = name.encode()
@fdb.transactional
def store_user_v2(tr, user_id, name, email, created_at):
"""Store user with v2 schema (enhanced)"""
dir = dm.get_user_v2_dir()
tr[dir.pack((user_id, 'name'))] = name.encode()
tr[dir.pack((user_id, 'email'))] = email.encode()
tr[dir.pack((user_id, 'created_at'))] = str(created_at).encode()
@fdb.transactional
def store_metric(tr, metric_name, timestamp, value):
"""Store timeseries metric"""
dir = dm.get_metrics_dir()
tr[dir.pack((metric_name, timestamp))] = str(value).encode()
# Migrate from v1 to v2
@fdb.transactional
def migrate_user_v1_to_v2(tr, user_id):
"""Migrate a user from v1 to v2 schema"""
v1_dir = dm.get_user_v1_dir()
v2_dir = dm.get_user_v2_dir()
# Read v1 data
name = tr[v1_dir.pack((user_id,))]
if not name:
return False
# Write to v2 with additional fields
tr[v2_dir.pack((user_id, 'name'))] = name
tr[v2_dir.pack((user_id, 'email'))] = b'migrated@example.com'
tr[v2_dir.pack((user_id, 'created_at'))] = b'0'
# Remove from v1
del tr[v1_dir.pack((user_id,))]
return True
# Use the functions
store_user_v1(db, 100, 'Old User')
store_user_v2(db, 200, 'New User', 'new@example.com', 1234567890)
# Migrate user
migrate_user_v1_to_v2(db, 100)
# Type safety: This would fail if wrong layer
try:
wrong_dir = fdb.directory.open(db, ['metrics'], layer=LAYER_USER_V1)
except fdb.FDBError:
print("Correctly rejected wrong layer type!")Demonstrate directory moving and reorganization without data migration.
import fdb
fdb.api_version(740)
db = fdb.open()
# Initial directory structure
users_old = fdb.directory.create_or_open(db, ['users'])
posts_old = fdb.directory.create_or_open(db, ['posts'])
# Add some data
@fdb.transactional
def populate_data(tr):
tr[users_old.pack((1,))] = b'Alice'
tr[users_old.pack((2,))] = b'Bob'
tr[posts_old.pack((101,))] = b'Post 1'
tr[posts_old.pack((102,))] = b'Post 2'
populate_data(db)
# Reorganize into new structure
print("Original directories:", fdb.directory.list(db))
# ['users', 'posts']
# Create new app namespace
app_dir = fdb.directory.create_or_open(db, ['myapp'])
# Move directories under app namespace
users_new = fdb.directory.move(db, ['users'], ['myapp', 'users'])
posts_new = fdb.directory.move(db, ['posts'], ['myapp', 'posts'])
print("After reorganization:", fdb.directory.list(db))
# ['myapp']
print("Under myapp:", fdb.directory.list(db, ['myapp']))
# ['users', 'posts']
# Data is still accessible with new directory objects
@fdb.transactional
def verify_data(tr):
# Read with new directory objects
user1 = tr[users_new.pack((1,))]
user2 = tr[users_new.pack((2,))]
post1 = tr[posts_new.pack((101,))]
post2 = tr[posts_new.pack((102,))]
print(f"User 1: {user1}") # b'Alice'
print(f"User 2: {user2}") # b'Bob'
print(f"Post 1: {post1}") # b'Post 1'
print(f"Post 2: {post2}") # b'Post 2'
verify_data(db)
# Rename a directory
fdb.directory.move(db, ['myapp', 'posts'], ['myapp', 'articles'])
print("After rename:", fdb.directory.list(db, ['myapp']))
# ['users', 'articles']
# Complex reorganization example
def reorganize_by_type(db):
"""Reorganize directories by data type"""
# Create type-based directories
fdb.directory.create_or_open(db, ['data', 'persistent'])
fdb.directory.create_or_open(db, ['data', 'temporary'])
# Move existing directories
fdb.directory.move(db, ['myapp', 'users'], ['data', 'persistent', 'users'])
fdb.directory.move(db, ['myapp', 'articles'], ['data', 'persistent', 'articles'])
# Clean up old structure
subdirs = fdb.directory.list(db, ['myapp'])
if not subdirs:
fdb.directory.remove(db, ['myapp'])
reorganize_by_type(db)
print("Final structure:")
def show_tree(path=(), indent=0):
subdirs = fdb.directory.list(db, path)
for name in subdirs:
print(' ' * indent + name)
show_tree(tuple(list(path) + [name]), indent + 1)
show_tree()
# data
# persistent
# users
# articles
# temporaryCombining Directory Layer with Subspace and Tuple encoding.
import fdb
import fdb.tuple
fdb.api_version(740)
db = fdb.open()
# Create directory structure
users = fdb.directory.create_or_open(db, ['users'])
sessions = fdb.directory.create_or_open(db, ['sessions'])
# DirectorySubspace inherits Subspace methods
# Use tuple encoding for complex keys
@fdb.transactional
def create_user_profile(tr, user_id, fields):
"""Store user profile with tuple-encoded keys"""
# Tuple encoding handles different data types
for field_name, value in fields.items():
key = users.pack((user_id, 'profile', field_name))
tr[key] = str(value).encode()
@fdb.transactional
def create_session(tr, user_id, session_id, ip_address, timestamp):
"""Store session with complex tuple key"""
# Tuples can contain mixed types
key = sessions.pack((user_id, timestamp, session_id))
value = fdb.tuple.pack((ip_address, 'active'))
tr[key] = value
# Create test data
create_user_profile(db, 123, {
'name': 'Alice',
'age': 30,
'country': 'US'
})
import time
now = int(time.time())
create_session(db, 123, 'sess_abc', '192.168.1.1', now)
create_session(db, 123, 'sess_def', '192.168.1.2', now + 100)
# Query with range operations
@fdb.transactional
def get_user_profile(tr, user_id):
"""Retrieve all profile fields"""
# Use range to get all profile fields
begin, end = users.range((user_id, 'profile'))
profile = {}
for kv in tr.get_range(begin, end):
# Unpack tuple to get field name
*_, field_name = users.unpack(kv.key)
profile[field_name] = kv.value.decode()
return profile
profile = get_user_profile(db, 123)
print(f"Profile: {profile}")
# Profile: {'name': 'Alice', 'age': '30', 'country': 'US'}
@fdb.transactional
def get_user_sessions(tr, user_id, start_time=None, end_time=None):
"""Get user sessions in time range"""
# Use tuple range with time boundaries
if start_time is None:
start_time = 0
if end_time is None:
end_time = 2**63 - 1
begin = sessions.pack((user_id, start_time))
end = sessions.pack((user_id, end_time))
sessions_list = []
for kv in tr.get_range(begin, end):
# Unpack key and value
user_id_key, timestamp, session_id = sessions.unpack(kv.key)
ip_address, status = fdb.tuple.unpack(kv.value)
sessions_list.append({
'session_id': session_id,
'timestamp': timestamp,
'ip_address': ip_address,
'status': status
})
return sessions_list
user_sessions = get_user_sessions(db, 123)
print(f"Sessions: {user_sessions}")
# Nested subspaces for organization
@fdb.transactional
def organize_with_nested_subspaces(tr):
"""Use nested subspaces for fine-grained organization"""
# Create nested subspace for user 123
user_123_space = users.subspace((123,))
# Create further nested subspaces
profile_space = user_123_space.subspace(('profile',))
settings_space = user_123_space.subspace(('settings',))
activity_space = user_123_space.subspace(('activity',))
# Store data in nested subspaces
tr[profile_space.pack(('name',))] = b'Alice'
tr[settings_space.pack(('theme',))] = b'dark'
tr[activity_space.pack((now, 'login'))] = b'success'
# Each subspace has its own prefix
print(f"User subspace: {user_123_space.key().hex()}")
print(f"Profile subspace: {profile_space.key().hex()}")
print(f"Settings subspace: {settings_space.key().hex()}")
organize_with_nested_subspaces(db)Using directories to isolate data between multiple applications sharing a database.
import fdb
fdb.api_version(740)
db = fdb.open()
# Application 1: E-commerce platform
class EcommerceApp:
def __init__(self, db):
self.db = db
self.root = fdb.directory.create_or_open(db, ['ecommerce'])
self.users = fdb.directory.create_or_open(db, ['ecommerce', 'users'])
self.products = fdb.directory.create_or_open(db, ['ecommerce', 'products'])
self.orders = fdb.directory.create_or_open(db, ['ecommerce', 'orders'])
@fdb.transactional
def create_order(self, tr, order_id, user_id, product_id, quantity):
tr[self.orders.pack((order_id, 'user'))] = str(user_id).encode()
tr[self.orders.pack((order_id, 'product'))] = str(product_id).encode()
tr[self.orders.pack((order_id, 'quantity'))] = str(quantity).encode()
@fdb.transactional
def get_order(self, tr, order_id):
begin, end = self.orders.range((order_id,))
order = {}
for kv in tr.get_range(begin, end):
field = self.orders.unpack(kv.key)[-1]
order[field] = kv.value.decode()
return order
# Application 2: Analytics platform
class AnalyticsApp:
def __init__(self, db):
self.db = db
self.root = fdb.directory.create_or_open(db, ['analytics'])
self.events = fdb.directory.create_or_open(db, ['analytics', 'events'])
self.metrics = fdb.directory.create_or_open(db, ['analytics', 'metrics'])
self.reports = fdb.directory.create_or_open(db, ['analytics', 'reports'])
@fdb.transactional
def log_event(self, tr, event_type, user_id, timestamp, data):
key = self.events.pack((event_type, timestamp, user_id))
tr[key] = data.encode()
@fdb.transactional
def get_events_by_type(self, tr, event_type, limit=100):
begin, end = self.events.range((event_type,))
events = []
for kv in tr.get_range(begin, end, limit=limit):
_, timestamp, user_id = self.events.unpack(kv.key)
events.append({
'timestamp': timestamp,
'user_id': user_id,
'data': kv.value.decode()
})
return events
# Application 3: Cache service
class CacheService:
def __init__(self, db):
self.db = db
self.root = fdb.directory.create_or_open(db, ['cache'])
self.data = fdb.directory.create_or_open(db, ['cache', 'data'])
@fdb.transactional
def set(self, tr, key, value, ttl):
import time
expires_at = int(time.time()) + ttl
cache_key = self.data.pack((key, expires_at))
tr[cache_key] = value.encode()
@fdb.transactional
def get(self, tr, key):
import time
now = int(time.time())
# Search for non-expired entries
begin, end = self.data.range((key,))
for kv in tr.get_range(begin, end):
cache_key, expires_at = self.data.unpack(kv.key)
if expires_at > now:
return kv.value.decode()
return None
# Initialize all applications
ecommerce = EcommerceApp(db)
analytics = AnalyticsApp(db)
cache = CacheService(db)
# Each application works in its own namespace
ecommerce.create_order(db, 1001, 123, 456, 2)
analytics.log_event(db, 'purchase', 123, 1234567890, 'order_1001')
cache.set(db, 'user_123_profile', 'Alice', ttl=3600)
# Verify isolation
print("All top-level directories:", fdb.directory.list(db))
# ['ecommerce', 'analytics', 'cache']
print("Ecommerce subdirs:", fdb.directory.list(db, ['ecommerce']))
# ['users', 'products', 'orders']
print("Analytics subdirs:", fdb.directory.list(db, ['analytics']))
# ['events', 'metrics', 'reports']
# Applications can be removed independently
def remove_application(db, app_name):
"""Completely remove an application's data"""
if fdb.directory.exists(db, [app_name]):
fdb.directory.remove(db, [app_name])
print(f"Removed {app_name}")
# Remove cache application (doesn't affect others)
remove_application(db, 'cache')
print("After removing cache:", fdb.directory.list(db))
# ['ecommerce', 'analytics']
# Cross-application access pattern
class ApplicationRegistry:
"""Central registry for multi-application access"""
def __init__(self, db):
self.db = db
self.apps = {}
def register(self, name, app):
self.apps[name] = app
def get_app(self, name):
return self.apps.get(name)
def list_apps(self):
"""List all registered applications"""
return fdb.directory.list(self.db)
def get_app_stats(self, app_name):
"""Get statistics about an application's data"""
if not fdb.directory.exists(self.db, [app_name]):
return None
@fdb.transactional
def count_keys(tr):
app_dir = fdb.directory.open(tr, [app_name])
begin, end = app_dir.range()
count = 0
for _ in tr.get_range(begin, end):
count += 1
return count
return {
'name': app_name,
'subdirectories': fdb.directory.list(self.db, [app_name]),
'key_count': count_keys(self.db)
}
registry = ApplicationRegistry(db)
registry.register('ecommerce', ecommerce)
registry.register('analytics', analytics)
# Get statistics
for app_name in registry.list_apps():
stats = registry.get_app_stats(app_name)
print(f"App: {stats['name']}, Subdirs: {stats['subdirectories']}, Keys: {stats['key_count']}")Plan your hierarchy carefully
Use consistent naming conventions
# Good: Consistent, clear naming
fdb.directory.create_or_open(db, ['app', 'users', 'profiles'])
fdb.directory.create_or_open(db, ['app', 'users', 'settings'])
fdb.directory.create_or_open(db, ['app', 'posts', 'content'])
# Avoid: Inconsistent naming
fdb.directory.create_or_open(db, ['app', 'Users', 'PROFILES'])
fdb.directory.create_or_open(db, ['app', 'userSettings'])Separate applications and versions
# Application separation
app1_users = fdb.directory.create_or_open(db, ['app1', 'users'])
app2_users = fdb.directory.create_or_open(db, ['app2', 'users'])
# Version separation with layer tags
users_v1 = fdb.directory.create_or_open(db, ['users'], layer=b'v1')
users_v2 = fdb.directory.create_or_open(db, ['users_v2'], layer=b'v2')Use create_or_open for idempotent operations
# Safe for repeated calls
users = fdb.directory.create_or_open(db, ['users'])
# Only when you need strict creation
try:
new_dir = fdb.directory.create(db, ['temp'])
except fdb.FDBError:
pass # Already existsCheck existence before expensive operations
# Check before removal
if fdb.directory.exists(db, ['temp']):
fdb.directory.remove(db, ['temp'])
# Or use remove_if_exists
fdb.directory.remove_if_exists(db, ['temp'])Move directories instead of migrating data
# Efficient: Just updates metadata
fdb.directory.move(db, ['old_path'], ['new_path'])
# Inefficient: Don't do this unless schema changes
# old_dir = fdb.directory.open(db, ['old'])
# new_dir = fdb.directory.create(db, ['new'])
# for kv in tr.get_range(old_dir.range()):
# new_key = transform_key(kv.key)
# tr[new_key] = kv.value
# fdb.directory.remove(db, ['old'])Use layer tags for type safety
# Define layer constants
LAYER_USER = b'user_data_v2'
LAYER_POST = b'post_data_v1'
# Create with layer tags
users = fdb.directory.create_or_open(db, ['users'], layer=LAYER_USER)
posts = fdb.directory.create_or_open(db, ['posts'], layer=LAYER_POST)
# Validate on open
users = fdb.directory.open(db, ['users'], layer=LAYER_USER)Cache directory objects
# Good: Open once, reuse
class DataManager:
def __init__(self, db):
self.db = db
self.users_dir = fdb.directory.create_or_open(db, ['users'])
self.posts_dir = fdb.directory.create_or_open(db, ['posts'])
def store_user(self, tr, user_id, data):
tr[self.users_dir.pack((user_id,))] = data
manager = DataManager(db)
# Avoid: Opening in every operation
def store_user_bad(tr, user_id, data):
users = fdb.directory.open(db, ['users']) # Inefficient
tr[users.pack((user_id,))] = dataBatch directory operations
# Efficient: Single transaction
@fdb.transactional
def initialize_app_structure(tr):
dirs = [
['app', 'users'],
['app', 'posts'],
['app', 'comments'],
['app', 'sessions']
]
for path in dirs:
fdb.directory.create_or_open(tr, path)
initialize_app_structure(db)Use transactions for directory operations
# Atomic directory operations
@fdb.transactional
def reorganize_atomically(tr):
# All operations succeed or all fail
fdb.directory.create(tr, ['new_structure'])
fdb.directory.move(tr, ['old'], ['new_structure', 'old'])
fdb.directory.remove_if_exists(tr, ['temp'])Handle common errors gracefully
# Directory already exists
try:
dir = fdb.directory.create(db, ['users'])
except fdb.FDBError as e:
if e.code == 1020: # directory_already_exists
dir = fdb.directory.open(db, ['users'])
else:
raise
# Directory doesn't exist
try:
dir = fdb.directory.open(db, ['users'])
except fdb.FDBError as e:
if e.code == 2100: # path doesn't exist
dir = fdb.directory.create(db, ['users'])
else:
raise
# Or use exists() to check
if not fdb.directory.exists(db, ['users']):
fdb.directory.create(db, ['users'])Validate layer tags
def open_with_version_check(db, path, expected_layer):
"""Open directory with version validation"""
try:
return fdb.directory.open(db, path, layer=expected_layer)
except fdb.FDBError:
# Layer mismatch or doesn't exist
actual_layer = None
if fdb.directory.exists(db, path):
dir = fdb.directory.open(db, path)
actual_layer = dir.get_layer()
raise ValueError(
f"Directory {path} has wrong layer. "
f"Expected {expected_layer}, got {actual_layer}"
)Schema migration pattern
def migrate_schema_v1_to_v2(db):
"""Migrate from schema v1 to v2"""
LAYER_V1 = b'schema_v1'
LAYER_V2 = b'schema_v2'
# Check if migration needed
if not fdb.directory.exists(db, ['data']):
return
data_dir = fdb.directory.open(db, ['data'])
if data_dir.get_layer() == LAYER_V2:
return # Already migrated
# Create new v2 directory
data_v2 = fdb.directory.create_or_open(
db, ['data_v2'],
layer=LAYER_V2
)
# Migrate data
@fdb.transactional
def migrate_data(tr):
begin, end = data_dir.range()
for kv in tr.get_range(begin, end):
# Transform data for v2
old_tuple = data_dir.unpack(kv.key)
new_tuple = transform_for_v2(old_tuple)
new_key = data_v2.pack(new_tuple)
tr[new_key] = transform_value_v2(kv.value)
migrate_data(db)
# Swap directories
fdb.directory.move(db, ['data'], ['data_v1_backup'])
fdb.directory.move(db, ['data_v2'], ['data'])Maintain migration history
def record_migration(db, from_version, to_version):
"""Record schema migration in directory metadata"""
import time
migrations_dir = fdb.directory.create_or_open(db, ['_migrations'])
@fdb.transactional
def record(tr):
timestamp = int(time.time())
key = migrations_dir.pack((timestamp, from_version, to_version))
tr[key] = b'completed'
record(db)Isolate sensitive data
# Use separate directories for different security levels
public_data = fdb.directory.create_or_open(db, ['public'])
private_data = fdb.directory.create_or_open(db, ['private'])
admin_data = fdb.directory.create_or_open(db, ['admin'])Use tenants for multi-tenant applications
# Tenant-based isolation (FoundationDB 7.1+)
tenant_a = db.open_tenant(b'tenant_a')
tenant_b = db.open_tenant(b'tenant_b')
# Each tenant has isolated directory namespace
tenant_a_users = fdb.directory.create_or_open(tenant_a, ['users'])
tenant_b_users = fdb.directory.create_or_open(tenant_b, ['users'])The Directory Layer provides powerful hierarchical namespace management for FoundationDB:
Use the Directory Layer when building multi-application systems, when you need relocatable data namespaces, or when you want to organize data hierarchically without manual prefix management.