Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c4760d4

Browse files
committedDec 1, 2022
api: native crud module support
Adds native api support for crud module [1] to use it from a connection object. 1. github.com/tarantool/crud Closes #205
1 parent 6c54109 commit c4760d4

File tree

14 files changed

+1777
-1
lines changed

14 files changed

+1777
-1
lines changed
 

‎.github/workflows/packing.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ jobs:
9999
- name: Install test requirements
100100
run: pip3 install -r requirements-test.txt
101101

102+
- name: Install the crud module for testing purposes
103+
run: tarantoolctl rocks install crud
104+
102105
- name: Run tests
103106
run: make test-pure-install
104107

@@ -143,6 +146,9 @@ jobs:
143146
- name: Install test requirements
144147
run: pip3 install -r requirements-test.txt
145148

149+
- name: Install the crud module for testing purposes
150+
run: tarantoolctl rocks install crud
151+
146152
- name: Setup WSL for tarantool
147153
uses: Vampire/setup-wsl@v1
148154
with:
@@ -331,6 +337,9 @@ jobs:
331337
- name: Install test requirements
332338
run: pip3 install -r requirements-test.txt
333339

340+
- name: Install the crud module for testing purposes
341+
run: tarantoolctl rocks install crud
342+
334343
- name: Run tests
335344
run: make test-pure-install
336345

@@ -492,6 +501,9 @@ jobs:
492501
- name: Install test requirements
493502
run: pip3 install -r requirements-test.txt
494503

504+
- name: Install the crud module for testing purposes
505+
run: tarantoolctl rocks install crud
506+
495507
- name: Run tests
496508
run: make test-pure-install
497509

‎.github/workflows/reusable_testing.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,7 @@ jobs:
3939
- name: Install test requirements
4040
run: pip3 install -r requirements-test.txt
4141

42+
- name: Install the crud module for testing purposes
43+
run: tarantoolctl rocks install crud
44+
4245
- run: make test

‎.github/workflows/testing.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ jobs:
8585
- name: Install test requirements
8686
run: pip3 install -r requirements-test.txt
8787

88+
- name: Install the crud module for testing purposes
89+
run: tarantoolctl rocks install crud
90+
8891
- name: Run tests
8992
run: make test
9093

@@ -141,6 +144,9 @@ jobs:
141144
- name: Install test requirements
142145
run: pip3 install -r requirements-test.txt
143146

147+
- name: Install the crud module for testing purposes
148+
run: tarantoolctl rocks install crud
149+
144150
- name: Run tests
145151
run: |
146152
source tarantool-enterprise/env.sh
@@ -194,6 +200,9 @@ jobs:
194200
- name: Install test requirements
195201
run: pip3 install -r requirements-test.txt
196202

203+
- name: Install the crud module for testing purposes
204+
run: tarantoolctl rocks install crud
205+
197206
- name: Run tests
198207
run: make test-pure-install
199208

@@ -236,6 +245,9 @@ jobs:
236245
- name: Install test requirements
237246
run: pip3 install -r requirements-test.txt
238247

248+
- name: Install the crud module for testing purposes
249+
run: tarantoolctl rocks install crud
250+
239251
- name: Setup WSL for tarantool
240252
uses: Vampire/setup-wsl@v1
241253
with:
@@ -316,6 +328,9 @@ jobs:
316328
- name: Install test requirements
317329
run: pip3 install -r requirements-test.txt
318330

331+
- name: Install the crud module for testing purposes
332+
run: tarantoolctl rocks install crud
333+
319334
- name: Setup WSL for tarantool
320335
uses: Vampire/setup-wsl@v1
321336
with:

‎CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Added
1010

