Documentation
¶
Overview ¶
Package blobfs is the root package of blobfs, a SQL-backed tree of directories and file metadata over an object store the library does not import.
Ids and names:
- NewID mints a row id, and ParseID checks one a caller supplies.
- RootID is the id of the one root directory of an install.
- NormalizeName puts a name in the form uniqueness compares, and ValidateName checks it; MaxNameLength is the longest name allowed.
The entities and their statuses:
- Directory is a node of the tree, File a file's metadata, and Object what the store reports about a stored object.
- Status is a file's place in the two-phase write and delete: StatusPending, StatusAvailable, or StatusDeleting. Transition checks a change of Status.
- DirectoryStatus is a directory's: DirectoryStatusActive or DirectoryStatusDeleting.
Keys:
- KeyValidator is the one question a write asks of the object store.
- NewKey builds a file's key, <id>/<sanitized name>, and validates it; SanitizeFilename builds the name's segment. A key is opaque: nothing in blobfs parses it.
Errors:
- The sentinels, ErrNotFound through ErrCycle, are what package data maps its outcomes onto, matched with errors.Is.
- IDError, NameError, KeyError, and TransitionError report a refused id, name, key, and status change, each matching its sentinel.
- ViolationError reports a violation of one of blobfs's constraints, naming the sentinel and the constraint; DeletingError reports a mutation refused because a row's delete has begun, naming whose.
- The Constraint constants, ConstraintPrimaryKeyDirectory and its siblings, name the constraints and unique indexes blobfs's DDL declares, as ViolationError.Constraint carries them.
The package is Go only; its one dependency outside the standard library is golang.org/x/text, for Unicode normalization. A consumer that keeps its own persistence takes only this package.
docs/concepts.md explains the model and docs/features.md states every rule, the schema included.
Index ¶
- Constants
- Variables
- func NewID() string
- func NewKey(store KeyValidator, id, name string) (string, error)
- func NormalizeName(name string) string
- func ParseID(id string) (string, error)
- func SanitizeFilename(name string) string
- func Transition(from, to Status) error
- func ValidateName(name string) error
- type DeletingError
- type Directory
- type DirectoryStatus
- type File
- type IDError
- type KeyError
- type KeyValidator
- type NameError
- type Object
- type Status
- type TransitionError
- type ViolationError
Constants ¶
const ( // ConstraintPrimaryKeyDirectory is the primary key of blobfs_directory. // A violation is ErrIDTaken: a caller-supplied id another directory // already carries. ConstraintPrimaryKeyDirectory = "blobfs_pk_directory" // ConstraintPrimaryKeyFile is the primary key of blobfs_file. A // violation is ErrIDTaken: a caller-supplied id another file already // carries. ConstraintPrimaryKeyFile = "blobfs_pk_file" // ConstraintUniqueDirectoryRoot is the partial unique index that allows // one directory row with no parent: the one root per install. A // violation is ErrRootDirectory. ConstraintUniqueDirectoryRoot = "blobfs_uq_directory_root" // ConstraintUniqueDirectoryParentName is the unique constraint on // blobfs_directory (parent_id, name). A violation is ErrNameTaken. A // name a deleting row holds is refused before the constraint is reached. ConstraintUniqueDirectoryParentName = "blobfs_uq_directory_parent_name" // ConstraintUniqueFileDirectoryName is the unique constraint on // blobfs_file (directory_id, name). A violation is ErrNameTaken. A name // a deleting row holds is refused before the constraint is reached. ConstraintUniqueFileDirectoryName = "blobfs_uq_file_directory_name" // ConstraintForeignKeyDirectoryParent is the foreign key from // blobfs_directory.parent_id to blobfs_directory.id. On an insert or a // move a violation is ErrNotFound; on a delete it means the directory // still has child directories. ConstraintForeignKeyDirectoryParent = "blobfs_fk_directory_parent" // ConstraintForeignKeyFileDirectory is the foreign key from // blobfs_file.directory_id to blobfs_directory.id. On an insert or a // move a violation is ErrNotFound; on a delete it means the directory // still has files. ConstraintForeignKeyFileDirectory = "blobfs_fk_file_directory" )
The names of the constraints and unique indexes blobfs's DDL declares, which package data maps to sentinels. They are public API: a violation reaches a consumer as ViolationError.Constraint.
const MaxNameLength = 255
MaxNameLength is the longest name a directory or file may carry, in runes: the per-component limit ext4, NTFS, and APFS share.
const RootID = "00000000-0000-0000-0000-000000000000"
RootID is the id of the one root directory of an install: the nil UUID, seeded by the directory migration. Every path starts at the root, and a consumer that needs the root reads it by this id instead of searching for the row with no parent.
Variables ¶
var ( // ErrNotFound reports a directory or file that does not exist, including // a parent or directory id that an insert or move referenced. ErrNotFound = errors.New("blobfs: not found") // ErrNameTaken reports a name a row of the same kind that is not deleting // already holds in the target directory. A deleting holder is refused with // its DeletingError instead; see Errors and constraints in // docs/features.md. ErrNameTaken = errors.New("blobfs: name taken") // ErrInvalidName reports a name ValidateName refused. A NameError // carries the reason. ErrInvalidName = errors.New("blobfs: invalid name") // ErrInvalidPath reports a path package data refused: one that starts // with a slash, or has a segment ValidateName refuses, which also matches // ErrInvalidName. ErrInvalidPath = errors.New("blobfs: invalid path") // ErrRootDirectory reports an operation refused because it targets the // root: creating a second row with no parent, or deleting, moving, or // renaming the root. There is exactly one root per install, seeded by // the schema with the id RootID. ErrRootDirectory = errors.New("blobfs: the root directory") // ErrInvalidKey reports a key the store refused. A KeyError carries the // key and the store's reason. ErrInvalidKey = errors.New("blobfs: invalid key") // ErrInvalidID reports a caller-supplied row id that ParseID refused: // text that is not a UUID, or the nil UUID, which is RootID. An IDError // carries the reason. ErrInvalidID = errors.New("blobfs: invalid id") // ErrIDTaken reports a caller-supplied row id that a row of the same // table already carries: the primary key refused the insert. ErrIDTaken = errors.New("blobfs: id taken") // ErrNotEmpty reports a directory delete refused because the directory // still has child directories or files. ErrNotEmpty = errors.New("blobfs: directory not empty") // ErrInvalidTransition reports a status change the transition table // does not allow. A TransitionError carries the two statuses. ErrInvalidTransition = errors.New("blobfs: invalid status transition") // ErrDeleting reports a mutation refused because the row is deleting or a // directory it reaches is, so nothing enters or leaves a branch being // deleted. Package data reports it as a DeletingError. ErrDeleting = errors.New("blobfs: row is deleting") // ErrNotDeleting reports a purge, the last step of the two-phase // delete, refused because the row exists and is not deleting: its // delete has not begun. The caller runs the delete's first step first. ErrNotDeleting = errors.New("blobfs: the file is not deleting") // ErrReferenced reports a delete refused by a foreign key blobfs does // not own: a consumer's row still references the file or directory. // The sqlate.ConstraintError stays reachable, so the consumer matches // the constraint's name against its own and classifies further. ErrReferenced = errors.New("blobfs: the row is referenced by a consumer's row") // ErrCycle reports a directory move whose new parent sits inside the // directory's own subtree, the directory itself included. ErrCycle = errors.New("blobfs: move would create a cycle") )
The sentinels package data maps its outcomes onto. A database violation of one of blobfs's own documented constraints becomes one of these, carried by a ViolationError; a violation of a consumer's constraint is left unclassified.
Functions ¶
func NewID ¶
func NewID() string
NewID mints a new row id: a version 7 UUID in its canonical string form, so ids sort in insertion order and bind to a uuid column as text.
func NewKey ¶
func NewKey(store KeyValidator, id, name string) (string, error)
NewKey builds a file's key from its id and display name and validates it against the store. The key is id, a slash, and SanitizeFilename(name). A refusal is a KeyError carrying the store's reason.
func NormalizeName ¶
NormalizeName returns name in Unicode normalization form C. Uniqueness is an exact match on the stored name, so package data normalizes every name before an insert or a rename: a composed and a decomposed spelling of the same name then collide instead of coexisting.
func ParseID ¶
ParseID checks a row id a caller supplies in place of a minted one and returns it in the canonical form NewID mints, so the row and the key built from its id carry the same text whatever form the caller wrote. Any form uuid.Parse accepts is taken. The nil UUID is refused, because it is RootID and belongs to the seeded root. A refusal is an IDError.
func SanitizeFilename ¶
SanitizeFilename turns a display name into the filename segment of a key. The segment is frozen when the pending row is inserted, is never kept in step with a later rename, and is never parsed back out of the key. It exists so an operator browsing the raw container can read it.
The result satisfies the key rules of the providers blobfs targets: each invalid UTF-8 sequence, slash, backslash, and control character becomes an underscore, trailing dots and whitespace are removed, and a name that nothing survives of becomes "file".
func Transition ¶
Transition returns nil when the transition table allows the change from one status to another and a TransitionError otherwise.
func ValidateName ¶
ValidateName reports whether name may be a directory or file name. A name is non-empty, valid UTF-8, at most MaxNameLength runes, contains no slash and no control character, and is neither "." nor "..". The rejection is a NameError. Validate the normalized form: the check counts runes, and normalization can change the count.
Types ¶
type DeletingError ¶ added in v0.3.0
DeletingError reports a mutation refused with ErrDeleting and names whose delete refused it: a file's own, with Directory false and ID the file, or a directory's, with Directory true and ID the directory. Err is the refusal's cause when it has one. See Errors and constraints in docs/features.md.
func (*DeletingError) Error ¶ added in v0.3.0
func (e *DeletingError) Error() string
func (*DeletingError) Is ¶ added in v0.3.0
func (e *DeletingError) Is(target error) bool
Is reports whether target is ErrDeleting.
func (*DeletingError) Unwrap ¶ added in v0.3.0
func (e *DeletingError) Unwrap() error
Unwrap returns the cause, or nil when there is none.
type Directory ¶
type Directory struct {
ID string `json:"id"`
ParentID *string `json:"parent_id"`
Name string `json:"name"`
Status DirectoryStatus `json:"status"`
Version int64 `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Directory is one node of the tree, a row of blobfs_directory. The root alone has a nil ParentID and the Name "/". Version is the concurrency token the guarded commands check. CreatedAt and UpdatedAt are in time.UTC, whatever time.Local is. The json tags are the scan and binding contract.
type DirectoryStatus ¶ added in v0.2.0
type DirectoryStatus string
DirectoryStatus is a directory row's status: active until the delete of its branch marks it deleting, which nothing undoes. It binds and scans as the status column's text. See Deleting a branch in docs/concepts.md.
const ( // DirectoryStatusActive marks a directory that accepts children, files, // and moves: every directory, the root included, until a mark. DirectoryStatusActive DirectoryStatus = "active" // DirectoryStatusDeleting marks a directory whose branch is being // removed. Nothing enters or leaves it, and its rows keep their names // until they are removed. DirectoryStatusDeleting DirectoryStatus = "deleting" )
func (DirectoryStatus) Mutable ¶ added in v0.2.0
func (s DirectoryStatus) Mutable() bool
Mutable reports whether a directory in status s accepts a create or a move beneath it, and a move of itself or of anything in it: every status but deleting.
func (DirectoryStatus) String ¶ added in v0.2.0
func (s DirectoryStatus) String() string
type File ¶
type File struct {
ID string `json:"id"`
DirectoryID string `json:"directory_id"`
Name string `json:"name"`
Status Status `json:"status"`
Key string `json:"key"`
Size *int64 `json:"size"`
ContentType string `json:"content_type"`
ETag *string `json:"etag"`
Version int64 `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
File is one file's metadata, a row of blobfs_file. Key is the object's key, built once by NewKey and never parsed back. Size and ETag are nil until the row is available, and ContentType is the declared type until the completion records the store's. CreatedAt and UpdatedAt are in time.UTC, whatever time.Local is. The json tags are the scan and binding contract.
type IDError ¶
IDError reports a row id that ParseID refused, with the reason. It matches ErrInvalidID under errors.Is.
type KeyError ¶
KeyError reports a key the store refused, with the store's reason. It matches ErrInvalidKey under errors.Is, and errors.As reaches the store's own error through Unwrap.
type KeyValidator ¶
type KeyValidator interface {
// ValidateKey returns a non-nil error that says why when the store
// refuses key.
ValidateKey(key string) error
}
KeyValidator is the one question a write asks of the object store: whether it accepts a key. It carries no maximum length; the store's own validation enforces its limit, counted the way the store counts.
type NameError ¶
NameError reports a name that ValidateName refused, with the reason. It matches ErrInvalidName under errors.Is.
type Object ¶
Object is what the store reports about a stored object, which the completion of a write records.
type Status ¶
type Status string
Status is a file row's place in the two-phase write and delete: pending until its object exists, then available, and deleting once its delete begins. It binds and scans as the status column's text.
const ( // StatusPending marks a row whose object has not been written yet. StatusPending Status = "pending" // StatusAvailable marks a row whose object exists in the store. StatusAvailable Status = "available" // StatusDeleting marks a row whose object is being removed. The row // keeps its name until it is purged. StatusDeleting Status = "deleting" )
type TransitionError ¶
TransitionError reports a refused status change. It matches ErrInvalidTransition, and ErrDeleting too when From is deleting.
func (*TransitionError) Error ¶
func (e *TransitionError) Error() string
func (*TransitionError) Is ¶
func (e *TransitionError) Is(target error) bool
Is reports whether e stands for target: ErrInvalidTransition always, and ErrDeleting when the refused change started from a deleting row.
type ViolationError ¶
ViolationError reports a database constraint violation a classifier mapped to a sentinel: the sentinel, the constraint's name, and the database's error as the cause. Unwrap yields the sentinel and the cause, and the message never prints the driver's text.
func (*ViolationError) Error ¶
func (e *ViolationError) Error() string
func (*ViolationError) Unwrap ¶
func (e *ViolationError) Unwrap() []error
Unwrap returns the sentinel and the cause, so errors.Is matches the sentinel and errors.As finds the cause's types.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package data is blobfs's persistence: its standard-tier SQL statements, the pattern namespace it publishes, and the Store that runs the statements through sqlate on any engine sqlate has a dialect for.
|
Package data is blobfs's persistence: its standard-tier SQL statements, the pattern namespace it publishes, and the Store that runs the statements through sqlate on any engine sqlate has a dialect for. |
|
datatest
Package datatest is the conformance suite of the data package: the checks every data.Store must pass against a live database, whatever the engine, the returning commands' form, and the variant.
|
Package datatest is the conformance suite of the data package: the checks every data.Store must pass against a live database, whatever the engine, the returning commands' form, and the variant. |
|
postgres
module
|