@@ -1,72 +1,270 @@
-Revision history for Perl extension DR::Tarantool.
+libdr-tarantool-perl (0.42-1) unstable; urgency=low
-0.01 Sat May 12 18:51:55 2012
- - original version; created by h2xs 1.23 with options
- -n DR::Tarantool
+ * Rebuild for Debian perl 5.18.
+ * Fix some cpan tests (that are run without tarantool).
-0.02 Sun May 20 10:38:44 MSK 2012
- - update documentation
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 18 Sep 2013 01:37:52 +0400
-0.03 Sun May 20 17:17:38 MSK 2012
- - fix forgotten depends
+libdr-tarantool-perl (0.41-1) unstable; urgency=low
-0.04 Tue May 22 17:52:21 MSK 2012
- - upgrade tests for new upstream library (there is no changes in driver)
+ * Fix broken xs-test (by cpan testers reports).
-0.05 Thu May 24 11:50:11 MSK 2012
- - call_lua always unpacks fields, even if You doesn't define
- their types.
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 02 Sep 2013 15:37:11 +0400
-0.06 Thu May 24 13:54:30 MSK 2012
- - sync methods return tuple or undef
+libdr-tarantool-perl (0.40-1) unstable; urgency=low
-0.07 Thu May 24 18:06:17 MSK 2012
- - iterator can construct Your objects.
+ * Add RealSyncClient module.
-0.08 Fri May 25 11:12:40 MSK 2012
- - add 'JSON' to filed type list.
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 28 Aug 2013 17:00:13 +0400
-0.09 Sat May 26 13:45:12 MSK 2012
- - fix iterators if iter->count > 2
+libdr-tarantool-perl (0.39-1) unstable; urgency=low
-0.11 Tue May 29 21:18:58 MSK 2012
- - add DR::Tarantool::CoroClient.
- - you can select tuples using partial index.
+ * Ping method works even connection isn't established.
-0.12 Sat Jun 2 22:37:15 MSK 2012
- - add INT & INT64 types (signed value)
- - add MONEY & BIGMONEY types
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 21 Aug 2013 17:33:08 +0400
-0.14 Mon Jun 4 10:23:52 MSK 2012
- - if Coro isn't installed all tests passed properly
+libdr-tarantool-perl (0.38-1) unstable; urgency=low
-0.15 Thu Jun 7 00:07:44 MSK 2012
- - fix tuple destructor (it doesn't crashes in global destructor)
+ * Parser doesn't segfault if tarantool replies by broken package.
-0.16 Sat Jun 23 16:39:13 MSK 2012
- - add some functions to iterator.
+ -- Dmitry E. Oboukhov <unera@debian.org> Sat, 01 Jun 2013 21:23:23 +0400
-0.17 Wed Jun 27 11:49:40 MSK 2012
- - backported to perl 5.8.8
+libdr-tarantool-perl (0.37-1) unstable; urgency=low
-0.18 Mon Jul 2 10:29:05 MSK 2012
- - add some functions to Space
+ * Update perldoc.
-0.19 Mon Jul 9 12:55:38 MSK 2012
- - optimization for parallel requests.
- - reduce stack requirements for tuple destructors (for Coro).
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 29 Apr 2013 14:37:25 +0400
-0.20 Tue Jul 10 10:46:36 MSK 2012
- - some optimizations in iterator.
+libdr-tarantool-perl (0.36-1) unstable; urgency=low
-0.22 Tue Jul 31 14:39:10 MSK 2012
- - prebuild tuple packages in space (so Tuple doesn't use AUTOLOAD)
- - you can use connector without any spaces
- (for example for only lua calls)
+ * Update perldoc.
+ * Extends readahead buffer to fix FTBFS, closes: #704266.
-0.23 Wed Sep 5 13:43:25 MSK 2012
- - add logging request/responses.
+ -- Dmitry E. Oboukhov <unera@debian.org> Sun, 21 Apr 2013 12:23:43 +0400
-0.24 Tue Dec 11 23:29:53 MSK 2012
- - Fix possibly memory leak
- - closes Debian FTBFS (fix tests)
+libdr-tarantool-perl (0.35-1) unstable; urgency=low
+
+ * Add stress test for tarantool, some additional test cases.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 30 Jan 2013 23:54:19 +0400
+
+libdr-tarantool-perl (0.34-1) unstable; urgency=low
+
+ * Fix some tests.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Tue, 29 Jan 2013 10:12:56 +0400
+
+libdr-tarantool-perl (0.33-2) unstable; urgency=low
+
+ * All requests that are called between reconnects will wait connection
+ instead error returning.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sun, 27 Jan 2013 16:48:26 +0400
+
+libdr-tarantool-perl (0.32-1) unstable; urgency=low
+
+ * Re-enable tests.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 23 Jan 2013 14:56:43 +0400
+
+libdr-tarantool-perl (0.31-1) unstable; urgency=low
+
+ * Disable some tests (try to localize some problems with cpan testers).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 21 Jan 2013 21:56:14 +0400
+
+libdr-tarantool-perl (0.30-1) unstable; urgency=low
+
+ * Fixed empty tuple list in tp.h.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sat, 19 Jan 2013 00:30:24 +0400
+
+libdr-tarantool-perl (0.29-1) unstable; urgency=low
+
+ * Fixed some warnings in tests.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Tue, 15 Jan 2013 22:31:13 +0400
+
+libdr-tarantool-perl (0.28-1) unstable; urgency=low
+
+ * Fix disconnect async method.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Tue, 15 Jan 2013 22:17:35 +0400
+
+libdr-tarantool-perl (0.27-1) unstable; urgency=low
+
+ * Don't use libtarantool for depends/build-depends.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Thu, 10 Jan 2013 17:53:46 +0400
+
+libdr-tarantool-perl (0.26-1) unstable; urgency=low
+
+ * Update homepage/vcs information.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 07 Jan 2013 03:01:47 +0400
+
+libdr-tarantool-perl (0.25-1) unstable; urgency=low
+
+ * Uses AE::io instead AE::Handle (benchmarks, benchmarks... :)).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sun, 06 Jan 2013 19:18:46 +0400
+
+libdr-tarantool-perl (0.24-1) unstable; urgency=low
+
+ * Fix possibly memory leak, closes Debian FTBFS (fix tests),
+ closes: #695660.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Tue, 11 Dec 2012 23:28:06 +0400
+
+libdr-tarantool-perl (0.23-1) unstable; urgency=low
+
+ * LLClient can log requests/reposnses (and error responses).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 05 Sep 2012 13:35:43 +0400
+
+libdr-tarantool-perl (0.22-1) unstable; urgency=low
+
+ * Prebuild tuple packages (so it doesn't use AUTOLOAD anymore).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 20 Aug 2012 21:19:44 +0400
+
+libdr-tarantool-perl (0.21-1) unstable; urgency=low
+
+ * Add some functions to iterator (sort, grep, ...).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sun, 15 Jul 2012 20:10:23 +0400
+
+libdr-tarantool-perl (0.20-1) unstable; urgency=low
+
+ * Some optimizations in iterators.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Tue, 10 Jul 2012 10:45:35 +0400
+
+libdr-tarantool-perl (0.19-1) unstable; urgency=low
+
+ * New upstream version: it is optimized for parallel requests.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Thu, 05 Jul 2012 23:50:03 +0400
+
+libdr-tarantool-perl (0.18-1) unstable; urgency=low
+
+ * HVs are created like perlxs: trying to be compatible with libcoro-perl.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 02 Jul 2012 10:12:39 +0400
+
+libdr-tarantool-perl (0.17-2) unstable; urgency=low
+
+ * Rebuilt for perl 5.14.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Fri, 29 Jun 2012 07:20:46 +0400
+
+libdr-tarantool-perl (0.17-1) unstable; urgency=low
+
+ * New upstream version (perl 5.8.8 compatible).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 27 Jun 2012 10:38:57 +0400
+
+libdr-tarantool-perl (0.16-1) unstable; urgency=low
+
+ * New upstream version. Add some functions to iterators.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sat, 23 Jun 2012 16:27:22 +0400
+
+libdr-tarantool-perl (0.15-1) unstable; urgency=low
+
+ * New upstream version.
+ Fix tuple destructor (it doesn't crashes in global destructor).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Thu, 07 Jun 2012 00:08:53 +0400
+
+libdr-tarantool-perl (0.14-1) unstable; urgency=low
+
+ * New version: Coro can be uninstalled: some tests will be skipped.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 04 Jun 2012 10:21:49 +0400
+
+libdr-tarantool-perl (0.12-1) unstable; urgency=low
+
+ * New version (provides new field types).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sat, 02 Jun 2012 22:35:47 +0400
+
+libdr-tarantool-perl (0.11-1) unstable; urgency=low
+
+ * New version. You can use parts of indexes in 'select'.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Tue, 29 May 2012 21:21:05 +0400
+
+libdr-tarantool-perl (0.10-1) unstable; urgency=low
+
+ * New version. Add DR::Tarantool::CoroClient.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 28 May 2012 20:05:12 +0400
+
+libdr-tarantool-perl (0.09-2) unstable; urgency=low
+
+ * Fix homepage section in debian/control.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 28 May 2012 10:00:48 +0400
+
+libdr-tarantool-perl (0.09-1) unstable; urgency=low
+
+ * New version. Fix tuple iterator.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sat, 26 May 2012 13:46:38 +0400
+
+libdr-tarantool-perl (0.08-2) unstable; urgency=low
+
+ * Add libjson-xs-perl into depends.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Fri, 25 May 2012 11:20:52 +0400
+
+libdr-tarantool-perl (0.08-1) unstable; urgency=low
+
+ * New version. Add 'JSON' to fields type list.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Fri, 25 May 2012 11:13:30 +0400
+
+libdr-tarantool-perl (0.07-1) unstable; urgency=low
+
+ * Iterators can construct objects. New version.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Thu, 24 May 2012 18:06:52 +0400
+
+libdr-tarantool-perl (0.06-1) unstable; urgency=low
+
+ * New version (sync methods return tuple or undef).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Thu, 24 May 2012 13:51:52 +0400
+
+libdr-tarantool-perl (0.05-1) unstable; urgency=low
+
+ * New version (some fixes in call_lua).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Thu, 24 May 2012 11:52:34 +0400
+
+libdr-tarantool-perl (0.04-1) unstable; urgency=low
+
+ * New version (upstream upgrades library).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Tue, 22 May 2012 17:51:25 +0400
+
+libdr-tarantool-perl (0.03-1) unstable; urgency=low
+
+ * New version.
+ * Add depends on Devel::GlobalDestruction.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sun, 20 May 2012 17:16:21 +0400
+
+libdr-tarantool-perl (0.02-1) unstable; urgency=low
+
+ * New version.
+ * Fixed documentation.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sun, 20 May 2012 09:36:46 +0400
+
+libdr-tarantool-perl (0.01-1) unstable; urgency=low
+
+ * Initial release. (Closes: #673646)
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Sun, 20 May 2012 01:53:06 +0400
@@ -11,7 +11,9 @@ lib/DR/Tarantool/AsyncClient.pm
lib/DR/Tarantool/CoroClient.pm
lib/DR/Tarantool/Iterator.pm
lib/DR/Tarantool/LLClient.pm
+lib/DR/Tarantool/LLSyncClient.pm
lib/DR/Tarantool.pm
+lib/DR/Tarantool/RealSyncClient.pm
lib/DR/Tarantool/Spaces.pm
lib/DR/Tarantool/StartTest.pm
lib/DR/Tarantool/SyncClient.pm
@@ -19,14 +21,25 @@ lib/DR/Tarantool/Tuple.pm
Makefile.PL
MANIFEST
README.pod
+st/Check/OneHash.pm
+st/Check/OneTree.pm
+st/Check/Order.pm
+st/Check/Ping.pm
+st/Check/XlogCleanup.pm
+st/init.lua
+st/stress.cfg
+st/stress.pl
+st/stress.tarantool.cfg
t/000-use.t
t/010-xs.t
t/020-low_level_client.t
+t/025-ll_synclient.t
t/030-spaces.t
t/033-iterator.t
t/040-tuple.t
t/050-async-client.t
t/060-sync-client.t
+t/065-realsync-client.t
t/070-coro-client.t
t/080-tarantool.t
t/090-parallel-requests.t
@@ -49,13 +62,5 @@ t/test-data/empty_tuple.00022-000-ok.bin
t/test-data/init.lua
t/test-data/llc-easy2.cfg
t/test-data/llc-easy.cfg
-st/init.lua
-st/stress.pl
-st/stress.tarantool.cfg
-st/Check/Order.pm
-st/Check/XlogCleanup.pm
-st/Check/Ping.pm
-st/Check/OneTree.pm
-st/Check/OneHash.pm
-st/stress.cfg
-META.yml Module meta-data (added by MakeMaker)
+META.yml Module YAML meta-data (added by MakeMaker)
+META.json Module JSON meta-data (added by MakeMaker)
@@ -0,0 +1,58 @@
+{
+ "abstract" : "a Perl driver for L<Tarantool|http://tarantool.org>",
+ "author" : [
+ "Dmitry E. Oboukhov <unera@debian.org>"
+ ],
+ "dynamic_config" : 1,
+ "generated_by" : "ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.120921",
+ "license" : [
+ "artistic_1"
+ ],
+ "meta-spec" : {
+ "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
+ "version" : "2"
+ },
+ "name" : "DR-Tarantool",
+ "no_index" : {
+ "directory" : [
+ "t",
+ "inc"
+ ]
+ },
+ "prereqs" : {
+ "build" : {
+ "requires" : {
+ "ExtUtils::MakeMaker" : "0"
+ }
+ },
+ "configure" : {
+ "requires" : {
+ "ExtUtils::MakeMaker" : "0"
+ }
+ },
+ "runtime" : {
+ "requires" : {
+ "AnyEvent" : "0",
+ "Carp" : "0",
+ "Cwd" : "0",
+ "Devel::GlobalDestruction" : "0",
+ "File::Path" : "0",
+ "File::Spec::Functions" : "0",
+ "File::Temp" : "0",
+ "IO::Socket::INET" : "0",
+ "JSON::XS" : "0"
+ }
+ }
+ },
+ "release_status" : "stable",
+ "resources" : {
+ "bugtracker" : {
+ "web" : "https://github.com/dr-co/dr-tarantool/issues"
+ },
+ "homepage" : "https://github.com/dr-co/dr-tarantool",
+ "repository" : {
+ "url" : "https://github.com/dr-co/dr-tarantool"
+ }
+ },
+ "version" : "0.42"
+}
@@ -1,33 +1,34 @@
---- #YAML:1.0
-name: DR-Tarantool
-version: 0.38
-abstract: a Perl driver for L<Tarantool|http://tarantool.org>
+---
+abstract: 'a Perl driver for L<Tarantool|http://tarantool.org>'
author:
- - Dmitry E. Oboukhov <unera@debian.org>
-license: artistic
-distribution_type: module
-configure_requires:
- ExtUtils::MakeMaker: 0
+ - 'Dmitry E. Oboukhov <unera@debian.org>'
build_requires:
- ExtUtils::MakeMaker: 0
+ ExtUtils::MakeMaker: 0
+configure_requires:
+ ExtUtils::MakeMaker: 0
+dynamic_config: 1
+generated_by: 'ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.120921'
+license: artistic
+meta-spec:
+ url: http://module-build.sourceforge.net/META-spec-v1.4.html
+ version: 1.4
+name: DR-Tarantool
+no_index:
+ directory:
+ - t
+ - inc
requires:
- AnyEvent: 0
- Carp: 0
- Cwd: 0
- Devel::GlobalDestruction: 0
- File::Path: 0
- File::Spec::Functions: 0
- File::Temp: 0
- IO::Socket::INET: 0
- JSON::XS: 0
+ AnyEvent: 0
+ Carp: 0
+ Cwd: 0
+ Devel::GlobalDestruction: 0
+ File::Path: 0
+ File::Spec::Functions: 0
+ File::Temp: 0
+ IO::Socket::INET: 0
+ JSON::XS: 0
resources:
- bugtracker: https://github.com/dr-co/dr-tarantool/issues
- homepage: https://github.com/dr-co/dr-tarantool
-no_index:
- directory:
- - t
- - inc
-generated_by: ExtUtils::MakeMaker version 6.57_05
-meta-spec:
- url: http://module-build.sourceforge.net/META-spec-v1.4.html
- version: 1.4
+ bugtracker: https://github.com/dr-co/dr-tarantool/issues
+ homepage: https://github.com/dr-co/dr-tarantool
+ repository: https://github.com/dr-co/dr-tarantool
+version: 0.42
@@ -19,6 +19,7 @@ WriteMakefile(
META_MERGE => {
resources => {
homepage => 'https://github.com/dr-co/dr-tarantool',
+ repository => 'https://github.com/dr-co/dr-tarantool',
bugtracker => 'https://github.com/dr-co/dr-tarantool/issues',
}
},
@@ -32,6 +33,6 @@ WriteMakefile(
LICENSE => 'artistic',
);
-if (open my $fh, '>>', 'Makefile') {
- print $fh "\n\nTEST_VERBOSE=1\n";
-}
+# if (open my $fh, '>>', 'Makefile') {
+# print $fh "\n\nTEST_VERBOSE=1\n";
+# }
@@ -301,7 +301,7 @@ our %EXPORT_TAGS = (
constant => [
qw(
TNT_INSERT TNT_SELECT TNT_UPDATE TNT_DELETE TNT_CALL TNT_PING
- TNT_FLAG_RETURN TNT_FLAG_ADD TNT_FLAG_REPLACE TNT_FLAG_BOX_QUIET
+ TNT_FLAG_RETURN TNT_FLAG_ADD TNT_FLAG_REPLACE
)
],
);
@@ -309,7 +309,7 @@ our %EXPORT_TAGS = (
our @EXPORT_OK = ( map { @$_ } values %EXPORT_TAGS );
$EXPORT_TAGS{all} = \@EXPORT_OK;
our @EXPORT = @{ $EXPORT_TAGS{client} };
-our $VERSION = '0.38';
+our $VERSION = '0.42';
=head1 EXPORT
@@ -330,6 +330,23 @@ sub tarantool {
}
+=head2 rsync_tarantool
+
+connects to L<Tarantool|http://tarantool.org> in synchronous mode
+using L<DR::Tarantool::RealSyncClient>.
+
+=cut
+
+sub rsync_tarantool {
+ require DR::Tarantool::RealSyncClient;
+ no warnings 'redefine';
+ *rsync_tarantool = sub {
+ DR::Tarantool::RealSyncClient->connect(@_);
+ };
+ goto \&rsync_tarantool;
+}
+
+
=head2 async_tarantool
connects to L<tarantool|http://tarantool.org> in async mode using
@@ -429,25 +429,19 @@ unsigned TNT_SELECT()
unsigned TNT_FLAG_RETURN()
CODE:
- RETVAL = TP_FRET;
+ RETVAL = TP_BOX_RETURN_TUPLE;
OUTPUT:
RETVAL
unsigned TNT_FLAG_ADD()
CODE:
- RETVAL = TP_FADD;
+ RETVAL = TP_BOX_ADD;
OUTPUT:
RETVAL
unsigned TNT_FLAG_REPLACE()
CODE:
- RETVAL = TP_FREP;
- OUTPUT:
- RETVAL
-
-unsigned TNT_FLAG_BOX_QUIET()
- CODE:
- RETVAL = TP_FQUIET;
+ RETVAL = TP_BOX_REPLACE;
OUTPUT:
RETVAL
@@ -1,3 +1,28 @@
+libdr-tarantool-perl (0.42-1) unstable; urgency=low
+
+ * Rebuild for Debian perl 5.18.
+ * Fix some cpan tests (that are run without tarantool).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 18 Sep 2013 01:37:52 +0400
+
+libdr-tarantool-perl (0.41-1) unstable; urgency=low
+
+ * Fix broken xs-test (by cpan testers reports).
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Mon, 02 Sep 2013 15:37:11 +0400
+
+libdr-tarantool-perl (0.40-1) unstable; urgency=low
+
+ * Add RealSyncClient module.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 28 Aug 2013 17:00:13 +0400
+
+libdr-tarantool-perl (0.39-1) unstable; urgency=low
+
+ * Ping method works even connection isn't established.
+
+ -- Dmitry E. Oboukhov <unera@debian.org> Wed, 21 Aug 2013 17:33:08 +0400
+
libdr-tarantool-perl (0.38-1) unstable; urgency=low
* Parser doesn't segfault if tarantool replies by broken package.
@@ -27,3 +27,6 @@ tarball:
-czf libdr-tarantool-perl_$(DEBVERSION).orig.tar.gz \
libdr-tarantool-perl-$(DEBVERSION)
+
+DEB_INSTALL_CHANGELOGS_ALL :=
+DEB_DH_INSTALLCHANGELOGS_ARGS := -XChanges
@@ -654,7 +654,7 @@ sub last_error_string { $_[0]->_llc->last_error_string }
=head1 VCS
The project is placed git repo on github:
-L<https://github.com/unera/dr-tarantool/>.
+L<https://github.com/dr-co/dr-tarantool/>.
=cut
@@ -284,11 +284,42 @@ Ping the server.
sub ping :method {
my ($self, $cb) = @_;
- $self->_check_cb( $cb );
my $id = $self->_req_id;
+ $self->_check_cb( $cb );
my $pkt = DR::Tarantool::_pkt_ping( $id );
- $self->_request( $id, $pkt, $cb );
- return;
+
+ if ($self->is_connected) {
+ $self->_request( $id, $pkt, $cb );
+ return;
+ }
+
+ unless($self->{reconnect_period}) {
+ $cb->({
+ status => 'fatal',
+ req_id => $id,
+ errstr => "Connection isn't established (yet)"
+ }
+ );
+ return;
+ }
+
+ my $this = $self;
+ weaken $this;
+
+ my $tmr;
+ $tmr = AE::timer $self->{reconnect_period}, 0, sub {
+ undef $tmr;
+ if ($this and $this->is_connected) {
+ $this->_request( $id, $pkt, $cb );
+ return;
+ }
+ $cb->({
+ status => 'fatal',
+ req_id => $id,
+ errstr => "Connection isn't established (yet)"
+ }
+ );
+ };
}
@@ -860,7 +891,7 @@ sub _check_operations {
=head1 VCS
The project is placed git repo on github:
-L<https://github.com/unera/dr-tarantool/>.
+L<https://github.com/dr-co/dr-tarantool/>.
=cut
@@ -0,0 +1,268 @@
+use utf8;
+use strict;
+use warnings;
+
+package DR::Tarantool::LLSyncClient;
+use Carp;
+use IO::Socket::UNIX;
+use IO::Socket::INET;
+require DR::Tarantool;
+
+my $LE = $] > 5.01 ? '<' : '';
+
+$Carp::Internal{ (__PACKAGE__) }++;
+
+sub connect {
+ my ($class, %opts) = @_;
+
+ my $host = $opts{host} || 'localhost';
+ my $port = $opts{port} or croak 'port is undefined';
+
+ my $reconnect_period = $opts{reconnect_period} || 0;
+ my $reconnect_always = $opts{reconnect_always} || 0;
+
+
+ my $raise_error = 1;
+ if (exists $opts{raise_error}) {
+ $raise_error = $opts{raise_error} ? 1 : 0;
+ }
+
+ my $self = bless {
+ host => $host,
+ port => $port,
+ raise_error => $raise_error,
+ reconnect_period => $reconnect_period,
+ id => 0,
+ } => ref ($class) || $class;
+
+ unless ($self->_connect()) {
+ unless ($reconnect_always) {
+ return undef unless $self->{raise_error};
+ croak "Can't connect to $self->{host}:$self->{port}: $@";
+ }
+ unless ($reconnect_period) {
+ return undef unless $self->{raise_error};
+ croak "Can't connect to $self->{host}:$self->{port}: $@";
+ }
+ }
+ return $self;
+}
+
+
+sub _connect {
+ my ($self) = @_;
+
+ if ($self->{host} eq 'unix/' or $self->{port} =~ /\D/) {
+ return $self->{fh} = IO::Socket::UNIX->new(Peer => $self->{port});
+ } else {
+ return $self->{fh} = IO::Socket::INET->new(
+ PeerHost => $self->{host},
+ PeerPort => $self->{port},
+ Proto => 'tcp',
+ );
+ }
+}
+
+sub _req_id {
+ my ($self) = @_;
+ return $self->{id}++ if $self->{id} < 0x7FFF_FFFE;
+ return $self->{id} = 0;
+}
+
+sub _request {
+ my ($self, $id, $pkt ) = @_;
+ until($self->{fh}) {
+ unless ($self->{reconnect_period}) {
+ $self->{last_error_string} = "Connection isn't established";
+ croak $self->{last_error_string} if $self->{raise_error};
+ return undef;
+ }
+ next if $self->_connect;
+ sleep $self->{reconnect_period};
+ }
+
+ my $len = length $pkt;
+
+ # send request
+ while($len > 0) {
+ no warnings; # closed socket
+ my $slen = syswrite $self->{fh}, $pkt;
+ goto SOCKET_ERROR unless defined $slen;
+ $len -= $slen;
+ substr $pkt, 0, $slen, '';
+ }
+
+ $pkt = '';
+ while(12 > length $pkt) {
+ no warnings; # closed socket
+ my $rl = sysread $self->{fh}, $pkt, 12 - length $pkt, length $pkt;
+ goto SOCKET_ERROR unless defined $rl;
+ }
+
+ my (undef, $blen) = unpack "L$LE L$LE", $pkt;
+
+ while(12 + $blen > length $pkt) {
+ no warnings; # closed socket
+ my $rl = sysread $self->{fh},
+ $pkt, 12 + $blen - length $pkt, length $pkt;
+ goto SOCKET_ERROR unless defined $rl;
+ }
+
+ my $res = DR::Tarantool::_pkt_parse_response( $pkt );
+ if ($res->{status} ne 'ok') {
+ $self->{last_error_string} = $res->{errstr};
+ $self->{last_code} = $res->{code};
+ # disconnect
+ delete $self->{fh} if $res->{status} =~ /^(fatal|buffer)$/;
+ croak $self->{last_error_string} if $self->{raise_error};
+ return undef;
+ }
+
+ $self->{last_error_string} = $res->{errstr} || '';
+ $self->{last_code} = $res->{code};
+ return $res;
+
+
+ SOCKET_ERROR:
+ delete $self->{fh};
+ $self->{last_error_string} = $!;
+ $self->{last_code} = undef;
+ croak $self->{last_error_string} if $self->{raise_error};
+ return undef;
+}
+
+sub ping :method {
+ my ($self) = @_;
+ unless ($self->{fh}) {
+ $self->_connect;
+ $self->{last_code} = -1;
+ $self->{last_error_string} = "Connection isn't established";
+ return 0 unless $self->{fh};
+ }
+ my $id = $self->_req_id;
+ my $pkt = DR::Tarantool::_pkt_ping( $id );
+ my $res = eval { $self->_request( $id, $pkt ); };
+ return 0 unless $res and $res->{status} eq 'ok';
+ return 1;
+}
+
+
+sub call_lua :method {
+
+ my $self = shift;
+ my $proc = shift;
+ my $tuple = shift;
+ $self->_check_tuple( $tuple );
+ my $flags = pop || 0;
+
+ my $id = $self->_req_id;
+ my $pkt = DR::Tarantool::_pkt_call_lua($id, $flags, $proc, $tuple);
+ return $self->_request( $id, $pkt );
+}
+
+sub select :method {
+ my $self = shift;
+ $self->_check_number( my $ns = shift );
+ $self->_check_number( my $idx = shift );
+ $self->_check_tuple_list( my $keys = shift );
+ $self->_check_number( my $limit = shift || 0x7FFFFFFF );
+ $self->_check_number( my $offset = shift || 0 );
+
+ my $id = $self->_req_id;
+ my $pkt =
+ DR::Tarantool::_pkt_select($id, $ns, $idx, $offset, $limit, $keys);
+ return $self->_request( $id, $pkt );
+}
+
+sub insert :method {
+
+ my $self = shift;
+ $self->_check_number( my $space = shift );
+ $self->_check_tuple( my $tuple = shift );
+ $self->_check_number( my $flags = pop || 0 );
+ croak "insert: tuple must be ARRAYREF" unless ref $tuple eq 'ARRAY';
+ $flags ||= 0;
+
+ my $id = $self->_req_id;
+ my $pkt = DR::Tarantool::_pkt_insert( $id, $space, $flags, $tuple );
+ return $self->_request( $id, $pkt );
+}
+
+sub update :method {
+
+ my $self = shift;
+ $self->_check_number( my $ns = shift );
+ $self->_check_tuple( my $key = shift );
+ $self->_check_operations( my $operations = shift );
+ $self->_check_number( my $flags = pop || 0 );
+
+ my $id = $self->_req_id;
+ my $pkt = DR::Tarantool::_pkt_update($id, $ns, $flags, $key, $operations);
+ return $self->_request( $id, $pkt );
+}
+
+
+sub delete :method {
+ my $self = shift;
+ my $ns = shift;
+ my $key = shift;
+ $self->_check_tuple( $key );
+ my $flags = pop || 0;
+
+ my $id = $self->_req_id;
+ my $pkt = DR::Tarantool::_pkt_delete($id, $ns, $flags, $key);
+ return $self->_request( $id, $pkt );
+}
+
+
+
+sub _check_tuple {
+ my ($self, $tuple) = @_;
+ croak 'Tuple must be ARRAYREF' unless 'ARRAY' eq ref $tuple;
+}
+
+sub _check_tuple_list {
+ my ($self, $list) = @_;
+ croak 'Tuplelist must be ARRAYREF of ARRAYREF' unless 'ARRAY' eq ref $list;
+ croak 'Tuplelist is empty' unless @$list;
+ $self->_check_tuple($_) for @$list;
+}
+
+sub _check_number {
+ my ($self, $number) = @_;
+ croak "argument must be number"
+ unless defined $number and $number =~ /^\d+$/;
+}
+
+sub _check_operation {
+ my ($self, $op) = @_;
+ croak 'Operation must be ARRAYREF' unless 'ARRAY' eq ref $op;
+ croak 'Wrong update operation: too short arglist' unless @$op >= 2;
+ croak "Wrong operation: $op->[1]"
+ unless $op->[1] and
+ $op->[1] =~ /^(delete|set|insert|add|and|or|xor|substr)$/;
+ $self->_check_number($op->[0]);
+}
+
+sub _check_operations {
+ my ($self, $list) = @_;
+ croak 'Operations list must be ARRAYREF of ARRAYREF'
+ unless 'ARRAY' eq ref $list;
+ croak 'Operations list is empty' unless @$list;
+ $self->_check_operation( $_ ) for @$list;
+}
+
+
+sub last_error_string {
+ return $_[0]->{last_error_string};
+}
+
+sub last_code {
+ return $_[0]->{last_code};
+}
+
+sub raise_error {
+ return $_[0]->{raise_error};
+}
+
+1;
@@ -0,0 +1,249 @@
+use utf8;
+use strict;
+use warnings;
+
+package DR::Tarantool::RealSyncClient;
+
+
+=head1 NAME
+
+DR::Tarantool::RealSyncClient - a synchronous driver for L<Tarantool/Box|http://tarantool.org>
+
+=head1 SYNOPSIS
+
+ my $client = DR::Tarantool::RealSyncClient->connect(
+ port => $tnt->primary_port,
+ spaces => $spaces
+ );
+
+ if ($client->ping) { .. };
+
+ my $t = $client->insert(
+ first_space => [ 1, 'val', 2, 'test' ], TNT_FLAG_RETURN
+ );
+
+ $t = $client->call_lua('luafunc' => [ 0, 0, 1 ], 'space_name');
+
+ $t = $client->select(space_name => $key);
+
+ $t = $client->update(space_name => 2 => [ name => set => 'new' ]);
+
+ $client->delete(space_name => $key);
+
+
+=head1 DESCRIPTION
+
+The module is a clone of L<DR::Tarantool::SyncClient> but it doesn't
+use L<AnyEvent> or L<Coro>.
+
+The module uses L<IO::Socket> sockets.
+
+=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/dr-co/dr-tarantool/>.
+
+=cut
+
+use DR::Tarantool::LLSyncClient;
+use DR::Tarantool::Spaces;
+use DR::Tarantool::Tuple;
+use Carp;
+$Carp::Internal{ (__PACKAGE__) }++;
+use Data::Dumper;
+use Scalar::Util 'blessed';
+
+my $unpack = sub {
+ my ($self, $res, $s) = @_;
+ return undef unless $res and $res->{status} eq 'ok';
+ return $s->tuple_class->unpack( $res->{tuples}, $s ) if $s;
+ return $res->{tuples};
+};
+
+sub connect {
+ my ($class, %opts) = @_;
+
+ my $host = $opts{host} || 'localhost';
+ my $port = $opts{port} or croak "port isn't defined";
+
+ my $spaces = blessed($opts{spaces}) ?
+ $opts{spaces} : DR::Tarantool::Spaces->new($opts{spaces});
+ my $reconnect_period = $opts{reconnect_period} || 0;
+ my $reconnect_always = $opts{reconnect_always} || 0;
+
+ my $client = DR::Tarantool::LLSyncClient->connect(
+ host => $host,
+ port => $port,
+ reconnect_period => $reconnect_period,
+ reconnect_always => $reconnect_always,
+ exists($opts{raise_error}) ?
+ ( raise_error => $opts{raise_error} ? 1: 0 )
+ : (),
+ );
+
+
+ return undef unless $client;
+ return bless { llc => $client, spaces => $spaces } => ref($class) || $class;
+}
+
+sub space {
+ my ($self, $name) = @_;
+ return $self->{spaces}->space($name);
+}
+
+
+sub ping {
+ my ($self) = @_;
+ $self->{llc}->ping;
+}
+
+sub insert {
+ my $self = shift;
+ my $space = shift;
+ $self->_llc->_check_tuple( my $tuple = shift );
+ my $flags = pop || 0;
+
+ my $s = $self->{spaces}->space($space);
+
+ my $res =
+ $self->_llc->insert( $s->number, $s->pack_tuple( $tuple ), $flags );
+ return $unpack->($self, $res, $s);
+}
+
+sub call_lua {
+ my $self = shift;
+ my $lua_name = shift;
+ my $args = shift;
+
+ unshift @_ => 'space' if @_ == 1;
+ my %opts = @_;
+
+ my $flags = $opts{flags} || 0;
+ my $space_name = $opts{space};
+ my $fields = $opts{fields};
+
+ my $s;
+ croak "You can't use 'fields' and 'space' at the same time"
+ if $fields and $space_name;
+
+ if ($space_name) {
+ $s = $self->space( $space_name );
+ } elsif ( $fields ) {
+ $s = DR::Tarantool::Space->new(
+ 0 =>
+ {
+ name => 'temp_space',
+ fields => $fields,
+ indexes => {}
+ },
+ );
+ } else {
+ $s = DR::Tarantool::Space->new(
+ 0 =>
+ {
+ name => 'temp_space',
+ fields => [],
+ indexes => {}
+ },
+ );
+ }
+
+ if ($opts{args}) {
+ my $sa = DR::Tarantool::Space->new(
+ 0 =>
+ {
+ name => 'temp_space_args',
+ fields => $opts{args},
+ indexes => {}
+ },
+ );
+ $args = $sa->pack_tuple( $args );
+ }
+
+ my $res = $self->_llc->call_lua( $lua_name, $args, $flags );
+
+ return $unpack->($self, $res, $s);
+}
+
+
+sub select {
+ my $self = shift;
+ my $space = shift;
+ my $keys = shift;
+
+ my ($index, $limit, $offset);
+
+ if (@_ == 1) {
+ $index = shift;
+ } elsif (@_ == 3) {
+ ($index, $limit, $offset) = @_;
+ } elsif (@_) {
+ my %opts = @_;
+ $index = $opts{index};
+ $limit = $opts{limit};
+ $offset = $opts{offset};
+ }
+
+ $index ||= 0;
+
+ my $s = $self->space($space);
+
+ my $res = $self->_llc->select(
+ $s->number,
+ $s->_index( $index )->{no},
+ $s->pack_keys( $keys, $index ),
+ $limit,
+ $offset
+ );
+
+ return $unpack->($self, $res, $s);
+}
+
+sub update {
+ my $self = shift;
+ my $space = shift;
+ my $key = shift;
+ my $op = shift;
+ my $flags = shift || 0;
+
+ my $s = $self->space($space);
+
+ my $res = $self->_llc->update(
+ $s->number,
+ $s->pack_primary_key( $key ),
+ $s->pack_operations( $op ),
+ $flags,
+ );
+ return $unpack->($self, $res, $s);
+}
+
+sub delete :method {
+ my $self = shift;
+ my $space = shift;
+ my $key = shift;
+ my $flags = shift || 0;
+
+ my $s = $self->space($space);
+
+ my $res = $self->_llc->delete(
+ $s->number,
+ $s->pack_primary_key( $key ),
+ $flags,
+ );
+ return $unpack->($self, $res, $s);
+}
+
+sub last_code { $_[0]->{llc}->last_code }
+sub last_error_string { $_[0]->{llc}->last_error_string }
+sub raise_error { $_[0]->raise_error };
+sub _llc { $_[0]{llc} }
+
+1;
@@ -687,7 +687,7 @@ sub pack_operations {
=head1 VCS
The project is placed git repo on github:
-L<https://github.com/unera/dr-tarantool/>.
+L<https://github.com/dr-co/dr-tarantool/>.
=cut
@@ -176,6 +176,13 @@ sub _restart {
sleep 0.01;
}
+
+ for (my $i = 0; $i < 100; $i++) {
+ last if $self->log =~ /entering event loop/;
+ sleep 0.01;
+ }
+
+ sleep 1 unless $self->log =~ /entering event loop/;
}
sub restart {
@@ -309,7 +316,7 @@ sub clean_xlogs {
=head1 VCS
The project is placed git repo on github:
-L<https://github.com/unera/dr-tarantool/>.
+L<https://github.com/dr-co/dr-tarantool/>.
=cut
@@ -175,7 +175,7 @@ sub DESTROY {
=head1 VCS
The project is placed git repo on github:
-L<https://github.com/unera/dr-tarantool/>.
+L<https://github.com/dr-co/dr-tarantool/>.
=cut
@@ -265,7 +265,7 @@ sub DESTROY { }
=head1 VCS
The project is placed git repo on github:
-L<https://github.com/unera/dr-tarantool/>.
+L<https://github.com/dr-co/dr-tarantool/>.
=cut
@@ -301,7 +301,7 @@ our %EXPORT_TAGS = (
constant => [
qw(
TNT_INSERT TNT_SELECT TNT_UPDATE TNT_DELETE TNT_CALL TNT_PING
- TNT_FLAG_RETURN TNT_FLAG_ADD TNT_FLAG_REPLACE TNT_FLAG_BOX_QUIET
+ TNT_FLAG_RETURN TNT_FLAG_ADD TNT_FLAG_REPLACE
)
],
);
@@ -309,7 +309,7 @@ our %EXPORT_TAGS = (
our @EXPORT_OK = ( map { @$_ } values %EXPORT_TAGS );
$EXPORT_TAGS{all} = \@EXPORT_OK;
our @EXPORT = @{ $EXPORT_TAGS{client} };
-our $VERSION = '0.38';
+our $VERSION = '0.42';
=head1 EXPORT
@@ -330,6 +330,23 @@ sub tarantool {
}
+=head2 rsync_tarantool
+
+connects to L<Tarantool|http://tarantool.org> in synchronous mode
+using L<DR::Tarantool::RealSyncClient>.
+
+=cut
+
+sub rsync_tarantool {
+ require DR::Tarantool::RealSyncClient;
+ no warnings 'redefine';
+ *rsync_tarantool = sub {
+ DR::Tarantool::RealSyncClient->connect(@_);
+ };
+ goto \&rsync_tarantool;
+}
+
+
=head2 async_tarantool
connects to L<tarantool|http://tarantool.org> in async mode using
@@ -7,7 +7,7 @@ use open qw(:std :utf8);
use lib qw(lib ../lib);
use lib qw(blib/lib blib/arch ../blib/lib ../blib/arch);
-use Test::More tests => 336;
+use Test::More tests => 335;
use Encode qw(decode encode);
@@ -32,7 +32,7 @@ like TNT_PING, qr{^\d+$}, 'TNT_PING';
like TNT_FLAG_RETURN, qr{^\d+$}, 'TNT_FLAG_RETURN';
like TNT_FLAG_ADD, qr{^\d+$}, 'TNT_FLAG_ADD';
like TNT_FLAG_REPLACE, qr{^\d+$}, 'TNT_FLAG_REPLACE';
-like TNT_FLAG_BOX_QUIET, qr{^\d+$}, 'TNT_FLAG_BOX_QUIET';
+# like TNT_FLAG_BOX_QUIET, qr{^\d+$}, 'TNT_FLAG_BOX_QUIET';
# like TNT_FLAG_NOT_STORE, qr{^\d+$}, 'TNT_FLAG_NOT_STORE';
my $LE = $] > 5.01 ? '<' : '';
@@ -200,7 +200,8 @@ for my $bin (sort @bins) {
}
}
-
+SKIP: {
+# skip 'Devel tests $ENV{DEVEL_TEST}=0', 120 unless $ENV{DEVEL_TEST};
for (1 .. 30) {
my $body = join '', map { chr int rand 256 } 1 .. (300 + int rand 300);
my $pkt =
@@ -212,6 +213,7 @@ for (1 .. 30) {
$body
;
$res = DR::Tarantool::_pkt_parse_response( $pkt );
+ diag explain $res unless
is $res->{status}, 'buffer', "Broken package $_";
$pkt =
pack 'LLLLa*',
@@ -222,6 +224,7 @@ for (1 .. 30) {
$body
;
$res = DR::Tarantool::_pkt_parse_response( $pkt );
+ diag explain $res unless
is $res->{status}, 'buffer', "Broken package $_, too long body";
$pkt =
@@ -233,16 +236,22 @@ for (1 .. 30) {
$body
;
$res = DR::Tarantool::_pkt_parse_response( $pkt );
+ diag explain $res unless
is $res->{status}, 'buffer', "Broken package $_, too short body";
$pkt =
pack 'LLLLa*',
TNT_SELECT,
- int rand 500,
- int rand 500,
+ 5 + int rand 500,
+ 5 + int rand 500,
0,
''
;
+
+ my $pkth = join '', map { sprintf '.%02x', ord $_ } split //, $pkt;
+
$res = DR::Tarantool::_pkt_parse_response( $pkt );
+ diag explain [ $res, $pkth, TNT_SELECT ] unless
is $res->{status}, 'buffer', "Broken package $_, zero length body";
}
+}
@@ -0,0 +1,189 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use utf8;
+use open qw(:std :utf8);
+use lib qw(lib ../lib);
+use lib qw(blib/lib blib/arch ../blib/lib ../blib/arch);
+
+use constant PLAN => 64;
+use Test::More tests => PLAN;
+use Encode qw(decode encode);
+
+
+BEGIN {
+ # Подготовка объекта тестирования для работы с utf8
+ my $builder = Test::More->builder;
+ binmode $builder->output, ":utf8";
+ binmode $builder->failure_output, ":utf8";
+ binmode $builder->todo_output, ":utf8";
+
+ use_ok 'DR::Tarantool::LLSyncClient';
+ use_ok 'DR::Tarantool::StartTest';
+ use_ok 'File::Spec::Functions', 'catfile';
+ use_ok 'File::Basename', 'dirname';
+ use_ok 'DR::Tarantool', ':constant';
+}
+my $LE = $] > 5.01 ? '<' : '';
+
+
+my $cfg_dir = catfile dirname(__FILE__), 'test-data';
+ok -d $cfg_dir, 'directory with test data';
+my $tcfg = catfile $cfg_dir, 'llc-easy.cfg';
+ok -r $tcfg, $tcfg;
+
+my $tnt = run DR::Tarantool::StartTest( cfg => $tcfg );
+
+SKIP: {
+ unless ($tnt->started and !$ENV{SKIP_TNT}) {
+ diag $tnt->log unless $ENV{SKIP_TNT};
+ skip "tarantool isn't started", PLAN - 7;
+ }
+
+ my $client = DR::Tarantool::LLSyncClient->connect(
+ port => $tnt->primary_port,
+ reconnect_period => 0.1
+ );
+
+
+ note 'ping';
+ ok $client->ping, 'ping';
+ close $client->{fh};
+ ok !$client->ping, 'ping disconnected';
+
+ note 'call_lua';
+ {
+ my $res = $client->call_lua('box.dostring', [ 'return "123", "abc"' ]);
+ isa_ok $res => 'HASH';
+ is $res->{status}, 'ok', 'status';
+ is_deeply $res->{tuples}, [[123],['abc']], 'tuples';
+ is $res->{code}, 0, 'code';
+ is $client->last_code, $res->{code}, 'code';
+ is $client->last_error_string, '', 'error';
+ is $res->{count}, 2, '2 tuples';
+ is $res->{type}, TNT_CALL, 'type';
+ }
+ {
+ my $res = eval {
+ $client->call_lua('box.dostring', [ 'error("abc")' ]); ## LINE1
+ };
+ like $@, qr{Lua error}, 'Error';
+ }
+
+
+ note 'insert';
+ {
+ for (1 .. 2) {
+ my $res = $client->insert(0,
+ [ pack("L$LE", 1), 'abc', pack "L$LE", $_ ],
+ TNT_FLAG_RETURN
+ );
+ isa_ok $res => 'HASH';
+ is $res->{status}, 'ok', 'status';
+ is $res->{type}, TNT_INSERT, 'type';
+ is $res->{count}, 1, 'count';
+ is_deeply $res->{tuples},
+ [[pack("L$LE", 1), 'abc', pack "L$LE", $_]], 'tuples';
+ }
+ my $res = eval {
+ $client->insert(0,
+ [ pack("L$LE", 1), 'abc', pack "L$LE", 1234 ],
+ TNT_FLAG_RETURN | TNT_FLAG_ADD
+ );
+ };
+ is $res, undef, 'no results';
+ like $@, qr{Duplicate key exists}, 'Error message';
+ ok $client->last_code, 'last_code';
+ like $client->last_error_string, qr{Duplicate key exists},
+ 'Error message';
+ }
+
+ note 'select';
+ {
+ my $res = $client->select(0, 0, [[ pack("L$LE", 1) ]], 2, 0);
+ isa_ok $res => 'HASH';
+ is $res->{status}, 'ok', 'status';
+ is $res->{type}, TNT_SELECT, 'type';
+ is $res->{count}, 1, 'count';
+ is_deeply $res->{tuples}, [[pack("L$LE", 1), 'abc', pack "L$LE", 2]],
+ 'tuples';
+ }
+ {
+ my $res = $client->select(0, 0, [[ pack("L$LE", 2) ]], 2, 0);
+ isa_ok $res => 'HASH';
+ is $res->{status}, 'ok', 'status';
+ is $res->{type}, TNT_SELECT, 'type';
+ is $res->{count}, 0, 'count';
+ is_deeply $res->{tuples}, [], 'tuples';
+ }
+
+ note 'update';
+ {
+ my $res = $client->update(
+ 0, # ns
+ [ pack "L$LE", 1 ], # keys
+ [
+ [ 1 => set => 'abcdef' ],
+ [ 1 => substr => 2, 2, ],
+ [ 1 => substr => 100, 1, 'tail' ],
+ [ 2 => 'delete' ],
+ [ 2 => insert => pack "L$LE" => 123 ],
+ [ 3 => insert => 'third' ],
+ [ 4 => insert => 'fourth' ],
+ ],
+ TNT_FLAG_RETURN, # flags
+ );
+
+ is $res->{code}, 0, '* update reply code';
+ is $res->{status}, 'ok', 'status';
+ is $res->{type}, TNT_UPDATE, 'type';
+ is $client->last_code, $res->{code}, 'operation code';
+ is $client->last_error_string, $res->{errstr} // '',
+ 'operation errstr';
+
+ is $res->{tuples}[0][1], 'abeftail',
+ 'updated tuple 1';
+ is $res->{tuples}[0][2], (pack "L$LE", 123),
+ 'updated tuple 2';
+ is $res->{tuples}[0][3], 'third', 'updated tuple 3';
+ is $res->{tuples}[0][4], 'fourth', 'updated tuple 4';
+
+ $res = $client->update(
+ 0,
+ [ pack "L$LE", 1 ],
+ [
+ [ 1 => set => '123' ]
+ ]
+ );
+ is $res->{code}, 0, 'update reply code';
+ is $res->{status}, 'ok', 'status';
+ is $res->{type}, TNT_UPDATE, 'type';
+ is $client->last_code, $res->{code}, 'operation code';
+ is $client->last_error_string, $res->{errstr} // '',
+ 'operation errstr';
+ is $res->{count}, 1, 'count';
+ is_deeply $res->{tuples}, [], 'no tuples';
+ }
+
+ note 'delete';
+
+ {
+ my $res = $client->delete(
+ 0, # ns
+ [ pack "L$LE", 1 ], # keys
+ TNT_FLAG_RETURN, # flags
+ );
+
+ is $res->{code}, 0, '* delete reply code';
+ is $res->{status}, 'ok', 'status';
+ is $res->{type}, TNT_DELETE, 'type';
+ is $client->last_code, $res->{code}, 'operation code';
+ is $client->last_error_string, $res->{errstr} // '',
+ 'operation errstr';
+
+ is $res->{tuples}[0][1], '123',
+ 'deleted tuple[1]';
+ }
+
+}
@@ -0,0 +1,192 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use utf8;
+use open qw(:std :utf8);
+use lib qw(lib ../lib);
+use lib qw(blib/lib blib/arch ../blib/lib ../blib/arch);
+
+use constant PLAN => 59;
+use Test::More tests => PLAN;
+use Encode qw(decode encode);
+
+my $LE = $] > 5.01 ? '<' : '';
+
+BEGIN {
+ # Подготовка объекта тестирования для работы с utf8
+ my $builder = Test::More->builder;
+ binmode $builder->output, ":utf8";
+ binmode $builder->failure_output, ":utf8";
+ binmode $builder->todo_output, ":utf8";
+
+ use_ok 'DR::Tarantool::LLClient', 'tnt_connect';
+ use_ok 'DR::Tarantool::StartTest';
+ use_ok 'DR::Tarantool', ':constant';
+ use_ok 'File::Spec::Functions', 'catfile';
+ use_ok 'File::Basename', 'dirname', 'basename';
+ use_ok 'AnyEvent';
+ use_ok 'DR::Tarantool::RealSyncClient';
+}
+
+my $cfg_dir = catfile dirname(__FILE__), 'test-data';
+ok -d $cfg_dir, 'directory with test data';
+my $tcfg = catfile $cfg_dir, 'llc-easy2.cfg';
+ok -r $tcfg, $tcfg;
+
+my $tnt = run DR::Tarantool::StartTest( cfg => $tcfg );
+
+my $spaces = {
+ 0 => {
+ name => 'first_space',
+ fields => [
+ {
+ name => 'id',
+ type => 'NUM',
+ },
+ {
+ name => 'name',
+ type => 'UTF8STR',
+ },
+ {
+ name => 'key',
+ type => 'NUM',
+ },
+ {
+ name => 'password',
+ type => 'STR',
+ },
+ {
+ name => 'json',
+ type => 'JSON',
+ }
+ ],
+ indexes => {
+ 0 => 'id',
+ 1 => 'name',
+ 2 => [ 'key', 'password' ],
+ },
+ }
+};
+
+SKIP: {
+ unless ($tnt->started and !$ENV{SKIP_TNT}) {
+ diag $tnt->log unless $ENV{SKIP_TNT};
+ skip "tarantool isn't started", PLAN - 9;
+ }
+
+ my $client = DR::Tarantool::RealSyncClient->connect(
+ port => $tnt->primary_port,
+ spaces => $spaces
+ );
+
+ isa_ok $client => 'DR::Tarantool::RealSyncClient';
+ is $client->last_code, undef, 'last_code';
+ is $client->last_error_string, undef, 'last_error_string';
+
+ ok $client->ping, '* ping';
+
+ my $t = $client->insert(
+ first_space => [ 1, 'привет', 2, 'test' ], TNT_FLAG_RETURN
+ );
+
+ isa_ok $t => 'DR::Tarantool::Tuple', '* insert tuple packed';
+ is $t->id, 1, 'id';
+ is $t->name, 'привет', 'name';
+ is $t->key, 2, 'key';
+ is $t->password, 'test', 'password';
+
+ $t = $client->insert(
+ first_space => [ 2, 'медвед', 3, 'test2' ], TNT_FLAG_RETURN
+ );
+
+ isa_ok $t => 'DR::Tarantool::Tuple', 'insert tuple packed';
+ is $t->id, 2, 'id';
+ is $t->name, 'медвед', 'name';
+ is $t->key, 3, 'key';
+ is $t->password, 'test2', 'password';
+
+
+ $t = $client->call_lua('box.select' =>
+ [ 0, 0, pack "L$LE" => 1 ], 'first_space');
+ isa_ok $t => 'DR::Tarantool::Tuple', '* call tuple packed';
+ is $t->id, 1, 'id';
+ is $t->name, 'привет', 'name';
+ is $t->key, 2, 'key';
+ is $t->password, 'test', 'password';
+
+
+ $t = $client->select(first_space => 1);
+ isa_ok $t => 'DR::Tarantool::Tuple', '* select tuple packed';
+ is $t->id, 1, 'id';
+ is $t->name, 'привет', 'name';
+ is $t->key, 2, 'key';
+ is $t->password, 'test', 'password';
+
+ $t = $client->select(first_space => 'привет', 'i1');
+ isa_ok $t => 'DR::Tarantool::Tuple', 'select tuple packed (i1)';
+ is $t->id, 1, 'id';
+ is $t->name, 'привет', 'name';
+ is $t->key, 2, 'key';
+ is $t->password, 'test', 'password';
+
+ $t = $client->select(first_space => [[2, 'test']], 'i2');
+ isa_ok $t => 'DR::Tarantool::Tuple', 'select tuple packed (i2)';
+ is $t->id, 1, 'id';
+ is $t->name, 'привет', 'name';
+ is $t->key, 2, 'key';
+ is $t->password, 'test', 'password';
+
+ $t = $client->update(first_space => 2 => [ name => set => 'привет1' ]);
+ is $t, undef, '* update without flags';
+ $t = $client->update(
+ first_space => 2 => [ name => set => 'привет медвед' ], TNT_FLAG_RETURN
+ );
+ isa_ok $t => 'DR::Tarantool::Tuple', 'update with flags';
+ is $t->name, 'привет медвед', '$t->name';
+
+
+ $t = $client->insert(first_space => [1, 2, 3, 4, undef], TNT_FLAG_RETURN);
+ is $t->json, undef, 'JSON insert: undef';
+
+ $t = $client->insert(first_space => [1, 2, 3, 4, 22], TNT_FLAG_RETURN);
+ is $t->json, 22, 'JSON insert: scalar';
+
+ $t = $client->insert(first_space => [1, 2, 3, 4, 'тест'], TNT_FLAG_RETURN);
+ is $t->json, 'тест', 'JSON insert: utf8 scalar';
+
+ $t = $client->insert(
+ first_space => [ 1, 2, 3, 4, { a => 'b' } ], TNT_FLAG_RETURN
+ );
+ isa_ok $t->json => 'HASH', 'JSON insert: hash';
+ is $t->json->{a}, 'b', 'JSON insert: hash value';
+
+ ok !eval {
+ $client->insert(
+ first_space => [ 1 .. 10 ], TNT_FLAG_RETURN | TNT_FLAG_ADD
+ );
+ 1
+ }, 'raise error';
+ like $@, qr{Duplicate key exists|Tuple already exists}, 'error message';
+
+ {
+ local $client->{llc}{raise_error};
+ ok eval {
+ $client->insert(
+ first_space => [ 1 .. 10 ], TNT_FLAG_RETURN | TNT_FLAG_ADD
+ );
+ 1
+ }, 'no raise error';
+ like $client->last_error_string,
+ qr{Duplicate key exists|Tuple already exists}, 'error message';
+ }
+
+ $t = $client->insert(
+ first_space => [ 1, 2, 3, 4, { привет => 'медвед' } ], TNT_FLAG_RETURN
+ );
+ isa_ok $t->json => 'HASH', 'JSON insert: hash';
+ is $t->json->{привет}, 'медвед', 'JSON insert: hash utf8 value';
+
+ ok $t = $client->delete(first_space => [ 1 ], TNT_FLAG_RETURN), 'delete';
+ is $t->json->{привет}, 'медвед', 'JSON delete: hash utf8 value';
+}
@@ -141,7 +141,7 @@ SKIP: {
cmp_ok $done_time, '>=', $period, 'delay minimum';
cmp_ok $done_time, '<', $period + .2, 'delay maximum';
$max = $done_time if $max < $done_time;
- cmp_ok $tuple->raw(1), '~~', $tlen, 'Length of tuple';
+ is $tuple->raw(1), $tlen, 'Length of tuple';
$cv->end;
});
@@ -2,14 +2,176 @@
#define TP_H_INCLUDED
/*
- * TP - Tarantool Protocol request constructor
+ * TP - Tarantool Protocol library.
* (http://tarantool.org)
*
* protocol description:
- * (https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt)
+ * https://github.com/tarantool/tarantool/blob/master/doc/box-protocol.txt
+ * -------------------
*
+ * TP - a C library designed to create requests and process
+ * replies to or from a Tarantool server.
+ *
+ * The library is designed to be used by a C/C++ application which
+ * requires sophisticated memory control and/or performance.
+ *
+ * The library does not support network operations. All operations
+ * are done in a user supplied buffer and with help of
+ * a user-supplied allocator.
+ *
+ * The primary purpose of the library was to spare implementors
+ * of Tarantool drivers in other languages, such as Perl,
+ * Ruby, Python, etc, from the details of the binary protocol, and
+ * also to make it possible to avoid double-buffering by writing
+ * directly to/from language objects from/to a serialized binary
+ * packet stream. This paradigm makes data transfer from domain
+ * language types (such as strings, scalars, numbers, etc) to
+ * the network format direct, and, therefore, most efficient.
+ *
+ * As a side effect, the library is usable in any kind of
+ * networking environment: synchronous with buffered sockets, or
+ * asynchronous event-based, as well as with cooperative
+ * multitasking.
+ *
+ * Before using the library, please get acquainted with
+ * Tarnatool binary protocol, documented at
+ * https://github.com/tarantool/tarantool/blob/master/doc/box-protocol.txt
+ *
+ * BASIC REQUEST STRUCTURE
+ * -----------------------
+ *
+ * Any request in Tarantool consists of a 12-byte header,
+ * containing request type, id and length, and an optional tuple
+ * or tuples. Similarly, a response carries back the same request
+ * type and id, and then a tuple or tuples.
+ *
+ * Below is a step-by-step tutorial for creating requests
+ * and unpacking responses.
+ *
+ * TO ASSEMBLE A REQUEST
+ * ---------------------
+ *
+ * (1) initialize an instance of struct tp with tp_init().
+ * Provide tp_init() with a buffer and an (optional) allocator
+ * function.
+ *
+ * (2) construct requests by sequentially calling necessary
+ * operations, such as tp_insert(), tp_delete(), tp_update(),
+ * tp_call(). Note: these operations only append to the buffer
+ * a request header, a body of the request, which is usually
+ * a tuple, must be appended to the buffer with a separate call.
+ * Each next call of tp_*() API appends request data to
+ * the tail of the buffer. If the buffer becomes too small to
+ * contain the binary stream, the reallocation function is
+ * invoked to enlarge the buffer.
+ * A buffer can contain multiple requests: Tarantool
+ * handles them all asynchronously, sending responses
+ * back as soon as they are ready. The request id can be then
+ * used to associate responses with requests.
+ *
+ * For example:
+ *
+ * char buf[256];
+ * struct tp req;
+ * // initialize request buffer
+ * tp_init(&req, buf, sizeof(buf), NULL, NULL);
+ * // append INSERT packet header to the buffer
+ * // request flags are empty, request id is 0
+ * tp_insert(&req, 0, 0);
+ * // begin appending a tuple to the request
+ * tp_tuple(&req);
+ * // append one tuple field
+ * tp_sz(&req, "key");
+ * // one more tuple field
+ * tp_sz(&req, "value");
+ *
+ * (3) the buffer can be used right after all requests are
+ * appended to it. tp_used() can be used to get the current
+ * buffer size:
+ *
+ * write(1, buf, tp_used(&req)); // write the buffer to stdout
+ *
+ * (4) When no longer needed, the buffer must be freed manually.
+ *
+ * For additional examples, please read the documentation for
+ * buffer operations.
+ *
+ * PROCESSING A REPLY
+ * ------------------
+ *
+ * (1) tp_init() must be called with a pointer to a buffer which
+ * already stores or will eventually receive the server response.
+ * Functions tp_reqbuf() and tp_req() can be then used to examine
+ * if a network buffer contains a full reply or not.
+ *
+ * Following is an example of tp_req() usage (reading from stdin
+ * and parsing it until a response is completely read):
+ *
+ * struct tp rep;
+ * tp_init(&rep, NULL, 0, tp_realloc, NULL);
+ *
+ * while (1) {
+ * ssize_t to_read = tp_req(&rep);
+ * printf("to_read: %zu\n", to_read);
+ * if (to_read <= 0)
+ * break;
+ * ssize_t new_size = tp_ensure(&rep, to_read);
+ * printf("new_size: %zu\n", new_size);
+ * if (new_size == -1)
+ * return -1;
+ * int rc = fread(rep.p, to_read, 1, stdin);
+ * if (rc != 1)
+ * return 1;
+ * // discard processed data and make space available
+ * // for new input:
+ * tp_use(&rep, to_read);
+ * }
+ *
+ * (2) tp_reply() function can be used to find out if the request
+ * is executed successfully or not:
+ * server_code = tp_reply(&reply);
+ *
+ * if (server_code != 0) {
+ * printf("error: %-.*s\n", tp_replyerrorlen(&rep),
+ * tp_replyerror(&rep));
+ * }
+ *
+ * Note: the library itself doesn't contain #defines for server
+ * error codes. They are defined in
+ * https://github.com/tarantool/tarantool/blob/master/include/errcode.h
+ *
+ * A server failure can be either transient or persistent. For
+ * example, a failure to allocate memory is transient: as soon as
+ * some data is deleted, the request can be executed again, this
+ * time successfully. A constraint violation is a non-transient
+ * error: it persists regardless of how many times a request
+ * is re-executed. Server error codes can be analyzed to better
+ * handle an error.
+ *
+ * (3) The server usually responds to any kind of request with a
+ * tuple. Tuple data can be accessed via tp_next(), tp_nextfield(),
+ * tp_gettuple(), tp_getfield().
+ *
+ * See the docs for tp_reply() and tp_next()/tp_nextfield() for an
+ * example.
+ *
+ * API RETURN VALUE CONVENTION
+ * ---------------------------
+ *
+ * API functions return 0 on success, -1 on error.
+ * If a function appends data to struct tp, it returns the
+ * size appended on success, or -1 on error.
+ *
+ * SEE ALSO
+ * --------
+ *
+ * TP is used by Tarantool Perl driver:
+ * https://github.com/dr-co/dr-tarantool/blob/master/Tarantool.xs
+*/
+
+/*
* Copyright (c) 2012-2013 Tarantool AUTHORS
- * (https://github.com/mailru/tarantool/blob/master/AUTHORS)
+ * (https://github.com/tarantool/tarantool/blob/master/AUTHORS)
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
@@ -37,7 +199,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
- */
+*/
#ifdef __cplusplus
extern "C" {
#endif
@@ -51,15 +213,24 @@ extern "C" {
#define tp_packed __attribute__((packed))
#define tp_inline __attribute__((forceinline))
#define tp_noinline __attribute__((noinline))
+#if defined(__GNUC__)
+#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
#define tp_hot __attribute__((hot))
+#endif
+#endif
+#if !defined(tp_hot)
+#define tp_hot
+#endif
#define tp_likely(expr) __builtin_expect(!! (expr), 1)
#define tp_unlikely(expr) __builtin_expect(!! (expr), 0)
struct tp;
-typedef char *(*tp_resizer)(struct tp *p, size_t req, size_t *size);
+/* Reallocation function, can be customized for own use */
+typedef char *(*tp_reserve)(struct tp *p, size_t req, size_t *size);
+/* request types. */
#define TP_PING 65280
#define TP_INSERT 13
#define TP_SELECT 17
@@ -67,11 +238,12 @@ typedef char *(*tp_resizer)(struct tp *p, size_t req, size_t *size);
#define TP_DELETE 21
#define TP_CALL 22
-#define TP_FRET 1
-#define TP_FADD 2
-#define TP_FREP 4
-#define TP_FQUIET 8
+/* requests flags */
+#define TP_BOX_RETURN_TUPLE 1
+#define TP_BOX_ADD 2
+#define TP_BOX_REPLACE 4
+/* update operation codes */
#define TP_OPSET 0
#define TP_OPADD 1
#define TP_OPAND 2
@@ -81,20 +253,21 @@ typedef char *(*tp_resizer)(struct tp *p, size_t req, size_t *size);
#define TP_OPDELETE 6
#define TP_OPINSERT 7
+/* internal protocol headers */
struct tp_h {
uint32_t type, len, reqid;
} tp_packed;
struct tp_hinsert {
- uint32_t s, flags;
+ uint32_t space, flags;
} tp_packed;
struct tp_hdelete {
- uint32_t s, flags;
+ uint32_t space, flags;
} tp_packed;
struct tp_hupdate {
- uint32_t s, flags;
+ uint32_t space, flags;
} tp_packed;
struct tp_hcall {
@@ -102,55 +275,103 @@ struct tp_hcall {
} tp_packed;
struct tp_hselect {
- uint32_t s, i, o, l;
+ uint32_t space, index;
+ uint32_t offset;
+ uint32_t limit;
uint32_t keyc;
} tp_packed;
+/*
+ * Main tp object - points either to a request buffer, or to
+ * a response.
+ *
+ * All fields except tp->p should not be accessed directly.
+ * Appropriate accessors should be used instead.
+*/
struct tp {
- struct tp_h *h;
- char *s, *p, *e;
- char *t, *f, *u;
- char *c;
- uint32_t tsz, fsz, tc;
- uint32_t code;
- uint32_t cnt;
- tp_resizer resizer;
- void *obj;
+ struct tp_h *h; /* current request header */
+ char *s, *p, *e; /* start, pos, end */
+ char *t, *f, *u; /* tuple, tuple field, update operation */
+ char *c; /* reply parsing position */
+ uint32_t tsz, fsz, tc; /* tuple size, field size, tuple count */
+ uint32_t code; /* reply server code */
+ uint32_t cnt; /* reply tuple count */
+ tp_reserve reserve; /* realloc function pointer */
+ void *obj; /* reallocation context pointer */
};
+/* Get the size of the allocated buffer */
static inline size_t
tp_size(struct tp *p) {
return p->e - p->s;
}
+/* Get the size of data in the buffer */
static inline size_t
tp_used(struct tp *p) {
return p->p - p->s;
}
+/* Get the size available for write */
static inline size_t
tp_unused(struct tp *p) {
return p->e - p->p;
}
+/* A common reallocation function, can be used
+ * for 'reserve' param in tp_init().
+ * Resizes the buffer twice the previous size using realloc().
+ *
+ * struct tp req;
+ * tp_init(&req, NULL, tp_realloc, NULL);
+ * tp_ping(&req); // will call the reallocator
+ *
+ * data must be manually freed when the buffer is no longer
+ * needed.
+ * (eg. free(p->s));
+ * if realloc will return NULL, then you must destroy previous memory.
+ * (eg.
+ * if (tp_realloc(p, ..) == NULL) {
+ * free(p->s)
+ * return NULL;
+ * }
+*/
tp_function_unused static char*
-tp_reallocator(struct tp *p, size_t req, size_t *size) {
+tp_realloc(struct tp *p, size_t required, size_t *size) {
size_t toalloc = tp_size(p) * 2;
- if (tp_unlikely(toalloc < req))
- toalloc = req;
+ if (tp_unlikely(toalloc < required))
+ toalloc = tp_size(p) + required;
*size = toalloc;
return realloc(p->s, toalloc);
}
-tp_function_unused static char*
-tp_reallocator_noloss(struct tp *p, size_t req, size_t *size) {
- *size = tp_size(p) + (req - tp_unused(p));
- return realloc(p->s, *size);
+
+/* Free function for use in a pair with tp_realloc */
+static inline void
+tp_free(struct tp *p) {
+ free(p->s);
}
+/* Get currently allocated buffer pointer */
+static inline char*
+tp_buf(struct tp *p) {
+ return p->s;
+}
+
+/* Main initialization function.
+ *
+ * reserve - reallocation function, may be NULL
+ * obj - pointer to be passed to the reallocation function as
+ * context
+ * buf - current buffer, may be NULL
+ * size - current buffer size
+ *
+ * Either a buffer pointer or a reserve function must be
+ * provided.
+*/
static inline void
tp_init(struct tp *p, char *buf, size_t size,
- tp_resizer resizer, void *obj) {
+ tp_reserve reserve, void *obj) {
p->s = buf;
p->p = p->s;
p->e = p->s + size;
@@ -163,18 +384,20 @@ tp_init(struct tp *p, char *buf, size_t size,
p->fsz = 0;
p->cnt = 0;
p->code = 0;
- p->resizer = resizer;
+ p->reserve = reserve;
p->obj = obj;
}
+/* Ensure that buffer has enough space to fill size bytes, resize
+ * buffer if needed. */
static tp_noinline ssize_t
tp_ensure(struct tp *p, size_t size) {
if (tp_likely(tp_unused(p) >= size))
return 0;
- if (tp_unlikely(p->resizer == NULL))
+ if (tp_unlikely(p->reserve == NULL))
return -1;
size_t sz;
- register char *np = p->resizer(p, size, &sz);
+ register char *np = p->reserve(p, size, &sz);
if (tp_unlikely(np == NULL))
return -1;
p->p = np + (p->p - p->s);
@@ -187,16 +410,26 @@ tp_ensure(struct tp *p, size_t size) {
if (tp_unlikely(p->u))
p->u = (np + (p->u - p->s));
p->s = np;
- p->e = np + sz;
+ p->e = np + sz;
return sz;
}
+/* Mark size bytes as used.
+ * Can be used to tell the buffer that a chunk has been read
+ * from the network into it.
+ */
static inline ssize_t
tp_use(struct tp *p, size_t size) {
p->p += size;
return tp_used(p);
}
+/* Append data to the buffer.
+ * Mostly unnecessary, but can be used to add any raw
+ * iproto-format data to the buffer.
+ * Normally tp_tuple(), tp_field() and tp_sz() should be used
+ * instead.
+ */
static inline ssize_t
tp_append(struct tp *p, const void *data, size_t size) {
if (tp_unlikely(tp_ensure(p, size) == -1))
@@ -205,24 +438,32 @@ tp_append(struct tp *p, const void *data, size_t size) {
return tp_use(p, size);
}
+/* Set the current request id.
+ *
+ * tp_ping(&req);
+ * tp_reqid(&req, 777);
+ */
static inline void
tp_reqid(struct tp *p, uint32_t reqid) {
assert(p->h != NULL);
p->h->reqid = reqid;
}
+/* Return the current request id */
static inline uint32_t
tp_getreqid(struct tp *p) {
assert(p->h != NULL);
return p->h->reqid;
}
+/* Get tuple count */
static inline uint32_t
tp_tuplecount(struct tp *p) {
assert(p->t != NULL);
return *(uint32_t*)(p->t);
}
+/* Write a tuple header */
static inline ssize_t
tp_tuple(struct tp *p) {
assert(p->h != NULL);
@@ -238,6 +479,7 @@ tp_tuple(struct tp *p) {
return tp_used(p);
}
+/* Leb128 calculation functions, internally used by the library */
static inline size_t
tp_leb128sizeof(uint32_t value) {
return ( tp_likely(value < (1 << 7))) ? 1 :
@@ -310,6 +552,10 @@ tp_leb128load(struct tp *p, uint32_t *value) {
return 0;
}
+/* Write a tuple field
+ * Note: the tuple must be started prior to calling
+ * this function with tp_tuple() call.
+ */
static inline ssize_t
tp_field(struct tp *p, const char *data, size_t size) {
assert(p->h != NULL);
@@ -325,6 +571,10 @@ tp_field(struct tp *p, const char *data, size_t size) {
return tp_used(p);
}
+/* Set the current request.
+ * Note: this is an internal helper function, not part of the
+ * tp.h API.
+ */
static inline void
tp_setreq(struct tp *p) {
p->h = (struct tp_h*)p->p;
@@ -332,18 +582,47 @@ tp_setreq(struct tp *p) {
p->u = NULL;
}
+/* Set current request and append data to the buffer.
+ * Note: this is an internal helper function, not part of the
+ * tp.h API. tp_ping(), tp_update() and other functions
+ * which directly create a request header should be used
+ * instead.
+ */
static inline ssize_t
tp_appendreq(struct tp *p, void *h, size_t size) {
+ int isallocated = p->p != NULL;
tp_setreq(p);
- return tp_append(p, h, size);
+ ssize_t rc = tp_append(p, h, size);
+ if (tp_unlikely(rc == -1))
+ return -1;
+ if (!isallocated)
+ p->h = (struct tp_h*)p->s;
+ return rc;
}
+/* Create a ping request.
+ *
+ * char buf[64];
+ * struct tp req;
+ * tp_init(&req, buf, sizeof(buf), NULL, NULL);
+ * tp_ping(&req);
+ */
static inline ssize_t
tp_ping(struct tp *p) {
struct tp_h h = { TP_PING, 0, 0 };
return tp_appendreq(p, &h, sizeof(h));
}
+/* Create an insert request.
+ *
+ * char buf[64];
+ * struct tp req;
+ * tp_init(&req, buf, sizeof(buf), NULL, NULL);
+ * tp_insert(&req, 0, TP_FRET);
+ * tp_tuple(&req);
+ * tp_sz(&req, "key");
+ * tp_sz(&req, "value");
+ */
static inline ssize_t
tp_insert(struct tp *p, uint32_t space, uint32_t flags) {
struct {
@@ -353,11 +632,20 @@ tp_insert(struct tp *p, uint32_t space, uint32_t flags) {
h.h.type = TP_INSERT;
h.h.len = sizeof(struct tp_hinsert);
h.h.reqid = 0;
- h.i.s = space;
+ h.i.space = space;
h.i.flags = flags;
return tp_appendreq(p, &h, sizeof(h));
}
+/* Create a delete request.
+ *
+ * char buf[64];
+ * struct tp req;
+ * tp_init(&req, buf, sizeof(buf), NULL, NULL);
+ * tp_delete(&req, 0, 0);
+ * tp_tuple(&req);
+ * tp_sz(&req, "key");
+ */
static inline ssize_t
tp_delete(struct tp *p, uint32_t space, uint32_t flags) {
struct {
@@ -367,33 +655,54 @@ tp_delete(struct tp *p, uint32_t space, uint32_t flags) {
h.h.type = TP_DELETE;
h.h.len = sizeof(struct tp_hdelete);
h.h.reqid = 0;
- h.d.s = space;
+ h.d.space = space;
h.d.flags = flags;
return tp_appendreq(p, &h, sizeof(h));
}
+/* Create a call request.
+ *
+ * char buf[64];
+ * struct tp req;
+ * tp_init(&req, buf, sizeof(buf), NULL, NULL);
+ *
+ * char proc[] = "hello_proc";
+ * tp_call(&req, 0, proc, sizeof(proc) - 1);
+ * tp_tuple(&req);
+ * tp_sz(&req, "arg1");
+ * tp_sz(&req, "arg2");
+ */
static inline ssize_t
-tp_call(struct tp *p, uint32_t flags, const char *name, size_t size) {
+tp_call(struct tp *p, uint32_t flags, const char *name, size_t name_len) {
struct {
struct tp_h h;
struct tp_hcall c;
} h;
- size_t sz = tp_leb128sizeof(size);
+ size_t sz = tp_leb128sizeof(name_len);
h.h.type = TP_CALL;
- h.h.len = sizeof(struct tp_hcall) + sz + size;
+ h.h.len = sizeof(struct tp_hcall) + sz + name_len;
h.h.reqid = 0;
h.c.flags = flags;
- if (tp_unlikely(tp_ensure(p, sizeof(h) + sz + size) == -1))
+ if (tp_unlikely(tp_ensure(p, sizeof(h) + sz + name_len) == -1))
return -1;
tp_setreq(p);
memcpy(p->p, &h, sizeof(h));
p->p += sizeof(h);
- tp_leb128save(p, size);
- memcpy(p->p, name, size);
- p->p += size;
+ tp_leb128save(p, name_len);
+ memcpy(p->p, name, name_len);
+ p->p += name_len;
return tp_used(p);
}
+/* Append a select request.
+ *
+ * char buf[64];
+ * struct tp req;
+ * tp_init(&req, buf, sizeof(buf), NULL, NULL);
+ * tp_select(&req, 0, 0, 0, 100);
+ * tp_tuple(&req);
+ * tp_sz(&req, "key");
+ */
static inline ssize_t
tp_select(struct tp *p, uint32_t space, uint32_t index,
uint32_t offset, uint32_t limit) {
@@ -404,14 +713,25 @@ tp_select(struct tp *p, uint32_t space, uint32_t index,
h.h.type = TP_SELECT;
h.h.len = sizeof(struct tp_hselect);
h.h.reqid = 0;
- h.s.s = space;
- h.s.i = index;
- h.s.o = offset;
- h.s.l = limit;
+ h.s.space = space;
+ h.s.index = index;
+ h.s.offset = offset;
+ h.s.limit = limit;
h.s.keyc = 0;
return tp_appendreq(p, &h, sizeof(h));
}
+/* Create an update request.
+ *
+ * char buf[64];
+ * struct tp req;
+ * tp_init(&req, buf, sizeof(buf), NULL, NULL);
+ * tp_update(&req, 0, 0);
+ * tp_tuple(&req);
+ * tp_sz(&req, "key");
+ * tp_updatebegin(&req);
+ * tp_op(&req, 1, TP_OPSET, "VALUE", 5);
+ */
static inline ssize_t
tp_update(struct tp *p, uint32_t space, uint32_t flags) {
struct {
@@ -421,11 +741,27 @@ tp_update(struct tp *p, uint32_t space, uint32_t flags) {
h.h.type = TP_UPDATE;
h.h.len = sizeof(struct tp_hupdate);
h.h.reqid = 0;
- h.u.s = space;
+ h.u.space = space;
h.u.flags = flags;
return tp_appendreq(p, &h, sizeof(h));
}
+/* Append the number of operations the update request
+ * is going to contain.
+ * Must be called right after appending the key which
+ * identifies the tuple which must be updated. Since
+ * the key can be multipart, tp_tuple() must be used to
+ * append it.
+ *
+ * In other words, this call sequence creates a proper
+ * UPDATE request:
+ * tp_init(...)
+ * tp_update()
+ * tp_tuple()
+ * tp_sz(), tp_sz(), ...
+ * tp_updatebegin()
+ * tp_op(), tp_op(), ...
+ */
static inline ssize_t
tp_updatebegin(struct tp *p) {
assert(p->h != NULL);
@@ -438,6 +774,14 @@ tp_updatebegin(struct tp *p) {
return tp_used(p);
}
+/* Append a single UPDATE operation.
+ *
+ * May be called after tp_updatebegin().
+ * Can be used to create TP_OPSET, TP_OPADD, TP_OPAND,
+ * TP_OPXOR, TP_OPOR operations.
+ *
+ * See: tp_update() for example.
+ */
static inline ssize_t
tp_op(struct tp *p, uint32_t field, uint8_t op, const char *data,
size_t size) {
@@ -464,35 +808,46 @@ tp_op(struct tp *p, uint32_t field, uint8_t op, const char *data,
return tp_used(p);
}
+/* Append a SPLICE operation. This operation is unlike any other,
+ * since it takes three arguments instead of one.
+ */
static inline ssize_t
-tp_opsplice(struct tp *p, uint32_t field, uint32_t off, uint32_t len,
- const char *data, size_t size) {
- uint32_t olen = tp_leb128sizeof(sizeof(off)),
- llen = tp_leb128sizeof(sizeof(len)),
- dlen = tp_leb128sizeof(size);
- uint32_t sz = olen + sizeof(off) + llen + sizeof(len) +
- dlen + size;
+tp_opsplice(struct tp *p, uint32_t field, uint32_t offset,
+ uint32_t cut, const char *paste, size_t paste_len) {
+ uint32_t olen = tp_leb128sizeof(sizeof(offset)),
+ clen = tp_leb128sizeof(sizeof(cut)),
+ plen = tp_leb128sizeof(paste_len);
+ uint32_t sz = olen + sizeof(offset) + clen + sizeof(cut) +
+ plen + paste_len;
ssize_t rc = tp_op(p, field, TP_OPSPLICE, NULL, sz);
if (tp_unlikely(rc == -1))
return -1;
p->p -= sz;
- tp_leb128save(p, sizeof(off));
- memcpy(p->p, &off, sizeof(off));
- p->p += sizeof(off);
- tp_leb128save(p, sizeof(len));
- memcpy(p->p, &len, sizeof(len));
- p->p += sizeof(len);
- tp_leb128save(p, size);
- memcpy(p->p, data, size);
- p->p += size;
+ tp_leb128save(p, sizeof(offset));
+ memcpy(p->p, &offset, sizeof(offset));
+ p->p += sizeof(offset);
+ tp_leb128save(p, sizeof(cut));
+ memcpy(p->p, &cut, sizeof(cut));
+ p->p += sizeof(cut);
+ tp_leb128save(p, paste_len);
+ memcpy(p->p, paste, paste_len);
+ p->p += paste_len;
return rc;
}
+/* Append a '\0' terminated string as a tuple field. */
static inline ssize_t
tp_sz(struct tp *p, const char *sz) {
return tp_field(p, sz, strlen(sz));
}
+/*
+ * Returns the number of bytes which are required to fully
+ * store a reply in the buffer.
+ * The return value can be negative, which indicates that
+ * there is a complete reply in the buffer which is not parsed
+ * and discarded yet.
+ */
static inline ssize_t
tp_reqbuf(const char *buf, size_t size) {
if (tp_unlikely(size < sizeof(struct tp_h)))
@@ -503,16 +858,27 @@ tp_reqbuf(const char *buf, size_t size) {
sz - size : -(size - sz);
}
+/* Same as tp_reqbuf(), but works on the buffer in struct tp.
+ */
static inline ssize_t
tp_req(struct tp *p) {
return tp_reqbuf(p->s, tp_size(p));
}
+/* Get the size of a yet unprocessed reply data.
+ *
+ * This is not part of the API.
+ */
static inline size_t
tp_unfetched(struct tp *p) {
return p->p - p->c;
}
+/* Advance the reply processed pointer.
+ *
+ * This is not part of the API, tp_use() is a higher level
+ * function.
+ */
static inline void*
tp_fetch(struct tp *p, int inc) {
assert(tp_unfetched(p) >= inc);
@@ -521,31 +887,68 @@ tp_fetch(struct tp *p, int inc) {
return po;
}
+static inline int
+tp_can_fetch(struct tp *p, int inc) {
+ if (tp_unfetched(p) >= inc)
+ return 1;
+ return 0;
+}
+
+
+/* Get the last server error.
+*/
static inline char*
tp_replyerror(struct tp *p) {
return p->c;
}
+/* Get the length of the last error message.
+ */
static inline int
tp_replyerrorlen(struct tp *p) {
return tp_unfetched(p);
}
+/* Get the tuple count in the response (there must be
+ * no error).
+ */
static inline uint32_t
tp_replycount(struct tp *p) {
return p->cnt;
}
+/* Get the current response return code.
+ */
static inline uint32_t
tp_replycode(struct tp *p) {
return p->code;
}
+/* Get the current response operation code. */
static inline uint32_t
tp_replyop(struct tp *p) {
return p->h->type;
}
+/*
+ * Initialize the buffer with a fully read server response.
+ * The response is parsed.
+ *
+ * struct tp rep;
+ * tp_init(&rep, reply_buf, reply_size, NULL, NULL);
+ *
+ * ssize_t server_code = tp_reply(&rep);
+ *
+ * printf("op: %d\n", tp_replyop(&rep));
+ * printf("count: %d\n", tp_replycount(&rep));
+ * printf("code: %zu\n", server_code);
+ *
+ * if (server_code != 0) {
+ * printf("error: %-.*s\n", tp_replyerrorlen(&rep),
+ * tp_replyerror(&rep));
+ * }
+ *
+ */
tp_function_unused static ssize_t
tp_reply(struct tp *p) {
ssize_t used = tp_req(p);
@@ -554,59 +957,87 @@ tp_reply(struct tp *p) {
/* this is end of packet in continious buffer */
p->p = p->e + used; /* end - used */
p->c = p->s;
+ if (!tp_can_fetch(p, sizeof(struct tp_h)))
+ return -1;
p->h = tp_fetch(p, sizeof(struct tp_h));
p->t = p->f = p->u = NULL;
p->cnt = 0;
p->code = 0;
if (tp_unlikely(p->h->type == TP_PING))
- return 0;
+ return 0;
if (tp_unlikely(p->h->type != TP_UPDATE &&
p->h->type != TP_INSERT &&
p->h->type != TP_DELETE &&
p->h->type != TP_SELECT &&
p->h->type != TP_CALL))
return -1;
+ if (!tp_can_fetch(p, sizeof(uint32_t)))
+ return -1;
p->code = *(uint32_t*)tp_fetch(p, sizeof(uint32_t));
if (p->code != 0)
return p->code;
/* BOX_QUIET */
if (tp_unlikely(tp_unfetched(p) == 0))
return p->code;
+ if (!tp_can_fetch(p, sizeof(uint32_t)))
+ return -1;
p->cnt = *(uint32_t*)tp_fetch(p, sizeof(uint32_t));
return p->code;
}
+/* Example: iteration over returned tuples.
+ *
+ * while (tp_next(&rep)) {
+ * printf("tuple fields: %d\n", tp_tuplecount(&rep));
+ * printf("tuple size: %d\n", tp_tuplesize(&rep));
+ * printf("[");
+ * while (tp_nextfield(&rep)) {
+ * printf("%-.*s", tp_getfieldsize(rep), tp_getfield(&rep));
+ * if (tp_hasnextfield(&rep))
+ * printf(", ");
+ * }
+ * printf("]\n");
+ * }
+*/
+
+/* Rewind iteration to the first tuple. */
static inline void
tp_rewind(struct tp *p) {
p->t = NULL;
p->f = NULL;
}
+/* Rewind iteration to the first tuple field of the current tuple. */
static inline void
tp_rewindfield(struct tp *p) {
p->f = NULL;
}
+/* Get the current tuple data, all fields. */
static inline char*
tp_gettuple(struct tp *p) {
return p->t;
}
+/* Get the current tuple size. */
static inline uint32_t
tp_tuplesize(struct tp *p) {
return p->tsz;
}
+/* Get the current field. */
static inline char*
tp_getfield(struct tp *p) {
return p->f;
}
+/* Get the current field size. */
static inline uint32_t
tp_getfieldsize(struct tp *p) {
return p->fsz;
}
+/* Get a pointer to the end of the current tuple. */
static inline char*
tp_tupleend(struct tp *p) {
/* tuple_size + p->t + cardinaltiy_size +
@@ -614,17 +1045,21 @@ tp_tupleend(struct tp *p) {
return p->t + 4 + p->tsz;
}
+/* Check if the response has a tuple.
+ * Automatically checked during tp_next() iteration. */
static inline int
tp_hasdata(struct tp *p) {
return tp_replyop(p) != TP_PING && tp_unfetched(p) > 0;
}
+/* Check if there is a one more tuple. */
static inline int
tp_hasnext(struct tp *p) {
assert(p->t != NULL);
return (p->p - tp_tupleend(p)) >= 4;
}
+/* Check if the current tuple has a one more field. */
static inline int
tp_hasnextfield(struct tp *p) {
assert(p->t != NULL);
@@ -634,6 +1069,9 @@ tp_hasnextfield(struct tp *p) {
return (tp_tupleend(p) - f) >= 1;
}
+/* Skip to the next tuple.
+ * Tuple can be accessed using:
+ * tp_tuplecount(), tp_tuplesize(), tp_gettuple(). */
static inline int
tp_next(struct tp *p) {
if (tp_unlikely(p->t == NULL)) {
@@ -647,12 +1085,14 @@ tp_next(struct tp *p) {
p->t = tp_tupleend(p) + 4;
fetch:
p->tsz = *(uint32_t*)(p->t - 4);
- if (tp_unlikely(p->t + p->tsz > p->e))
+ if (tp_unlikely((p->t + p->tsz) > p->e))
return -1;
p->f = NULL;
return 1;
}
+/* Skip to the next field.
+ * Data can be accessed using: tp_getfieldsize(), tp_getfield(). */
static inline int
tp_nextfield(struct tp *p) {
assert(p->t != NULL);
@@ -667,10 +1107,10 @@ tp_nextfield(struct tp *p) {
p->f += p->fsz;
fetch:;
register int rc = tp_leb128load(p, &p->fsz);
- if (tp_unlikely(p->f + p->fsz > p->e))
- return -1;
if (tp_unlikely(rc == -1))
return -1;
+ if (tp_unlikely((p->f + p->fsz) > p->e))
+ return -1;
return 1;
}