Quick start
===========

Connecting to the server
------------------------

Create a connection to the server:

.. code-block:: python

    >>> import tarantool
    >>> conn = tarantool.Connection('localhost', 3301, user='user', password='pass')

Data manipulation
-----------------

Select
^^^^^^

:meth:`~tarantool.Connection.select` a tuple with id ``'AAAA'`` from
the space ``demo`` using primary index:

.. code-block:: python

    >>> resp = conn.select('demo', 'AAAA')
    >>> len(resp)
    1
    >>> resp[0]
    ['AAAA', 'Alpha']

:meth:`~tarantool.Connection.select` a tuple with secondary index
key ``'Alpha'`` from the space ``demo`` with secondary index ``sec``:

.. code-block:: python

    >>> resp = conn.select('demo', 'Alpha', index='sec')
    >>> resp
    - ['AAAA', 'Alpha']

Insert
^^^^^^

:meth:`~tarantool.Connection.insert` the tuple ``('BBBB', 'Bravo')``
into the space ``demo``:

.. code-block:: python

    >>> conn.insert('demo', ('BBBB', 'Bravo'))
    - ['BBBB', 'Bravo']

Throws an error if there is already a tuple with the same primary key.

.. code-block:: python

    >>> try:
    ...     conn.insert('demo', ('BBBB', 'Bravo'))
    ... except Exception as exc:
    ...     print(exc)
    ... 
    (3, 'Duplicate key exists in unique index "pk" in space "demo" with old tuple - ["BBBB", "Bravo"] and new tuple - ["BBBB", "Bravo"]')

Replace
^^^^^^^

:meth:`~tarantool.Connection.replace` inserts the tuple
``('CCCC', 'Charlie')`` into the space ``demo``, if there is no tuple
with primary key ``'CCCC'``:

.. code-block:: python

    >>> conn.replace('demo', ('CCCC', 'Charlie'))
    - ['CCCC', 'Charlie']

If there is already a tuple with the same primary key, replaces it:

.. code-block:: python

    >>> conn.replace('demo', ('CCCC', 'Charlie-2'))
    - ['CCCC', 'Charlie-2']

Update
^^^^^^

:meth:`~tarantool.Connection.update` the tuple with id ``'BBBB'`` placing
the value ``'Bravo-2'`` into the field ``1``:

.. code-block:: python

    >>> conn.update('demo', 'BBBB', [('=', 1, 'Bravo-2')])
    - ['BBBB', 'Bravo-2']

Field numeration starts from zero, so the field ``0`` is the first element
in the tuple. Tarantool 2.3.1 and newer supports field name identifiers.

Upsert
^^^^^^

:meth:`~tarantool.Connection.upsert` inserts the tuple, if tuple with
id ``'DDDD'`` not exists. Otherwise, updates tuple fields.

.. code-block:: python

    >>> conn.upsert('demo', ('DDDD', 'Delta'), [('=', 1, 'Delta-2')])

    >>> conn.select('demo', 'DDDD')
    - ['DDDD', 'Delta']
    >>> conn.upsert('demo', ('DDDD', 'Delta'), [('=', 1, 'Delta-2')])

    >>> conn.select('demo', 'DDDD')
    - ['DDDD', 'Delta-2']

Delete
^^^^^^

:meth:`~tarantool.Connection.delete` a tuple identified by id ``'AAAA'``:

.. code-block:: python

    >>> conn.delete('demo', 'AAAA')
    - [('AAAA', 'Alpha')]

Creating a space instance
-------------------------

An instance of :class:`~tarantool.space.Space` is a named object to access
the key space.

Create a ``demo`` object that will be used to access the space 
with id ``'demo'``:

.. code-block:: python

    >>> demo = conn.space('demo')

You can use the space instance to do data manipulations without
specifying space id.

.. code-block:: python

    >>> demo.select('AAAA')
    - ['AAAA', 'Alpha']
    >>> demo.insert(('BBBB', 'Bravo'))
    - ['BBBB', 'Bravo']

Call server-side functions
--------------------------

:meth:`~tarantool.Connection.call` a stored Lua procedure:

.. code-block:: python

    >>> conn.call("my_add", (1, 2))
    - 3

Evaluate Lua code
-----------------

:meth:`~tarantool.Connection.eval` arbitrary Lua code on a server:

.. code-block:: python

    >>> lua_code = r"""
    ...     local a, b = ...
    ...     return a + b
    ... """
    >>> conn.eval(lua_code, (1, 2))
    - 3

Execute SQL query
-----------------

:meth:`~tarantool.Connection.execute` SQL query on a Tarantool server:

.. code-block:: python

    >>> conn.execute('insert into "demo" values (:id, :name)', {'id': 'BBBB', 'name': 'Bravo'})


Connecting to a cluster of servers
----------------------------------

Create a connection to several servers:

