DBIx-QuickORM

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


    - Rename select to query
    - Rename sqla-source to query-source
    - Work on eliminating the assumption that SQLAbstract will always do the work

0.000010  2025-05-07 06:36:38-07:00 America/Los_Angeles (TRIAL RELEASE)

    - Still felshing out functionality after the rewrite
    - Add 'forked' async emulation
    - Add 'aside' async functionality
    - Add more txn callbacks

0.000009  2025-05-06 08:17:08-07:00 America/Los_Angeles (TRIAL RELEASE)

0.000008  2025-05-02 01:20:14-07:00 America/Los_Angeles (TRIAL RELEASE)

0.000007  2025-05-02 01:04:21-07:00 America/Los_Angeles (TRIAL RELEASE)

0.000006  2025-05-02 01:02:32-07:00 America/Los_Angeles

0.000005  2025-05-02 00:19:35-07:00 America/Los_Angeles (TRIAL RELEASE)

lib/DBIx/QuickORM/Connection.pm  view on Meta::CPAN


    my $txn = DBIx::QuickORM::Connection::Transaction->new(
        id            => $self->{+_TXN_COUNTER}++,
        savepoint     => $sp,
        trace         => \@caller,
        on_fail       => $params{on_fail},
        on_success    => $params{on_success},
        on_completion => $params{on_completion},
    );

    $self->_txn_attach_relative_callbacks($txn, \%params);

    push @{$txns} => $txn;
    weaken($txns->[-1]);

    my $finalize = $self->_txn_finalizer($sp);

    unless($cb) {
        $txn->set_no_last(1);
        $txn->set_finalize($finalize);
        return $txn;

lib/DBIx/QuickORM/Connection.pm  view on Meta::CPAN

    }

    croak "A transaction is already open, but it is not controlled by DBIx::QuickORM" if $dialect->in_txn;

    $dialect->start_txn;
    return undef;
}

=pod

=item $con->_txn_attach_relative_callbacks($txn, \%params)

Attaches C<on_parent_*> callbacks to the current innermost transaction and
C<on_root_*> callbacks to the outermost one. Called before C<$txn> is pushed
onto the stack.

=cut

sub _txn_attach_relative_callbacks {
    my $self = shift;
    my ($txn, $params) = @_;

    my $txns = $self->{+TRANSACTIONS};

    # With an empty stack the new txn is its own root, but it has no parent;
    # on_parent_* callbacks are documented as no-ops in that case. Stack
    # entries are weak references, so check definedness before using them.
    my $parent = @$txns ? $txns->[-1] : undef;
    my $root   = @$txns ? $txns->[0]  : $txn;

    if ($parent) {
        $parent->add_fail_callback($params->{'on_parent_fail'})             if $params->{on_parent_fail};
        $parent->add_success_callback($params->{'on_parent_success'})       if $params->{on_parent_success};
        $parent->add_completion_callback($params->{'on_parent_completion'}) if $params->{on_parent_completion};
    }

lib/DBIx/QuickORM/Connection.pm  view on Meta::CPAN


    return;
}

=pod

=item $cb = $con->_txn_finalizer($savepoint_or_undef)

Builds the one-shot finalize callback that pops the transaction off the
stack, commits or rolls back (savepoint or real transaction), and fires the
transaction's callbacks via C<terminate>.

=cut

