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
@type partition() :: %{content: FDBC.Subspace.t(), metadata: FDBC.Subspace.t()}
Functions
@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.
@spec create!(t(), FDBC.Database.t() | FDBC.Transaction.t(), [String.t()], keyword()) :: :ok
Similar to create/4
but raises an error on failure.
@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.
Returns the label of the provided directory.
@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.
@spec list!(t(), FDBC.Transaction.t(), [String.t()]) :: [String.t()]
Similar to list/3
but raises an error on failure.
@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.
@spec move!(t(), FDBC.Transaction.t(), [String.t()], [String.t()], keyword()) :: :ok
Similar to move/4
but raises an error on failure.
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 value0xFE
is used for the metadata subspace.
@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 fromcreate/3
will also be honoured.
@spec open!(t(), FDBC.Transaction.t(), [String.t()], keyword()) :: t()
Similar to open/4
but raises an error on failure.
Returns the path of the provided directory.
@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.
@spec remove!(t(), FDBC.Transaction.t(), [String.t()]) :: :ok
Similar to remove/3
but raises an error on failure.
@spec subspace(t()) :: FDBC.Subspace.t() | nil
Returns the Subspace.t()
of the provided directory, or nil
if the
directory is a partition.
@spec subspace!(t()) :: FDBC.Subspace.t()
Similar to subspace/1
but raises an error on failure.