11+
- Support [crud module](https://github.com/tarantool/crud) native API (#205).
12+
1113
### Changed
1214

1315
### Fixed

‎docs/source/api/submodule-crud.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module :py:mod:`tarantool.crud`
2+
================================
3+
4+
.. automodule:: tarantool.crud

‎docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ API Reference
6363
api/module-tarantool.rst
6464
api/submodule-connection.rst
6565
api/submodule-connection-pool.rst
66+
api/submodule-crud.rst
6667
api/submodule-dbapi.rst
6768
api/submodule-error.rst
6869
api/submodule-mesh-connection.rst

‎docs/source/quick-start.rst

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,154 @@ with out-of-band message processing:
347347
348348
print(callback_res)
349349
>>> [[[100, 1]], [[200, 1]], [[300, 1]]]
350+
351+
352+
Interaction with the crud module
353+
----------------------------------
354+
355+
Through the :class:`~tarantool.Connection` object, you can access
356+
`crud module <https://github.com/tarantool/crud>`_ methods:
357+
358+
.. code-block:: python
359+
360+
>>> import tarantool
361+
>>> from tarantool.error import CrudModuleError, DatabaseError
362+
>>> conn = tarantool.Connection(host='localhost',port=3301)
363+
364+
>>> conn.crud_
365+
conn.crud_count( conn.crud_insert( conn.crud_insert_object_many(
366+
conn.crud_min( conn.crud_replace_object( conn.crud_stats(
367+
conn.crud_unflatten_rows( conn.crud_upsert_many( conn.crud_delete(
368+
conn.crud_insert_many( conn.crud_len( conn.crud_replace(
369+
conn.crud_replace_object_many( conn.crud_storage_info( conn.crud_update(
370+
conn.crud_upsert_object( conn.crud_get( conn.crud_insert_object(
371+
conn.crud_max( conn.crud_replace_many( conn.crud_select(
372+
conn.crud_truncate( conn.crud_upsert( conn.crud_upsert_object_many(
373+
374+
As an example, consider :class:`~tarantool.Connection.crud_insert` and :class:`~tarantool.Connection.crud_insert_object_many`.
375+
It is recommended to enclose calls in the try-except construction as follows:
376+
377+
.. code-block:: python
378+
379+
# Try without exception:
380+
>>> try:
381+
... res = conn.crud_insert('tester', (3500,300,'Rob'))
382+
... except CrudModuleError as e:
383+
... exc_crud = e
384+
...
385+
>>> res
386+
<tarantool.crud.CrudResult object at 0x11a56e320>
387+
>>> res.
388+
res.metadata res.rows # fields like in lua implementation.
389+
>>> res.rows
390+
[[3500, 300, 'Rob']]
391+
>>> res.metadata
392+
[{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}]
393+
394+
# Try with exception:
395+
>>> try:
396+
... res = conn.crud_insert('tester', (3500,300,'Rob'))
397+
... except CrudModuleError as e:
398+
... exc_crud = e
399+
...
400+
>>> exc_crud
401+
CrudModuleError(None, <tarantool.crud.CrudError object at 0x10a276950>)
402+
>>> exc_crud.
403+
exc_crud.args exc_crud.err exc_crud.res exc_crud.with_traceback(
404+
>>> exc_crud.args
405+
(None, <tarantool.crud.CrudError object at 0x10a276950>)
406+
>>> exc_crud.err
407+
<tarantool.crud.CrudError object at 0x10a276950>
408+
>>> exc_crud.err. # fields like in lua implementation.
409+
exc_crud.err.class_name exc_crud.err.err exc_crud.err.file exc_crud.err.line exc_crud.err.str
410+
>>> exc_crud.err.class_name
411+
'InsertError'
412+
>>> exc_crud.err.str
413+
'InsertError: Failed to insert: Duplicate key exists in unique index "primary_index" in space "tester" with old tuple - [3500, 300, "Rob"] and new tuple - [3500, 300, "Rob"]'
414+
>>> exc_crud.res is None # using only in case of *_many.
415+
True
416+
417+
# In case of batch operation (*_many), CrudModuleError contains both result and errors:
418+
>>> try:
419+
... res = conn.crud_insert_object_many('tester', ({'id':1800,'bucket_id':100,'name':'Mike'}, {'id':2800,'bucket_id':100,'second_name':'Bill'}, {'id':3800,'bucket_id':100,'name':'Bob'}, {'id':1,'bucket_id':100,'name':'Rob'}), {'timeout':100, 'rollback_on_error':False})
420+
... except CrudModuleError as e:
421+
... exc_crud = e
422+
...
423+
>>> exc_crud
424+
CrudModuleError(<tarantool.crud.CrudResult object at 0x11a56f310>, [<tarantool.crud.CrudError object at 0x11a56e9e0>, <tarantool.crud.CrudError object at 0x11a56f490>])
425+
>>> exc_crud.
426+
exc_crud.args exc_crud.errs exc_crud.res exc_crud.with_traceback(
427+
>>> exc_crud.res # some of the lines were inserted.
428+
<tarantool.crud.CrudResult object at 0x11a56f310>
429+
>>> exc_crud.res.rows
430+
[[1800, 100, 'Mike'], [3800, 100, 'Bob']]
431+
>>> exc_crud.errs # some of the lines were not inserted.
432+
[<tarantool.crud.CrudError object at 0x11a56e9e0>, <tarantool.crud.CrudError object at 0x11a56f490>]
433+
>>> exc_crud.errs[0].str
434+
'CallError: Failed for 56d9c861-a8ac-459c-9715-09094ebb94a7: Function returned an error: Duplicate key exists in unique index "primary_index" in space "tester" with old tuple - [1, 100, "Mike"] and new tuple - [1, 100, "Rob"]'
435+
>>> exc_crud.errs[1].str
436+
'InsertManyError: Failed to flatten object: FlattenError: Object is specified in bad format: FlattenError: Unknown field "second_name" is specified'
437+
>>> exc_crud.args
438+
(<tarantool.crud.CrudResult object at 0x11a56f310>, [<tarantool.crud.CrudError object at 0x11a56e9e0>, <tarantool.crud.CrudError object at 0x11a56f490>])
439+
440+
If module crud not found on the router:
441+
442+
.. code-block:: python
443+
444+
>>> try:
445+
... res = conn.crud_insert('tester', (22221,300,'Rob'))
446+
... except CrudModuleError as e:
447+
... exc_crud = e
448+
... except DatabaseError as e:
449+
... exc_db = e
450+
...
451+
>>> exc_db
452+
DatabaseError(33, "Procedure 'crud.insert' is not defined. Ensure that you're calling crud.router and user have enough grants")
453+
>>> exc_db.
454+
exc_db.args exc_db.code exc_db.extra_info exc_db.message exc_db.with_traceback(
455+
>>> exc_db.args
456+
(33, "Procedure 'crud.insert' is not defined. Ensure that you're calling crud.router and user have enough grants")
457+
>>> exc_db.code
458+
33
459+
>>> exc_db.message
460+
"Procedure 'crud.insert' is not defined. Ensure that you're calling crud.router and user have enough grants"
461+
>>> exc_db.extra_info
462+
BoxError(type='ClientError', file='/tmp/tarantool-20221003-6335-edruh3/tarantool-2.10.3/src/box/lua/call.c', line=112, message="Procedure 'crud.insert' is not defined. Ensure that you're calling crud.router and user have enough grants", errno=0, errcode=33, fields=None, prev=None)
463+
464+
Using :class:`~tarantool.Connection.crud_select` and :class:`~tarantool.Connection.crud_unflatten_rows`:
465+
466+
.. code-block:: python
467+
468+
>>> try:
469+
... res = conn.crud_select('tester', {}, {'first':2})
470+
... except CrudModuleError as e:
471+
... exc_crud = e
472+
...
473+
>>> res
474+
<tarantool.crud.CrudResult object at 0x10a276d10>
475+
>>> res.
476+
res.metadata res.rows
477+
>>> res.rows
478+
[[1, 100, 'Mike'], [2, 100, 'Mike']]
479+
>>> res.metadata
480+
[{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}]
481+
>>> r = conn.crud_unflatten_rows(res.rows, res.metadata)
482+
>>> r
483+
[{'id': 1, 'bucket_id': 100, 'name': 'Mike'}, {'id': 2, 'bucket_id': 100, 'name': 'Mike'}]
484+
485+
Using :class:`~tarantool.Connection.crud_truncate` and :class:`~tarantool.Connection.crud_len`:
486+
487+
.. code-block:: python
488+
489+
>>> try:
490+
... res = conn.crud_len('tester')
491+
... except CrudModuleError as e:
492+
... exc_crud = e
493+
>>> res
494+
26
495+
>>> try:
496+
... res = conn.crud_truncate('tester')
497+
... except CrudModuleError as e:
498+
... exc_crud = e
499+
>>> res
500+
True

‎tarantool/connection.py

Lines changed: 776 additions & 0 deletions
Large diffs are not rendered by default.

‎tarantool/crud.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
This module provides API for interaction with a crud module.
3+
"""
4+
5+
from tarantool.error import DatabaseError
6+
7+
8+
class CrudResponse(object):
9+
"""
10+
Contains response fields from the `crud`_ module that correspond to the lua implementation.
11+
12+
.. _crud: https://github.com/tarantool/crud/
13+
"""
14+
15+
def __init__(self, response):
16+
"""
17+
Sets response fields as in lua implementation.
18+
19+
:param response: The response object of the crud module call.
20+
:type response: :class:`~tarantool.response.Response`
21+
"""
22+
23+
if isinstance(response, dict):
24+
for response_field_name in response.keys():
25+
if isinstance(response_field_name, bytes):
26+
setattr(self, response_field_name.decode(), response[response_field_name])
27+
else:
28+
setattr(self, response_field_name, response[response_field_name])
29+
elif isinstance(response, str) or isinstance(response, bytes):
30+
self.str = response
31+
self.err = response
32+
else:
33+
raise RuntimeError('Unable to decode response to object due to unknown type')
34+
35+
36+
class CrudResult(CrudResponse):
37+
"""
38+
Contains result's fields from result variable of crud module operation.
39+
"""
40+
41+
42+
class CrudError(CrudResponse):
43+
"""
44+
Contains error's fields from error variable of crud module operation.
45+
"""
46+
47+
48+
def wrapCrudUndefinedProcedureError(e: DatabaseError):
49+
"""
50+
Supplements information about an unsuccessful attempt to find the crud module procedure.
51+
52+
:param e: The error object of the crud module call.
53+
:type e: :class:`~tarantool.error.DatabaseError`
54+
"""
55+
56+
info = ". Ensure that you're calling crud.router and user have enough grants"
57+
e.message = e.message + info
58+
e.extra_info.message = e.extra_info.message + info
59+
e.args = (e.args[0], e.args[1] + info)
60+
61+
return e
62+
63+
def call_crud(conn, *args):
64+
"""
65+
Calls the crud via connection.call with try/except block.
66+
67+
:param conn: The connection object for the crud module call.
68+
:type conn: :class:`~tarantool.connection.Connection`
69+
70+
:param args: The arguments to pass to the crud module.
71+
:type args: :obj:`tuple`
72+
"""
73+
74+
try:
75+
crud_resp = conn.call(*args)
76+
except DatabaseError as e:
77+
if e.code == 33:
78+
e = wrapCrudUndefinedProcedureError(e)
79+
raise e
80+
81+
return crud_resp

‎tarantool/error.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,33 @@ class PoolTolopogyError(DatabaseError):
330330
pass
331331

332332

333+
class CrudModuleError(DatabaseError):
334+
"""
335+
Exception raised for errors that are related to the operation result of `crud`_ module.
336+
337+
.. _crud: https://github.com/tarantool/crud
338+
"""
339+
340+
def __init__(self, *args):
341+
"""
342+
Sets fields with result and errors.
343+
344+
:param args: The tuple from the crud module with result and errors.
345+
:type args: :obj:`tuple`
346+
"""
347+
348+
# Sets tarantool.crud.CrudResult object.
349+
self.res = args[0]
350+
351+
if isinstance(args[1], (tuple, list)):
352+
# Case for crud_*_many().
353+
# Sets list of tarantool.crud.CrudError objects.
354+
self.errs = args[1]
355+
else:
356+
# Sets tarantool.crud.CrudError object.
357+
self.err = args[1]
358+
359+
333360
# always print this warnings
334361
warnings.filterwarnings("always", category=NetworkWarning)
335362

‎test/suites/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@
2222
from .test_package import TestSuite_Package
2323
from .test_error_ext import TestSuite_ErrorExt
2424
from .test_push import TestSuite_Push
25+
from .test_crud import TestSuite_Crud
2526

2627
test_cases = (TestSuite_Schema_UnicodeConnection,
2728
TestSuite_Schema_BinaryConnection,
2829
TestSuite_Request, TestSuite_Protocol, TestSuite_Reconnect,
2930
TestSuite_Mesh, TestSuite_Execute, TestSuite_DBAPI,
3031
TestSuite_Encoding, TestSuite_Pool, TestSuite_Ssl,
3132
TestSuite_Decimal, TestSuite_UUID, TestSuite_Datetime,
32-
TestSuite_Interval, TestSuite_ErrorExt, TestSuite_Push,)
33+
TestSuite_Interval, TestSuite_ErrorExt, TestSuite_Push,
34+
TestSuite_Crud,)
3335

3436
def load_tests(loader, tests, pattern):
3537
suite = unittest.TestSuite()

‎test/suites/crud_server.lua

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env tarantool
2+
3+
os = require('os')
4+
crud = require('crud')
5+
vshard = require('vshard')
6+
7+
admin_listen = os.getenv("ADMIN")
8+
primary_listen = os.getenv("LISTEN")
9+
10+
require('console').listen(admin_listen)
11+
box.cfg{
12+
listen = primary_listen,
13+
memtx_memory = 0.1 * 1024^3, -- 0.1 GiB
14+
pid_file = "box.pid",
15+
}
16+
17+
box.schema.user.grant(
18+
'guest',
19+
'read,write,execute',
20+
'universe'
21+
)
22+
box.schema.create_space(
23+
'tester', {
24+
format = {
25+
{name = 'id', type = 'unsigned'},
26+
{name = 'bucket_id', type = 'unsigned'},
27+
{name = 'name', type = 'string'},
28+
}
29+
})
30+
box.space.tester:create_index('primary_index', {
31+
parts = {
32+
{field = 1, type = 'unsigned'},
33+
},
34+
})
35+
box.space.tester:create_index('bucket_id', {
36+
parts = {
37+
{field = 2, type = 'unsigned'},
38+
},
39+
unique = false,
40+
})
41+
42+
-- Setup vshard.
43+
_G.vshard = vshard
44+
box.once('guest', function()
45+
box.schema.user.grant('guest', 'super')
46+
end)
47+
local uri = 'guest@localhost:' .. primary_listen
48+
local cfg = {
49+
bucket_count = 300,
50+
sharding = {
51+
[box.info().cluster.uuid] = {
52+
replicas = {
53+
[box.info().uuid] = {
54+
uri = uri,
55+
name = 'storage',
56+
master = true,
57+
},
58+
},
59+
},
60+
},
61+
}
62+
vshard.storage.cfg(cfg, box.info().uuid)
63+
vshard.router.cfg(cfg)
64+
vshard.router.bootstrap()
65+
66+
-- Initialize crud.
67+
crud.init_storage()
68+
crud.init_router()

‎test/suites/crud_server_cfg.lua

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env tarantool
2+
3+
os = require('os')
4+
crud = require('crud')
5+
vshard = require('vshard')
6+
7+
box.cfg{
8+
listen = 3301,
9+
}
10+
11+
box.schema.user.grant(
12+
'guest',
13+
'read,write,execute',
14+
'universe'
15+
)
16+
box.schema.create_space(
17+
'tester', {
18+
format = {
19+
{name = 'id', type = 'unsigned'},
20+
{name = 'bucket_id', type = 'unsigned'},
21+
{name = 'name', type = 'string'},
22+
}
23+
})
24+
box.space.tester:create_index('primary_index', {
25+
parts = {
26+
{field = 1, type = 'unsigned'},
27+
},
28+
})
29+
box.space.tester:create_index('bucket_id', {
30+
parts = {
31+
{field = 2, type = 'unsigned'},
32+
},
33+
unique = false,
34+
})
35+
36+
-- Setup vshard.
37+
_G.vshard = vshard
38+
box.once('guest', function()
39+
box.schema.user.grant('guest', 'super')
40+
end)
41+
local uri = 'guest@localhost:' .. '3301'
42+
local cfg = {
43+
bucket_count = 3000,
44+
sharding = {
45+
[box.info().cluster.uuid] = {
46+
replicas = {
47+
[box.info().uuid] = {
48+
uri = uri,
49+
name = 'storage',
50+
master = true,
51+
},
52+
},
53+
},
54+
},
55+
}
56+
vshard.storage.cfg(cfg, box.info().uuid)
57+
vshard.router.cfg(cfg)
58+
vshard.router.bootstrap()
59+
60+
-- Initialize crud.
61+
crud.init_storage()
62+
crud.init_router()
63+
64+
require('console').start()

‎test/suites/test_crud.py

Lines changed: 570 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.