From d174a4db9bcf6d2d8fff6e8472d0734a12148556 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Mon, 27 Feb 2023 14:19:54 +0300 Subject: [PATCH 1/2] internal: remove unreachable code in schema Part of #282 --- tarantool/schema.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tarantool/schema.py b/tarantool/schema.py index d4f13f6b..b60538e6 100644 --- a/tarantool/schema.py +++ b/tarantool/schema.py @@ -97,7 +97,6 @@ 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] From 4b457ea1c5a16542a1ef8ba37f879be1d1d1f0e6 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Mon, 27 Feb 2023 14:29:44 +0300 Subject: [PATCH 2/2] iproto: fix schema with constraints fetch Before this patch, only schemas with 3-level nesting were expected. Simple foreign keys schema has 4-level nesting. After this patch, nesting depth up to 32 is supported. (There are no known schemas with such nesting, but this should be enough for any future extensions.) Closes #282 --- CHANGELOG.md | 3 +++ tarantool/schema.py | 11 ++++++-- test/suites/lib/skip.py | 11 ++++++++ test/suites/test_schema.py | 54 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59fcc06e..357b95f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/tarantool/schema.py b/tarantool/schema.py index b60538e6..94f49048 100644 --- a/tarantool/schema.py +++ b/tarantool/schema.py @@ -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. @@ -102,7 +109,7 @@ def __init__(self, index_row, space): 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) @@ -159,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) diff --git a/test/suites/lib/skip.py b/test/suites/lib/skip.py index 5cc40d65..c37fba0d 100644 --- a/test/suites/lib/skip.py +++ b/test/suites/lib/skip.py @@ -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') diff --git a/test/suites/test_schema.py b/test/suites/test_schema.py index 7e26b1a0..55460018 100644 --- a/test/suites/test_schema.py +++ b/test/suites/test_schema.py @@ -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 @@ -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(): @@ -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"):