View Source FDBC.Directory (fdbc v0.1.4)

FoundationDB provides the directories layer as a tool for managing related subspaces. Directories are a recommended approach for administering applications. Each application should create or open at least one directory to manage its subspaces.

Directories are identified by hierarchical paths analogous to the paths in a Unix-like file system. A path is represented as a tuple of strings. Each directory has an associated subspace used to store its content. The directory layer maps each path to a short prefix used for the corresponding subspace. In effect, directories provide a level of indirection for access to subspaces.

This design has significant benefits: while directories are logically hierarchical as represented by their paths, their subspaces are not physically nested in a corresponding way. For example, suppose we create a few directories with increasing paths, such as:

iex> r = FDBC.Directory.new()
iex> a = FDBC.Directory.create!(r, db, ['alpha'])
iex> b = FDBC.Directory.create!(r, db, ['alpha', 'bravo'])
iex> c = FDBC.Directory.create!(r, db, ['alpha', 'bravo', 'charlie'])

The prefixes of a, b, and c are allocated independently and will usually not increase in length. The indirection from paths to subspaces allows keys to be kept short and makes it fast to move directories (i.e., rename their paths).

Paths in the directory layer are always relative. In particular, paths are interpreted relative to the directory in which an operation is performed. For example, we could have created the directories a, b, and c as follows:

iex> r = FDBC.Directory.new()
iex> a = FDBC.Directory.open!(r, db, ['alpha'], create: true)
iex> b = FDBC.Directory.open!(a, db, ['bravo'], create: true)
iex> c = FDBC.Directory.open!(b, db, ['charlie'], create: true)

Usage

Unlike the upstream implementations this one tries to remain closer to the analogy of a 'Unix-like' file system. By default new/1 will create default root directory which is that with a content subspace of <<>> and a metadata prefix of <<0xFE>>.

Info

When comparing to upstream documentation what its refers to as node_subspace is called metadata here.

The create/3 function will create a new directory but not return it, to get the newly created directory in the same request open/3 should be used:

iex> :ok = FDBC.Directory.new() |> FDBC.Directory.create!(db, ['users'])
iex> users_dir = FDBC.Directory.new() |> FDBC.Directory.open!(db, ['users'], create: true)

A directory that is not a partition exposes its FDBC.Subspace for usage. The root directory is a partition.

iex> r = FDBC.Directory.new()
iex> nil = FDBC.Directory.subspace(r)
iex> users_dir = FDBC.Directory.open!(r, db, ["users"], create: true)
iex> %FDBC.Subspace{} = FDBC.Directory.subspace(users_dir)

If the directory was created previously (e.g., in a prior session or by another client), you can open it via its path:

iex> {:ok, users} = FDBC.Directory.new() |> FDBC.Directory.open(db, ['users'])

Similarly to a file system directories can be enumerated, moved and removed, where all paths are relative to the directory provided:

iex> subdirs = FDBC.Directory.new() |> FDBC.Directory.list(db, [])
iex> FDBC.Directory.new() |> FDBC.Directory.move(db, ["a"], ["b"])
iex> FDBC.Directory.new() |> FDBC.Directory.remove(db, ["a"])

Partitions

Under normal operation, a directory does not share a common key prefix with its subdirectories. As a result, related directories are not necessarily located together in key-space. This means that you cannot use a single range query to read all contents of a directory and its descendants simultaneously, for example.

For most applications this behavior is acceptable, but in some cases it may be useful to have a directory tree in your hierarchy where every directory shares a common prefix. For this purpose, the directory layer supports creating partitions within a directory. A partition is a directory whose prefix is prepended to all of its descendant directories’ prefixes.

A partition can be created by setting :partition to in options, this will also set the directories label to partition and thus trying to create a partition with a custom label will raise an exception.

iex> r = FDBC.Directory.new()
iex> FDBC.Directory.create(r, db, ["p1"], partition: true)
iex> users_dir = FDBC.Directory.open!(r, db, ["p1", "users"], create: true)

Directory partitions have the following drawbacks, and in general they should not be used unless specifically needed:

  • Directories cannot be moved between different partitions.

  • Directories in a partition have longer prefixes than their counterparts outside of partitions, which reduces performance. Nesting partitions inside of other partitions results in even longer prefixes.

  • The root directory of a partition cannot be used to pack/unpack keys and therefore cannot be used to create subspaces. You must create at least one subdirectory of a partition in order to store content in it.

Summary

Functions

Creates a directory at the given path relative to the current working directory.

Similar to create/4 but raises an error on failure.

Checks if a directory exists for the given path relative to the current working directory.

Returns the label of the provided directory.

List the immediate subdirectories for the given path relative to the current working directory.

Similar to list/3 but raises an error on failure.

