Коннекторы¶
В этой главе описаны API для различных языков программирования.
Протокол¶
Tarantool’s binary protocol was designed with a focus on asynchronous I/O and easy integration with proxies. Each client request starts with a variable-length binary header, containing request id, request type, instance id, log sequence number, and so on.
Также в заголовке обязательно указывается длина запроса, что облегчает обработку данных. Ответ на запрос посылается по мере готовности. В заголовке ответа указывается тот же идентификатор и тип запроса, что и в изначальном запросе. По идентификатору можно легко соотнести запрос с ответом, даже если ответ был получен не в порядке отсылки запросов.
Вдаваться в тонкости реализации Tarantool-протокола нужно только при разработке нового коннектора для Tarantool’а — см. полное описание бинарного протокола в Tarantool’е в виде аннотированных BNF-диаграмм (Backus-Naur Form). В остальных случаях достаточно взять уже существующий коннектор для нужного вам языка программирования. Такие коннекторы позволяют легко хранить структуры данных из разных языков в формате Tarantool’а.
Пример пакета данных¶
The Tarantool API exists so that a client program can send a request packet to
a server instance, and receive a response. Here is an example of a what the client
would send for box.space[513]:insert{'A', 'BB'}
. The BNF description of
the components is on the page about
Tarantool’s binary protocol.
Компонент Байт #0 Байт #1 Байт #2 Байт #3 код для вставки 02 остаток заголовка … … … … число из 2 цифр: ID пространства cd 02 01 код для кортежа 21 число из 1 цифры: количество полей = 2 92 строка из 1 символа: поле[1] a1 41 строка из 2 символов: поле[2] a2 42 42
Now, you could send that packet to the Tarantool instance, and interpret the
response (the page about
Tarantool’s binary protocol has a
description of the packet format for responses as well as requests). But it
would be easier, and less error-prone, if you could invoke a routine that
formats the packet according to typed parameters. Something like
response = tarantool_routine("insert", 513, "A", "B");
. And that is why APIs
exist for drivers for Perl, Python, PHP, and so on.
Настройка окружения для примеров работы с коннекторами¶
This chapter has examples that show how to connect to a Tarantool instance via the Perl, PHP, Python, node.js, and C connectors. The examples contain hard code that will work if and only if the following conditions are met:
- the Tarantool instance (tarantool) is running on localhost (127.0.0.1) and is listening on
port 3301 (
box.cfg.listen = '3301'
), - space
examples
has id = 999 (box.space.examples.id = 999
) and has a primary-key index for a numeric field (box.space[999].index[0].parts[1].type = "num"
), - для пользователя „guest“ настроены привилегии на чтение и запись.
It is easy to meet all the conditions by starting the instance and executing this script:
box.cfg{listen=3301}
box.schema.space.create('examples',{id=999})
box.space.examples:create_index('primary', {type = 'hash', parts = {1, 'num'}})
box.schema.user.grant('guest','read,write','space','examples')
box.schema.user.grant('guest','read','space','_space')
Erlang¶
Perl¶
Наиболее популярным Tarantool-коннектором для языка Perl является DR::Tarantool. Он устанавливается отдельно от Tarantool’а, например с помощью cpan (см. CPAN, the Comprehensive Perl Archive Network), и требует предварительной установки еще несколько зависимых модулей. Вот пример установки этого коннектора под Ubuntu:
$ sudo cpan install AnyEvent
$ sudo cpan install Devel::GlobalDestruction
$ sudo cpan install Coro
$ sudo cpan install Test::Pod
$ sudo cpan install Test::Spelling
$ sudo cpan install PAR::Dist
$ sudo cpan install List::MoreUtils
$ sudo cpan install DR::Tarantool
Here is a complete Perl program that inserts [99999,'BB']
into space[999]
via the Perl API. Before trying to run, check that the server instance is listening at
localhost:3301
and that the space examples
exists, as
described earlier.
To run, paste the code into a file named example.pl
and say
perl example.pl
. The program will connect using an application-specific
definition of the space. The program will open a socket connection with the
Tarantool instance at localhost:3301
, then send an INSERT request, then — if
all is well — end without displaying any messages. If Tarantool is not running
on localhost
with listen port = 3301, the program will print “Connection
refused”.
#!/usr/bin/perl
use DR::Tarantool ':constant', 'tarantool';
use DR::Tarantool ':all';
use DR::Tarantool::MsgPack::SyncClient;
my $tnt = DR::Tarantool::MsgPack::SyncClient->connect(
host => '127.0.0.1', # поиск Tarantool-сервера по адресу localhost
port => 3301, # на порту 3301
user => 'guest', # имя пользователя; здесь же можно добавить 'password=>...'
spaces => {
999 => { # определение пространства space[999] ...
name => 'examples', # имя пространства space[999] = 'examples'
default_type => 'STR', # если тип поля в space[999] не задан, то = 'STR'
fields => [ { # определение полей в пространстве space[999] ...
name => 'field1', type => 'NUM' } ], # имя поля space[999].field[1]='field1', тип ='NUM'
indexes => { # определение индексов пространства space[999] ...
0 => {
name => 'primary', fields => [ 'field1' ] } } } } );
$tnt->insert('examples' => [ 99999, 'BB' ]);
The example program uses field type names „STR“ and „NUM“ instead of „string“ and „unsigned“, which will be the type names for Tarantool version 1.7.
В этой программе мы привели пример использования лишь одного запроса. Для полноценной работы с Tarantool’ом с помощью Perl API, пожалуйста, обратитесь к документации из CPAN-репозитория DR::Tarantool.
PHP¶
The most commonly used PHP driver is
tarantool-php.
It is not supplied as part of the Tarantool repository; it must be installed
separately, for example with git. See installation instructions.
in the driver’s README
file.
Here is a complete PHP program that inserts [99999,'BB']
into a space named
examples
via the PHP API. Before trying to run, check that the server instance is
listening at localhost:3301
and that the space examples
exists, as
described earlier. To run, paste the code into
a file named example.php
and say
php -d extension=~/tarantool-php/modules/tarantool.so example.php
.
The program will open a socket connection with the Tarantool instance at
localhost:3301
, then send an INSERT request, then — if all is well — print
«Insert succeeded». If the tuple already exists, the program will print
“Duplicate key exists in unique index „primary“ in space „examples“”.
<?php
$tarantool = new Tarantool('localhost', 3301);
try {
$tarantool->insert('examples', array(99999, 'BB'));
echo "Insert succeeded\n";
} catch (Exception $e) {
echo "Exception: ", $e->getMessage(), "\n";
}
The example program only shows one request and does not show all that’s necessary for good practice. For that, please see tarantool/tarantool-php project at GitHub.
Besides, you can use an alternative PHP driver from another GitHub project: it includes a client (see tarantool-php/client) and a mapper for that client (see tarantool-php/mapper).
Python¶
Далее приводится пример полноценной программы на языке Python, которая осуществляет вставку кортежа [99999,'Value','Value']
в пространство examples
с помощью высокоуровневого Tarantool API для языка Python.
#!/usr/bin/python
from tarantool import Connection
c = Connection("127.0.0.1", 3301)
result = c.insert("examples",(99999,'Value', 'Value'))
print result
To prepare, paste the code into a file named example.py
and install
the tarantool-python
connector with either pip install tarantool>0.4
to install in /usr
(requires root privilege) or
pip install tarantool>0.4 --user
to install in ~
i.e. user’s
default directory. Before trying to run, check that the server instance is listening at
localhost:3301
and that the space examples
exists, as
described earlier.
To run the program, say python example.py
. The program will connect
to the Tarantool server, will send the request, and will not throw any exception if
all went well. If the tuple already exists, the program will throw
tarantool.error.DatabaseError: (3, "Duplicate key exists in unique index 'primary' in space 'examples'")
.
The example program only shows one request and does not show all that’s necessary for good practice. For that, please see tarantool-python project at GitHub. For an example of using Python API with queue managers for Tarantool, see queue-python project at GitHub.
Node.js¶
The most commonly used node.js driver is the Node Tarantool driver. It is not supplied as part of the Tarantool repository; it must be installed separately. The most common way to install it is with npm. For example, on Ubuntu, the installation could look like this after npm has been installed:
npm install tarantool-driver --global
Here is a complete node.js program that inserts [99999,'BB']
into
space[999]
via the node.js API. Before trying to run, check that the server instance
is listening at localhost:3301
and that the space examples
exists, as
described earlier. To run, paste the code into
a file named example.rs
and say node example.rs
. The program will
connect using an application-specific definition of the space. The program will
open a socket connection with the Tarantool instance at localhost:3301
, then
send an INSERT request, then — if all is well — end after saying «Insert
succeeded». If Tarantool is not running on localhost
with listen port =
3301, the program will print “Connect failed”. If user guest
does not have
authorization to connect, the program will print «Auth failed». If the insert
request fails for any reason, for example because the tuple already exists,
the program will print «Insert failed».
var TarantoolConnection = require('tarantool-driver');
var conn = new TarantoolConnection({port: 3301});
var insertTuple = [99999, "BB"];
conn.connect().then(function() {
conn.auth("guest", "").then(function() {
conn.insert(999, insertTuple).then(function() {
console.log("Insert succeeded");
process.exit(0);
}, function(e) { console.log("Insert failed"); process.exit(1); });
}, function(e) { console.log("Auth failed"); process.exit(1); });
}, function(e) { console.log("Connect failed"); process.exit(1); });
The example program only shows one request and does not show all that’s necessary for good practice. For that, please see The node.js driver repository.
C#¶
The most commonly used csharp driver is the ProGaudi tarantool-csharp driver. It is not supplied as part of the Tarantool repository; it must be installed separately. The makers recommend installation on Windows and not on other platforms. However, to be consistent with the other instructions in this chapter, here is an unofficial way to install the driver on Ubuntu 16.04.
Install dotnet preview from Microsoft – mono will not work, and dotnet from xbuild will not work. Read the Microsoft End User License Agreement first, because it is not an ordinary open-source agreement and there will be a message during installation saying «This software may collect information about you and your use of the software, and send that to Microsoft.» The dotnet instructions are at https://www.microsoft.com/net/core#ubuntu.
# Install tarantool-csharp from the github repository source -- nuget will
# not work, so building from source is necessary, thus:
cd ~
mkdir dotnet
cd dotnet
git clone https://github.com/progaudi/tarantool-csharp tarantool-csharp
cd tarantool-csharp
dotnet restore
cd src/tarantool.client
dotnet build
# Find the .dll file that was produced by the "dotnet build" step. The next
instruction assumes it was produced in 'bin/Debug/netcoreapp1.0'.
cd bin/Debug/netcoreapp1.0
# Find the project.json file used for samples. The next instruction assumes
# the docker-compose/dotnet directory has a suitable one, which is true at
# time of writing.
cp ~/dotnet/tarantool-csharp/samples/docker-compose/dotnet/project.json project.json
dotnet restore
Do not change directories now, the example program should be in the same directory as the .dll file.
Here is a complete C# program that inserts [99999,'BB']
into space
examples
via the tarantool-csharp API. Before trying to run, check that the
server is listening at localhost:3301
and that the space examples
exists, as described earlier. To run, paste the
code into a file named example.cs
and say dotnet run example.cs
.
The program will connect using an application-specific definition of the space.
The program will open a socket connection with the Tarantool server at
localhost:3301
, then send an INSERT request, then — if all is well — end
without saying anything. If Tarantool is not running on localhost
with
listen port = 3301, or if user guest
does not have authorization to connect,
or if the insert request fails for any reason, the program will print an error
message, among other things.
using System;
using System.Linq;
using System.Threading.Tasks;
using ProGaudi.Tarantool.Client;
using ProGaudi.Tarantool.Client.Model;
public class HelloWorld
{
static public void Main ()
{
Test().Wait();
}
static async Task Test()
{
var tarantoolClient = await Box.Connect("127.0.0.1:3301");
var schema = tarantoolClient.getSchema();
var space = await schema.getSpace("examples");
await space.Insert(TarantoolTuple.Create(99999, "BB"));
}
}
The same program should work on Windows with far less difficulty – just install with nuget and run.
The example program only shows one request and does not show all that’s necessary for good practice. For that, please see The tarantool-csharp driver repository.
C¶
В этом разделе даны два примера использования высокоуровневого API для Tarantool’а и языка C.
Пример 1¶
Далее приводится пример полноценной программы на языке C, которая осуществляет вставку кортежа [99999,'B']
в пространство examples
с помощью высокоуровневого Tarantool API для языка C.
#include <stdio.h>
#include <stdlib.h>
#include <tarantool/tarantool.h>
#include <tarantool/tnt_net.h>
#include <tarantool/tnt_opt.h>
void main() {
struct tnt_stream *tnt = tnt_net(NULL); /* См. ниже = НАСТРОЙКА */
tnt_set(tnt, TNT_OPT_URI, "localhost:3301");
if (tnt_connect(tnt) < 0) { /* См. ниже = СОЕДИНЕНИЕ */
printf("Connection refused\n");
exit(-1);
}
struct tnt_stream *tuple = tnt_object(NULL); /* См. ниже = СОЗДАНИЕ ЗАПРОСА */
tnt_object_format(tuple, "[%d%s]", 99999, "B");
tnt_insert(tnt, 999, tuple); /* См. ниже = ОТПРАВКА ЗАПРОСА */
tnt_flush(tnt);
struct tnt_reply reply; tnt_reply_init(&reply); /* См. ниже = ПОЛУЧЕНИЕ ОТВЕТА */
tnt->read_reply(tnt, &reply);
if (reply.code != 0) {
printf("Insert failed %lu.\n", reply.code);
}
tnt_close(tnt); /* См. ниже = ЗАВЕРШЕНИЕ */
tnt_stream_free(tuple);
tnt_stream_free(tnt);
}
Скопируйте исходный код программы в файл с именем example.c
и установите коннектор tarantool-c
. Вот один из способов установки tarantool-c
(под Ubuntu):
$ git clone git://github.com/tarantool/tarantool-c.git ~/tarantool-c
$ cd ~/tarantool-c
$ git submodule init
$ git submodule update
$ cmake .
$ make
$ make install
Чтобы скомпилировать и слинковать тестовую программу, выполните следующую команду:
$ # иногда это необходимо:
$ export LD_LIBRARY_PATH=/usr/local/lib
$ gcc -o example example.c -ltarantool
Before trying to run,
check that a server instance is listening at localhost:3301
and that the space
examples
exists, as
described earlier.
To run the program, say ./example
. The program will connect
to the Tarantool instance, and will send the request.
If Tarantool is not running on localhost with listen address = 3301, the program
will print “Connection refused”.
If the insert fails, the program will print «Insert failed» and an error number
(see all error codes in the source file
/src/box/errcode.h).
Далее следуют примечания, на которые мы ссылались в комментариях к исходному коду тестовой программы.
НАСТРОЙКА: Настройка начинается с создания потока (tnt_stream
).
struct tnt_stream *tnt = tnt_net(NULL);
tnt_set(tnt, TNT_OPT_URI, "localhost:3301");
In this program, the stream will be named tnt
.
Before connecting on the tnt
stream, some options may have to be set.
The most important option is TNT_OPT_URI.
In this program, the URI is localhost:3301
, since that is where the
Tarantool instance is supposed to be listening.
Описание функции:
struct tnt_stream *tnt_net(struct tnt_stream *s) int tnt_set(struct tnt_stream *s, int option, variant option-value)
CONNECT: Now that the stream named tnt
exists and is associated with a
URI, this example program can connect to a server.
if (tnt_connect(tnt) < 0)
{ printf("Connection refused\n"); exit(-1); }
Описание функции:
int tnt_connect(struct tnt_stream *s)
Попытка соединения может и не удаться, например если Tarantool-сервер не запущен или в URI-строке указан неверный пароль. В случае неудачи функция tnt_connect()
вернет -1.
СОЗДАНИЕ ЗАПРОСА: В большинстве запросов требуется передавать структурированные данные, например содержимое кортежа.
struct tnt_stream *tuple = tnt_object(NULL);
tnt_object_format(tuple, "[%d%s]", 99999, "B");
В данной программе мы используем запрос INSERT, а кортеж содержит целое число и строку. Это простой набор значений без каких-либо вложенных структур или массивов. И передаваемые значения мы можем указать самым простым образом — аналогично тому, как это сделано в стандартной C-функции printf()
: %d
для обозначения целого числа, %s
для обозначения строки, затем числовое значение, затем указатель на строковое значение.
Описание функции:
ssize_t tnt_object_format(struct tnt_stream *s, const char *fmt, ...)
ОТПРАВКА ЗАПРОСА: Отправка запросов на изменение данных в базе делается аналогично тому, как это делается в Tarantool-библиотеке box
.
tnt_insert(tnt, 999, tuple);
tnt_flush(tnt);
В данной программе мы делаем INSERT-запрос. В этом запросе мы передаем поток tnt
, который ранее использовали для установки соединения, и поток tuple
, который также ранее настроили с помощью функции tnt_object_format()
.
Описание функции:
ssize_t tnt_insert(struct tnt_stream *s, uint32_t space, struct tnt_stream *tuple) ssize_t tnt_replace(struct tnt_stream *s, uint32_t space, struct tnt_stream *tuple) ssize_t tnt_select(struct tnt_stream *s, uint32_t space, uint32_t index, uint32_t limit, uint32_t offset, uint8_t iterator, struct tnt_stream *key) ssize_t tnt_update(struct tnt_stream *s, uint32_t space, uint32_t index, struct tnt_stream *key, struct tnt_stream *ops)
ПОЛУЧЕНИЕ ОТВЕТА: На большинство запросов клиент получает ответ, который содержит информацию о том, был ли данный запрос успешно выполнен, а также содержит набор кортежей.
struct tnt_reply reply; tnt_reply_init(&reply);
tnt->read_reply(tnt, &reply);
if (reply.code != 0)
{ printf("Insert failed %lu.\n", reply.code); }
Данная программа проверяет, был ли запрос выполнен успешно, но никак не интерпретирует оставшуюся часть ответа.
Описание функции:
struct tnt_reply *tnt_reply_init(struct tnt_reply *r) tnt->read_reply(struct tnt_stream *s, struct tnt_reply *r) void tnt_reply_free(struct tnt_reply *r)
ЗАВЕРШЕНИЕ: По окончании сессии нам нужно закрыть соединение, созданное с помощью функции tnt_connect()
, и удалить объекты, созданные на этапе настройки.
tnt_close(tnt);
tnt_stream_free(tuple);
tnt_stream_free(tnt);
Описание функции:
void tnt_close(struct tnt_stream *s) void tnt_stream_free(struct tnt_stream *s)
Пример 2¶
Далее приводится еще один пример полноценной программы на языке C, которая осуществляет выборку по индекс-ключу [99999]
из пространства examples
с помощью высокоуровневого Tarantool API для языка C. Для вывода результатов в этой программе используются функции из библиотеки MsgPuck. Эти функции нужны для декодирования массивов значений в формате MessagePack.
#include <stdio.h>
#include <stdlib.h>
#include <tarantool/tarantool.h>
#include <tarantool/tnt_net.h>
#include <tarantool/tnt_opt.h>
#define MP_SOURCE 1
#include <msgpuck.h>
void main() {
struct tnt_stream *tnt = tnt_net(NULL);
tnt_set(tnt, TNT_OPT_URI, "localhost:3301");
if (tnt_connect(tnt) < 0) {
printf("Connection refused\n");
exit(1);
}
struct tnt_stream *tuple = tnt_object(NULL);
tnt_object_format(tuple, "[%d]", 99999); /* кортеж tuple = ключ для поиска */
tnt_select(tnt, 999, 0, (2^32) - 1, 0, 0, tuple);
tnt_flush(tnt);
struct tnt_reply reply; tnt_reply_init(&reply);
tnt->read_reply(tnt, &reply);
if (reply.code != 0) {
printf("Select failed.\n");
exit(1);
}
char field_type;
field_type = mp_typeof(*reply.data);
if (field_type != MP_ARRAY) {
printf("no tuple array\n");
exit(1);
}
long unsigned int row_count;
uint32_t tuple_count = mp_decode_array(&reply.data);
printf("tuple count=%u\n", tuple_count);
unsigned int i, j;
for (i = 0; i < tuple_count; ++i) {
field_type = mp_typeof(*reply.data);
if (field_type != MP_ARRAY) {
printf("no field array\n");
exit(1);
}
uint32_t field_count = mp_decode_array(&reply.data);
printf(" field count=%u\n", field_count);
for (j = 0; j < field_count; ++j) {
field_type = mp_typeof(*reply.data);
if (field_type == MP_UINT) {
uint64_t num_value = mp_decode_uint(&reply.data);
printf(" value=%lu.\n", num_value);
} else if (field_type == MP_STR) {
const char *str_value;
uint32_t str_value_length;
str_value = mp_decode_str(&reply.data, &str_value_length);
printf(" value=%.*s.\n", str_value_length, str_value);
} else {
printf("wrong field type\n");
exit(1);
}
}
}
tnt_close(tnt);
tnt_stream_free(tuple);
tnt_stream_free(tnt);
}
Аналогично первому примеру, сохраните исходный код программы в файле с именем example2.c
.
Чтобы скомпилировать и слинковать тестовую программу, выполните следующую команду:
$ gcc -o example2 example2.c -ltarantool
Для запуска программы выполните команду ./example2
.
В этих двух программах мы привели пример использования лишь двух запросов. Для полноценной работы с Tarantool’ом с помощью C API, пожалуйста, обратитесь к документации из проекта tarantool-c на GitHub.
Интерпретация возвращаемых значений¶
При работе с любым Tarantool-коннектором функции, вызванные с помощью Tarantool’а, возвращают значения в формате MsgPack. Если функция была вызвана через API коннектора, то формат возвращаемых значений будет следующим: скалярные значения возвращаются в виде кортежей (сначала идет идентификатор типа из формата MsgPack, а затем идет значение); все прочие (не скалярные) значения возвращаются в виде групп кортежей (сначала идет идентификатор массива в формате MsgPack, а затем идут скалярные значения). Но если функция была вызвана в рамках бинарного протокола (с помощью команды eval
), а не через API коннектора, то подобных изменений формата возвращаемых значений не происходит.
Далее приводится пример создания Lua-функции. Поскольку эту функцию будет вызывать внешний пользователь „guest“, то нужно настроить привилегии на исполнение с помощью grant
. Эта функция возвращает пустой массив, строку-скаляр, два логических значения и короткое целое число. Значение будут теми же, что описаны в разделе про MsgPack в таблице Стандартные типы в MsgPack-кодировке.
tarantool> box.cfg{listen=3301}
2016-03-03 18:45:52.802 [27381] main/101/interactive I> ready to accept requests
---
...
tarantool> function f() return {},'a',false,true,127; end
---
...
tarantool> box.schema.func.create('f')
---
...
tarantool> box.schema.user.grant('guest','execute','function','f')
---
...
Далее идет пример программы на C, из который мы вызываем эту Lua-функцию. Хотя в примере использован код на C, результат будет одинаковым, на каком бы языке ни была написана вызываемая программа: Perl, PHP, Python, Go или Java.
#include <stdio.h>
#include <stdlib.h>
#include <tarantool/tarantool.h>
#include <tarantool/tnt_net.h>
#include <tarantool/tnt_opt.h>
void main() {
struct tnt_stream *tnt = tnt_net(NULL); /* SETUP */
tnt_set(tnt, TNT_OPT_URI, "localhost:3301");
if (tnt_connect(tnt) < 0) { /* CONNECT */
printf("Connection refused\n");
exit(-1);
}
struct tnt_stream *arg; arg = tnt_object(NULL); /* MAKE REQUEST */
tnt_object_add_array(arg, 0);
struct tnt_request *req1 = tnt_request_call(NULL); /* CALL function f() */
tnt_request_set_funcz(req1, "f");
uint64_t sync1 = tnt_request_compile(tnt, req1);
tnt_flush(tnt); /* SEND REQUEST */
struct tnt_reply reply; tnt_reply_init(&reply); /* GET REPLY */
tnt->read_reply(tnt, &reply);
if (reply.code != 0) {
printf("Call failed %lu.\n", reply.code);
exit(-1);
}
const unsigned char *p= (unsigned char*)reply.data; /* PRINT REPLY */
while (p < (unsigned char *) reply.data_end)
{
printf("%x ", *p);
++p;
}
printf("\n");
tnt_close(tnt); /* TEARDOWN */
tnt_stream_free(arg);
tnt_stream_free(tnt);
}
По завершении программа выведет на экран следующие значения:
dd 0 0 0 5 90 91 a1 61 91 c2 91 c3 91 7f
Первые пять байт — dd 0 0 0 5
— это фрагмент данных в формате MsgPack, означающий «32-битный заголовок массива со значением 5» (см. спецификацию на формат MsgPack). Остальные значения описаны в таблице Стандартные типы в MsgPack-кодировке.