sub _txn_finalizer {
    my $self = shift;
    my ($sp) = @_;

    my $txns    = $self->{+TRANSACTIONS};
    my $dialect = $self->dialect;

lib/DBIx/QuickORM/Connection/Transaction.pm  view on Meta::CPAN


=encoding UTF-8

=head1 NAME

DBIx::QuickORM::Connection::Transaction - One transaction or savepoint on a
DBIx::QuickORM connection.

=head1 DESCRIPTION

Represents a single transaction (or savepoint) and the callbacks queued
against it. C<commit> and C<rollback> record the outcome and break out of the
enclosing C<QORM_TRANSACTION> loop; C<terminate> records the final result and
fires the queued success / fail / completion callbacks. An optional finalize
callback runs when the transaction completes or, as a safety net, when the
object is destroyed while still pending.

=head1 SYNOPSIS

    QORM_TRANSACTION: {
        my $txn = DBIx::QuickORM::Connection::Transaction->new(id => $id);
        $txn->add_success_callback(sub { ... });
        ...
        $txn->commit;

lib/DBIx/QuickORM/Connection/Transaction.pm  view on Meta::CPAN

The savepoint name when this transaction is implemented as a savepoint, undef
for a top-level transaction. Use C<is_savepoint> for a boolean check.

=item on_success

=item on_fail

=item on_completion

Callback queues (arrayrefs, or a single coderef normalized to one) fired by
C<terminate>. Success or fail callbacks run depending on the outcome,
followed by completion callbacks in both cases.

=item verbose

When true, C<commit> / C<rollback> warn a trace line. A string longer than
one character is used as the transaction name in that warning.

=item result

Undef while open; 1 on success, 0 on failure once terminated.

lib/DBIx/QuickORM/Connection/Transaction.pm  view on Meta::CPAN


    no warnings 'exiting';
    last QORM_TRANSACTION;
}

=pod

=item ($ok, $errors) = $txn->terminate($res, $err)

Records the final result, clears the callback queues, then runs the
success-or-fail callbacks followed by the completion callbacks. Returns a
list: a boolean for whether all callbacks succeeded, and an arrayref of any
callback errors (undef when none). The savepoint name is retained so
post-completion callbacks can still see C<is_savepoint>.

=cut

sub terminate {
    my $self = shift;
    my ($res, $err) = @_;

    $self->{+RESULT} = $res ? 1 : 0;
    $self->{+ERRORS} = $res ? undef : $err;

lib/DBIx/QuickORM/Dialect.pm  view on Meta::CPAN

# {{{ Schema Builder Code
###############################################################################

=pod

=item $schema = $dialect->build_schema_from_db(%params)

Introspects the live database and returns a L<DBIx::QuickORM::Schema>.
Requires an C<autofill> object. After all tables are built it runs the
autofill C<tables> hook with the complete name-to-table hashref under the
C<tables> key, giving callbacks one place to inspect or adjust the full set.

=cut

sub build_schema_from_db {
    my $self = shift;
    my %params = @_;

    croak "No autofill object provided" unless $params{autofill};

    my $dbh = $self->dbh;

lib/DBIx/QuickORM/Manual.pm  view on Meta::CPAN


Fetch, create, update, and delete rows with handles: where clauses, ordering,
limiting, iterators, and more.

=item L<DBIx::QuickORM::Manual::Relations>

Define links (foreign keys) and follow them between rows, plus joins.

=item L<DBIx::QuickORM::Manual::Transactions>

Transactions, nested transactions and savepoints, callbacks, and automatic
retry.

=item L<DBIx::QuickORM::Manual::Async>

Asynchronous, aside, and forked queries, and other multi-connection work.

=item L<DBIx::QuickORM::Manual::Types>

Inflating and deflating column values (JSON, UUID, ...) and writing your own
type classes.

lib/DBIx/QuickORM/Manual/Features.pm  view on Meta::CPAN

Fetch, create, update, and delete rows with handles (where/order/limit/
iterators). See L<DBIx::QuickORM::Manual::Querying>.

=item Relations

Define links (foreign keys) and follow them between rows, plus joins. See
L<DBIx::QuickORM::Manual::Relations>.

=item Transactions

Transactions, nested transactions / savepoints, callbacks, and auto-retry.
See L<DBIx::QuickORM::Manual::Transactions>.

=item Async, aside, and forked queries

Run queries asynchronously, on a side connection, or in a forked child. See
L<DBIx::QuickORM::Manual::Async>.

=item Caching / row identity

One in-memory copy of each row per connection. See

lib/DBIx/QuickORM/Manual/QuickStart.pm  view on Meta::CPAN


The full handle interface: where clauses, ordering, limiting, iterators,
create, update, and delete.

=item L<DBIx::QuickORM::Manual::Relations>

Define links (foreign keys), follow them, and join across them.

=item L<DBIx::QuickORM::Manual::Transactions>

Nested transactions, savepoints, callbacks, and automatic retry.

=item L<DBIx::QuickORM::Manual>

The documentation hub, linking every tutorial, guide, and reference.

=back

=head1 SOURCE

The source code repository for DBIx-QuickORM can be found at

lib/DBIx/QuickORM/Manual/Recipes.pm  view on Meta::CPAN

=item L<DBIx::QuickORM::Manual::Querying>

Fetch, create, update, and delete rows with handles.

=item L<DBIx::QuickORM::Manual::Relations>

Define links (foreign keys) and follow them between rows, plus joins.

=item L<DBIx::QuickORM::Manual::Transactions>

Transactions, nested transactions and savepoints, callbacks, and automatic
retry.

=item L<DBIx::QuickORM::Manual::Async>

Asynchronous, aside, and forked queries, and other multi-connection work.

=item L<DBIx::QuickORM::Manual::Types>

Inflating and deflating column values (JSON, UUID, ...) and writing your own
type classes.

lib/DBIx/QuickORM/Manual/Transactions.pm  view on Meta::CPAN


=head1 NAME

DBIx::QuickORM::Manual::Transactions - A guide to transactions in
L<DBIx::QuickORM>.

=head1 DESCRIPTION

This guide covers transactions in L<DBIx::QuickORM>: starting a transaction,
nesting transactions as savepoints, queuing success / fail / completion
callbacks, controlling a transaction by hand, and automatically retrying work
when a connection is lost.

Transactions are controlled through the connection
(L<DBIx::QuickORM::Connection>). Each transaction or savepoint is represented
by a L<DBIx::QuickORM::Connection::Transaction> object.

This is part of the L<DBIx::QuickORM> documentation; see
L<DBIx::QuickORM::Manual> for the documentation hub.

=head1 BASIC TRANSACTIONS

lib/DBIx/QuickORM/Manual/Transactions.pm  view on Meta::CPAN

            # rolling this back undoes only $bar's insert
        });

        $baz->insert(...);
    });                        # commit happens here