Move the source directory to the destination directory, relative to the current working directory.

Similar to move/4 but raises an error on failure.

The default instance of the root directory.

Opens the directory at the given path relative to the current working directory.

Similar to open/4 but raises an error on failure.

Returns the path of the provided directory.

Remove the directory at the given path relative to the current working directory.

Similar to remove/3 but raises an error on failure.

Returns the Subspace.t() of the provided directory, or nil if the directory is a partition.

Similar to subspace/1 but raises an error on failure.

Types

partition()

@type partition() :: %{content: FDBC.Subspace.t(), metadata: FDBC.Subspace.t()}

t()

@type t() :: %FDBC.Directory{allow_manual_prefixes: term(), file_system: term()}

Functions

create(dir, db_or_tr, path, opts \\ [])

@spec create(t(), FDBC.Database.t() | FDBC.Transaction.t(), [String.t()], keyword()) ::
  :ok | {:error, term()}

Creates a directory at the given path relative to the current working directory.

Options

  • :label - the label to assign to the directory to aid in its identification.

  • :parents - allows the creation of parent directories if not present, otherwise an exception will be raised.

  • :partition - the directory is created as a partition whose prefix is prepended to all of its descendant directories’ prefixes.

  • :prefix - creates the directory with the provided prefix, otherwise the prefix is allocated automatically.

create!(dir, db_or_tr, path, opts \\ [])

@spec create!(t(), FDBC.Database.t() | FDBC.Transaction.t(), [String.t()], keyword()) ::
  :ok

Similar to create/4 but raises an error on failure.

exists?(dir, db_or_tr, path \\ [])

@spec exists?(t(), FDBC.Database.t() | FDBC.Transaction.t(), [String.t()]) ::
  boolean()

Checks if a directory exists for the given path relative to the current working directory.

If no path is provided then the current working directory is checked.

label(dir)

@spec label(t()) :: binary() | nil

Returns the label of the provided directory.

list(dir, db_or_tr, path \\ [])

@spec list(t(), FDBC.Database.t() | FDBC.Transaction.t(), [String.t()]) ::
  {:ok, [String.t()]} | {:error, term()}

List the immediate subdirectories for the given path relative to the current working directory.

list!(dir, tr, path \\ [])

@spec list!(t(), FDBC.Transaction.t(), [String.t()]) :: [String.t()]

Similar to list/3 but raises an error on failure.

move(dir, db_or_tr, source, destination, opts \\ [])

@spec move(
  t(),
  FDBC.Database.t() | FDBC.Transaction.t(),
  [String.t()],
  [String.t()],
  keyword()
) ::
  :ok | {:error, term()}

Move the source directory to the destination directory, relative to the current working directory.

Options

  • :parents - allows the creation of parent directories if not present, otherwise an error will occur.

move!(dir, tr, source, destination, opts \\ [])

@spec move!(t(), FDBC.Transaction.t(), [String.t()], [String.t()], keyword()) :: :ok

Similar to move/4 but raises an error on failure.

new(opts \\ [])

@spec new(keyword()) :: t()

The default instance of the root directory.

Options

  • :allow_manual_prefixes - allows the creation of manual prefixes, if not enabled attempts to provide a manual prefix will result in an exception.

  • :partition - allows for a different partition other than the root to be used. By default an empty prefix is used for the partition's content subspace, while the value 0xFE is used for the metadata subspace.

open(directory, db_or_tr, path, opts \\ [])

@spec open(t(), FDBC.Database.t() | FDBC.Transaction.t(), [String.t()], keyword()) ::
  {:ok, t()} | {:error, term()}

Opens the directory at the given path relative to the current working directory.

Options

  • :create - allow the directory to be created if it does not exist, in which case the options from create/3 will also be honoured.

open!(dir, tr, path, opts \\ [])

@spec open!(t(), FDBC.Transaction.t(), [String.t()], keyword()) :: t()

Similar to open/4 but raises an error on failure.

path(dir)

@spec path(t()) :: [String.t()]

Returns the path of the provided directory.

remove(directory, db_or_tr, path)

@spec remove(t(), FDBC.Database.t() | FDBC.Transaction.t(), [String.t()]) ::
  :ok | {:error, term()}

Remove the directory at the given path relative to the current working directory.

remove!(dir, tr, path)

@spec remove!(t(), FDBC.Transaction.t(), [String.t()]) :: :ok

Similar to remove/3 but raises an error on failure.

subspace(dir)

@spec subspace(t()) :: FDBC.Subspace.t() | nil

Returns the Subspace.t() of the provided directory, or nil if the directory is a partition.

subspace!(dir)

@spec subspace!(t()) :: FDBC.Subspace.t()

Similar to subspace/1 but raises an error on failure.