.. code-block:: python

    >>> import tarantool
    >>> conn = tarantool.ConnectionPool(
    ...        [{'host':'localhost', 'port':3301},
    ...        {'host':'localhost', 'port':3302}],
    ...        user='user', password='pass')

:class:`~tarantool.ConnectionPool` is best suited to work with
a single replicaset. Its API is the same as a single server
:class:`~tarantool.Connection`, but requests support ``mode``
parameter (a :class:`tarantool.Mode` value) to choose between
read-write and read-only pool instances:

.. code-block:: python

    >>> resp = conn.select('demo', 'AAAA', mode=tarantool.Mode.PREFER_RO)
    >>> resp
    - ['AAAA', 'Alpha']


Receiving out-of-band messages
----------------------------------

Receiving out-of-band messages from a server that uses box.session.push 
call is supported for methods: :meth:`~tarantool.Connection.call`, 
:meth:`~tarantool.Connection.eval`, :meth:`~tarantool.Connection.select`, 
:meth:`~tarantool.Connection.insert`, :meth:`~tarantool.Connection.replace`, 
:meth:`~tarantool.Connection.update`, :meth:`~tarantool.Connection.upsert`, 
:meth:`~tarantool.Connection.delete`.

To work with out-of-band messages, 2 optional arguments are used in 
the methods listed above:

 * `on_push` - callback, launched with the received data for each out-of-band message. Two arguments for this callback are expected:
   
    * the first is the received from an out-of-band message data.

    * the second is `on_push_ctx`, variable for working with callback context (for example, recording the result or pass data to callback).
 * `on_push_ctx` - result of the `on_push` work can be written to this variable, or through this variable you can pass data to `on_push` callback.

Below is an example of the proposed API with method :meth:`~tarantool.Connection.call` 
and :meth:`~tarantool.Connection.insert`. In the described example, before the end 
of the :meth:`~tarantool.Connection.call` and :meth:`~tarantool.Connection.insert`, 
out-of-band messages are processed via specified callback.

In the example below, two shells are used, in the first we will configure the server:

.. code-block:: lua

    fiber = require('fiber')
    box.cfg({listen = 3301})
    box.schema.user.grant(
        'guest',
        'read,write,execute',
        'universe'
    )
    function server_function()
        x = {0,0}
        while x[1] < 3 do
            x[1] = x[1] + 1
            fiber.sleep(1)
            box.session.push(x)
        end
        fiber.sleep(1)
        return x
    end

In the second shell, we will execute a :meth:`~tarantool.Connection.call` 
with receiving out-of-band messages from the server:

.. code-block:: python

    import tarantool

    def callback(data, on_push_ctx=[]):
        print('run callback with data: ', data)
        data[0][1] = data[0][1] + 1
        on_push_ctx.append(data)

    callback_res = []

    conn = tarantool.Connection(port=3301)
    res = conn.call(
        'server_function',
        on_push=callback, 
        on_push_ctx=callback_res
    )

    # receiving out-of-band messages, 
    # the conn.call is not finished yet.

    >>> run callback with data:  [[1, 0]]
    >>> run callback with data:  [[2, 0]]
    >>> run callback with data:  [[3, 0]]

    # the conn.call is finished now.

    print(res)
    >>> [3, 0]

    print(callback_res)
    >>> [[[1, 1]], [[2, 1]], [[3, 1]]]

Let's go back to the first shell with the server and 
create a space and a trigger for it:

.. code-block:: lua

    box.schema.create_space(
     'tester', {
      format = {
        {name = 'id', type = 'unsigned'},
        {name = 'name', type = 'string'},
      }
    })
    box.space.tester:create_index(
     'primary_index', {
      parts = {
        {field = 1, type = 'unsigned'},
      }
    })
    function on_replace_callback()
        x = {0,0}
        while x[1] < 300 do
            x[1] = x[1] + 100
            box.session.push(x)
        end
        return x
    end
    box.space.tester:on_replace(
        on_replace_callback
    )

Now, in the second shell, we will execute an :meth:`~tarantool.ConnectionPool.insert` 
with out-of-band message processing:

.. code-block:: python

    callback_res = []

    conn_pool = tarantool.ConnectionPool(
            [{'host':'localhost', 'port':3301}],
            user='guest')

    res = conn_pool.insert(
        'tester',
        (1, 'Mike'),
        on_push=callback,
        on_push_ctx=callback_res,
    )

    # receiving out-of-band messages, 
    # the conn_pool.insert is not finished yet.

    >>> run callback with data:  [[100, 0]]
    >>> run callback with data:  [[200, 0]]
    >>> run callback with data:  [[300, 0]]

    # the conn_pool.insert is finished now.

    print(res)
    >>> [1, 'Mike']

    print(callback_res)
    >>> [[[100, 1]], [[200, 1]], [[300, 1]]]
