MR::Ta...l::Box(3pm)

✨ NAME ✨

so like, welcome to the docs for MR::Tarantool::Box(3pm) bestie πŸ’– β€” it's a driver for an efficient Tarantool/Box NoSQL in-memory storage. that's it, that's the whole vibe.


🌸 SYNOPSIS 🌸

my $box = MR::Tarantool::Box->new({
    servers => "127.0.0.1:33013",
    name    => "My Box",              # mostly used for debug purposes
    spaces => [ {
        indexes => [ {
            index_name   => 'idx1',
            keys         => [0],
        }, {
            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, $ 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
    }, {
        #...
    } ],
    default_space => "primary",

    timeout   => 1.0,                 # seconds
    retry     => 3,
    debug     => 9,                   # output to STDERR some debugging info
    raise     => 0,                   # don't raise an exception in case of error
});

my $bool  = $box->Insert(1, 2,3, 4,5,6,7,8,"asdf")                            or die $box->ErrorStr;
my $bool  = $box->Insert(2, 2,4, 4,5,6,7,8,"asdf",{space => "primary"})       or die $box->ErrorStr;
my $tuple = $box->Insert(3, 3,3, 4,5,6,7,8,"asdf",{want_inserted_tuple => 1}) or die $box->ErrorStr;

# Select by single-field key
my $tuple  = $box->Select(1);                                                 # scalar context - scalar result: $tuple
my @tuples = $box->Select(1,2,3);                                             # list   context - list   result: ($tuple, $tuple, ...)
my $tuples = $box->Select([1,2,3],{space => "primary", use_index => "idx1"}); #                arrayref result: [$tuple, $tuple, ...]

# Select by multi-field key
my $tuples = $box->Select([[2,3]],{use_index => "idx2"}); # by full key
my $tuples = $box->Select([[2]]  ,{use_index => "idx2"}); # by partial key

my $bool  = $box->UpdateMulti(1,[ f4 => add => 3 ]);
my $bool  = $box->UpdateMulti(2,[ f4 => add => 3 ],{space => "primary"});
my $tuple = $box->UpdateMulti(3,[ f4 => add => 3 ],{want_updated_tuple => 1});

my $bool  = $box->Delete(1);
my $tuple = $box->Delete(2, {want_deleted_tuple => 1});

πŸ¦‹ DESCRIPTION πŸ¦‹

🧚 METHODS 🧚

🌟 new

my $box = $class->new(\%args);

%args: β€” here's the whole menu, no skips, we go through every single one πŸ’…

  • spaces => [ \%space, ... ] β€” the list of spaces you're hooking up.

%space:

  • space => $space_id_uint32 β€” the space id exactly as you set it in the Tarantool/Box config.
  • name => $space_name_string β€” a self-descriptive space id, and it gets mapped into space.
  • format => $format_string β€” a pack()-compatible tuple format string. allowed formats are: "QqLlSsC(c&$)*", where & stands for bytestring and $ stands for utf8 string. heads up: Qq is usable only if perl itself supports int64. tuples' fields get packed/unpacked according to this format. and a * at the end of format turns on LongTuple. periodt.
  • hashify => $coderef β€” drop in a callback to turn each tuple into a cute good-looking hash πŸ₯° it receives the space id and the resultset as arguments. no return value needed, it just does its thing.
$coderef = sub {
    my ($space_id, $resultset) = @_;
    $_ = { FieldName1 => $_->[0], FieldName2 => $_->[1], ... } for @$resultset;
};
  • fields => $arrayref β€” hand it an arrayref of field names matching the format and every tuple becomes a good-looking hash ✨ names must begin with [A-Za-z]. if LongTuple is enabled, the last field gets used to fold the tailing fields.
  • long_fields => $arrayref β€” an arrayref of field names matching (xxx)* that turns the tailing fields into a good-looking array of hashes. names must begin with [A-Za-z]. this one only works with LongTuple enabled, no cap.
  • indexes => [ \%index, ... ] β€” the index list.

%index:

  • id => $index_id_uint32 β€” the index id as set in the Tarantool/Box config inside the current space. if you don't set it, the order position in indexes is treated as the id.
  • name => $index_name_string β€” a self-descriptive index id, which gets mapped into index_id.
  • keys => [ $field_no_uint32, ... ] β€” a properly ordered arrayref of the fields' numbers that are indexed. order matters bestie, don't freestyle it.
  • default_index => $default_index_name_string_or_id_uint32 β€” the index id or name used by default for the current space in select operations. you must set this if there's more than one \%index in there.
  • primary_key_index => $primary_key_name_string_or_id_uint32 β€” the index id or name used by default for the current space in update operations. by default it's just set to default_index.
  • default_space => $default_space_name_string_or_id_uint32 β€” the space space or name used by default. must be set if there's more than one \%space in play.
  • timeout => $timeout_fractional_seconds_float || 23 β€” the common timeout for network operations, in fractional seconds. default 23.
  • select_timeout => $select_timeout_fractional_seconds_float || 2 β€” the timeout for select queries' network operations. peep "select_retry" for the rest of the lore.
  • retry => $retry_int || 1 β€” the common retries number for network operations. default 1.
  • select_retry => $select_retry_int || 3 β€” the retries number for select queries' network operations. default 3.
sometimes you need a short timeout for select's and a long timeout for critical update's, because when a timeout hits you straight up don't know if the update has succeeded. and for that exact same reason we can't retry update operations.

so yeah β€” increasing timeout and setting retry => 1 for updates lowers the possibility of those situations (but of course does not exclude them at all), and guarantees that we don't do the same thing more than once. slay.

  • soft_retry => $soft_retry_int || 3 β€” the common retries number for Tarantool/Box temporary errors (these are marked by 1 in the lowest byte of error_code). in that case we know for sure the request was declined by Tarantool/Box for some reason (a tuple was locked for another update, for example), so we can try it again πŸ™Œ

this is also limited by retry/select_retry (depending on the query type).

  • retry_delay => $retry_delay_fractional_seconds_float || 1 β€” a delay between retries for network operations. default 1.
  • raise => $raise_bool || 1 β€” should we raise exceptions? if so, exceptions get raised when no more retries are left and all the tries failed (with timeout, fatal, or temporary error). default is 1.
  • debug => $debug_level_int || 0 β€” debug level. 0 prints nothing, 9 prints everything. default 0.
  • name => $name β€” a string used for self-description. mainly for debugging purposes.

πŸ’€ Error

the last error code, or 'fail' for some network reason β€” often a timeout. iconic behaviour honestly.

$box->Insert(@tuple) or die sprintf "Error %X", $box->Error; # die "Error 202"

πŸ’€ ErrorStr

the last error code and its description, all cozy in a single string.

$box->Insert(@tuple) or die $box->ErrorStr;                  # die "Error 00000202: Illegal Parameters"

πŸ“ž Call

call a stored procedure. on success it returns an arrayref of the result tuple(s) 🫢

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";
  • @procedure_params β€” an array of bytestrings passed as-is to the procecedure.
  • %options
    • unpack_format β€” the format used to unpack the result tuple, same deal as the format option for new().

🌈 Add, Insert, Replace

$box->Add(@tuple) or die $box->ErrorStr;         # only store a new tuple
$box->Replace(@tuple, { space => "secondary" }); # only store an existing tuple
$box->Insert(@tuple, { space => "main" });       # store anyway

insert a @tuple into the storage, into $options{space} or the default_space space. all three of them return "true" when it works out πŸ’–

all of them take the exact same parameters:

  • @tuple β€” the tuple to insert. all fields must be defined, no half-finished vibes. all fields get pack()ed according to format (go read "new").
  • %options
    • space => $space_id_uint32_or_name_string β€” pick which storage space you're working on.

and the difference between the three? it's all about how they treat a tuple with the same primary key πŸ‘€

  • Add will succeed if and only if a duplicate-key tuple does not exist.
  • Replace will succeed if and only if a duplicate-key tuple does exist.
  • Insert will succeed anyway. the duplicate-key tuple gets overwritten. no thoughts, just vibes.

πŸ” Select

select tuple(s) from storage.

my $key = $id;
my $key = [ $firstname, $lastname ];
my @keys = ($key, ...);

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;
  • $key, @keys, \@keys β€” the keys you wanna select by. all keys must be defined, bestie.

contextual behaviour, because context is everything ✨:

  • in scalar context, you can select one $key, and the resulting tuple gets returned. check $box->Error to see if there was an actual error, or if there's just no such key in the storage.
  • in list context, you can select several @keys, and the resulting tuples get returned. check $box->Error to see if there was an actual error, or if there's just no such keys in the storage.
  • if you select \@keys then \@tuples gets returned on success. @tuples will be empty if there are no such keys, and false gets returned in case of error.

other notes, iykyk:

  • if you select using an index on multiple fields, each $key should be given as a key-tuple: $key = [ $key_field1, $key_field2, ... ].
  • %options
    • space => $space_id_uint32_or_name_string β€” specify the storage space to select from, by id or by name.
  • use_index => $index_id_uint32_or_name_string β€” picks the index to use, by id or by name, whichever you vibe with.
  • limit => $limit_uint32 β€” max tuples to select, bestie. It's already set to MAX_INT32 by default so like... go off I guess.
  • raw => $bool β€” don't "hashify" (go peek at "new"), and disable "utf8" processing. raw bytes, no filter πŸ’…
  • hash_by => $by β€” returns a hashref of the resultset. If you "hashify" the result set then $by must be a field name of the hash you return, otherwise it must be a number of a field of the tuple. On error you get "False" back, which is not the vibe. 😭

πŸ—‘οΈ Delete

Deletes a tuple from storage. Returns false if something goes wrong, no cap.

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;

πŸŽ€ %options

  • space => $space_id_uint32_or_name_string β€” which storage space to work on, by id or by name.
  • want_deleted_tuple => $bool β€” if $bool is truthy, it returns the deleted tuple. give it back, king πŸ‘‘

✨ UpdateMulti ✨

Applies several update operations to a tuple at once. one shot, one opportunity.

my @op = ([ f1 => add => 10 ], [ f1 => and => 0xFF], [ f2 => set => time() ], [ misc_string => cutend => 3 ]);

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_updated;

my $updated_tuple_set = $box->UpdateMulti($key, @op, { want_result => 1 }) or die $box->ErrorStr;
warn "Nothing was updated" unless @$updated_tuple_set;

Different fields can be updated in one shot. The same field can even be updated more than once. All update operations happen atomically β€” like, all or nothing, periodt. Returns false upon error.

πŸ¦‹ @op = ([ $field => $op => $value ], ...)

  • $field β€” the field-to-update, as a number or a name (go see "fields", "LongTuple").
  • $op β€” the actual operation, bestie:
    • set β€” Set $field to $value.
    • add, and, xor, or β€” Apply an arithmetic operation to $field with argument $value. Currently arithmetic operations are supported only for int32 (4-byte length) fields (and $values too). that's the limit, sorry not sorry.
    • splice, substr β€” Apply a perl-like splice operation to $field. $value = [$OFFSET, $LENGTH, $REPLACE_WITH]. substr is just an alias, same energy.
    • append, prepend β€” Append or prepend $field with the $value string.
    • cutbeg, cutend β€” Cut $value bytes from the beginning or the end of $field. βœ‚οΈ

πŸŽ€ %options

  • space => $space_id_uint32_or_name_string β€” which storage space to work on, by id or by name.
  • want_updated_tuple => $bool β€” if $bool is truthy, it returns the updated tuple. 🫢

🌈 AnyEvent

"Insert, UpdateMulti, Select, Delete, Call" methods can all be handed these options:

  • callback => sub { my ($data, $error) = @_; } β€” Do an async request using AnyEvent. $data contains unpacked data, processed according to the request options. $error contains a message string if something went wrong. Set up raise => 0 to actually use this option, iykyk.

πŸ’œ "Continuations"

"Select" methods can be given these options:

  • return_fh => 1 β€” The request only does the send operation on the network, and returns { fh => $IO_Handle, continue => $code }, or false if the send operation failed. $code reads data from the network, unpacks it, processes it according to the options, and returns it.
You've gotta handle timeouts and retries yourself, bestie (using the select() call, for example). it's giving DIY.

Usage example, literally:

my $continuation = $box->Select(13,{ return_fh => 1 });
ok $continuation, "select/continuation";

my $rin = '';
vec($rin,$continuation->{fh}->fileno,1) = 1;
my $ein = $rin;
ok 0 <= select($rin,undef,$ein,2), "select/continuation/select";

my $res = $continuation->{continue}->();
use Data::Dumper;
is_deeply $res, [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], "select/continuation/result";

πŸ‘‘ LongTuple

If "format" given to "new", or "unpack_format" given to "Call", ends with a star ("*"), long tuple is enabled. The last field (or group of fields) of "format" represents the variable-length tail of the tuple. The "long_fields" option given to "new" will fold that tail into an array of hashes. iconic behaviour fr fr.

$box->Insert(1,"2",3);         #1
$box->Insert(3,"2",3,4,5);     #2
$box->Insert(5,"2",3,4,5,6,7); #3

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 => [] },        #1
    { a => 3, b => "2", c => 3, d => [4,5] },     #2
    { a => 5, b => "2", c => 3, d => [4,5,6,7] }, #3
];

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 => [] },                               #1
    { a => 3, b => "2", c => 3, d => [{d1=>4, d2=>5}] },                 #2
    { a => 5, b => "2", c => 3, d => [{d1=>4, d2=>5}, {d1=>6, d2=>7}] }, #3
];

"UpdateMulti" can be given a field number in several ways, bestie:

  • $linear_index_int
$box->UpdateMulti(5, [ 5 => set => $val ]) #3: set 6 to $val
  • an arrayref of [$index_of_folded_subtuple_int, $long_field_name_str_or_index_int]
$box->UpdateMulti(5, [ [1,0]    => set => $val ]) #3: set 6 to $val
$box->UpdateMulti(5, [ [1,'d1'] => set => $val ]) #3: set 6 to $val

🌸 utf8

Utf8 strings are supported very simply, no overthinking. When you push any data to tarantool (with any query, read or write), the utf8 flag is set off, so all data is pushed as a bytestring. When reading the response, for fields marked with a dollar sign "$" (peep "new") β€” including such fields in the "LongTuple" tail β€” the utf8 flag is set on. That's it, that's all. Validity is on you. 😭


This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


πŸ”­ SEE ALSO


and that's the whole page, bestie 🫢 this one's from perl v5.30.3 and it's dated 2020-07-21 β€” go forth and box those tuples, you ate πŸ’…βœ¨

πŸ“¦ where this page comes from