or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

directory.mdfdb.mdindex.mdsubspace.mdtuple.md
tile.json

directory.mddocs/

Directory Package - Hierarchical Directory Layer

Overview

The directory package provides a tool for managing related subspaces in FoundationDB through hierarchical, path-based organization. Directories are a recommended approach for administering applications, offering a level of indirection for accessing subspaces through a filesystem-like interface.

Package Path: github.com/apple/foundationdb/bindings/go/src/fdb/directory

Key Features:

  • Hierarchical path-based organization analogous to Unix-like file systems
  • Automatic allocation of short prefixes for subspaces
  • Transactional directory operations
  • Layer metadata for type identification
  • Support for both default root directory and custom directory layers

Use Case: Each application should create or open at least one directory to manage its subspaces, providing logical organization separate from physical key layout.

For general guidance, see the Directories section of the Developer Guide.

Core Concepts

Hierarchical Paths

Directories are identified by hierarchical paths represented as slices of strings:

  • A path []string{"app", "users", "profiles"} represents a nested structure
  • Each directory has an associated subspace for storing content
  • The directory layer maps paths to short prefixes automatically

Indirection Layer

The directory layer provides indirection between logical paths and physical prefixes:

  • Applications reference directories by path (e.g., []string{"myapp", "data"})
  • The directory layer maps paths to short, automatically-allocated prefixes
  • This allows reorganizing data without changing application code

Layer Metadata

Directories support optional layer metadata:

  • A byte slice identifier stored when creating a directory
  • Verified when opening a directory to ensure type compatibility
  • Enables multiple application types to coexist safely

Directory Interface

Type Definition

{ .api }

type Directory interface {
    CreateOrOpen(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error)
    Open(rt fdb.ReadTransactor, path []string, layer []byte) (DirectorySubspace, error)
    Create(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error)
    CreatePrefix(t fdb.Transactor, path []string, layer []byte, prefix []byte) (DirectorySubspace, error)
    Move(t fdb.Transactor, oldPath []string, newPath []string) (DirectorySubspace, error)
    MoveTo(t fdb.Transactor, newAbsolutePath []string) (DirectorySubspace, error)
    Remove(t fdb.Transactor, path []string) (bool, error)
    Exists(rt fdb.ReadTransactor, path []string) (bool, error)
    List(rt fdb.ReadTransactor, path []string) ([]string, error)
    GetLayer() []byte
    GetPath() []string
}

Directory represents a subspace of keys in a FoundationDB database, identified by a hierarchical path. All path parameters are relative to the Directory on which the method is called.

Methods

{ .api }

CreateOrOpen

func (d Directory) CreateOrOpen(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error)

Opens the directory at the specified path, creating it if it doesn't exist. Parent directories are created automatically if necessary.

Parameters:

  • t: Transactor for the operation
  • path: Path relative to this Directory
  • layer: Optional layer metadata (nil for none)

Returns:

  • DirectorySubspace for the directory
  • Error if layer mismatch occurs

Behavior:

  • If directory is new and layer is specified, layer is recorded
  • If directory exists and layer is specified, it's compared against the stored layer
  • Returns error if layers differ

Example:

// Create or open a directory for user data
dir := directory.Root()
userDir, err := dir.CreateOrOpen(db, []string{"myapp", "users"}, nil)
if err != nil {
    log.Fatal(err)
}

Open

func (d Directory) Open(rt fdb.ReadTransactor, path []string, layer []byte) (DirectorySubspace, error)

Opens an existing directory at the specified path.

Parameters:

  • rt: ReadTransactor for the operation
  • path: Path relative to this Directory
  • layer: Optional layer metadata to verify (nil for none)

Returns:

  • DirectorySubspace for the directory
  • ErrDirNotExists if directory doesn't exist
  • ErrParentDirDoesNotExist if parent directory missing
  • Error if layer mismatch occurs

Example:

// Open an existing directory
dir := directory.Root()
userDir, err := dir.Open(db, []string{"myapp", "users"}, nil)
if err == directory.ErrDirNotExists {
    // Handle directory not found
}

Create

func (d Directory) Create(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error)

Creates a new directory at the specified path.

Parameters:

  • t: Transactor for the operation
  • path: Path relative to this Directory
  • layer: Optional layer metadata to store (nil for none)

