NAME

Net::Blossom::Server::Backend::Postgres - Postgres storage backend for Blossom servers

SYNOPSIS

use Net::Blossom::Server;
use Net::Blossom::Server::Backend::Postgres;

my $storage = Net::Blossom::Server::Backend::Postgres->new(
    dsn      => 'dbi:Pg:dbname=blossom;host=/var/run/postgresql',
    username => 'blossom',
    password => $password,
    base_url => 'https://cdn.example.com',
);
$storage->deploy_schema;

my $server = Net::Blossom::Server->new(storage => $storage);

DESCRIPTION

Net::Blossom::Server::Backend::Postgres is a Postgres storage backend for Net::Blossom::Server. It stores Blossom blob bytes and metadata in Postgres and implements the Net::Blossom::Server::Storage contract.

Postgres access is provided through DBI and DBD::Pg.

Blob bodies are stored as PostgreSQL large objects. Uploads are written to a temporary file and imported only after the server has validated the hash. Downloads are returned as streams, so blob bodies are not loaded into Perl memory as a whole.

Each active download uses a cloned DBI connection and a read transaction until the body reaches EOF or is closed. Deployments must allow enough PostgreSQL connections for their concurrent downloads. Very large public media services may still prefer a backend that stores blob bytes outside the metadata database.

This backend serializes uploads and deletes for the same hash with a transaction-level PostgreSQL advisory lock. The lock is released when the transaction commits or rolls back. Direct SQL writes to the backend tables do not participate in this locking protocol. Operations for different hashes may run concurrently.

The backend coordinates separate Net::Blossom::Server::Backend::Postgres::MetadataStore and Net::Blossom::Server::Backend::Postgres::BlobStore components on one DBI handle. Applications normally use this top-level storage class.

UPGRADING FROM 0.001001

deploy_schema automatically separates the current large-object schema into metadata and blob-data tables. Existing large-object identifiers, descriptors, and owners are preserved. Back up the database before upgrading.

CONSTRUCTOR

new

my $storage = Net::Blossom::Server::Backend::Postgres->new(
    dsn      => $dsn,
    username => $username,
    password => $password,
    base_url => $url,
);

Creates a storage object. dsn is a DBI Postgres data source string. username and password are optional and are passed to DBI->connect. base_url is the public HTTP or HTTPS URL prefix used when descriptors are created. It may include a path prefix, but not userinfo, query, or fragment parts. Trailing slashes are removed.

Instead of dsn, callers may pass an existing DBI handle as dbh. The handle must be a Postgres handle with AutoCommit enabled so the backend can manage its transactions. The backend clones this handle for each active blob download.

The backend uses the connection's current schema at construction time. All later operations, including cloned download connections, remain bound to that schema.

Optional connect_attrs may be supplied with dsn. The backend always forces AutoCommit, RaiseError, PrintError, and pg_enable_utf8 to values needed by the storage implementation.

METHODS

dbh

my $dbh = $storage->dbh;

Returns the DBI handle used by the backend.

base_url

my $url = $storage->base_url;

Returns the normalized descriptor URL prefix.

metadata_store

Returns the PostgreSQL metadata-store component.

blob_store

Returns the PostgreSQL large-object store component.

deploy_schema

$storage->deploy_schema;

Creates the required Postgres tables and indexes if they do not already exist. They are created in the schema captured by new. This method is safe to call more than once. Metadata indexes are built concurrently, so existing tables remain writable while an index is added. The method requires AutoCommit and repairs an invalid index left by an interrupted build. It migrates the 0.001001 schema automatically.

begin_upload

my $upload = $storage->begin_upload(%context);

Starts a blob upload and returns an upload writer. The server core writes bytes to a temporary file and later calls commit with validated blob metadata. A new blob is imported as a PostgreSQL large object transactionally.

get_blob

my $result = $storage->get_blob($sha256);

Returns a Net::Blossom::Server::BlobResult for $sha256, or undef when the blob is absent. Its body is a stream backed by a dedicated DBI connection. Reading to EOF or calling close releases that connection.

get_blob_range

Returns one requested byte range as a positioned, bounded large-object stream, or undef when the blob is absent. Reading the range or calling close releases its dedicated DBI connection.

head_blob

my $descriptor = $storage->head_blob($sha256);

Returns a Net::Blossom::BlobDescriptor without returning the blob body, or undef when the blob is absent.

delete_blob

my $deleted = $storage->delete_blob($sha256, pubkey => $pubkey);

Deletes one owner relationship when pubkey is supplied. The blob bytes are deleted with pg_lo_unlink when the final owner is removed. Without pubkey, the blob and all owners are deleted.

list_blobs

my $descriptors = $storage->list_blobs($pubkey, limit => 100);

Returns descriptors owned by $pubkey, sorted by uploaded descending and sha256 ascending. cursor and limit follow the Net::Blossom::Server::Storage contract.

SEE ALSO

PostgreSQL large objects, "Large Objects" in DBD::Pg

INTERNAL METHODS

BUILDARGS

Normalizes constructor arguments for Class::Tiny.