DBIO
view release on metacpan or search on metacpan
lib/DBIO/Row.pm view on Meta::CPAN
if (my $backend = $storage->_async_storage) {
return $backend_build->($backend, $fc);
}
$self->throw_exception(
"not an async connection -- connect with { async => ... } to use ${name}_async"
) unless defined $storage->_async_mode;
my @r = eval { $sync_run->() };
return $@ ? $fc->fail($@) : $fc->done(@r);
}
sub insert_async {
my $self = shift;
return $self->_row_run_async('insert',
sub {
my ($backend, $fc) = @_;
my $rsrc = $self->result_source;
$self->throw_exception("No result_source set on this object; can't insert")
unless $rsrc;
return $fc->done($self) if $self->in_storage;
# Related-object multi-create is a multi-statement (and transactional)
# cascade, not a single insert_async; run it synchronously and resolve.
if ( %{ $self->{_relationship_data} || {} }
or
%{ $self->{_inflated_column} || {} } ) {
my $r = eval { $self->insert };
return $@ ? $fc->fail($@) : $fc->done($r);
}
my %current_rowdata = $self->get_columns;
return $backend->insert_async( $rsrc, { %current_rowdata } )->then(sub {
my ($returned_cols) = @_;
$self->_store_inserted_columns(\%current_rowdata, $returned_cols);
$self->store_storage_values if @{$self->storage_value_columns};
return $self;
});
},
sub { $self->insert },
);
}
sub update {
my ($self, $upd) = @_;
# ProxyResultSetMethod (integrated helper): strip proxy slots from the
# dirty-column set so UPDATE statements never include them. "delete
# local" localises the deletion for the scope of this sub, so the
# original dirty flags are restored when update returns.
$self->{_dirty_columns} ||= {};
delete local @{$self->{_dirty_columns}}{@{$self->_proxy_slots||[]}};
# OnColumnChange (integrated helper): short-circuit when nothing is
# registered. When callbacks exist, build the before/around/after
# chain, then delegate to _update_body via around-wrapper; the whole
# orchestration is a no-op if none of the registered columns are dirty.
my $has_change_cbs =
$self->_before_change
|| $self->_around_change
|| $self->_after_change;
if ($has_change_cbs) {
$self->set_inflated_columns($upd) if $upd;
my %dirty = $self->get_dirty_columns
or return $self;
my @all_before = @{$self->_before_change || []};
my @all_around = @{$self->_around_change || []};
my @all_after = @{$self->_after_change || []};
my @before = grep { defined $dirty{$_->{column}} } @all_before;
my @around = grep { defined $dirty{$_->{column}} } @all_around;
my @after = grep { defined $dirty{$_->{column}} } @all_after;
my $allow_override = $self->on_column_change_allow_override_args;
my $final = $allow_override
? sub { $self->_update_body }
: sub { $self->_update_body($upd) };
for (reverse @around) {
my $fn = $_->{method};
my $old = $self->get_storage_value($_->{column});
my $new = $dirty{$_->{column}};
my $old_final = $final;
$final = sub { $self->$fn($old_final, $old, $new) };
}
my $txn_wrap = List::Util::first {
defined $dirty{$_->{column}} && $_->{txn_wrap}
} @all_before, @all_around, @all_after;
my $guard;
$guard = $self->result_source->schema->txn_scope_guard if $txn_wrap;
for (@before) {
my $fn = $_->{method};
my $old = $self->get_storage_value($_->{column});
my $new = $dirty{$_->{column}};
$self->$fn($old, $new);
}
my $ret = $final->();
for (@after) {
my $fn = $_->{method};
my $old = $self->get_storage_value($_->{column});
my $new = $dirty{$_->{column}};
$self->$fn($old, $new);
}
$guard->commit if $txn_wrap;
return $ret;
}
return $self->_update_body($upd);
}
# Core update logic, extracted so OnColumnChange (above) can wrap it
# with before/around/after callbacks without mutual recursion.
sub _update_body {
my ($self, $upd) = @_;
$self->set_inflated_columns($upd) if $upd;
my %to_update = $self->get_dirty_columns
or return $self;
$self->throw_exception( "Not in database" ) unless $self->in_storage;
my $rows = $self->result_source->storage->update(
$self->result_source, \%to_update, $self->_storage_ident_condition
);
if ($rows == 0) {
$self->throw_exception( "Can't update ${self}: row not found" );
} elsif ($rows > 1) {
$self->throw_exception("Can't update ${self}: updated more than one row");
}
$self->{_dirty_columns} = {};
$self->{related_resultsets} = {};
delete $self->{_column_data_in_storage};
# StorageValues (integrated helper): refresh snapshot post-UPDATE.
$self->store_storage_values if @{$self->storage_value_columns};
return $self;
}
sub delete {
my $self = shift;
if (ref $self) {
$self->throw_exception( "Not in database" ) unless $self->in_storage;
$self->result_source->storage->delete(
$self->result_source, $self->_storage_ident_condition
);
delete $self->{_column_data_in_storage};
$self->in_storage(0);
}
else {
my $rsrc = try { $self->result_source_instance }
or $self->throw_exception("Can't do class delete without a ResultSource instance");
my $attrs = @_ > 1 && ref $_[$#_] eq 'HASH' ? { %{pop(@_)} } : {};
my $query = ref $_[0] eq 'HASH' ? $_[0] : {@_};
$rsrc->resultset->search(@_)->delete;
}
return $self;
}
sub get_column {
my ($self, $column) = @_;
$self->throw_exception( "Can't fetch data as class method" ) unless ref $self;
return $self->{_column_data}{$column}
if exists $self->{_column_data}{$column};
lib/DBIO/Row.pm view on Meta::CPAN
Re-selects the row from the database, losing any changes that had
been made. Throws an exception if a proper C<WHERE> clause identifying
the database row can not be constructed (i.e. if the original object
does not contain its entire
L<primary key|DBIO::Manual::Intro/The Significance and Importance of Primary Keys>).
This method can also be used to refresh from storage, retrieving any
changes made since the row was last read from storage.
$attrs, if supplied, is expected to be a hashref of attributes suitable for passing as the
second argument to C<< $resultset->search($cond, $attrs) >>;
Note: If you are using L<DBIO::Replicated::Storage> as your storage, a default of
C<< { force_pool => 'master' } >> is automatically set for
you. Prior to C<< DBIO 0.08109 >> (before 2010) one would have been
required to explicitly wrap the entire operation in a transaction to guarantee
that up-to-date results are read from the master database.
=head2 throw_exception
See L<DBIO::Schema/throw_exception>.
=head2 TO_JSON
my $hashref = $row->TO_JSON;
Returns a hashref of the row's column values suitable for JSON
serialization. Automatically excludes C<text>, C<ntext>, and C<blob>
columns unless C<< is_serializable => 1 >> is set in the column info.
Numeric columns are numified for correct JSON output.
=head2 self_rs
my $rs = $row->self_rs;
Returns a ResultSet containing only this row, useful for applying
ResultSet methods to a single row.
=head2 StorageValues
Per-column opt-in snapshot of the values as last seen in storage.
Mark a column with C<< keep_storage_value => 1 >> and DBIO will record
its current stored value at C<new>, C<insert>, C<update> and
C<inflate_result> time. The snapshot uses the column accessor, so
inflated / filtered values are captured rather than raw storage values.
__PACKAGE__->add_column(title => {
data_type => 'varchar',
keep_storage_value => 1,
});
$row->title('New');
$row->get_storage_value('title'); # the old value
$row->update;
$row->get_storage_value('title'); # now 'New'
=head2 OnColumnChange
Register C<before_column_change>, C<after_column_change>, and
C<around_column_change> callbacks on a Result class. Callbacks fire
from L</update> only if the named column is actually dirty. The "old"
value comes from L</get_storage_value>, so pair this with
C<< keep_storage_value => 1 >> on the column if you need a real
previous value; otherwise it will be C<undef>.
__PACKAGE__->before_column_change(
amount => { method => 'bank_transfer', txn_wrap => 1 },
);
Callback signatures:
before: $self->$method($old, $new)
after: $self->$method($old, $new) # $old may now equal $new
around: $self->$method($next, $old, $new)
C<before> callbacks fire in definition order, C<after> callbacks fire
in reverse order, C<around> callbacks wrap in definition order (the
innermost being the first declared). If any registered arg has
C<< txn_wrap => 1 >> the whole update is wrapped in a
C<txn_scope_guard>.
See L<DBIx::Class::Helper::Row::OnColumnChange/on_column_change_allow_override_args>
for C<on_column_change_allow_override_args> semantics.
=head2 ProxyResultSetMethod
Expose a C<with_foo> ResultSet method as a row accessor with a
transparent fallback: if the column was already selected via the
ResultSet method it is returned from the cached row data; otherwise
the ResultSet method is re-run for this row.
package MyApp::Schema::ResultSet::Foo;
sub with_friend_count { ... }
package MyApp::Schema::Result::Foo;
__PACKAGE__->proxy_resultset_method('friend_count');
$foo_rs->first->friend_count; # lazy fetch
$foo_rs->with_friend_count->first->friend_count; # cached
The generated accessor stores the fetched value under the slot name in
C<_column_data> as a cache. Proxied slots are excluded from C<copy>
and C<update> so they are never written as actual columns.
=head2 id
my @pk = $result->id;
=over
=item Arguments: none
=item Returns: A list of primary key values
=back
Returns the primary key(s) for a row. Can't be called as a class method.
Actually implemented in L<DBIO::PK>
=head1 AUTHOR
DBIO & DBIx::Class Authors
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2026 DBIO Authors
Portions Copyright (C) 2005-2025 DBIx::Class Authors
Based on DBIx::Class, heavily modified.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut
( run in 1.234 second using v1.01-cache-2.11-cpan-6aa56a78535 )