Use C<is_savepoint> on a transaction object to tell which kind it is.

=head1 CALLBACKS

You can queue callbacks to run when a transaction finishes. They are fired
after the underlying commit or rollback has been issued.

=over 4

=item on_success

Runs only if the transaction commits.

=item on_fail

lib/DBIx/QuickORM/Manual/Transactions.pm  view on Meta::CPAN


Pass them as parameters to C<txn()>:

    $con->txn(
        on_success    => sub { my $txn = shift; ... },
        on_fail       => sub { my $txn = shift; ... },
        on_completion => sub { my $txn = shift; ... },
        action        => sub { my $txn = shift; ... },
    );

You can also queue callbacks against an existing transaction object directly
with C<add_success_callback>, C<add_fail_callback>, and
C<add_completion_callback>.

=head2 PARENT AND ROOT CALLBACKS

When you are inside a nested transaction you can attach callbacks to an outer
transaction instead of the current one. This is useful when a savepoint wants
to defer work until the enclosing transaction actually commits.

The C<on_parent_*> variants attach to the immediate parent transaction; the
C<on_root_*> variants attach to the outermost (root) transaction no matter how
deeply nested you are:

    $con->txn(
        on_parent_success    => sub { ... },
        on_parent_fail       => sub { ... },
        on_parent_completion => sub { ... },

        on_root_success    => sub { ... },
        on_root_fail       => sub { ... },
        on_root_completion => sub { ... },

        action => sub { ... },
    );

When there is no parent or root transaction above the current one, the
corresponding parent / root callbacks are simply no-ops.

=head1 LONG-LIVED TRANSACTIONS

If you need a transaction that is not bound to a single callback, call C<txn()>
without an action. It returns a live L<DBIx::QuickORM::Connection::Transaction>
object that you control by hand:

    my $txn = $con->txn();
    ...
    $txn->commit;    # or $txn->rollback;

lib/DBIx/QuickORM/Schema/Autofill.pm  view on Meta::CPAN

=pod

=encoding UTF-8

=head1 NAME

DBIx::QuickORM::Schema::Autofill - Autofill configuration for schema introspection.

=head1 DESCRIPTION

Holds the type maps, affinity callbacks, and hooks used while autofilling a
schema from a live database. It maps introspected SQL types to
L<DBIx::QuickORM::Type> classes, runs user-supplied hooks at well-known points,
and generates field and link accessors on autovivified row classes.

