use utf8;
use strict;
use warnings;
=head1 NAME
DR::Tarantool::Tuple - tuple container for L<DR::Tarantool>
=head1 SYNOPSIS
my $tuple = new DR::Tarantool::Tuple([ 1, 2, 3]);
my $tuple = new DR::Tarantool::Tuple([ 1, 2, 3], $space);
my $tuple = unpack DR::Tarantool::Tuple([ 1, 2, 3], $space);
$tuple->next( $other_tuple );
$f = $tuple->raw(0);
$f = $tuple->name_field;
=head1 DESCRIPTION
Tuple contains normalized (unpacked) fields. You can access the fields
by their indexes (see L<raw> function) or by their names (if they are
described in space).
Each tuple can contain references to L<next> tuple and L<iter>ator.
So If You extract more than one tuple, You can access them.
=head1 METHODS
=cut
package DR::Tarantool::Tuple;
use DR::Tarantool::Iterator;
use Scalar::Util 'weaken', 'blessed';
use Carp;
$Carp::Internal{ (__PACKAGE__) }++;
=head2 new
Constructor.
my $t = DR::Tarantool::Tuple->new([1, 2, 3]);
my $t = DR::Tarantool::Tuple->new([1, 2, 3], $space);
=cut
sub new :method {
my ($class, $tuple, $space) = @_;
$class = ref $class if ref $class;
# hack to replace default autoload
$class = $space->tuple_class if $space and $class eq __PACKAGE__;
croak 'wrong space' if defined $space and !blessed $space;
croak 'tuple must be ARRAYREF [of ARRAYREF]' unless 'ARRAY' eq ref $tuple;
croak "tuple can't be empty" unless @$tuple;
$tuple = [ $tuple ] unless 'ARRAY' eq ref $tuple->[0];
my $iterator = DR::Tarantool::Iterator->new(
$tuple,
data => $space,
item_class => ref($class) || $class,
item_constructor => '_new'
);
return bless {
idx => 0,
iterator => $iterator,
} => ref($class) || $class;
}
sub _new {
my ($class, $item, $idx, $iterator) = @_;
return bless {
idx => $idx,
iterator => $iterator,
} => ref($class) || $class;
}
=head2 unpack
Constructor.
my $t = DR::Tarantool::Tuple->unpack([1, 2, 3], $space);
=cut
sub unpack :method {
my ($class, $tuple, $space) = @_;
croak 'wrong space' unless blessed $space;
return undef unless defined $tuple;
croak 'tuple must be ARRAYREF [of ARRAYREF]' unless 'ARRAY' eq ref $tuple;
return undef unless @$tuple;
if ('ARRAY' eq ref $tuple->[0]) {
my @tu;
push @tu => $space->unpack_tuple($_) for @$tuple;
return $class->new(\@tu, $space);
}
return $class->new($space->unpack_tuple($tuple), $space);
}
=head2 raw
Returns raw data from tuple.
my $array = $tuple->raw;
my $field = $tuple->raw(0);
=cut
sub raw :method {
my ($self, $fno) = @_;
my $item = $self->{iterator}->raw_item( $self->{idx} );
return $item unless defined $fno;
croak 'wrong field number' unless $fno =~ /^-?\d+$/;
return undef if $fno < -@$item;
return undef if $fno >= @$item;
return $item->[ $fno ];
}
=head2 next
Appends or returns the following tuple.
my $next_tuple = $tuple->next;
=cut
sub next :method {
my ($self, $tuple) = @_;
my $iterator = $self->{iterator};
my $idx = $self->{idx} + 1;
# if tuple is exists next works like 'iterator->push'
if ('ARRAY' eq ref $tuple) {
$iterator->push( $tuple );
$idx = $iterator->count - 1;
}
return undef unless $idx < $iterator->count;
my $next = bless {
idx => $idx,
iterator => $iterator,
} => ref($self);
return $next;
}
=head2 iter
Returns iterator linked with the tuple.
my $iterator = $tuple->iter;
my $iterator = $tuple->iter('MyTupleClass', 'new');
while(my $t = $iterator->next) {
# the first value of $t and $tuple are the same
...
}
=head3 Arguments
=over
=item package (optional)
=item method (optional)
if 'package' and 'method' are present, $iterator->L<next> method will
construct objects using C<< $package->$method( $next_tuple ) >>
if 'method' is not present and 'package' is present, iterator will
bless raw array into 'package'
=back
=cut
sub iter :method {
my ($self, $class, $method) = @_;
my $iterator = $self->{iterator};
if ($class) {
return $self->{iterator}->clone(
item_class => $class,
item_constructor => sub {
my ($c, $item, $idx) = @_;
if ($method) {
my $bitem = bless {
idx => $idx,
iterator => $iterator,
} => ref($self);
return $c->$method( $bitem );
}
return bless [ @$item ] => ref($c) || $c;
}
);
}
return $self->{iterator};
}
=head2 tail
Returns tail of tuple (array of unnamed fields). The function always
return B<ARRAYREF> (as L<raw>).
=cut
sub tail {
my ($self) = @_;
my $space = $self->{iterator}->data;
my $raw = $self->raw;
return [ @$raw[ $space->tail_index .. $#$raw ] ] if $space;
return $raw;
}
sub DESTROY { }
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2011 Dmitry E. Oboukhov <unera@debian.org>
Copyright (C) 2011 Roman V. Nikolaev <rshadow@rambler.ru>
This program is free software, you can redistribute it and/or
modify it under the terms of the Artistic License.
=head1 VCS
The project is placed git repo on github:
L<https://github.com/unera/dr-tarantool/>.
=cut
1;