diff --git a/connector/perl/lib/MR/Tarantool/Box.pm b/connector/perl/lib/MR/Tarantool/Box.pm index 905c92d08000c4c03652944b389aabfd00f4e2cb..a89938779b85563d6b982bc137db00601c947d01 100644 --- a/connector/perl/lib/MR/Tarantool/Box.pm +++ b/connector/perl/lib/MR/Tarantool/Box.pm @@ -19,9 +19,10 @@ MR::Tarantool::Box - A driver for an efficient Tarantool/Box NoSQL in-memory sto index_name => 'idx2', keys => [1,2], }, ], - space => 1, # space id, as set in Tarantool/Box config - name => "primary", # self-descriptive space-id - format => "QqLlSsCc&", # pack()-compatible, Qq must be supported by perl itself, & stands for byte-string. + space => 1, # space id, as set in Tarantool/Box config + name => "primary", # self-descriptive space-id + format => "QqLlSsCc&$", # pack()-compatible, Qq must be supported by perl itself, + # & stands for byte-string, $ stands for utf8 string. default_index => 'idx1', fields => [qw/ id f2 field3 f4 f5 f6 f7 f8 misc_string /], # turn each tuple into hash, field names according to format }, { @@ -66,6 +67,7 @@ use warnings; use Scalar::Util qw/looks_like_number/; use List::MoreUtils qw/each_arrayref zip/; use Time::HiRes qw/sleep/; +use Encode; use MR::IProto (); @@ -79,7 +81,7 @@ use constant { sub IPROTOCLASS () { 'MR::IProto' } use vars qw/$VERSION %ERRORS/; -$VERSION = 0.0.21; +$VERSION = 0.0.22; BEGIN { *confess = \&MR::IProto::confess } @@ -152,9 +154,10 @@ Self-descriptive space id, which will be mapped into C<space>. =item B<format> => $format_string -C<pack()>-compatible tuple format string, allowed formats: C<QqLlSsCc&>, -where C<&> stands for bytestring. C<Qq> usable only if perl supports +C<pack()>-compatible tuple format string, allowed formats: C<QqLlSsC(c&$)*>, +where C<&> stands for bytestring, C<$> stands for L</utf8> string. C<Qq> usable only if perl supports int64 itself. Tuples' fields are packed/unpacked according to this C<format>. +C<< * >> at the end of C<format> enables L</LongTuple>. =item B<hashify> => B<$coderef> @@ -170,6 +173,15 @@ It receives C<space> id and resultset as arguments. No return value needed. Specify an arrayref of fields names according to C<format> to turn each tuple into a good-looking hash. Names must begin with C<< [A-Za-z] >>. +If L</LongTuple> enabled, last field will be used to fold tailing fields. + +=item B<long_fields> => B<$arrayref> + +Specify an arrayref of fields names according to C<< (xxx)* >> to turn +tailing fields into a good-looking array of hashes. +Names must begin with C<< [A-Za-z] >>. +Works with L</LongTuple> enabled only. + =item B<indexes> => [ \%index, ... ] @@ -264,6 +276,31 @@ A string used for self-description. Mainly used for debugging purposes. =cut +sub _make_unpack_format { + my ($ns,$prefix) = @_; + $ns->{format} =~ s/\s+//g; + confess "${prefix} bad format `$ns->{format}'" unless $ns->{format} =~ m/^[\$\&lLsScCqQ]*(?:\([\$\&lLsScCqQ]+\)\*|\*)?$/; + $ns->{long_tuple} = 1 if $ns->{format} =~ s/\*$//; + $ns->{long_format} = ''; + my @f_long; + if ($ns->{long_tuple}) { + $ns->{format} =~ s/( \( [^\)]* \) | . )$//x; + $ns->{long_format} = $1; + $ns->{long_format} =~ s/[()]*//g; + @f_long = split //, $ns->{long_format}; + $ns->{long_byfield_unpack_format} = [ map { m/[\&\$]/ ? 'w/a*' : "x$_" } @f_long ]; + $ns->{long_field_format} = [ map { m/[\&\$]/ ? 'a*' : $_ } @f_long ]; + $ns->{long_utf8_fields} = [ grep { $f_long[$_] eq '$' } 0..$#f_long ]; + } + my @f = split //, $ns->{format}; + $ns->{byfield_unpack_format} = [ map { m/[\&\$]/ ? 'w/a*' : "x$_" } @f ]; + $ns->{field_format} = [ map { m/[\&\$]/ ? 'a*' : $_ } @f ]; + $ns->{unpack_format} = join('', @{$ns->{byfield_unpack_format}}); + $ns->{unpack_format} .= '('.join('', @{$ns->{long_byfield_unpack_format}}).')*' if $ns->{long_tuple}; + $ns->{string_keys} = { map { $_ => 1 } grep { $f[$_] =~ m/[\&\$]/ } 0..$#f }; + $ns->{utf8_fields} = { map { $_ => $_ } grep { $f[$_] eq '$' } 0..$#f }; +} + sub new { my ($class, $arg) = @_; my $self; @@ -300,15 +337,12 @@ sub new { confess "space[$namespace] no indexes defined" unless $ns->{indexes} && @{$ns->{indexes}}; $namespaces{$namespace} = $ns; $namespaces{$ns->{name}} = $ns if $ns->{name}; - confess "space[$namespace] bad format `$ns->{format}'" if $ns->{format} =~ m/[^&lLsScCqQ ]/; - $ns->{format} =~ s/\s+//g; - my @f = split //, $ns->{format}; - $ns->{byfield_unpack_format} = [ map { /&/ ? 'w/a*' : "x$_" } @f ]; - $ns->{field_format} = [ map { /&/ ? 'a*' : $_ } @f ]; - $ns->{unpack_format} = join('', @{$ns->{byfield_unpack_format}}); + + _make_unpack_format($ns,"space[$namespace]"); + $ns->{append_for_unpack} = '' unless defined $ns->{append_for_unpack}; $ns->{check_keys} = {}; - $ns->{string_keys} = { map { $_ => 1 } grep { $f[$_] eq '&' } 0..$#f }; + my $inames = $ns->{index_names} = {}; my $i = -1; for my $index (@{$ns->{indexes}}) { @@ -319,7 +353,7 @@ sub new { confess "space[$namespace]index[$index_name($i)] already defined" if $inames->{$index_name} || $inames->{$i}; $index->{id} = $i unless defined $index->{id}; $inames->{$i} = $inames->{$index_name} = $index; - int $_ == $_ and $_ >= 0 and $_ < @f or confess "space[$namespace]index[$index_name] bad key `$_'" for @{$ns->{keys}}; + int $_ == $_ and $_ >= 0 and $_ < @{$ns->{field_format}} or confess "space[$namespace]index[$index_name] bad key `$_'" for @{$ns->{keys}}; $ns->{check_keys}->{$_} = int !! $ns->{string_keys}->{$_} for @{$index->{keys}}; $index->{string_keys} ||= $ns->{string_keys}; } @@ -332,13 +366,19 @@ sub new { $ns->{default_index} ||= 0; $ns->{primary_key_index} ||= 0; } - $ns->{fields} ||= $arg->{default_fields}; + $ns->{fields} ||= $arg->{default_fields}; + $ns->{long_fields} ||= $arg->{default_long_fields}; if($ns->{fields}) { confess "space[$namespace] fields must be ARRAYREF" unless ref $ns->{fields} eq 'ARRAY'; - confess "space[$namespace] fields number must match format" if @{$ns->{fields}} != @f; + confess "space[$namespace] fields number must match format" if @{$ns->{fields}} != int(!!$ns->{long_tuple})+@{$ns->{field_format}}; m/^[A-Za-z]/ or confess "space[$namespace] fields names must begin with [A-Za-z]: bad name $_" for @{$ns->{fields}}; $ns->{fields_hash} = { map { $ns->{fields}->[$_] => $_ } 0..$#{$ns->{fields}} }; } + if($ns->{long_fields}) { + confess "space[$namespace] long_fields must be ARRAYREF" unless ref $ns->{long_fields} eq 'ARRAY'; + confess "space[$namespace] long_fields number must match format" if @{$ns->{long_fields}} != @{$ns->{long_field_format}}; + m/^[A-Za-z]/ or confess "space[$namespace] long_fields names must begin with [A-Za-z]: bad name $_" for @{$ns->{long_fields}}; + } $ns->{default_raw} = 1 if !defined$ns->{default_raw} and defined $ns->{hashify} and !$ns->{hashify}; } $self->{namespaces} = \%namespaces; @@ -576,15 +616,21 @@ sub Call { my $unpack_format = $param->{unpack_format}; if($unpack_format) { - $unpack_format = [ split /\s*/, $unpack_format ] - unless 'ARRAY' eq ref $unpack_format; - - $unpack_format = join '', map { /&/ ? 'w/a*' : "x$_" } @$unpack_format; + $unpack_format = join '', @$unpack_format if ref $unpack_format; + my $f = { format => $unpack_format }; + _make_unpack_format($f, "CALL"); + $unpack_format = $f->{unpack_format}; } local $namespace->{unpack_format} = $unpack_format if $unpack_format; # XXX local $namespace->{append_for_unpack} = '' if $unpack_format; # shit... + $tuple = [ map { + my $x = $_; + Encode::_utf8_off($x) if Encode::is_utf8($x,0); + $x; + } @$tuple ]; + $self->_chat ( msg => 22, payload => pack("L w/a* L(w/a*)*", $flags, $sp_name, scalar(@$tuple), @$tuple), @@ -670,7 +716,6 @@ sub Insert { $param->{want_result} = $param->{want_inserted_tuple} if !defined $param->{want_result}; - my $flags = $param->{_flags} || 0; $flags |= WANT_RESULT if $param->{want_result}; @@ -684,10 +729,13 @@ sub Insert { } my $chkkey = $namespace->{check_keys}; my $fmt = $namespace->{field_format}; - confess "Wrong fields number in tuple" if @tuple != @$fmt; + my $long_fmt = $namespace->{long_field_format}; + my $chk_divisor = $namespace->{long_tuple} ? @$long_fmt : @$fmt; + confess "Wrong fields number in tuple" if 0 != (@tuple - @$fmt) % $chk_divisor; for (0..$#tuple) { confess "$self->{name}: ref in tuple $_=`$tuple[$_]'" if ref $tuple[$_]; no warnings 'uninitialized'; + Encode::_utf8_off($_) if Encode::is_utf8($_,0); if(exists $chkkey->{$_}) { if($chkkey->{$_}) { confess "$self->{name}: undefined key $_" unless defined $tuple[$_]; @@ -695,7 +743,7 @@ sub Insert { confess "$self->{name}: not numeric key $_=`$tuple[$_]'" unless looks_like_number($tuple[$_]) && int($tuple[$_]) == $tuple[$_]; } } - $tuple[$_] = pack($fmt->[$_], $tuple[$_]); + $tuple[$_] = pack($_ < @$fmt ? $fmt->[$_] : $long_fmt->[$_ % @$long_fmt], $tuple[$_]); } $self->_debug("$self->{name}: INSERT[${\join ' ', map {join' ',unpack'(H2)*',$_} @tuple}]") if $self->{debug} >= 4; @@ -779,6 +827,7 @@ sub _pack_keys { $strkey = $strkey->{$keys->[0]}; foreach (@_[NPRM..$#_]) { ($_) = @$_ if ref $_ eq 'ARRAY'; + Encode::_utf8_off($_) if Encode::is_utf8($_,0); unless ($strkey) { confess "$self->{name}: not numeric key [$_]" unless looks_like_number($_) && int($_) == $_; $_ = pack($fmt, $_); @@ -792,6 +841,7 @@ sub _pack_keys { unless ($strkey->{$keys->[$i]}) { confess "$self->{name}: not numeric key [$i][$k->[$i]]" unless looks_like_number($k->[$i]) && int($k->[$i]) == $k->[$i]; } + Encode::_utf8_off($k->[$i]) if Encode::is_utf8($k->[$i],0); $k->[$i] = pack($fmt->[$keys->[$i]], $k->[$i]); } $k = pack('L(w/a*)*', scalar(@$k), @$k); @@ -804,7 +854,8 @@ sub _PackSelect { return '' unless @keys; $self->_pack_keys($namespace, $param->{index}, @keys); my $format = ""; - if ($param->{format}) { + if ($param->{format}) { #broken + confess "broken" if $namespace->{long_tuple}; my $f = $namespace->{byfield_unpack_format}; $param->{unpack_format} = join '', map { $f->[$_->{field}] } @{$param->{format}}; $format = pack 'l*', scalar @{$param->{format}}, map { @@ -823,11 +874,39 @@ sub _PackSelect { sub _PostSelect { my ($self, $r, $param, $namespace) = @_; if(!$param->{raw}) { + my @utf8_fields = values %{$namespace->{utf8_fields}}; + my $long_utf8_fields = $namespace->{long_utf8_fields}; + if(@utf8_fields or $long_utf8_fields && @$long_utf8_fields) { + my $long_tuple = $namespace->{long_tuple}; + for my $row (@$r) { + Encode::_utf8_on($row->[$_]) for @utf8_fields; + if ($long_tuple && @$long_utf8_fields) { + my $i = @{$namespace->{field_format}}; + my $n = int( (@$row-$i-1) / @$long_utf8_fields ); + Encode::_utf8_on($row->[$_]) for map do{ $a=$_; map $a+$i+@$long_utf8_fields*$_, 0..$n }, @$long_utf8_fields; + } + } + } + my $hashify = $param->{hashify} || $namespace->{hashify} || $self->{hashify}; - if($hashify) { + if ($hashify) { $hashify->($namespace->{namespace}, $r); } elsif( $namespace->{fields} ) { - $_ = { zip @{$namespace->{fields}}, @$_ } for @$r; + my @f = @{$namespace->{fields}}; + my @f_long; + my $last; + if ($namespace->{long_tuple}) { + $last = pop @f; + @f_long = @{$namespace->{long_fields}} if $namespace->{long_fields}; + } + for my $row (@$r) { + my $h = { zip @{$namespace->{fields}}, @{[splice(@$row,0,0+@f)]} }; + if($last) { + $row = [ map +{ zip @f_long, @{[splice(@$row,0,0+@f_long)]} }, 0..((@$row-1)/@f_long) ] if @f_long; + $h->{$last} = $row; + } + $row = $h; + } } } } @@ -908,7 +987,7 @@ Max tuples to select. It is set to C<< MAX_INT32 >> by default. =item B<raw> => $bool -Don't C<hashify> (see L</new>). +Don't C<hashify> (see L</new>), disable L</utf8> processing. =item B<hash_by> => $by @@ -1417,6 +1496,52 @@ Usage example: =back +=head2 LongTuple + +If C<format> given to L</new>, or C<unpack_format> given to L</Call> ends with a star (C<< * >>) +I<long tuple> is enabled. Last field or group of fields of C<format> represent variable-length +tail of the tuple. C<long_fields> option given to L</new> will fold the tail into array of hashes. + + $box->Insert(1,"2",3); + $box->Insert(3,"2",3,4,5); + $box->Insert(5,"2",3,4,5,6,7); + +If we set up + + format => "L&CL*", + fields => [qw/ a b c d /], # d is the folding field here + # no long_fields - no folding into hash + +we'll get: + + $result = $box->Select([1,2,3,4,5]); + $result = [ + { a => 1, b => "2", c => 3, d => [] }, + { a => 3, b => "2", c => 3, d => [4,5] }, + { a => 5, b => "2", c => 3, d => [4,5,6,7] }, + ]; + +And if we set up + + format => "L&C(LL)*", + fields => [qw/ a b c d /], # d is the folding field here + long_fields => [qw/ d1 d2 /], + +we'll get: + + $result = [ + { a => 1, b => "2", c => 3, d => [] }, + { a => 3, b => "2", c => 3, d => [{d1=>4, d2=>5}] }, + { a => 5, b => "2", c => 3, d => [{d1=>4, d2=>5}, {d1=>6, d2=>7}] }, + ]; + + +=head2 utf8 + +Utf8 strings are supported very simply. When pushing any data to tarantool (with any query, read or write), +the utf8 flag is set off, so all data is pushed as bytestring. When reading response, for fields marked +a dollar sign C<< $ >> (see L</new>) (including such in L</LongTuple> tail) utf8 flag is set on. +That's all. Validity is on your own. =head1 LICENCE AND COPYRIGHT diff --git a/connector/perl/lib/MR/Tarantool/Box/Singleton.pm b/connector/perl/lib/MR/Tarantool/Box/Singleton.pm index 9d4f72d42962b963cb0961deae135c1e7d4d45ba..a99417acb998a69f15fbf30e86a662fd6c3c3107 100644 --- a/connector/perl/lib/MR/Tarantool/Box/Singleton.pm +++ b/connector/perl/lib/MR/Tarantool/Box/Singleton.pm @@ -95,17 +95,20 @@ if I<< fields >> were not set explicitly for that space. =cut -sub mkfields { - my($class, @fields) = @_; +sub _mkfields { + my($class, $f, $F, @fields) = @_; no strict 'refs'; - confess "Fields are already defined for $class" if @{"${class}::fields"}; - @{"${class}::fields"} = @fields; - %{"${class}::fields"} = map { $fields[$_] => $_ } 0..$#fields; - eval qq{ sub ${class}::TUPLE_$fields[$_] () { $_ } } for 0..$#fields; - eval qq{ sub ${class}::FIELDS () { \@${class}::fields } }; - eval qq{ sub ${class}::FIELDS_HASH () { \\\%${class}::fields } }; + confess "$f are already defined for $class" if @{"${class}::${f}"}; + @{"${class}::${f}"} = @fields; + %{"${class}::${f}"} = map { $fields[$_] => $_ } 0..$#fields; + eval qq{ sub ${class}::${F}TUPLE_$fields[$_] () { $_ } } for 0..$#fields; + eval qq{ sub ${class}::${F}FIELDS () { \@${class}::${f} } }; + eval qq{ sub ${class}::${F}FIELDS_HASH () { \\\%${class}::${f} } }; } +sub mkfields { $_[0]->_mkfields('fields', '', @_[1..$#_]) } +sub mklongfields { $_[0]->_mkfields('long_fields', 'LONG_', @_[1..$#_]) } + =pod =head3 declare_stored_procedure @@ -118,7 +121,7 @@ sub mkfields { options => { default => options }, # MR::Tarantool::Box->Call \%options params => [ qw{ P1 P2 P3 Param4 }], # names - unpack_format => [qw/ & L S C /], + unpack_format => "&LSC(L$)*", params_format => [qw{ C S L a* }], params_default => [ 1, 2, undef, 'the_default' ], # undef's are mandatory params @@ -174,7 +177,7 @@ its parameter mandatory. C<< pack() >>-compatible format to pack input parameters. Must match C<params>. -=item B<unpack_format> => \@format +=item B<unpack_format> => $format C<< pack() >>-compatible format to unpack procedure output. @@ -236,17 +239,22 @@ sub declare_stored_procedure { confess "`unpack` method $fn is not provided by class ${class}" unless $class->can($fn); $unpack = sub { $class->$fn(@_) }; } - $options->{unpack_format} = [ "a*" ]; + if ($opts{unpack_raw}) { + $options->{unpack} = $unpack; + undef $unpack; + } + $options->{unpack_format} = '&*'; } else { - confess "no `unpack` nor `unpack_format` given; it must be an arrayref" if !exists $opts{unpack_format} or ref $opts{unpack_format} ne 'ARRAY'; + confess "no `unpack` nor `unpack_format` given" if !exists $opts{unpack_format}; my $f = $opts{unpack_format}; + $f = join '', @$f if ref $f; $options->{unpack_format} = $f; } my $method = $opts{method_name} or confess "`method_name` not given"; confess "bad `method_name` $method" unless $method =~ m/^[a-zA-Z]\w*$/; my $fn = "${class}::${method}"; - confess "Method $method id already defined in class $class" if defined &{$fn}; + confess "Method $method is already defined in class $class" if defined &{$fn}; do { no strict 'refs'; *$fn = sub { @@ -323,7 +331,8 @@ sub _new_instance { $config->{param}->{name} ||= $class; $config->{param}->{spaces} ||= $class->SPACES; - $config->{param}->{default_fields} ||= [ $class->FIELDS ] if $class->can('FIELDS'); + $config->{param}->{default_fields} ||= [ $class->FIELDS ] if $class->can('FIELDS'); + $config->{param}->{default_long_fields}||= [ $class->LONG_FIELDS ] if $class->can('LONG_FIELDS'); $config->{param}->{raise} = $class->RAISE unless defined $config->{param}->{raise}; $config->{param}->{timeout} ||= $class->TIMEOUT; diff --git a/connector/perl/t/box.pl b/connector/perl/t/box.pl index bb6941ffc675b5de9758638f0202c1f81654e410..c979186c7408f79e840e9cbe06ae40ff1759473e 100644 --- a/connector/perl/t/box.pl +++ b/connector/perl/t/box.pl @@ -13,7 +13,7 @@ use FindBin qw($Bin); use lib "$Bin"; use Carp qw/confess/; -use Test::More tests => 339; +use Test::More tests => 360; use Test::Exception; use List::MoreUtils qw/zip/; @@ -898,7 +898,9 @@ foreach my $r (@res) { } my $flds; -BEGIN{ $flds = [qw/ f1 f2 f3 f4 /] } +my $lflds; +BEGIN{ $flds = [qw/ f1 f2 f3 f4 LL /] } +BEGIN{ $lflds = [qw/ l1 l2 l3 /] } { package TestBox; use MR::Tarantool::Box::Singleton; @@ -906,6 +908,7 @@ BEGIN{ $flds = [qw/ f1 f2 f3 f4 /] } BEGIN { __PACKAGE__->mkfields(@$flds); + __PACKAGE__->mklongfields(@$lflds); } sub SERVER { $server } @@ -917,7 +920,7 @@ BEGIN{ $flds = [qw/ f1 f2 f3 f4 /] } index_name => 'primary_id', keys => [TUPLE_f1], } ], - format => 'l&&&', + format => 'l&$&(&$&)*', default_index => 'primary_id', }]} @@ -926,24 +929,52 @@ BEGIN{ $flds = [qw/ f1 f2 f3 f4 /] } $box = 'TestBox'; #$box = $CLASS->new(def_param_flds); #ok $box->isa($CLASS), 'connect'; - do { - my $tuples = [ @$tuples[0..2] ]; + my $tuples = [ + [1, "asdasdasd1", "qqq\xD0\x8Eqqq1", "ww\xD0\x8Eww1", "la\xD0\x8Elalala11", "la\xD0\x8Elala11", "lala11"], + [2, "asdasdasd2", "qqq\xD0\x8Eqqq2", "ww\xD0\x8Eww2", "la\xD0\x8Elalala21", "la\xD0\x8Elala21", "lala21", "lalalala22", "lalala22", "lala22", "lalalala23", "lalala23", "lala23"], + [3, "asdasdasd3", "qqq\xD0\x8Eqqq3", "ww\xD0\x8Eww3", "la\xD0\x8Elalala31", "la\xD0\x8Elala31", "lala31", "lalalala32", "lalala32", "lala32"], + [4, "asdasdasd4", "qqq\xD0\x8Eqqq4", "ww\xD0\x8Eww4", "la\xD0\x8Elalala41", "la\xD0\x8Elala41", "lala41", "lalalala42", "lalala42", "lala42", "lalalala43", "lalala43", "lala43"], + [5, "asdasdasd5", "qqq\xD0\x8Eqqq5", "ww\xD0\x8Eww5", "la\xD0\x8Elalala51", "la\xD0\x8Elala51", "lala51"], + ]; + + my $check = []; + for my $tuple (@$tuples) { + my $i = 0; + Encode::_utf8_on($tuple->[2+$i*3]), ++$i while @$tuple > 1+$i*3; + + my $t = { zip @{[@$flds[0..($#$flds-1)]]}, @{[@$tuple[0..($#$flds-1)]]} }; + my $l = $t->{$flds->[-1]} = []; + + $i = 1; + push(@$l, { zip @$lflds, @{[@$tuple[(1+$i*3)..(1+$i*3+2)]]} }), ++$i while @$tuple > 1+$i*3; + + push @$check, $t; + } + foreach my $tuple (@$tuples) { cleanup $tuple->[0]; } - foreach my $tuple (@$tuples) { - is_deeply [$box->Insert(@$tuple, {want_inserted_tuple => 1})], [{zip @$flds, @$tuple}], "flds/insert \'$tuple->[0]\'"; + foreach my $i (0..$#$tuples) { + is_deeply [$box->Insert(@{$tuples->[$i]}, {want_inserted_tuple => 1})], [$check->[$i]], "flds/insert \'$tuples->[$i]->[0]\'"; } - is_deeply [$box->Select([[$tuples->[0]->[0]]])], [{zip @$flds, @{$tuples->[0]}}], 'select by primary_num1 index'; - is_deeply [$box->UpdateMulti($tuples->[0]->[0],[ $flds->[3] => set => $tuples->[0]->[3] ],{want_updated_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update1'; - ok $box->UpdateMulti($tuples->[0]->[0],[ $flds->[3] => set => $tuples->[0]->[3] ]), 'update2'; - is_deeply [$box->UpdateMulti($tuples->[0]->[0],[ 3 => set => $tuples->[0]->[3] ],{want_updated_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update3'; - ok $box->UpdateMulti($tuples->[0]->[0],[ 3 => set => $tuples->[0]->[3] ]), 'update4'; + is_deeply [$box->Select([[$tuples->[0]->[0]]])], [$check->[0]], 'select by primary_num1 index'; + + my $res; + is_deeply [$res=$box->Select([map {$_->[0]} @$tuples],{want=>'arrayref'})], [$check], 'select all'; + # print $res->[0]->{f3}, "\n"; + # print $check->[0]->{f3}, "\n"; + ok $res->[$_]->{f3} eq $check->[$_]->{f3}, "utf8chk" for 0..$#$tuples; + ok $res->[$_]->{LL}->[0]->{l2} eq $check->[$_]->{LL}->[0]->{l2}, "utf8chklong" for 0..$#$tuples; + + is_deeply [$box->UpdateMulti($tuples->[2]->[0],[ $flds->[3] => set => $tuples->[2]->[3] ],{want_updated_tuple => 1})], [$check->[2]], 'update1'; + ok $box->UpdateMulti($tuples->[2]->[0],[ $flds->[3] => set => $tuples->[2]->[3] ]), 'update2'; + is_deeply [$box->UpdateMulti($tuples->[2]->[0],[ 3 => set => $tuples->[2]->[3] ],{want_updated_tuple => 1})], [$check->[2]], 'update3'; + ok $box->UpdateMulti($tuples->[2]->[0],[ 3 => set => $tuples->[2]->[3] ]), 'update4'; - is_deeply [$box->Delete($tuples->[0]->[0],{want_deleted_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update3'; + is_deeply [$box->Delete($tuples->[$_]->[0],{want_deleted_tuple => 1})], [$check->[$_]], "delete$_" for 0..$#$tuples; };