Async-Redis
view release on metacpan or search on metacpan
lib/Async/Redis/Iterator.pm view on Meta::CPAN
package Async::Redis::Iterator;
use strict;
use warnings;
use 5.018;
use Future::AsyncAwait;
sub new {
my ($class, %args) = @_;
return bless {
redis => $args{redis},
command => $args{command} // 'SCAN',
key => $args{key}, # For HSCAN/SSCAN/ZSCAN
match => $args{match},
count => $args{count},
type => $args{type}, # For SCAN TYPE filter
cursor => 0,
done => 0,
}, $class;
}
async sub next {
my ($self) = @_;
# Already exhausted
return undef if $self->{done};
my @args;
# Build command args based on scan type
if ($self->{command} eq 'SCAN') {
@args = ($self->{cursor});
}
else {
# HSCAN, SSCAN, ZSCAN take key first, then cursor
@args = ($self->{key}, $self->{cursor});
}
# Add MATCH pattern if specified
if (defined $self->{match}) {
push @args, 'MATCH', $self->{match};
}
# Add COUNT hint if specified
if (defined $self->{count}) {
push @args, 'COUNT', $self->{count};
}
# Add TYPE filter for SCAN (Redis 6.0+)
if ($self->{command} eq 'SCAN' && defined $self->{type}) {
push @args, 'TYPE', $self->{type};
}
# Execute scan command
my $result = await $self->{redis}->command($self->{command}, @args);
# Result is [cursor, [elements...]]
my ($new_cursor, $elements) = @$result;
# Update cursor
$self->{cursor} = $new_cursor;
# Check if iteration complete (cursor returned to 0)
if ($new_cursor eq '0' || $new_cursor == 0) {
$self->{done} = 1;
}
# Return batch (may be empty)
# Return undef only when done AND no elements in final batch
return $elements && @$elements ? $elements : ($self->{done} ? undef : []);
}
sub reset {
my ($self) = @_;
$self->{cursor} = 0;
$self->{done} = 0;
}
sub cursor { shift->{cursor} }
sub done { shift->{done} }
1;
__END__
=head1 NAME
Async::Redis::Iterator - Cursor-based SCAN iterator
=head1 SYNOPSIS
my $iter = $redis->scan_iter(match => 'user:*', count => 100);
while (my $batch = await $iter->next) {
for my $key (@$batch) {
say $key;
}
}
=head1 DESCRIPTION
Iterator provides async cursor-based iteration over Redis SCAN commands:
=over 4
=item * C<SCAN> - iterate keys
=item * C<HSCAN> - iterate hash field/value pairs
=item * C<SSCAN> - iterate set members
=item * C<ZSCAN> - iterate sorted set members and scores as a flat list
=back
=head1 METHODS
=head2 next
my $batch = await $iter->next;
Return the next batch as an arrayref, or C<undef> when the scan is complete and
there are no elements in the final batch. Redis may return empty intermediate
batches; those are returned as empty arrayrefs and are still truthy in Perl.
=head2 reset
$iter->reset;
Reset the cursor to zero so iteration can start again.
=head2 cursor
Return the current Redis cursor.
=head2 done
Return true after Redis has returned cursor C<0>.
=head1 BEHAVIOR
=over 4
=item * Returns batches of elements, not individual items
=item * Cursor managed internally
=item * C<next> returns C<undef> when iteration is complete
=item * Safe during key modifications, but Redis may return duplicates or miss keys
=item * C<count> is a hint, not a guarantee
=back
=cut
( run in 0.831 second using v1.01-cache-2.11-cpan-39bf76dae61 )