Returns:

  • DirectorySubspace for the new directory
  • ErrDirAlreadyExists if directory already exists
  • Error if parent directory doesn't exist

Behavior:

  • If layer is specified, it's recorded and checked on future opens
  • Automatically allocates a short prefix for the directory's subspace

Example:

// Create a new directory with layer metadata
dir := directory.Root()
layer := []byte("user-profiles")
profileDir, err := dir.Create(db, []string{"myapp", "profiles"}, layer)
if err == directory.ErrDirAlreadyExists {
    // Handle already exists
}

CreatePrefix

func (d Directory) CreatePrefix(t fdb.Transactor, path []string, layer []byte, prefix []byte) (DirectorySubspace, error)

Creates a directory with a manually specified prefix instead of automatic allocation.

Parameters:

  • t: Transactor for the operation
  • path: Path relative to this Directory
  • layer: Optional layer metadata
  • prefix: Byte slice prefix to use for this directory's subspace

Returns:

  • DirectorySubspace for the new directory
  • Error if manual prefixes not allowed or prefix already in use

Restrictions:

  • The root Directory must have been created with allowManualPrefixes=true
  • The default root directory does NOT allow manual prefixes
  • Prefix must not conflict with existing directories

Example:

// Create a custom root directory that allows manual prefixes
nodeSS := subspace.FromBytes([]byte{0xFE})
contentSS := subspace.AllKeys()
customRoot := directory.NewDirectoryLayer(nodeSS, contentSS, true)

// Now can use manual prefixes
prefix := []byte{0x01, 0x02}
dir, err := customRoot.CreatePrefix(db, []string{"custom"}, nil, prefix)

Move

func (d Directory) Move(t fdb.Transactor, oldPath []string, newPath []string) (DirectorySubspace, error)

Moves a directory from one path to another, both relative to this Directory.

Parameters:

  • t: Transactor for the operation
  • oldPath: Current path of directory
  • newPath: Target path for directory

Returns:

  • DirectorySubspace for the directory at its new location
  • Error if source doesn't exist, destination exists, or parent of destination doesn't exist

Important Notes:

  • No effect on the physical prefix or existing open references
  • Clients with the directory already open can continue using it
  • Cannot move a directory to be a subdirectory of itself
  • Cannot move between partitions

Example:

// Move a directory to reorganize
dir := directory.Root()
movedDir, err := dir.Move(db,
    []string{"myapp", "old-location"},
    []string{"myapp", "new-location"})

MoveTo

func (d Directory) MoveTo(t fdb.Transactor, newAbsolutePath []string) (DirectorySubspace, error)

Moves this directory to a new absolute path (relative to the root directory).

Parameters:

  • t: Transactor for the operation
  • newAbsolutePath: New absolute path relative to root

Returns:

  • DirectorySubspace for the directory at its new location
  • Error if destination exists or parent doesn't exist

Important Notes:

  • Similar to Move but operates on the directory itself
  • Cannot be called on the root directory

Example:

// Move a directory to a new location
userDir, _ := directory.Open(db, []string{"myapp", "users"}, nil)
movedDir, err := userDir.MoveTo(db, []string{"myapp", "v2", "users"})

Remove

func (d Directory) Remove(t fdb.Transactor, path []string) (bool, error)

Removes the directory at the specified path, including all its content and subdirectories.

Parameters:

  • t: Transactor for the operation
  • path: Path of directory to remove

Returns:

  • true if directory existed and was removed
  • false if no directory exists at path
  • Error if operation fails

Warning: Clients that have already opened this directory can still insert data into its contents after removal. Use appropriate coordination.

Example:

// Remove a directory and all its contents
dir := directory.Root()
removed, err := dir.Remove(db, []string{"myapp", "temp-data"})
if removed {
    fmt.Println("Directory removed successfully")
}

Exists

func (d Directory) Exists(rt fdb.ReadTransactor, path []string) (bool, error)

Checks if a directory exists at the specified path.

Parameters:

  • rt: ReadTransactor for the operation
  • path: Path relative to this Directory

Returns:

  • true if directory exists
  • false if directory doesn't exist
  • Error if operation fails

Example:

