From 56facf7d7adc0e532bed3be3eaa8c05b87404d09 Mon Sep 17 00:00:00 2001 From: Yuriy Nevinitsin <nevinitsin@corp.mail.ru> Date: Tue, 6 Dec 2011 21:58:34 +0400 Subject: [PATCH] Make up the perl driver --- connector/perl/Makefile.PL | 4 +- connector/perl/lib/MR/SilverBox.pm | 17 + connector/perl/lib/MR/Tarantool/Box.pm | 587 ++++++++++++++++++++++--- connector/perl/t/box.pl | 74 ++-- 4 files changed, 595 insertions(+), 87 deletions(-) create mode 100644 connector/perl/lib/MR/SilverBox.pm diff --git a/connector/perl/Makefile.PL b/connector/perl/Makefile.PL index 5956d5c711..7c3a1ed937 100644 --- a/connector/perl/Makefile.PL +++ b/connector/perl/Makefile.PL @@ -1,8 +1,8 @@ use ExtUtils::MakeMaker; WriteMakefile( - NAME => "MR::SilverBox", - VERSION_FROM => "lib/MR/SilverBox.pm", + NAME => "MR::Tarantool", + VERSION_FROM => "lib/MR/Tarantool/Box.pm", MAKEFILE => 'Makefile', PREREQ_PM => { 'Scalar::Util' => 0, diff --git a/connector/perl/lib/MR/SilverBox.pm b/connector/perl/lib/MR/SilverBox.pm new file mode 100644 index 0000000000..886b019be9 --- /dev/null +++ b/connector/perl/lib/MR/SilverBox.pm @@ -0,0 +1,17 @@ +package MR::SilverBox; +use MR::Tarantool::Box; +use base qw/MR::Tarantool::Box/; + +=pod + +=head1 NAME + +A backward-compatiblity module. Is it obsolete and is unsupported. Do not use. + +=head1 SEE ALSO + +L<MR::Tarantool::Box> + +=cut + +1; diff --git a/connector/perl/lib/MR/Tarantool/Box.pm b/connector/perl/lib/MR/Tarantool/Box.pm index 40d17ed8d9..1c552f2100 100644 --- a/connector/perl/lib/MR/Tarantool/Box.pm +++ b/connector/perl/lib/MR/Tarantool/Box.pm @@ -1,4 +1,48 @@ -package MR::SilverBox; +package MR::Tarantool::Box; + +=pod + +=head1 NAME + +A driver for an efficient Tarantool/Box NoSQL in-memory storage. + +=head1 SYNOPSIS + + my $box = MR::Tarantool::Box->new({ + servers => "127.0.0.1:33013", + name => "My Box", # primarily used for debug purposes + spaces => [ { + indexes => [ { + index_name => 'idx1', + keys => [0], + }, { + index_name => 'idx2', + keys => [1], + }, ], + 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. + default_index => 'idx1', + }, { + #... + } ], + default_space => "primary", + + timeout => 1, + retry => 3, + debug => 9, # output to STDERR some debugging info + raise => 0, # dont raise an exception in case of error + }); + + $box->Insert(1,2,3,4,5,6,7,8,"asdf") or die $box->ErrorStr; + $box->Insert(1,2,3,4,5,6,7,8,"asdf",{space => "primary"}) or die $box->ErrorStr; + + my $tuples = $box->Select(1); + my $tuples = $box->Select(1,{space => "primary", use_index => "idx1"}); + +=head1 DESCRIPTION + +=cut use strict; use warnings; @@ -7,7 +51,6 @@ use List::MoreUtils qw/each_arrayref/; use Time::HiRes qw/sleep/; use MR::IProto (); -use MR::Storage::Const (); use constant { WANT_RESULT => 1, @@ -17,13 +60,169 @@ use constant { sub IPROTOCLASS () { 'MR::IProto' } -sub ERRSTRCLASS () { 'MR::Storage::Const::Errors::SilverBox' } -use vars qw/$VERSION/; -$VERSION = 0; +use vars qw/$VERSION %ERRORS/; +$VERSION = 1.4.3; BEGIN { *confess = \&MR::IProto::confess } +%ERRORS = ( + 0x00000000 => q{OK}, + 0x00000100 => q{Non master connection, but it should be}, + 0x00000200 => q{Illegal parametrs}, + 0x00000300 => q{Uid not from this storage range}, + 0x00000400 => q{Tuple is marked as read-only}, + 0x00000500 => q{Tuple isn't locked}, + 0x00000600 => q{Tuple is locked}, + 0x00000700 => q{Failed to allocate memory}, + 0x00000800 => q{Bad integrity}, + 0x00000a00 => q{Unsupported command}, + + 0x00000b00 => q{Can't do select}, + + 0x00001800 => q{Can't register new user}, + 0x00001a00 => q{Can't generate alert id}, + 0x00001b00 => q{Can't del node}, + + 0x00001c00 => q{User isn't registered}, + 0x00001d00 => q{Syntax error in query}, + 0x00001e00 => q{Unknown field}, + 0x00001f00 => q{Number value is out of range}, + 0x00002000 => q{Insert already existing object}, + 0x00002200 => q{Can not order result}, + 0x00002300 => q{Multiple update/delete forbidden}, + 0x00002400 => q{Nothing affected}, + 0x00002500 => q{Primary key update forbidden}, + 0x00002600 => q{Incorrect protocol version}, + 0x00002700 => q{WAL failed}, + 0x00003000 => q{Procedure return type is not supported in the binary protocol}, + 0x00003100 => q{Tuple doesn't exist}, + 0x00003200 => q{Procedure is not defined}, + 0x00003300 => q{Lua error}, + 0x00003400 => q{Space is disabled}, + 0x00003500 => q{No such index in space}, + 0x00003600 => q{Field was not found in the tuple}, + 0x00003700 => q{Tuple already exists}, + 0x00003800 => q{Duplicate key exists in a unique index}, + 0x00003900 => q{Space does not exists}, +); + + + +=pod + +=head3 new + + my $box = $class->new(\%args); + +%args: + +=over + +=item B<spaces> => [ \%space, ... ] + +%space: + +=over + +=item B<space> => $space_id_uint32 + +Space id as set in Tarantool/Box config. + +=item B<name> => $space_name_string + +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 +int64 itself. Tuples' fields are packed/unpacked according to this C<format>. + +=item B<indexes> => [ \%index, ... ] + +%index: + +=over + +=item B<id> => $index_id_uint32 + +Index id as set in Tarantool/Box config within current C<space>. +If not set, order position in C<indexes> is theated as C<id>. + +=item B<name> => $index_name_string + +Self-descriptive index id, which will be mapped into C<index_id>. + +=item B<keys> => [ $field_no_uint32, ... ] + +Properly ordered arrayref of fields' numbers which are indexed. + +=back + +=item B<default_index> => $default_index_name_string_or_id_uint32 + +Index C<id> or C<name> to be used by default for the current C<space>. +Must be set if there are more than one C<\%index>es. + +=back + +=item B<default_space> => $default_space_name_string_or_id_uint32 + +Space C<space> or C<name> to be used by default. Must be set if there are +more than one C<\%space>s. + +=item B<timeout> => $timeout_fractional_seconds_float || 23 + +A common timeout for network operations. + +=item B<select_timeout> => $select_timeout_fractional_seconds_float || 2 + +Select queries timeout for network operations. See L<select_retry>. + +=item B<retry> => $retry_int || 1 + +A common retries number for network operations. + +=item B<select_retry> => $select_retry_int || 3 + +Select queries retries number for network operations. + +Sometimes we need short timeout for select's and long timeout for B<critical> update's, +because in case of timeout we B<don't know if the update has succeeded>. For the same +reason we B<can't retry> update operation. + +So increasing C<timeout> and setting C<< retry => 1 >> for updates lowers possibility of +such situations (but, of course, does not exclude them at all), and guarantees that +we dont do the same more then once. + +=item B<soft_retry> => $soft_retry_int || 3 + +A common retries number for Tarantool/Box B<temporary errors> (these marked by 1 in the +lowest byte of C<error_code>). In that case we B<know for sure> that the B<request was +declined> by Tarantool/Box for some reason (a tuple was locked for another update, for +example), and we B<can> try it again. + +This is also limited by C<retry>/C<select_retry> +(depending on query type). + +=item B<raise> => $raise_bool || 1 + +Should we raise an exceptions? If so, exceptions are raised when no more retries left and +all tries failed (with timeout, fatal, or temporary error). + +=item B<debug> => $debug_level_int || 0 + +Debug level, 0 - print nothing, 9 - print everything + +=item B<name> => $name + +A string used for self-description. Mainly used for debugging purposes. + +=back + +=cut + sub new { my ($class, $arg) = @_; my $self; @@ -34,7 +233,7 @@ sub new { $self->{retry} = $arg->{retry} || 1; $self->{retry_delay} = $arg->{retry_delay} || 1; $self->{select_retry} = $arg->{select_retry} || 3; - $self->{softretry} = $arg->{softretry} || 3; + $self->{softretry} = $arg->{soft_retry} || $arg->{softretry} || 3; $self->{debug} = $arg->{'debug'} || 0; $self->{ipdebug} = $arg->{'ipdebug'} || 0; $self->{raise} = 1; @@ -43,16 +242,16 @@ sub new { $self->{default_raw} = exists $arg->{default_raw} ? $arg->{default_raw} : !$self->{hashify}; $self->{select_timeout} = $arg->{select_timeout} || $self->{timeout}; $self->{iprotoclass} = $arg->{iprotoclass} || $class->IPROTOCLASS; - $self->{errstrclass} = $arg->{errstrclass} || $class->ERRSTRCLASS; $self->{_last_error} = 0; - $arg->{namespaces} = [@{ $arg->{namespaces} }]; + $arg->{spaces} = $arg->{namespaces} = [@{ $arg->{spaces} ||= $arg->{namespaces} }]; my %namespaces; - for my $ns (@{$arg->{namespaces}}) { + for my $ns (@{$arg->{spaces}}) { $ns = { %$ns }; - my $namespace = $ns->{namespace}; - confess "ns[?] `namespace' not set" unless defined $namespace; - confess "ns[$namespace] already defined" if $namespaces{$namespace} || $ns->{name}&&$namespaces{$ns->{name}}; + my $namespace = defined $ns->{space} ? $ns->{space} : $ns->{namespace}; + $ns->{space} = $ns->{namespace} = $namespace; + confess "ns[?] `space' not set" unless defined $namespace; + confess "ns[$namespace] already defined" if $namespaces{$namespace} or $ns->{name}&&$namespaces{$ns->{name}}; confess "ns[$namespace] no indexes defined" unless $ns->{indexes} && @{$ns->{indexes}}; $namespaces{$namespace} = $ns; $namespaces{$ns->{name}} = $ns if $ns->{name}; @@ -87,12 +286,14 @@ sub new { } } $self->{namespaces} = \%namespaces; - if (values %namespaces > 1) { - confess "default_namespace not given" unless defined $arg->{default_namespace}; - confess "default_namespace $arg->{default_namespace} does not exist" unless $namespaces{$arg->{default_namespace}}; + if (@{$arg->{spaces}} > 1) { + $arg->{default_namespace} = $arg->{default_space} if defined $arg->{default_space}; + confess "default_space not given" unless defined $arg->{default_namespace}; + confess "default_space $arg->{default_namespace} does not exist" unless $namespaces{$arg->{default_namespace}}; $self->{default_namespace} = $arg->{default_namespace}; } else { - $self->{default_namespace} = $arg->{default_namespace} || $arg->{namespaces}->[0]->{namespace}; + $self->{default_namespace} = $arg->{default_space} || $arg->{default_namespace} || $arg->{spaces}->[0]->{space}; + confess "default_space $self->{default_namespace} does not exist" unless $namespaces{$self->{default_namespace}}; } bless $self, $class; $self->_connect($arg->{'servers'}); @@ -117,10 +318,24 @@ sub _connect { }); } +=pod + +=head3 Error + +Last error code, or 'fail' for some network reason, oftenly a timeout. + + $box->Insert(@tuple) or die sprintf "Error %X", $box->Error; # die "Error 202" + +=head3 ErrorStr + +Last error code and description in a single string. + + $box->Insert(@tuple) or die $box->ErrorStr; # die "Error 00000202: Illegal Parameters" + +=cut + sub ErrorStr { - my ($self, $code) = @_; - return $self->{_last_error_msg} if $self->{_last_error} eq 'fail'; - return $self->{errstrclass}->ErrorStr($code || $self->{_last_error}); + return $_[0]->{_last_error_msg}; } sub Error { @@ -134,10 +349,10 @@ sub _chat { $param{unpack} = sub { my $data = $_[0]; confess __LINE__."$self->{name}: [common]: Bad response" if length $data < 4; - my ($full_code, @err_code) = unpack('LX[L]CCCC', substr($data, 0, 4, '')); + my ($full_code, @err_code) = unpack('LX[L]CSC', substr($data, 0, 4, '')); # $err_code[0] = severity: 0 -> ok, 1 -> transient, 2 -> permanent; # $err_code[1] = description; - # $err_code[3] = da box project; + # $err_code[2] = da box project; return (\@err_code, \$data, $full_code); }; @@ -158,12 +373,12 @@ sub _chat { my ($ret_code, $data, $full_code) = @{$ret->{ok}}; $self->{_last_error} = $full_code; if ($ret_code->[0] == 0) { - my $ret = $orig_unpack->($$data,$ret_code->[3]); + my $ret = $orig_unpack->($$data,$ret_code->[2]); confess __LINE__."$self->{name}: [common]: Bad response (more data left)" if length $$data > 0; return $ret; } - $message = $ret_code->[0] == 0 ? "ok" : $data ? sprintf "Error %08X: %s", $full_code, $$data : $self->{errstrclass}->ErrorStr($full_code); + $self->{_last_error_msg} = $message = $ret_code->[0] == 0 ? "ok" : sprintf "Error %08X: %s", $full_code, $$data || $ERRORS{$full_code & 0xFFFFFF00} || 'Unknown error'; $self->_debug("$self->{name}: $message") if $self->{debug} >= 1; if ($ret_code->[0] == 2) { #fatal error $self->_raise($message) if $self->{raise}; @@ -197,29 +412,62 @@ sub _raise { sub _validate_param { my ($self, $args, @pnames) = @_; - my $param = ref $args->[-1] eq 'HASH' ? {%{pop @$args}} : {}; + my $param = $args && @$args && ref $args->[-1] eq 'HASH' ? {%{pop @$args}} : {}; + my %pnames = map { $_ => 1 } @pnames; + $pnames{space} = 1; + $pnames{namespace} = 1; foreach my $pname (keys %$param) { - confess "$self->{name}: unknown param $pname\n" if 0 == grep { $_ eq $pname } @pnames; + confess "$self->{name}: unknown param $pname\n" unless exists $pnames{$pname}; } + $param->{namespace} = $param->{space} if exists $param->{space} and defined $param->{space}; $param->{namespace} = $self->{default_namespace} unless defined $param->{namespace}; - confess "$self->{name}: bad namespace `$param->{namespace}'" unless exists $self->{namespaces}->{$param->{namespace}}; + confess "$self->{name}: bad space `$param->{namespace}'" unless exists $self->{namespaces}->{$param->{namespace}}; my $ns = $self->{namespaces}->{$param->{namespace}}; $param->{use_index} = $ns->{default_index} unless defined $param->{use_index}; confess "$self->{name}: bad index `$param->{use_index}'" unless exists $ns->{index_names}->{$param->{use_index}}; $param->{index} = $ns->{index_names}->{$param->{use_index}}; - return ($param, map { /namespace/ ? $self->{namespaces}->{$param->{namespace}} : $param->{$_} } @pnames); + return ($param, $self->{namespaces}->{$param->{namespace}}, map { $param->{$_} } @pnames); } +=pod + +=head3 Call + +Call a stored procedure. Returns an arrayref of the result tuple(s) upon success. + + my $results = $box->Call('stored_procedure_name', \@procedure_params, \%options) or die $box->ErrorStr; # Call failed + my $result_tuple = @$results && $results->[0] or warn "Call succeeded, but returned nothing"; + +=over + +=item B<@procedure_params> + +An array of bytestrings to be passed as is to the procecedure. + +=item B<%options> + +=over + +=item B<unpack_format> + +Format to unpack the result tuple, the same as C<format> option for C<new()> + +=back + +=back + +=cut + sub Call { - my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace flags raise unpack unpack_format/); + my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/flags raise unpack unpack_format/); my ($self, $sp_name, $tuple) = @_; my $flags = $param->{flags} || 0; local $self->{raise} = $param->{raise} if defined $param->{raise}; - $self->_debug("$self->{name}: CALL[$sp_name][${\join ' ', map {join' ',unpack'(H2)*',$_} @$tuple}]") if $self->{debug} >= 4; + $self->_debug("$self->{name}: CALL($sp_name)[${\join ' ', map {join' ',unpack'(H2)*',$_} @$tuple}]") if $self->{debug} >= 4; confess "All fields must be defined" if grep { !defined } @$tuple; confess "Bad `unpack_format` option" if exists $param->{unpack_format} and ref $param->{unpack_format} ne 'ARRAY'; @@ -229,12 +477,64 @@ sub Call { local $namespace->{append_for_unpack} = '' if $unpack_format; # shit... $self->_chat ( - msg => 22, - payload => pack("L w/a* L(w/a*)*", $flags, $sp_name, scalar(@$tuple), @$tuple), - unpack => $param->{unpack} || sub { $self->_unpack_select($namespace, "CALL", @_) }, + msg => 22, + payload => pack("L w/a* L(w/a*)*", $flags, $sp_name, scalar(@$tuple), @$tuple), + unpack => $param->{unpack} || sub { $self->_unpack_select($namespace, "CALL", @_) }, + callback => $param->{callback}, ); } +=pod + +=head3 Add, Set, Replace + + $box->Add(@tuple) or die $box->ErrorStr; + $box->Set(@tuple, { space => "main" }); + $box->Replace(@tuple, { space => "secondary" }); + +Insert a C<< @tuple >> into the storage into C<$options{space}> or C<default_space> space. +All of them return C<true> upon success. + +All of them have the same parameters: + +=over + +=item B<@tuple> + +A tuple to insert. All fields must be defined. All fields will be C<pack()>ed according to C<format> (see L<new>) + +=item B<%options> + +=over + +=item B<space> => $space_id_uint32_or_name_string + +Specify storage space to work on. + +=back + +=back + +The difference between them is the behaviour concerning tuple with the same primary key: + +=over + +=item * + +B<Add> will fail if a duplicate-key tuple B<exists> + +=item * + +B<Replace> will fail if a duplicate-key tuple B<does not exists> + +=item * + +B<Set> will B<overwrite> a duplicate-key tuple + +=back + +=cut + sub Add { # store tuple if tuple identified by primary key _does_not_ exist my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {}; $param->{action} = 'add'; @@ -254,10 +554,10 @@ sub Replace { # store tuple if tuple identified by primary key _does_ exist } sub Insert { - my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace _flags action/); + my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/_flags action/); my ($self, @tuple) = @_; - $self->_debug("$self->{name}: INSERT(@{[map {qq{`$_'}} @tuple]})") if $self->{debug} >= 3; + $self->_debug("$self->{name}: INSERT(NS:$namespace->{namespace},TUPLE:[@{[map {qq{`$_'}} @tuple]}])") if $self->{debug} >= 3; my $flags = $param->{_flags} || 0; $param->{action} ||= 'set'; @@ -286,9 +586,10 @@ sub Insert { $self->_debug("$self->{name}: INSERT[${\join ' ', map {join' ',unpack'(H2)*',$_} @tuple}]") if $self->{debug} >= 4; $self->_chat ( - msg => 13, - payload => pack("LLL (w/a*)*", $namespace->{namespace}, $flags, scalar(@tuple), @tuple), - unpack => sub { $self->_unpack_affected($flags, $namespace, @_) } + msg => 13, + payload => pack("LLL (w/a*)*", $namespace->{namespace}, $flags, scalar(@tuple), @tuple), + unpack => sub { $self->_unpack_affected($flags, $namespace, @_) }, + callback => $param->{callback}, ); } @@ -333,7 +634,7 @@ sub _unpack_select_multi { sub _unpack_affected { my ($self, $flags, $ns) = @_; - ($flags & WANT_RESULT) ? $self->_unpack_select($ns, "AFFECTED", $_[3])->[0] : unpack('L', substr($_[3],0,4,'')); + ($flags & WANT_RESULT) ? $self->_unpack_select($ns, "AFFECTED", $_[3])->[0] : unpack('L', substr($_[3],0,4,''))||'0E0'; } sub NPRM () { 3 } @@ -397,15 +698,90 @@ sub _PostSelect { } } -my @select_param_ok = qw/namespace use_index raw want next_rows limit offset raise hashify timeout format hash_by/; +=pod + +=head3 Select + +Select tuple(s) from storage + + my $tuple = $box->Select($key) or $box->Error && die $box->ErrorStr; + my $tuple = $box->Select($key, \%options) or $box->Error && die $box->ErrorStr; + + my @tuples = $box->Select(@keys) or $box->Error && die $box->ErrorStr; + my @tuples = $box->Select(@keys, \%options) or $box->Error && die $box->ErrorStr; + + my $tuples = $box->Select(\@keys) or die $box->ErrorStr; + my $tuples = $box->Select(\@keys, \%options) or die $box->ErrorStr; + +=over + +=item B<$key>, B<@keys>, B<\@keys> + +Specify keys to select. All keys must be defined. + +=over + +=item * + +In scalar context, you can select one C<$key>, and the resulting tuple will be returned. +Check C<< $box->Error >> to see if there was an error or there is just no such key +in the storage + +=item * + +In list context, you can select several C<@keys>, and the resulting tuples will be returned. +Check C<< $box->Error >> to see if there was an error or there is just no such keys +in the storage + +=item * + +If you select C<< \@keys >> then C<< \@tuples >> will be returned upon success. @tuples will +be empty if there are no such keys, and false will be returned in case of error. + +=back + +=item B<%options> + +=over + +=item B<space> => $space_id_uint32_or_name_string + +Specify storage (by id or name) space to select from. + +=item B<use_index> => $index_id_uint32_or_name_string + +Specify index (by id or name) to use. + +=item B<hashify> => $coderef + +Override C<hashify> option (see L<new>). + +=item B<raw> => $bool + +Don't C<hashify>. + +=item B<hash_by> => $by + +Return a hashref of the resultset. If you C<hashify> the result set, +then C<$by> must be a field name of the hash you return, +else it must be a number of field of the tuple. +False will be returned in case of error. + +=back + +=back + +=cut + +my @select_param_ok = qw/use_index raw want next_rows limit offset raise hashify timeout format hash_by/; sub Select { confess q/Select isnt callable in void context/ unless defined wantarray; my ($param, $namespace) = $_[0]->_validate_param(\@_, @select_param_ok); my ($self, @keys) = @_; local $self->{raise} = $param->{raise} if defined $param->{raise}; - @keys = @{$keys[0]} if ref $keys[0] eq 'ARRAY' and 1 == @{$param->{index}->{keys}} || ref $keys[0]->[0] eq 'ARRAY'; + @keys = @{$keys[0]} if @keys && ref $keys[0] eq 'ARRAY' and 1 == @{$param->{index}->{keys}} || (@keys && ref $keys[0]->[0] eq 'ARRAY'); - $self->_debug("$self->{name}: SELECT($namespace->{namespace}/$param->{use_index})[@{[map{ref$_?qq{[@$_]}:$_}@keys]}]") if $self->{debug} >= 3; + $self->_debug("$self->{name}: SELECT(NS:$namespace->{namespace},IDX:$param->{use_index})[@{[map{ref$_?qq{[@$_]}:$_}@keys]}]") if $self->{debug} >= 3; my ($msg,$payload); if(exists $param->{next_rows}) { @@ -428,6 +804,7 @@ sub Select { unpack => sub { $self->_unpack_select($namespace, "SELECT", @_) }, retry => $self->{select_retry}, timeout => $param->{timeout} || $self->{select_timeout}, + callback => $param->{callback}, ) or return; } @@ -484,7 +861,8 @@ sub SelectUnion { payload => pack("L (a*)*", scalar(@reqs), map { $_->{payload} } @reqs), unpack => sub { $self->_unpack_select_multi([map { $_->{namespace} } @reqs], "SMULTI", @_) }, retry => $self->{select_retry}, - timeout => $param->{timeout} || $self->{select_timeout}, + timeout => $param->{select_timeout} || $self->{timeout}, + callback => $param->{callback}, ) or return; confess __LINE__."$self->{name}: something wrong" if @$r != @reqs; my $ea = each_arrayref $r, \@reqs; @@ -494,19 +872,56 @@ sub SelectUnion { return $r; } +=pod + +=head3 Delete + +Delete tuple from storage. Return false upon error. + + my $n_deleted = $box->Delete($key) or die $box->ErrorStr; + my $n_deleted = $box->Delete($key, \%options) or die $box->ErrorStr; + warn "Nothing was deleted" unless int $n_deleted; + + my $deleted_tuple_set = $box->Delete($key, { want_deleted_tuples => 1 }) or die $box->ErrorStr; + warn "Nothing was deleted" unless @$deleted_tuple_set; + +=over + +=item B<%options> + +=over + +=item B<space> => $space_id_uint32_or_name_string + +Specify storage space (by id or name) to work on. + +=item B<want_deleted_tuples> => $bool + +if C<$bool> then return arrayref of deleted tuple(s). + +=back + +=back + +=cut + sub Delete { - my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace/); + my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/want_deleted_tuples/); my ($self, $key) = @_; - $self->_debug("$self->{name}: DELETE($key)") if $self->{debug} >= 3; + my $flags = 0; + $flags |= WANT_RESULT if $param->{want_deleted_tuple}; + + $self->_debug("$self->{name}: DELETE(NS:$namespace->{namespace},KEY:$key,F:$flags)") if $self->{debug} >= 3; confess "$self->{name}\->Delete: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}}; $self->_pack_keys($namespace, $param->{index}, $key); - $self->_chat ( - msg => 20, - payload => pack("L a*", $namespace->{namespace}, $key), - unpack => sub { $self->_unpack_affected(0, $namespace, @_) } + $self->_chat( + msg => $flags ? 21 : 20, + payload => $flags ? pack("L L a*", $namespace->{namespace}, $flags, $key) : pack("L a*", $namespace->{namespace}, $key), + unpack => sub { $self->_unpack_affected($flags, $namespace, @_) }, + callback => $param->{callback}, ); } @@ -564,11 +979,80 @@ BEGIN { } } +=pod + +=head3 Update + +Update tuple in storage. Return false upon error. + + my $n_updated = $box->UpdateMulti($key, @op) or die $box->ErrorStr; + my $n_updated = $box->UpdateMulti($key, @op, \%options) or die $box->ErrorStr; + warn "Nothing was updated" unless int $n_deleted; + + my $updated_tuple_set = $box->UpdateMulti($key, @op, { want_result => 1 }) or die $box->ErrorStr; + warn "Nothing was updated" unless @$updated_tuple_set; + +=over + +=item B<@op> = ([ $field_num => $op => $value ], ...) + +=over + +=item B<$field_num> + +Field-to-update number. + +=item B<$op> + +=over + +=item B<set> + +Set C<< $field_num >> field to C<< $value >> + +=item B<add>, B<and>, B<xor>, B<or> + +Apply an arithmetic operation to C<< $field_num >> with argument C<< $value >> +Currently arithmetic operations are supported only for int32 (4-byte length) fields (and C<$value>s too) + +=item B<splice>, B<substr> + +Apply a perl-like L<splice> operation to C<< $field_num >>. B<$value> = [$OFFSET, $LENGTH, $REPLACE_WITH]. +substr is just an alias. + +=item B<append>, B<prepend> + +Append or prepend C<< $field_num >> with C<$value> string. + +=item B<cutbeg>, B<cutend> + +Cut C<< $value >> bytes from beginning or end of C<< $field_num >>. + +=back + +=back + +=item B<%options> + +=over + +=item B<space> => $space_id_uint32_or_name_string + +Specify storage space (by id or name) to work on. + +=item B<want_result> => $bool + +if C<$bool> then return arrayref of deleted tuple(s). + +=back + +=cut + sub UpdateMulti { - my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace want_result _flags raw/); + my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/want_result _flags/); my ($self, $key, @op) = @_; - $self->_debug("$self->{name}: UPDATEMULTI($namespace->{namespace}=$key)[@{[map{qq{[@$_]}}@op]}]") if $self->{debug} >= 3; + $self->_debug("$self->{name}: UPDATEMULTI(NS:$namespace->{namespace},KEY:$key)[@{[map{qq{[@$_]}}@op]}]") if $self->{debug} >= 3; confess "$self->{name}\->UpdateMulti: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}}; confess "$self->{name}: too many op" if scalar @op > 128; @@ -617,7 +1101,8 @@ sub UpdateMulti { $self->_chat( msg => 19, payload => pack("LL a* L (a*)*" , $namespace->{namespace}, $flags, $key, scalar(@op), @op), - unpack => sub { $self->_unpack_affected($flags, $namespace, @_) } + unpack => sub { $self->_unpack_affected($flags, $namespace, @_) }, + callback => $param->{callback}, ); } diff --git a/connector/perl/t/box.pl b/connector/perl/t/box.pl index 810a919f14..0ea2e5a784 100644 --- a/connector/perl/t/box.pl +++ b/connector/perl/t/box.pl @@ -7,7 +7,7 @@ BEGIN { } use FindBin qw($Bin); use lib "$Bin"; -use TBox (); +#use TBox (); use Carp qw/confess/; use Test::More tests => 218; @@ -16,12 +16,12 @@ use Test::Exception; local $SIG{__DIE__} = \&confess; our $CLASS; -BEGIN { $CLASS = $ENV{BOXCLASS} || 'MR::SilverBox'; eval "require $CLASS" or die $@; } +BEGIN { $CLASS = $ENV{BOXCLASS} || 'MR::Tarantool::Box'; eval "require $CLASS" or die $@; } -use constant ILL_PARAM => qr/$CLASS: Error 00000202/; -use constant TUPLE_NOT_EXISTS => qr/$CLASS: Error 00003102/; -use constant TUPLE_EXISTS => qr/$CLASS: Error 00003702/; -use constant INDEX_VIOLATION => qr/$CLASS: Error 00003802/; +use constant ILL_PARAM => qr/Error 00000202/; +use constant TUPLE_NOT_EXISTS => qr/Error 00003102/; +use constant TUPLE_EXISTS => qr/Error 00003702/; +use constant INDEX_VIOLATION => qr/Error 00003802/; use constant TOO_BIG_FIELD => qr/too big field/; @@ -30,6 +30,7 @@ my $server = (shift || $ENV{BOX}) or die; sub cleanup ($) { my ($id) = @_; + die unless defined $id; ok defined $box->Delete($id), 'delete of possible existing record'; ok $box->Delete($id) == 0, 'delete of non existing record'; } @@ -49,17 +50,20 @@ sub def_param { namespace => 0, format => $format, default_index => 'primary_id', - } ]} + name => 'main', + } ], + default_space => "main", + } } $box = $CLASS->new(def_param('l&SSLL&')); ok $box->isa($CLASS), 'connect'; cleanup 13; -ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '777'), 'insert'; +ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '777',{space => 'main'}), 'insert'; is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '777'], 'select/insert'; -ok $box->Insert(13, q/some_email@test.mail.ru/, 2, 2, 3, 4, '666'), 'replace'; +ok $box->Insert(13, q/some_email@test.mail.ru/, 2, 2, 3, 4, '666',{namespace => 'main'}), 'replace'; is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 2, 3, 4, '666'], 'select/replace'; ok $box->Update(13, 3 => 1) == 1, 'update of some field'; @@ -88,7 +92,7 @@ cleanup 13; ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'insert'; is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/insert'; -throws_ok sub { $box->UpdateMulti(13, [6 => splice => [-10]]) }, qr/Illegal parametrs/, "splice/bad_params_1"; +throws_ok sub { $box->UpdateMulti(13, [6 => splice => [-10]]) }, ILL_PARAM, "splice/bad_params_1"; ok $box->UpdateMulti(13, [6 => splice => [100]]), "splice/big_offset"; is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select'; @@ -409,8 +413,8 @@ sub def_param1 { } ]} } -$box = MR::SilverBox->new(def_param1); -ok $box->isa('MR::SilverBox'), 'connect'; +$box = $CLASS->new(def_param1); +ok $box->isa($CLASS), 'connect'; my @tuple1 = (13, 'mail.ru', 123); cleanup $tuple1[0]; @@ -435,7 +439,7 @@ is_deeply [$box->Select([[$tuple2[1], $tuple2[2]]], { use_index => 'secondary_co sub def_param_bad { my $format = 'l&&'; return { servers => $server, - namespaces => [ { + spaces => [ { indexes => [ { index_name => 'primary_num1', keys => [0], @@ -452,12 +456,12 @@ sub def_param_bad { } ]} } -$box = MR::SilverBox->new(def_param_bad); -ok $box->isa('MR::SilverBox'), 'connect'; +$box = $CLASS->new(def_param_bad); +ok $box->isa($CLASS), 'connect'; my @tuple_bad = (13, 'mail.ru', '123'); cleanup $tuple_bad[0]; -throws_ok sub { $box->Insert(@tuple_bad) }, qr/Illegal parametrs/, "index_constains/bad_field_type"; +throws_ok sub { $box->Insert(@tuple_bad) }, ILL_PARAM, "index_constains/bad_field_type"; ## Check unique tree index @@ -466,9 +470,9 @@ sub def_param_unique { return { servers => $server, namespaces => [ { indexes => [ { - index_name => 'id', - keys => [0], - }, { + index_name => 'id', + keys => [0], + }, { index_name => 'email', keys => [1], }, { @@ -478,17 +482,17 @@ sub def_param_unique { index_name => 'lastname', keys => [3], } , { - index_name => 'fullname', - keys => [2, 3] - } ], - namespace => 27, + index_name => 'fullname', + keys => [2, 3] + } ], + space => 27, format => $format, default_index => 'id', } ]} } -$box = MR::SilverBox->new(def_param_unique); -ok $box->isa('MR::SilverBox'), 'connect'; +$box = $CLASS->new(def_param_unique); +ok $box->isa($CLASS), 'connect'; my $tuples = [ [1, 'rtokarev@corp.mail.ru', 'Roman', 'Tokarev'], [2, 'vostrikov@corp.mail.ru', 'Yuri', 'Vostrikov'], @@ -502,7 +506,7 @@ foreach my $tuple (@$tuples) { foreach my $tuple (@$tuples) { if ($tuple == $tuples->[-1] || $tuple == $tuples->[-2]) { - throws_ok sub { $box->Insert(@$tuple) }, qr/Index violation/, "unique_tree_index/insert \'$tuple->[0]\'"; + throws_ok sub { $box->Insert(@$tuple) }, INDEX_VIOLATION, "unique_tree_index/insert \'$tuple->[0]\'"; } else { ok $box->Insert(@$tuple), "unique_tree_index/insert \'$tuple->[0]\'"; } @@ -521,19 +525,21 @@ foreach my $r (@res) { sub def_param_u64 { my $format = '&&&&'; return { servers => $server, - namespaces => [ { + spaces => [ { indexes => [ { - index_name => 'id', - keys => [0], - } ], - namespace => 20, + index_name => 'id', + keys => [0], + } ], + space => 20, format => $format, default_index => 'id', - } ]} + } ], + debug => 0, + } } -$box = MR::SilverBox->new(def_param_u64); -ok $box->isa('MR::SilverBox'), 'connect'; +$box = $CLASS->new(def_param_u64); +ok $box->isa($CLASS), 'connect'; $_->[0] = pack('ll', $_->[0], 0) foreach @$tuples; -- GitLab