Skip to content

iproto: fix schema with constraints fetch #285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Discovery iproto features only for Tarantools since version 2.10.0 (#283).

### Fixed
- Schema fetch for spaces with foreign keys (#282).

## 0.12.0 - 2023-02-13

### Added
Expand Down
12 changes: 9 additions & 3 deletions tarantool/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
import tarantool.const as const


"""
Max possible known schema depth is 4 if foreign keys are used (since
Tarantool 2.10), but there are no restrictions in protocol.
"""
MAX_RECURSION_DEPTH = 32


class RecursionError(Error):
"""
Report the situation when max recursion depth is reached.
Expand Down Expand Up @@ -97,13 +104,12 @@ def __init__(self, index_row, space):
"""

self.iid = index_row[1]
self.name = index_row[2]
self.name = to_unicode(index_row[2])
self.index = index_row[3]
self.unique = index_row[4]
self.parts = []
try:
parts_raw = to_unicode_recursive(index_row[5], 3)
parts_raw = to_unicode_recursive(index_row[5], MAX_RECURSION_DEPTH)
except RecursionError as e:
errmsg = 'Unexpected index parts structure: ' + str(e)
raise SchemaError(errmsg)
Expand Down Expand Up @@ -160,7 +166,7 @@ def __init__(self, space_row, schema):
self.schema[self.name] = self
self.format = dict()
try:
format_raw = to_unicode_recursive(space_row[6], 3)
format_raw = to_unicode_recursive(space_row[6], MAX_RECURSION_DEPTH)
except RecursionError as e:
errmsg = 'Unexpected space format structure: ' + str(e)
raise SchemaError(errmsg)
Expand Down
11 changes: 11 additions & 0 deletions test/suites/lib/skip.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,14 @@ def skip_or_run_auth_type_test_call(self):

return skip_or_run_test_tarantool_call(self, '2.11.0',
'does not support auth type')

def skip_or_run_constraints_test(func):
"""Decorator to skip or run tests related to spaces with
schema constraints.

Tarantool supports schema constraints only since 2.10.0 version.
See https://github.com/tarantool/tarantool/issues/6436
"""

return skip_or_run_test_tarantool(func, '2.10.0',
'does not support schema constraints')
54 changes: 54 additions & 0 deletions test/suites/test_schema.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import sys
import unittest
import tarantool
import pkg_resources

from .lib.tarantool_server import TarantoolServer
from .lib.skip import skip_or_run_constraints_test
from tarantool.error import NotSupportedError


Expand Down Expand Up @@ -102,6 +105,33 @@ def setUpClass(self):
end
""")

if self.srv.admin.tnt_version >= pkg_resources.parse_version('2.10.0'):
self.srv.admin("""
box.schema.create_space(
'constr_tester_1', {
format = {
{ name = 'id', type = 'unsigned' },
{ name = 'payload', type = 'number' },
}
})
box.space.constr_tester_1:create_index('I1', { parts = {'id'} })

box.space.constr_tester_1:replace({1, 999})

box.schema.create_space(
'constr_tester_2', {
format = {
{ name = 'id', type = 'unsigned' },
{ name = 'table1_id', type = 'unsigned',
foreign_key = { fk_video = { space = 'constr_tester_1', field = 'id' } },
},
{ name = 'payload', type = 'number' },
}
})
box.space.constr_tester_2:create_index('I1', { parts = {'id'} })
box.space.constr_tester_2:create_index('I2', { parts = {'table1_id'} })
""")

def setUp(self):
# prevent a remote tarantool from clean our session
if self.srv.is_started():
Expand Down Expand Up @@ -541,8 +571,32 @@ def test_08_schema_fetch_disable_via_connection_pool(self):
self._run_test_schema_fetch_disable(self.pool_con_schema_disable,
mode=tarantool.Mode.ANY)

@skip_or_run_constraints_test
def test_09_foreign_key_info_fetched_to_schema(self):
self.assertIn('foreign_key', self.sch.get_space('constr_tester_2').format['table1_id'])

@skip_or_run_constraints_test
def test_10_foreign_key_valid_replace(self):
self.assertSequenceEqual(
self.con.replace('constr_tester_2', [1, 1, 623]),
[[1, 1, 623]])

@skip_or_run_constraints_test
def test_11_foreign_key_invalid_replace(self):
with self.assertRaisesRegex(tarantool.DatabaseError,
'foreign tuple was not found'):
self.con.replace('constr_tester_2', [2, 999, 623])

@classmethod
def tearDownClass(self):
# We need to drop spaces with foreign keys with predetermined order,
# otherwise remote server clean() will fail to clean up resources.
if self.srv.admin.tnt_version >= pkg_resources.parse_version('2.10.0'):
self.srv.admin("""
box.space.constr_tester_2:drop()
box.space.constr_tester_1:drop()
""")

self.con.close()
self.con_schema_disable.close()
if not sys.platform.startswith("win"):
Expand Down