// Check if a directory exists before opening
dir := directory.Root()
exists, err := dir.Exists(db, []string{"myapp", "cache"})
if !exists {
    // Create the directory
    dir.Create(db, []string{"myapp", "cache"}, nil)
}

List

func (d Directory) List(rt fdb.ReadTransactor, path []string) ([]string, error)

Returns the names of immediate subdirectories at the specified path.

Parameters:

  • rt: ReadTransactor for the operation
  • path: Path of directory to list

Returns:

  • Slice of subdirectory names (last component of each subdirectory's path)
  • ErrDirNotExists if directory doesn't exist
  • Error if operation fails

Example:

// List all subdirectories
dir := directory.Root()
subdirs, err := dir.List(db, []string{"myapp"})
// subdirs might be: ["users", "products", "orders"]

GetLayer

func (d Directory) GetLayer() []byte

Returns the layer metadata specified when this directory was created.

Returns: Layer byte slice, or empty slice if no layer was specified

Example:

dir, _ := directory.Open(db, []string{"myapp", "users"}, nil)
layer := dir.GetLayer()
if string(layer) == "user-profiles" {
    // Handle user profile directory
}

GetPath

func (d Directory) GetPath() []string

Returns the path with which this directory was opened.

Returns: Path slice representing the directory's location

Example:

dir, _ := directory.Open(db, []string{"myapp", "users", "profiles"}, nil)
path := dir.GetPath()
// path is: []string{"myapp", "users", "profiles"}

DirectorySubspace Interface

Type Definition

{ .api }

type DirectorySubspace interface {
    subspace.Subspace
    Directory
}

DirectorySubspace combines the Directory and Subspace interfaces, representing a Directory that can also be used as a Subspace to store key/value pairs.

Key Characteristics:

  • Provides all Directory methods for managing subdirectories
  • Provides all Subspace methods for packing keys and storing data
  • Returned by directory operations on non-root directories
  • Enables both hierarchical organization and data storage

Example Usage:

// Open a directory and use it as both Directory and Subspace
userDir, _ := directory.CreateOrOpen(db, []string{"myapp", "users"}, nil)

// Use as a Subspace to store data
db.Transact(func(tr fdb.Transaction) (interface{}, error) {
    // Pack a key using the directory's prefix
    key := userDir.Pack(tuple.Tuple{"user123", "email"})
    tr.Set(key, []byte("user@example.com"))
    return nil, nil
})

// Use as a Directory to create subdirectories
profileDir, _ := userDir.CreateOrOpen(db, []string{"profiles"}, nil)

Constructor Functions

Functions

{ .api }

Root

func Root() Directory

Returns the default root directory for the database.

Returns: Default root Directory

Characteristics:

  • Stores metadata in keys beginning with 0xFE
  • Allocates directory prefixes from 0x00 through 0xFD
  • Does NOT allow manual prefixes
  • Cannot be moved or removed (returns error)
  • Appropriate for otherwise empty databases

Caution: May conflict with existing data in databases with other partitioning schemes. Consider using NewDirectoryLayer for non-empty databases.

Example:

// Use the default root directory
root := directory.Root()
appDir, err := root.CreateOrOpen(db, []string{"myapp"}, nil)

NewDirectoryLayer

func NewDirectoryLayer(nodeSS, contentSS subspace.Subspace, allowManualPrefixes bool) Directory

Creates a custom root directory with specified subspaces for metadata and content.

Parameters:

  • nodeSS: Subspace for storing directory metadata
  • contentSS: Subspace for allocating directory content prefixes
  • allowManualPrefixes: If true, allows CreatePrefix; if false, only automatic allocation

Returns: New root Directory with custom configuration

Use Cases:

  • Sharing a database with other content
  • Restricting directory layer to specific keyspace regions
  • Enabling manual prefix allocation
  • Multiple independent directory hierarchies

Example:

// Create a custom directory layer in a restricted keyspace
nodeSS := subspace.FromBytes([]byte{0xAA, 0xFE})
contentSS := subspace.FromBytes([]byte{0xAA})
customRoot := directory.NewDirectoryLayer(nodeSS, contentSS, false)

// Use the custom root
appDir, err := customRoot.CreateOrOpen(db, []string{"myapp"}, nil)

Comparison with Default Root:

// Default root configuration (equivalent to):
defaultRoot := directory.NewDirectoryLayer(
    subspace.FromBytes([]byte{0xFE}),
    subspace.AllKeys(),
    false,
)

Package-Level Convenience Functions

These functions operate on the default root directory returned by Root(). They provide a convenient alternative to explicitly calling Root().

Functions

{ .api }

CreateOrOpen (Package-Level)

func CreateOrOpen(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error)

Opens or creates a directory at the path relative to the default root directory.

Parameters:

  • t: Transactor for the operation
  • path: Path relative to default root
  • layer: Optional layer metadata

Returns:

  • DirectorySubspace for the directory
  • Error if operation fails

Equivalent to: directory.Root().CreateOrOpen(t, path, layer)

Example:

// Create or open using package-level function
userDir, err := directory.CreateOrOpen(db, []string{"myapp", "users"}, nil)

// Equivalent to:
root := directory.Root()
userDir, err := root.CreateOrOpen(db, []string{"myapp", "users"}, nil)

Open (Package-Level)

func Open(rt fdb.ReadTransactor, path []string, layer []byte) (DirectorySubspace, error)

Opens an existing directory at the path relative to the default root directory.

Parameters:

  • rt: ReadTransactor for the operation
  • path: Path relative to default root
  • layer: Optional layer metadata to verify

Returns:

  • DirectorySubspace for the directory
  • ErrDirNotExists if directory doesn't exist
  • Error if operation fails

Equivalent to: directory.Root().Open(rt, path, layer)

Example:

// Open using package-level function
userDir, err := directory.Open(db, []string{"myapp", "users"}, nil)

Create (Package-Level)

func Create(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error)

Creates a new directory at the path relative to the default root directory.

Parameters:

  • t: Transactor for the operation
  • path: Path relative to default root
  • layer: Optional layer metadata

Returns:

  • DirectorySubspace for the new directory
  • ErrDirAlreadyExists if directory exists
  • Error if operation fails

Equivalent to: directory.Root().Create(t, path, layer)

Example:

// Create using package-level function
profileDir, err := directory.Create(db, []string{"myapp", "profiles"}, []byte("profiles"))

Move (Package-Level)

func Move(t fdb.Transactor, oldPath []string, newPath []string) (DirectorySubspace, error)

Moves a directory from oldPath to newPath, both relative to the default root directory.

Parameters:

  • t: Transactor for the operation
  • oldPath: Current path of directory
  • newPath: Target path for directory

Returns:

  • DirectorySubspace for the directory at new location
  • Error if operation fails

Equivalent to: directory.Root().Move(t, oldPath, newPath)

Example:

// Move using package-level function
movedDir, err := directory.Move(db,
    []string{"myapp", "old"},
    []string{"myapp", "new"})

Exists (Package-Level)

func Exists(rt fdb.ReadTransactor, path []string) (bool, error)

Checks if a directory exists at the path relative to the default root directory.

Parameters:

  • rt: ReadTransactor for the operation
  • path: Path relative to default root

Returns:

  • true if directory exists, false otherwise
  • Error if operation fails

Equivalent to: directory.Root().Exists(rt, path)

Example:

// Check existence using package-level function
exists, err := directory.Exists(db, []string{"myapp", "cache"})

List (Package-Level)

func List(rt fdb.ReadTransactor, path []string) ([]string, error)

Lists immediate subdirectories at the path relative to the default root directory.

Parameters:

  • rt: ReadTransactor for the operation
  • path: Path to list

Returns:

  • Slice of subdirectory names
  • ErrDirNotExists if directory doesn't exist
  • Error if operation fails

Equivalent to: directory.Root().List(rt, path)

Example:

// List using package-level function
subdirs, err := directory.List(db, []string{"myapp"})

Error Variables

Variables

{ .api }

var (
    // ErrDirAlreadyExists is returned when trying to create a directory that already exists
    ErrDirAlreadyExists = errors.New("the directory already exists")

    // ErrDirNotExists is returned when opening or listing a directory that does not exist
    ErrDirNotExists = errors.New("the directory does not exist")

    // ErrParentDirDoesNotExist is returned when opening a directory and one or more
    // parent directories in the path do not exist
    ErrParentDirDoesNotExist = errors.New("the parent directory does not exist")
)

Usage:

dir, err := directory.Open(db, []string{"myapp", "users"}, nil)
if err == directory.ErrDirNotExists {
    // Handle directory not found - maybe create it
    dir, err = directory.Create(db, []string{"myapp", "users"}, nil)
} else if err == directory.ErrParentDirDoesNotExist {
    // Parent directory missing - create parent first
    directory.CreateOrOpen(db, []string{"myapp"}, nil)
    dir, err = directory.Create(db, []string{"myapp", "users"}, nil)
}

newDir, err := directory.Create(db, []string{"myapp", "cache"}, nil)
if err == directory.ErrDirAlreadyExists {
    // Directory already exists - open it instead
    newDir, _ = directory.Open(db, []string{"myapp", "cache"}, nil)
}

Usage Examples

Basic Directory Creation and Opening

package main

import (
    "log"

    "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"
)

func main() {
    fdb.MustAPIVersion(730)
    db := fdb.MustOpenDefault()
    defer db.Close()

    // Create or open a directory for your application
    appDir, err := directory.CreateOrOpen(db, []string{"myapp"}, nil)
    if err != nil {
        log.Fatal(err)
    }

    // Create subdirectories for different data types
    userDir, err := appDir.CreateOrOpen(db, []string{"users"}, nil)
    if err != nil {
        log.Fatal(err)
    }

    productDir, err := appDir.CreateOrOpen(db, []string{"products"}, nil)
    if err != nil {
        log.Fatal(err)
    }

    // Use directories as subspaces to store data
    db.Transact(func(tr fdb.Transaction) (interface{}, error) {
        // Store user data
        userKey := userDir.Pack(tuple.Tuple{"user123", "email"})
        tr.Set(userKey, []byte("user@example.com"))

        // Store product data
        productKey := productDir.Pack(tuple.Tuple{"product456", "price"})
        tr.Set(productKey, []byte("29.99"))

        return nil, nil
    })
}

Hierarchical Organization

// Create a deep directory hierarchy
func setupApplicationDirectories(db fdb.Database) error {
    // Main application directory
    appDir, err := directory.CreateOrOpen(db, []string{"myapp"}, nil)
    if err != nil {
        return err
    }

    // User data hierarchy
    userDir, err := appDir.CreateOrOpen(db, []string{"users"}, nil)
    if err != nil {
        return err
    }

    // User subdirectories
    _, err = userDir.CreateOrOpen(db, []string{"profiles"}, []byte("user-profiles"))
    if err != nil {
        return err
    }

    _, err = userDir.CreateOrOpen(db, []string{"settings"}, []byte("user-settings"))
    if err != nil {
        return err
    }

    _, err = userDir.CreateOrOpen(db, []string{"activity"}, []byte("user-activity"))
    if err != nil {
        return err
    }

    // Product data hierarchy
    productDir, err := appDir.CreateOrOpen(db, []string{"products"}, nil)
    if err != nil {
        return err
    }

    _, err = productDir.CreateOrOpen(db, []string{"catalog"}, nil)
    if err != nil {
        return err
    }

    _, err = productDir.CreateOrOpen(db, []string{"inventory"}, nil)
    if err != nil {
        return err
    }

    return nil
}

Using Layer Metadata

// Define layer constants for different data types
const (
    LayerUserProfiles = "user-profiles-v1"
    LayerUserSettings = "user-settings-v1"
    LayerProductCatalog = "product-catalog-v2"
)

func openTypedDirectory(db fdb.Database, path []string, expectedLayer string) (directory.DirectorySubspace, error) {
    // Open directory and verify layer matches expected type
    dir, err := directory.Open(db, path, []byte(expectedLayer))
    if err != nil {
        return nil, fmt.Errorf("failed to open directory with layer %s: %w", expectedLayer, err)
    }
    return dir, nil
}

func createVersionedDirectory(db fdb.Database, path []string, layer string) (directory.DirectorySubspace, error) {
    // Create directory with layer metadata for versioning
    dir, err := directory.Create(db, path, []byte(layer))
    if err == directory.ErrDirAlreadyExists {
        // Directory exists - verify layer matches
        dir, err = directory.Open(db, path, []byte(layer))
        if err != nil {
            return nil, fmt.Errorf("directory exists but layer mismatch: %w", err)
        }
    } else if err != nil {
        return nil, err
    }
    return dir, nil
}

// Usage
func main() {
    fdb.MustAPIVersion(730)
    db := fdb.MustOpenDefault()
    defer db.Close()

    // Create directory with layer metadata
    profileDir, err := createVersionedDirectory(db,
        []string{"myapp", "users", "profiles"},
        LayerUserProfiles)
    if err != nil {
        log.Fatal(err)
    }

    // Open and verify layer
    verifiedDir, err := openTypedDirectory(db,
        []string{"myapp", "users", "profiles"},
        LayerUserProfiles)
    if err != nil {
        log.Fatal(err)
    }

    // Use the directory
    log.Printf("Opened directory with layer: %s", verifiedDir.GetLayer())
}

Moving and Reorganizing Directories

// Reorganize directory structure
func reorganizeDirectories(db fdb.Database) error {
    root := directory.Root()

    // Check if old location exists
    exists, err := root.Exists(db, []string{"myapp", "old-location"})
    if err != nil {
        return err
    }

    if exists {
        // Move directory to new location
        movedDir, err := root.Move(db,
            []string{"myapp", "old-location"},
            []string{"myapp", "v2", "new-location"})
        if err != nil {
            return fmt.Errorf("failed to move directory: %w", err)
        }

        log.Printf("Moved directory to: %v", movedDir.GetPath())
    }

    return nil
}

// Move directory using MoveTo
func moveToNewLocation(db fdb.Database, dir directory.DirectorySubspace) error {
    currentPath := dir.GetPath()
    newPath := append([]string{"archive"}, currentPath...)

    movedDir, err := dir.MoveTo(db, newPath)
    if err != nil {
        return fmt.Errorf("failed to move directory: %w", err)
    }

    log.Printf("Moved from %v to %v", currentPath, movedDir.GetPath())
    return nil
}

Listing and Exploring Directory Structure

// Recursively list entire directory tree
func listDirectoryTree(db fdb.Database, dir directory.Directory, path []string, indent string) error {
    // List subdirectories
    subdirs, err := dir.List(db, path)
    if err != nil {
        return err
    }

    for _, name := range subdirs {
        fullPath := append(path, name)
        fmt.Printf("%s%s/\n", indent, name)

        // Recursively list subdirectories
        if err := listDirectoryTree(db, dir, fullPath, indent+"  "); err != nil {
            return err
        }
    }

    return nil
}

// Usage
func main() {
    fdb.MustAPIVersion(730)
    db := fdb.MustOpenDefault()
    defer db.Close()

    root := directory.Root()

    fmt.Println("Directory structure:")
    if err := listDirectoryTree(db, root, []string{"myapp"}, "  "); err != nil {
        log.Fatal(err)
    }

    // Output might look like:
    // Directory structure:
    //   users/
    //     profiles/
    //     settings/
    //     activity/
    //   products/
    //     catalog/
    //     inventory/
}

Using DirectorySubspace for Data Storage

// Demonstrate using DirectorySubspace for both organization and storage
func userDataExample(db fdb.Database) error {
    // Create user directory
    userDir, err := directory.CreateOrOpen(db, []string{"myapp", "users"}, nil)
    if err != nil {
        return err
    }

    // Use as Subspace to store user data
    _, err = db.Transact(func(tr fdb.Transaction) (interface{}, error) {
        // Store user records using tuple keys
        userID := "user123"

        // Email
        emailKey := userDir.Pack(tuple.Tuple{userID, "email"})
        tr.Set(emailKey, []byte("user@example.com"))

        // Name
        nameKey := userDir.Pack(tuple.Tuple{userID, "name"})
        tr.Set(nameKey, []byte("John Doe"))

        // Age
        ageKey := userDir.Pack(tuple.Tuple{userID, "age"})
        tr.Set(ageKey, []byte("30"))

        return nil, nil
    })

    if err != nil {
        return err
    }

    // Read user data back
    _, err = db.Transact(func(tr fdb.Transaction) (interface{}, error) {
        userID := "user123"

        // Read email
        emailKey := userDir.Pack(tuple.Tuple{userID, "email"})
        email := tr.Get(emailKey).MustGet()

        fmt.Printf("User email: %s\n", string(email))

        return nil, nil
    })

    return err
}

// Scan all data within a directory
func scanDirectoryData(db fdb.Database, dirPath []string) error {
    dir, err := directory.Open(db, dirPath, nil)
    if err != nil {
        return err
    }

    _, err = db.Transact(func(tr fdb.Transaction) (interface{}, error) {
        // Get range covering entire directory subspace
        kr, err := fdb.PrefixRange(dir.Bytes())
        if err != nil {
            return nil, err
        }

        // Iterate over all keys in the directory
        iter := tr.GetRange(kr, fdb.RangeOptions{}).Iterator()
        for iter.Advance() {
            kv, err := iter.Get()
            if err != nil {
                return nil, err
            }

            // Unpack the key to get tuple components
            t, err := dir.Unpack(kv.Key)
            if err != nil {
                return nil, err
            }

            fmt.Printf("Key: %v, Value: %s\n", t, string(kv.Value))
        }

        return nil, nil
    })

    return err
}

Custom Directory Layer for Partitioned Applications

// Create isolated directory layers for different applications
func setupPartitionedApplications(db fdb.Database) error {
    // Application A - uses keyspace 0xAA
    appA_nodeSS := subspace.FromBytes([]byte{0xAA, 0xFE})
    appA_contentSS := subspace.FromBytes([]byte{0xAA})
    appA_root := directory.NewDirectoryLayer(appA_nodeSS, appA_contentSS, false)

    appA_dir, err := appA_root.CreateOrOpen(db, []string{"users"}, nil)
    if err != nil {
        return err
    }

    // Application B - uses keyspace 0xBB
    appB_nodeSS := subspace.FromBytes([]byte{0xBB, 0xFE})
    appB_contentSS := subspace.FromBytes([]byte{0xBB})
    appB_root := directory.NewDirectoryLayer(appB_nodeSS, appB_contentSS, false)

    appB_dir, err := appB_root.CreateOrOpen(db, []string{"products"}, nil)
    if err != nil {
        return err
    }

    // Both applications can use same logical paths without conflict
    log.Printf("App A users directory: %v", appA_dir.GetPath())
    log.Printf("App B products directory: %v", appB_dir.GetPath())

    return nil
}

// Create a directory layer that allows manual prefixes
func setupCustomPrefixes(db fdb.Database) error {
    // Create root that allows manual prefixes
    nodeSS := subspace.FromBytes([]byte{0xCC, 0xFE})
    contentSS := subspace.FromBytes([]byte{0xCC})
    customRoot := directory.NewDirectoryLayer(nodeSS, contentSS, true)

    // Now we can use CreatePrefix
    prefix := []byte{0xCC, 0x01}
    dir, err := customRoot.CreatePrefix(db,
        []string{"fixed-prefix-dir"},
        nil,
        prefix)
    if err != nil {
        return err
    }

    log.Printf("Created directory with manual prefix: %x", dir.Bytes())
    return nil
}

Checking Existence and Conditional Creation

// Safely create directory only if it doesn't exist
func createIfNotExists(db fdb.Database, path []string, layer []byte) (directory.DirectorySubspace, error) {
    // Check if directory exists
    exists, err := directory.Exists(db, path)
    if err != nil {
        return nil, fmt.Errorf("failed to check existence: %w", err)
    }

    if exists {
        // Open existing directory
        log.Printf("Directory %v already exists, opening...", path)
        return directory.Open(db, path, layer)
    }

    // Create new directory
    log.Printf("Creating new directory %v", path)
    return directory.Create(db, path, layer)
}

// Handle all directory errors gracefully
func openDirectoryWithFallback(db fdb.Database, path []string, layer []byte) (directory.DirectorySubspace, error) {
    dir, err := directory.Open(db, path, layer)

    if err == directory.ErrDirNotExists {
        // Directory doesn't exist - create it
        log.Printf("Directory %v not found, creating...", path)
        return directory.Create(db, path, layer)
    } else if err == directory.ErrParentDirDoesNotExist {
        // Parent missing - create parent first
        log.Printf("Parent directory missing, creating hierarchy...")
        return directory.CreateOrOpen(db, path, layer)
    }

    return dir, err
}

Removing Directories

// Remove directory with confirmation
func removeDirectorySafely(db fdb.Database, path []string) error {
    // Check if directory exists
    exists, err := directory.Exists(db, path)
    if err != nil {
        return err
    }

    if !exists {
        log.Printf("Directory %v does not exist", path)
        return nil
    }

    // List contents to show what will be removed
    subdirs, err := directory.List(db, path)
    if err != nil {
        return err
    }

    if len(subdirs) > 0 {
        log.Printf("Warning: Directory %v has subdirectories: %v", path, subdirs)
    }

    // Remove the directory
    removed, err := directory.Remove(db, path)
    if err != nil {
        return fmt.Errorf("failed to remove directory: %w", err)
    }

    if removed {
        log.Printf("Successfully removed directory %v", path)
    }

    return nil
}

// Clean up temporary directories
func cleanupTempDirectories(db fdb.Database) error {
    root := directory.Root()

    // List all subdirectories under temp
    tempDirs, err := root.List(db, []string{"myapp", "temp"})
    if err == directory.ErrDirNotExists {
        // No temp directory, nothing to clean
        return nil
    } else if err != nil {
        return err
    }

    // Remove each temporary subdirectory
    for _, name := range tempDirs {
        path := []string{"myapp", "temp", name}
        _, err := root.Remove(db, path)
        if err != nil {
            log.Printf("Failed to remove %v: %v", path, err)
        } else {
            log.Printf("Removed temporary directory: %s", name)
        }
    }

    return nil
}

Best Practices

Path-Based Organization

  1. Use descriptive path names: Choose clear, meaningful names for directory paths

    // Good
    directory.CreateOrOpen(db, []string{"myapp", "users", "profiles"}, nil)
    
    // Avoid
    directory.CreateOrOpen(db, []string{"ma", "u", "p"}, nil)
  2. Organize by domain: Group related data hierarchically

    // User domain
    []string{"myapp", "users", "profiles"}
    []string{"myapp", "users", "settings"}
    []string{"myapp", "users", "activity"}
    
    // Product domain
    []string{"myapp", "products", "catalog"}
    []string{"myapp", "products", "inventory"}
  3. Keep hierarchy shallow: Avoid overly deep directory structures (3-5 levels typically sufficient)

Layer Metadata Usage

  1. Use layers for type safety: Specify layer metadata to prevent incompatible directory access

    layer := []byte("user-profiles-v1")
    directory.Create(db, path, layer)
  2. Version your layers: Include version information in layer names for schema evolution

    const (
        LayerV1 = "user-profiles-v1"
        LayerV2 = "user-profiles-v2"
    )
  3. Consistent layer naming: Use consistent naming conventions across your application

Error Handling

  1. Check specific errors: Handle directory-specific errors appropriately

    if err == directory.ErrDirNotExists {
        // Create missing directory
    } else if err == directory.ErrDirAlreadyExists {
        // Open existing directory
    }
  2. Use CreateOrOpen for flexibility: When either creating or opening is acceptable

    dir, err := directory.CreateOrOpen(db, path, layer)

Performance Considerations

  1. Cache DirectorySubspace references: Reuse directory references rather than repeatedly opening

    // Open once and reuse
    userDir, _ := directory.Open(db, []string{"users"}, nil)
    
    // Use multiple times
    for _, userID := range userIDs {
        key := userDir.Pack(tuple.Tuple{userID})
        // ... use key
    }
  2. Batch directory operations: Create multiple directories in single transaction when possible

    db.Transact(func(tr fdb.Transaction) (interface{}, error) {
        dir1, _ := root.Create(tr, []string{"dir1"}, nil)
        dir2, _ := root.Create(tr, []string{"dir2"}, nil)
        return nil, nil
    })

Directory Lifecycle

  1. Initialize directories at startup: Create application directory structure during initialization
  2. Avoid removing directories with active clients: Coordinate directory removal to avoid data races
  3. Use MoveTo for reorganization: Move directories instead of recreating to preserve data
  4. Document directory structure: Maintain documentation of your application's directory hierarchy