=head1 SYNOPSIS

    my $autofill = DBIx::QuickORM::Schema::Autofill->new(
        types      => {...},
        affinities => {...},
        hooks      => {...},

lib/DBIx/QuickORM/Schema/Autofill.pm  view on Meta::CPAN

=head1 ATTRIBUTES

=over 4

=item types

Hashref mapping SQL type names to type objects/classes.

=item affinities

Hashref mapping affinity names to arrayrefs of callbacks.

=item hooks

Hashref mapping hook names to arrayrefs of callbacks.

=item autorow

The autovivified row class configuration.

=item skip

Nested hashref describing what to skip during autofill.

=back

=cut

# Maps each valid hook name to its seed key: the args key whose value is
# threaded through the callbacks registered for that hook.
my %HOOKS = (
    column         => 'column',
    columns        => 'columns',
    index          => 'index',
    indexes        => 'indexes',
    links          => 'links',
    post_column    => 'column',
    post_table     => 'table',
    pre_column     => 'column',
    pre_table      => 'table',

lib/DBIx/QuickORM/Schema/Autofill.pm  view on Meta::CPAN

=pod

=item $out = $autofill->hook($name, \%args, $seed)

Run every callback registered for the named hook as a pipeline. Each hook has
a designated seed key in C<\%args> (for example C<table> for the table hooks,
C<name> for the accessor hooks). Every callback is called with the args (plus
C<autofill>) with the running value under the seed key, and its return value
becomes the running value passed to the next callback and ultimately returned.
A callback that modifies the seed in place must still return it so the
callbacks after it (and the caller) see the same value. The pipeline starts
from C<$seed> when given, otherwise from the seed key's value in C<\%args>;
with a single registered callback this matches the old single-callback
behavior exactly.

=cut

sub hook {
    my $self = shift;
    my ($hook, $args, $seed) = @_;

lib/DBIx/QuickORM/Schema/Autofill.pm  view on Meta::CPAN

        $from = $from->{$arg} or return 0;
    }
    return $from;
}

=pod

=item $autofill->process_column(\%col)

Resolve the column's scalar-ref type into a real type object, using the type map
first and then affinity callbacks. Updates the column's C<type> and C<affinity>
in place when a match is found.

=cut

sub process_column {
    my $self = shift;
    my ($col) = @_;

    my $type = $col->{type};
    my $tref = ref($type);

t/AI/transaction_audit.t  view on Meta::CPAN

    ok($txn->rolled_back, "transaction rolled back");
    is(scalar(@{$con->transactions}), 0, "transaction stack is clean");
    ok(!$con->dialect->in_txn, "no transaction left open on the database");

    my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
    my ($count) = $dbh->selectrow_array("SELECT COUNT(*) FROM things WHERE name = 'wedge'");
    $dbh->disconnect;
    is($count, 0, "the insert really was rolled back");
};

subtest on_parent_callbacks => sub {
    my $con = connect_orm();

    my %fired;
    my $ok = eval {
        $con->txn(
            on_parent_fail       => sub { $fired{parent_fail}++ },
            on_parent_completion => sub { $fired{parent_completion}++ },
            on_root_fail         => sub { $fired{root_fail}++ },
            on_root_completion   => sub { $fired{root_completion}++ },
            action               => sub { die "boom\n" },

t/AI/transaction_audit.t  view on Meta::CPAN

    ok(!$ok, "root transaction failed");
    is(\%fired, {root_fail => 1, root_completion => 1}, "on_parent_* are no-ops without a parent, on_root_* fire on self");

    %fired = ();
    $con->txn(sub {
        $con->txn(
            on_parent_success    => sub { $fired{parent_success}++ },
            on_parent_completion => sub { $fired{parent_completion}++ },
            action               => sub { 1 },
        );
        is(\%fired, {}, "parent callbacks have not fired before the parent completes");
    });
    is(\%fired, {parent_success => 1, parent_completion => 1}, "on_parent_* attach to the real parent when nested");
};

subtest already_complete_croaks => sub {
    my $con = connect_orm();

    my $txn = $con->txn();
    $txn->commit;
    ok($txn->complete, "transaction completed");

t/AI/transaction_audit.t  view on Meta::CPAN

    my $con = connect_orm();

    my $saw;
    $con->txn(sub {
        $con->txn(
            on_completion => sub { my $t = shift; $saw = $t->is_savepoint },
            action        => sub { 1 },
        );
    });

    is($saw, 1, "post-completion callbacks still see is_savepoint true for a savepoint txn");
};

done_testing;

t/AI/transaction_extra.t  view on Meta::CPAN

        $con->handle('items')->insert({name => 'outer_keep2'});
    });

    is(
        disk_names(),
        [sort(@$before, 'outer_keep', 'outer_keep2')],
        "outer changes persisted, inner savepoint rollback discarded only its row",
    );
};

subtest callbacks_on_commit => sub {
    my %seen;
    $con->txn(
        action        => sub { $seen{action}++ },
        on_success    => sub { $seen{success}++ },
        on_fail       => sub { $seen{fail}++ },
        on_completion => sub { $seen{completion}++ },
    );
    is(\%seen, {action => 1, success => 1, completion => 1}, "commit fires success+completion, not fail");
};

subtest callbacks_on_rollback => sub {
    my %seen;
    $con->txn(
        action        => sub { $seen{action}++; $_[0]->rollback },
        on_success    => sub { $seen{success}++ },
        on_fail       => sub { $seen{fail}++ },
        on_completion => sub { $seen{completion}++ },
    );
    is(\%seen, {action => 1, fail => 1, completion => 1}, "rollback fires fail+completion, not success");
};

subtest callbacks_added_to_object => sub {
    my %seen;
    $con->txn(sub {
        my $t = shift;
        $t->add_success_callback(sub { $seen{success}++ });
        $t->add_fail_callback(sub { $seen{fail}++ });
        $t->add_completion_callback(sub { $seen{completion}++ });
    });
    is(\%seen, {success => 1, completion => 1}, "add_*_callback success path");
};



( run in 3.126 seconds using v1.01-cache-2.11-cpan-140bd7fdf52 )