tarantool / tarantool-python

Python client library for Tarantool
https://www.tarantool.io
BSD 2-Clause "Simplified" License
100 stars 48 forks source link

Executing stored procedures with List type input #162

Closed enescaglar closed 4 years ago

enescaglar commented 4 years ago

Hi,

I am trying to call a function on tarantool which inserts given list input into a space. Function looks like below, it gets list, loop over records and inserts into a space named tester. When I call the function within tarantool like the following, it works as expected and inserts records into the space.

tarantool> lst = {{1,'Test',1}, {2, 'Test2', 2}} tarantool> batch_insert_tester(lst)

When I try to call the function through the library, it fails with the following error.

raise DatabaseError(self._return_code, self._return_message) tarantool.error.DatabaseError: (32, '[string "function batch_insert_tester(list)..."]:4: attempt to index local \'record\' (a number value)')

This is the code I have been using, as an input I am sending list of value tuples (tester space has 3 fields). How can I call a function with a list input like this?

connection = tarantool.connect(...) tester_data = [] tester_data.append((3, 'ABBA', 3)) tester_data.append((4, 'ABBA', 4)) connection.call('batch_insert_tester', tester_data)

Here is the function defined on tarantool:

function batch_inserttester(list) box.begin() for , record in ipairs(list) do box.space.tester:replace{record[1], record[2], record[3]} end box.commit() end

Totktonada commented 4 years ago

In short: connection.call('batch_insert_tester', [tester_data]) (the second argument is a list of arguments to pass into the Lua function).

I'll give a bit background soon.

enescaglar commented 4 years ago

Ok, thank you this worked. I passed the arguments as tuple, when given as a list input it worked like you said. If the documentation can be improved by adding a sample call with a list input (if there is already I might have missed it) would be great. If there is a way that I can contribute to the documentation, I would be happy to help as well.

Appreciate the answer.

Totktonada commented 4 years ago

Sorry for the long read. It is for ones who will confused by the same API details and will want to understand what is going on.

Let's define a function to use in the example below (very similar to your one):

function log_tuples(tuples)
    for _, t in ipairs(tuples) do
        require('log').info('tuple: ' .. require('json').encode(t))
    end
end

(Note: It is written for ease experimenting: it is not good style for an application or a library code. Please, avoid global functions and requires inside a function where possible.)

First, let's look as the net.box — tarantool built-in connector — as the example.

tuples = {}
table.insert(tuples, {1, 2, 3})
table.insert(tuples, {4, 5, 6})
table.insert(tuples, {7, 8, 9})
connection:call('log_tuples', {tuples})

Result:

2020-08-03 23:02:54.340 [24265] main/119/main I> tuple: [1,2,3]
2020-08-03 23:02:54.340 [24265] main/119/main I> tuple: [4,5,6]
2020-08-03 23:02:54.340 [24265] main/119/main I> tuple: [7,8,9]

Why we need to wrap the argument into the list? Because one may want to pass several arguments to a function, say:

function log_tuples(tuples, prefix)
    local prefix = prefix or ''
    for _, t in ipairs(tuples) do
        require('log').info(prefix .. 'tuple: ' .. require('json').encode(t))
    end
end

Check:

tuples = {}
table.insert(tuples, {1, 2, 3})
table.insert(tuples, {4, 5, 6})
table.insert(tuples, {7, 8, 9})
connection:call('log_tuples', {tuples, 'my_app | '})

Result:

2020-08-03 23:07:56.260 [24265] main/121/main I> my_app | tuple: [1,2,3]
2020-08-03 23:07:56.260 [24265] main/121/main I> my_app | tuple: [4,5,6]
2020-08-03 23:07:56.260 [24265] main/121/main I> my_app | tuple: [7,8,9]

Back to Python. Everything is very same as for net.box:

import tarantool
t = tarantool.connect('localhost', 3301)
tuples = []
tuples.append((1, 2, 3))
tuples.append((4, 5, 6))
tuples.append((7, 8, 9))
t.call('log_tuples', [tuples])

The result is the same as for the first net.box call.

However there is strange peculiar in the tarantool-python API (the code below is part of the call method):

https://github.com/tarantool/tarantool-python/blob/bd37703cdb50983a681a08891659c5b0754be8c6/tarantool/connection.py#L375-L377

So if several arguments are passed to the call method and the first one is a list or a tuple, then a caller don't need to wrap arguments into the list. The following two calls are equivalent (when tuples is a list or a tuple, of course):

t.call('log_tuples', [tuples, 'my_app | '])
t.call('log_tuples', tuples, 'my_app | ')

Why? Who knows... I guess somebody in the past had the thought that it is convenient to have such autoguessing.

Totktonada commented 4 years ago

BTW, there is the Telegram channel, where tarantool developers and power users answer questions interactively. Questions are usually answered faster in the chat.

If the documentation can be improved by adding a sample call with a list input (if there is already I might have missed it) would be great. If there is a way that I can contribute to the documentation, I would be happy to help as well.

The is the documentation (the link is in the header of README) with the following relevant pages:

I would not say that the documentation is satisfying. The documentation is generated from the doc/ directory in the source code (when a new version of the connector is released). Feel free to open a pull request against this repository.

We're not always very responsible (shame on me), so expect a delay between sending PR and review / merge.

I think this issue is resolved and should be closed, but if you have a clear plan around the documentation, let's file another one about it.

enescaglar commented 4 years ago

Thank you for all the detailed explanation and the code samples and for sharing the telegram channel info, I will look into it for further questions/discussions.