NAME
DBIx::Class::Async::ResultSet - Asynchronous resultset for DBIx::Class::Async
VERSION
Version 0.25
SYNOPSIS
use DBIx::Class::Async::ResultSet;
# Typically obtained from DBIx::Class::Async::Schema
my $rs = $schema->resultset('User');
# Synchronous methods (return Future objects)
$rs->all->then(sub {
my ($users) = @_;
foreach my $user (@$users) {
say "User: " . $user->name;
}
});
$rs->search({ active => 1 })->count->then(sub {
my ($count) = @_;
say "Active users: $count";
});
# Asynchronous future methods
$rs->all_future->then(sub {
my ($data) = @_;
# Raw data arrayref
});
# Chaining methods
$rs->search({ status => 'active' })
->order_by('created_at')
->rows(10)
->all->then(sub {
my ($active_users) = @_;
# Process results
});
# Create new records
$rs->create({
name => 'Alice',
email => 'alice@example.com',
})->then(sub {
my ($new_user) = @_;
say "Created user ID: " . $new_user->id;
});
DESCRIPTION
DBIx::Class::Async::ResultSet provides an asynchronous result set interface for DBIx::Class::Async. It mimics the DBIx::Class::ResultSet API but returns Future objects for database operations, allowing non-blocking asynchronous database access.
This class supports both synchronous-style iteration (using next and reset) and asynchronous operations (using then callbacks). All database operations are delegated to the underlying DBIx::Class::Async instance.
CONSTRUCTOR
new
my $rs = DBIx::Class::Async::ResultSet->new(
schema => $schema, # DBIx::Class::Schema instance
async_db => $async_db, # DBIx::Class::Async instance
source_name => $source_name, # Result source name
);
Creates a new asynchronous result set.
- Parameters
-
schema-
A DBIx::Class::Schema instance. Required.
async_db-
A DBIx::Class::Async instance. Required.
source_name-
The name of the result source (table). Required.
- Throws
-
Croaks if any required parameter is missing.
new_result
my $row = $rs->new_result($hashref);
A helper method that inflates a raw hash of database columns into a blessed Row object.
It dynamically generates a specialised subclass under the DBIx::Class::Async::Row::* namespace based on the source_name of the current ResultSet. This allows for cleaner method resolution and avoids namespace pollution across different tables.
The returned object will be an instance of a class that inherits from DBIx::Class::Async::Row.
Returns undef if the provided data is empty or undefined.
new_result_set
my $new_rs = $rs->new_result_set({
entries => \@prefetched_data,
is_prefetched => 1
});
Creates a new instance (clone) of the current ResultSet class, inheriting the schema, database connection, and source name.
This is primarily used internally to handle prefetched relationships. When a has_many relationship is accessed, this method creates a "virtual" ResultSet seeded with the data already retrieved in the initial query, preventing unnecessary follow-up database hits.
Accepts a hashref of attributes to override in the new instance.
METHODS
all
$rs->all->then(sub {
my ($rows) = @_;
# $rows is an arrayref of DBIx::Class::Async::Row objects
});
Returns all rows matching the current search criteria as DBIx::Class::Async::Row objects, with prefetched relationships properly inflated.
- Returns
-
A Future that resolves to an array reference of DBIx::Class::Async::Row objects.
- Notes
-
Results are cached internally for use with
nextandresetmethods.
all_future
$rs->all_future->then(sub {
my ($data) = @_;
# $data is an arrayref of raw hashrefs
});
Returns all rows matching the current search criteria as raw data.
- Returns
-
A Future that resolves to an array reference of hash references containing raw row data.
- Notes
-
This method bypasses row object creation for performance. Use
allif you need DBIx::Class::Async::Row objects.
as_query
my ($cond, $attrs) = $rs->as_query;
Returns the internal search conditions and attributes.
- Returns
-
A list containing two hash references: conditions and attributes.
count
$rs->count->then(sub {
my ($count) = @_;
say "Found $count rows";
});
Returns the count of rows matching the current search criteria.
- Returns
-
A Future that resolves to the number of matching rows.
count_future
$rs->count_future->then(sub {
my ($count) = @_;
# Same as count(), alias for API consistency
});
Alias for count. Returns the count of rows matching the current search criteria.
- Returns
-
A Future that resolves to the number of matching rows.
create
$rs->create({ name => 'Alice', email => 'alice@example.com' })
->then(sub {
my ($new_row) = @_;
say "Created row ID: " . $new_row->id;
});
Creates a new row in the database.
- Parameters
- Returns
-
A Future that resolves to a DBIx::Class::Async::Row object representing the newly created row.
cursor
my $cursor = $rs->cursor;
Returns a DBIx::Class::Async::Cursor object for the current resultset. This is used to stream through large data sets asynchronously without loading all records into memory at once.
delete
$rs->search({ status => 'inactive' })->delete->then(sub {
my ($deleted_count) = @_;
say "Deleted $deleted_count rows";
});
Deletes all rows matching the current search criteria.
- Returns
-
A Future that resolves to the number of rows deleted.
- Notes
-
This method fetches all matching rows first to count them and get their IDs, then deletes them individually. For large result sets, consider using a direct SQL delete via the underlying database handle.
delete_all
$rs->delete_all->then(sub {
my ($deleted_count) = @_;
say "Deleted $deleted_count rows";
});
Fetches all objects and deletes them one at a time via "delete" in DBIx::Class::Row.
- Arguments
-
None
- Returns
-
A Future that resolves to the number of rows deleted.
- Difference from delete()
-
delete_allwill run DBIC-defined triggers (such asbefore_delete,after_delete), and will handle cascading deletes through relationships, whiledelete()performs a more efficient bulk delete that bypasses Row-level operations.Use
delete_allwhen you need: - Row-level triggers to fire - Cascading deletes to work properly - Accurate counts of rows affectedUse
deletewhen you need: - Better performance for large datasets - Direct database-level deletion - Example
-
# Delete with triggers $rs->search({ expired => 1 })->delete_all->then(sub { my ($count) = @_; say "Deleted $count expired records with triggers"; }); # Compare with bulk delete (no triggers) $rs->search({ expired => 1 })->delete->then(sub { my ($count) = @_; say "Bulk deleted $count records (no triggers)"; });
find
$rs->find($id)->then(sub {
my ($row) = @_;
if ($row) {
say "Found: " . $row->name;
} else {
say "Not found";
}
});
Finds a single row by primary key.
- Parameters
- Returns
-
A Future that resolves to a DBIx::Class::Async::Row object if found, or
undefif not found. - Throws
-
Dies if composite primary key is not supported.
find_or_new
my $future = $rs->find_or_new({ name => 'Alice' }, { key => 'user_name' });
$future->on_done(sub {
my $user = shift;
$user->insert if !$user->in_storage;
});
Finds a record using find. If no row is found, it returns a new Result object inflated with the provided data. This object is not yet saved to the database.
find_or_create
my $future = $rs->find_or_create({ name => 'Bob' });
Attempts to find a record. If it does not exist, it performs an INSERT and returns the resulting Result object.
first
$rs->first->then(sub {
my ($row) = @_;
if ($row) {
say "First row: " . $row->name;
}
});
Returns the first row matching the current search criteria.
- Returns
-
A Future that resolves to a DBIx::Class::Async::Row object if found, or
undefif no rows match.
first_future
$rs->first_future->then(sub {
# Same as single_future, alias for API consistency
});
Alias for single_future.
get
my $rows = $rs->get;
# Returns cached rows, or empty arrayref if not fetched
Returns the currently cached rows.
- Returns
-
Array reference of cached rows (either raw data or row objects, depending on how they were fetched).
- Notes
-
This method returns immediately without performing any database operations. It only returns data that has already been fetched via
all,all_future, or similar methods.
get_column
$rs->get_column('name')->then(sub {
my ($names) = @_;
# $names is an arrayref of name values
});
Returns values from a single column for all rows matching the current criteria.
- Parameters
- Returns
-
A Future that resolves to an array reference of column values.
next
while (my $row = $rs->next) {
say "Row: " . $row->name;
}
Returns the next row from the cached result set.
- Returns
-
A DBIx::Class::Async::Row object, or
undefwhen no more rows are available. - Notes
-
If no rows have been fetched yet, this method performs a blocking fetch via
all. The results are cached for subsequentnextcalls. Callresetto restart iteration.
populate
# Array of hashrefs format
$rs->populate([
{ name => 'Alice', email => 'alice@example.com' },
{ name => 'Bob', email => 'bob@example.com' },
])->then(sub {
my ($users) = @_;
say "Created " . scalar(@$users) . " users";
});
# Column list + rows format
$rs->populate([
[qw/ name email /],
['Alice', 'alice@example.com'],
['Bob', 'bob@example.com'],
])->then(sub {
my ($users) = @_;
});
Creates multiple rows at once. More efficient than calling create multiple times.
- Arguments
- Returns
-
A Future that resolves to an arrayref of created DBIx::Class::Async::Row objects.
populate_bulk
my $future = $rs->populate_bulk(\@large_dataset);
$future->on_done(sub {
print "Bulk insert successful\n";
});
A high-performance version of populate intended for large-scale data ingestion.
Arguments:
[ \%col_data, ... ]or[ \@column_list, [ \@row_values, ... ] ]Return Value: A Future that resolves to a truthy value (1) on success.
Key Differences from populate:
- 1. Context: Executes the database operation in void context on the worker side.
- 2. No Inflation: Does not create Result objects for the inserted rows.
- 3. Efficiency: Reduces memory overhead and Inter-Process Communication (IPC) payload by only returning a success status rather than the full row data.
Use this method when you need to "fire and forget" large amounts of data where individual object manipulation is not required immediately after insertion.
prefetch
my $rs_with_prefetch = $rs->prefetch('related_table');
Adds a prefetch clause to the result set for eager loading of related data.
- Parameters
- Returns
-
A new result set object with the prefetch clause added.
- Notes
-
This method returns a clone of the result set and does not modify the original.
related_resultset
my $users_rs = $orders_rs->related_resultset('user');
Returns a new ResultSet for a related table based on a relationship name. The new ResultSet will be constrained to only include records that are related to records in the current ResultSet.
reset
$rs->reset;
# Now $rs->next will start from the first row again
Resets the internal iterator position.
- Returns
-
The result set object itself (for chaining).
result_source
my $source = $rs->result_source;
Returns the result source object for this result set.
- Returns
-
A DBIx::Class::ResultSource object.
search
my $filtered_rs = $rs->search({ active => 1 }, { order_by => 'name' });
Adds search conditions and attributes to the result set.
- Parameters
- Returns
-
A new result set object with the combined conditions and attributes.
- Notes
-
This method returns a clone of the result set. Conditions and attributes are merged with any existing ones from the original result set.
search_related
# Scalar context: returns a ResultSet
my $new_rs = $rs->search_related('orders');
# List context: returns a Future (implicit ->all)
my $future = $rs->search_related('orders');
my @orders = $future->get;
In scalar context, works exactly like "search_related_rs". In list context, it returns a Future that resolves to the list of objects in that relationship.
search_related_rs
my $rel_rs = $rs->search_related_rs('relationship_name', \%cond?, \%attrs?);
Returns a new DBIx::Class::Async::ResultSet representing the specified relationship. This is a synchronous metadata operation and does not hit the database.
single
my $row = $rs->single;
# Returns first row (blocking), or undef
Returns the first row from the result set (blocking version).
- Returns
-
A DBIx::Class::Async::Row object, or
undefif no rows match. - Notes
-
This method performs a blocking fetch. For non-blocking operation, use
firstorsingle_future.
single_future
$rs->single_future->then(sub {
my ($row) = @_;
if ($row) {
# Process single row
}
});
Returns a single row matching the current search criteria (non-blocking).
- Returns
-
A Future that resolves to a DBIx::Class::Async::Row object if found, or
undefif not found. - Notes
-
For simple primary key lookups, this method optimizes by using
findinternally. For complex queries, it addsrows => 1to the search attributes.
search_future
$rs->search_future->then(sub {
# Same as all_future, alias for API consistency
});
Alias for all_future.
slice
my ($first, $second, $third) = $rs->slice(0, 2);
my @records = $rs->slice(5, 10);
my $sliced_rs = $rs->slice(0, 9); # scalar context
Returns a resultset or object list representing a subset of elements from the resultset. Indexes are from 0.
- Parameters
- Returns
-
In list context: Array of DBIx::Class::Async::Row objects.
In scalar context: A new DBIx::Class::Async::ResultSet with appropriate
rowsandoffsetattributes set. - Examples
-
# Get first 3 records my ($one, $two, $three) = $rs->slice(0, 2); # Get records 10-19 my @batch = $rs->slice(10, 19); # Get a ResultSet for records 5-14 (for further chaining) my $subset_rs = $rs->slice(5, 14); my $count = $subset_rs->count->get;
source
my $source = $rs->source;
Alias for result_source.
- Returns
-
A DBIx::Class::ResultSource object.
sub source { shift->_get_source }
source_name
my $source_name = $rs->source_name;
Returns the source name for this result set.
- Returns
-
The source name (string).
update
$rs->search({ status => 'pending' })->update({ status => 'processed' })
->then(sub {
my ($rows_affected) = @_;
say "Updated $rows_affected rows";
});
Updates all rows matching the current search criteria.
- Parameters
- Returns
-
A Future that resolves to the number of rows affected.
- Notes
-
This performs a bulk update using the search conditions. For individual row updates, use
updateon the row object instead.
update_or_create
my $future = $rs->update_or_create({
email => 'user@example.com',
name => 'Updated Name',
active => 1
}, { key => 'user_email' });
$future->on_done(sub {
my $row = shift;
print "Upserted user ID: " . $row->id;
});
An "upsert" operation. It first attempts to locate an existing record using the unique constraints provided in the data hashref (or specified by the key attribute).
If a matching record is found, it is updated with the remaining values in the hashref.
If no matching record is found, a new record is inserted into the database.
Returns a Future which, when resolved, provides the DBIx::Class::Async::Row object representing the updated or newly created record.
update_or_new
my $future = $rs->update_or_new({
email => 'user@example.com',
name => 'New Name'
}, { key => 'user_email' });
Similar to "update_or_create", but with a focus on in-memory instantiation.
If a matching record is found in the database, it is updated and the resulting object is returned.
If no record is found, a new row object is instantiated (using "new_result") but not yet saved to the database.
This is useful for workflows where you want to ensure an object is synchronized with the database if it exists, but you aren't yet ready to commit a new record to storage.
Returns a Future resolving to a DBIx::Class::Async::Row object.
CHAINABLE MODIFIERS
The following methods return a new result set with the specified attribute added or modified:
rows($number)- Limits the number of rows returnedpage($number)- Specifies the page number for paginationorder_by($spec)- Specifies the sort ordercolumns($spec)- Specifies which columns to selectgroup_by($spec)- Specifies GROUP BY clausehaving($spec)- Specifies HAVING clausedistinct($bool)- Specifies DISTINCT modifier
Example:
my $paginated = $rs->rows(10)->page(2)->order_by('created_at DESC');
These methods do not modify the original result set and do not execute any database queries.
INTERNAL METHODS
These methods are for internal use and are documented for completeness.
_do_search_related
my $new_async_rs = $rs->_do_search_related($rel_name, $cond, $attrs);
An internal helper method that performs the heavy lifting for "search_related" and "search_related_rs".
NOTE: This method exists to break deep recursion issues caused by DBIx::Class method aliasing. It bypasses the standard method dispatcher by manually instantiating a native DBIx::Class::ResultSet to calculate relationship metadata before re-wrapping the result in the Async class.
Arguments: Same as "search_related_rs".
Returns: A new DBIx::Class::Async::ResultSet object.
_get_source
my $source = $rs->_get_source;
Returns the result source object, loading it lazily if needed.
_inflate_prefetch
Inflates prefetched relationship data into the row object.
_inflate_simple_prefetch
Inflates a simple (non-nested) prefetched relationship.
_inflate_nested_prefetch
Inflates nested prefetched relationships (e.g., 'comments.user').
_find_reverse_relationship
Finds the reverse relationship name on the related source.
SEE ALSO
DBIx::Class::Async - Asynchronous DBIx::Class interface
DBIx::Class::ResultSet - Synchronous DBIx::Class result set interface
DBIx::Class::Async::Row - Asynchronous row objects
Future - Asynchronous programming abstraction
AUTHOR
Mohammad Sajid Anwar, <mohammad.anwar at yahoo.com>
REPOSITORY
https://github.com/manwar/DBIx-Class-Async
BUGS
Please report any bugs or feature requests through the web interface at https://github.com/manwar/DBIx-Class-Async/issues. I will be notified and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc DBIx::Class::Async::ResultSet
You can also look for information at:
BUG Report
CPAN Ratings
Search MetaCPAN
LICENSE AND COPYRIGHT
Copyright (C) 2026 Mohammad Sajid Anwar.
This program is free software; you can redistribute it and / or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:
http://www.perlfoundation.org/artistic_license_2_0
Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License.By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.
If your Modified Version has been derived from a Modified Version made by someone other than you,you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.
This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.
This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement,then this Artistic License to you shall terminate on the date that such litigation is filed.
Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.