NAME

D - Simple interface to work with DB tables

SYNOPSIS

use D;

my $schema =  D::db;      # DBIx::Class::Schema
my $dbh    =  D::dbh;     # DBI handle

my $users  =  D::S  User => { active => 1 };
my $user   =  D::SS User => 1;

my $count  =  D::q00 'SELECT count(*) FROM "User"';

# Find by ID, another PK or set of conditions
my $row =  D::T User =>  7;
my $row =  D::T User =>  { id => 7 };
my $row =  D::T User =>  { email => 'a@example.com' };
my $row =  D::T User =>  [ { f1 => 'v1' }, { f2 => 'v2' } ]; # See SQL::Abstract
my $row =  D::T User =>  { field1 => 'cond1', field2 => 'cond2' };

# Find existing or create by merging COND and DATA. COND merged only if it is a HASH.
# The create dies if user will try to use custom values for PKs
my $row =  D::T User =>  { email => 'a@example.com' }, { name => 'Alice' };
my $row =  D::T User =>  undef, { name => 'Alice' }  # Save as D::C User => { name => 'Alice' };

# Find and update. If not found `{ f2 => 'v2' }` will be created.
# NOTE: It is still not clear should we create when nothing was found and 'update'
# flag is true. Or should we return `undef`.
my $row =  D::T User =>  { f1 => 'value1' }, { f2 => 'v2' }, 'update';


# Update $row
D::RU $row, { f1 => 'v1' };

# Delete $row
D::RD $row;


# Work with transaction
my $atomic =  D::txn;        # ->txn_scope_guard
...
if( ... ) { return; }        # transaction will be rolled back automatically
$atomic->commit;


# All methods from DBIx::Class::Storage are available
my $db =  D::db;
$db->txn_begin;
$db->svp_begin;
$db->txn_do( $coderef );
$db->svp_release;
$db->svp_rollback;
$db->txn_rollback;
$db->txn_commit;

DESCRIPTION

D is a small convenience layer around DBIx::Class and DBI.

It provides shortcuts for:

- Getting a schema/DBI handle - Running raw SQL queries - Basic CRUD helpers using a DBIx::Class resultset

Most helpers accept a table name (Result class moniker) as the first argument.

CONFIGURATION

If you call "db" or "connect" without arguments, configuration is read from C::config->{DbAccess}.

Expected keys:

schema   DBIx::Class::Schema class name (required)
DRVR     DBI driver name (for example: Pg)
NAME     database name
HOST     database host
PORT     database port (optional)
USER     database user
PASS     database password
DSN      optional prebuilt DBI DSN (will be generated if missing)

Timezone:

- If possible, "connect" sets PostgreSQL timezone from $ENV{PGTZ} or $ENV{TZ}; otherwise it defaults to Europe/Zaporozhye.

FUNCTIONS

db

my $schema =  D::db;
my $schema =  D::db $schema;

Return (and cache) a DBIx::Class::Schema instance. When called without arguments, it calls "connect".

dbh

my $dbh = D::dbh;

Shortcut for D::db->storage->dbh.

txn

my $guard =  D::txn;

Return a transaction scope guard (see "txn_scope_guard" in DBIx::Class::Storage).

do

D::do $statement, @bind;

Shortcut for D::dbh->do. See "do" in DBI.

q

my $rows =  D::q $statement, \%attr, @bind;
my $rows =  D::q 'SELECT * FROM users';

Shortcut for DBI::selectall_arrayref.

q00

my $value =  D::q00 $statement, \%attr, @bind;

Return the first field of the first row from "q". This is useful for queries like:

SELECT count(*) FROM table
SELECT name FROM table LIMIT 1

rs

my $result_set = D::rs $statement, @bind;

Prepare and execute a statement and return resultset.

qh

my $rows = D::qh $statement, \%attr, @bind;

Query and return an array of hashes.

report

my $rs = D::report $sql, \@columns, @bind;

Run arbitrary SQL using DBIx::Class::Report and return its resultset.

dsn

my $dsn = D::dsn \%db_conf;

Build a DBI DSN from config keys.

connect

my $schema = D::connect;   # Pass through to DBI->connect
my $schema = D::connect( $schema_class, $dsn, $user, $pass );

Connect to the database and return a schema instance. See "connect" in DBI. Here `$schema_class` is DBIx::Class schema of your application. See Schema.

obtain_data

my $row = D::obtain_data($table, $cond, $data, $update);

Try to find a row by:

- ID (when $cond is a scalar) - Condition (when $cond is a hashref)

If $update is true and $data is provided, the found row is updated.

If nothing was found, and the search was not done by ID, a new row is created from the merged $cond and $data.

This helper refuses to create rows with an explicit primary key id.

T

my $rs  =  D::T 'User';
my $row =  D::T 'User', $id;
my $row =  D::T 'User', \%cond, \%data;
my $row =  D::T 'User', \%cond, \%data, 'update';

Entry point for table access:

- With one argument returns a resultset (see "S"). - With more arguments calls "obtain_data".

INSECURE

my $rs  =  D::INSECURE 'User';
my $row =  D::INSECURE 'User', \%cond, \%data;

Like "T", but disables security guard logic for the duration of the call.

C

my $row = D::C $table, \%data;

Create a row in $table.

S

my $rs = D::S $table, \%cond, @attrs;

Search rows in $table and return a resultset.

SS

my $row =  D::SS $table, \%cond;

Search and return a single row.

RU

D::RU $row, \%data;

Update a row with provided data.

RD

D::RD $row;
D::RD $table, \%cond;

Delete a row.

In secure mode, table access routes through $rs->guard(C::C) (see "SECURITY NOTES").

columns_info

my $info =  D::columns_info 'User';
my $req  =  D::columns_info 'User', { required => 1 };

Return column metadata from the result source. If required is true, nullable columns and columns with defaults are excluded. See "columns_info" in DBIx::Class::ResultSource.

SECURITY NOTES

Secure mode is enabled by default and is meant to restrict data access and mutations through per-resultset guard logic.

This distribution includes only a stub guard implementation in Schema::ResultSet; applications are expected to override it.

Even in insecure mode, "obtain_data" refuses to create rows with an explicit primary key id.

SEE ALSO

Mojolicious::Plugin::DbAccess, DBIx::Class, DBI

COPYRIGHT AND LICENSE

Copyright (c) 2026 Eugen Konkov.

This program is free software, you can redistribute it and/or modify it under the terms of the MIT License.