diff --git a/src/box/errcode.h b/src/box/errcode.h index af28a92651f0b2971f8d74bfffd183c099df245b..3d00c2941f8c65fa5331e5925e3db871dbc25674 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -91,7 +91,7 @@ struct errcode_record { /* 36 */_(ER_NO_SUCH_SPACE, "Space '%s' does not exist") \ /* 37 */_(ER_NO_SUCH_FIELD, "Field %d was not found in the tuple") \ /* 38 */_(ER_EXACT_FIELD_COUNT, "Tuple field count %u does not match space field count %u") \ - /* 39 */_(ER_INDEX_FIELD_COUNT, "Tuple field count %u is less than required by a defined index (expected %u)") \ + /* 39 */_(ER_INDEX_FIELD_COUNT, "Tuple field count %u is less than required (expected at least %u)") \ /* 40 */_(ER_WAL_IO, "Failed to write to disk") \ /* 41 */_(ER_MORE_THAN_ONE_TUPLE, "Get() doesn't support partial keys and non-unique indexes") \ /* 42 */_(ER_ACCESS_DENIED, "%s access on %s is denied for user '%s'") \ diff --git a/src/box/tuple.c b/src/box/tuple.c index 6bfea3e6a5ef208b514b9b289174969eba18c2de..e6dd5db44ede9d07411146334e992035a50e1803 100644 --- a/src/box/tuple.c +++ b/src/box/tuple.c @@ -130,16 +130,18 @@ tuple_validate_raw(struct tuple_format *format, const char *tuple) (unsigned) format->exact_field_count); return -1; } - if (unlikely(field_count < format->field_count)) { + if (unlikely(field_count < format->min_field_count)) { diag_set(ClientError, ER_INDEX_FIELD_COUNT, (unsigned) field_count, - (unsigned) format->field_count); + (unsigned) format->min_field_count); return -1; } /* Check field types */ - for (uint32_t i = 0; i < format->field_count; i++) { - struct tuple_field *field = &format->fields[i]; + struct tuple_field *field = &format->fields[0]; + uint32_t i = 0; + uint32_t defined_field_count = MIN(field_count, format->field_count); + for (; i < defined_field_count; ++i, ++field) { if (key_mp_type_validate(field->type, mp_typeof(*tuple), ER_FIELD_TYPE, i + TUPLE_INDEX_BASE, field->is_nullable)) diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c index 9e08df5e51b2006d1f1d86488ed85a3a2087f8e8..819cdc71edda6c66fd980a09147b489ea2983d84 100644 --- a/src/box/tuple_format.c +++ b/src/box/tuple_format.c @@ -137,6 +137,8 @@ tuple_format_create(struct tuple_format *format, struct key_def * const *keys, if (tuple_format_add_name(format, name_pos, len, i, true) != 0) return -1; name_pos += len + 1; + if (i + 1 > format->min_field_count && !fields[i].is_nullable) + format->min_field_count = i + 1; } /* Initialize remaining fields */ for (uint32_t i = field_count; i < format->field_count; i++) @@ -303,6 +305,7 @@ tuple_format_alloc(struct key_def * const *keys, uint16_t key_count, format->field_count = field_count; format->index_field_count = index_field_count; format->exact_field_count = 0; + format->min_field_count = index_field_count; return format; error_name_hash_reserve: @@ -453,10 +456,10 @@ tuple_init_field_map(const struct tuple_format *format, uint32_t *field_map, (unsigned) format->exact_field_count); return -1; } - if (unlikely(field_count < format->field_count)) { + if (unlikely(field_count < format->min_field_count)) { diag_set(ClientError, ER_INDEX_FIELD_COUNT, (unsigned) field_count, - (unsigned) format->field_count); + (unsigned) format->min_field_count); return -1; } @@ -469,7 +472,9 @@ tuple_init_field_map(const struct tuple_format *format, uint32_t *field_map, mp_next(&pos); /* other fields...*/ ++field; - for (uint32_t i = 1; i < format->field_count; i++, ++field) { + uint32_t i = 1; + uint32_t defined_field_count = MIN(field_count, format->field_count); + for (; i < defined_field_count; ++i, ++field) { mp_type = mp_typeof(*pos); if (key_mp_type_validate(field->type, mp_type, ER_FIELD_TYPE, i + TUPLE_INDEX_BASE, diff --git a/src/box/tuple_format.h b/src/box/tuple_format.h index f915f80734c680740e0dc3f96ef8014343170d82..bb92e14bbc739c3536fca70f7d9b516bf0ed1248 100644 --- a/src/box/tuple_format.h +++ b/src/box/tuple_format.h @@ -137,6 +137,11 @@ struct tuple_format { * element is used by an index. */ uint32_t index_field_count; + /** + * The minimal field count that must be specified. + * index_field_count <= min_field_count <= field_count. + */ + uint32_t min_field_count; /* Length of 'fields' array. */ uint32_t field_count; /** Field names hash. Key - name, value - field number. */ diff --git a/src/box/vy_stmt.c b/src/box/vy_stmt.c index ea28c77c2ce974ec16bfae510c4f7a2210950ae7..df545ac3be01bd49199de996128104006686732b 100644 --- a/src/box/vy_stmt.c +++ b/src/box/vy_stmt.c @@ -246,7 +246,7 @@ vy_stmt_new_with_ops(struct tuple_format *format, const char *tuple_begin, const char *tmp = tuple_begin; uint32_t field_count = mp_decode_array(&tmp); - assert(field_count >= format->field_count); + assert(field_count >= format->min_field_count); (void) field_count; size_t ops_size = 0; diff --git a/test/box/alter.result b/test/box/alter.result index 88808d4fc0f2aca162ef2a43cdf3b80d6e9d6ef4..0b8ffe90fed298fa0378ce042261fa7f0563e856 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -984,11 +984,11 @@ s:replace{1, '2', {3, 3}, 4.4, -5, true, {7}, 8, 9} ... s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}} --- -- error: Tuple field count 7 is less than required by a defined index (expected 9) +- error: Tuple field count 7 is less than required (expected at least 9) ... s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8} --- -- error: Tuple field count 8 is less than required by a defined index (expected 9) +- error: Tuple field count 8 is less than required (expected at least 9) ... s:truncate() --- @@ -1478,7 +1478,7 @@ s:format(format) -- Fail, not enough fields. s:replace{2, 2, 2, 2, 2} --- -- error: Tuple field count 5 is less than required by a defined index (expected 6) +- error: Tuple field count 5 is less than required (expected at least 6) ... s:replace{2, 2, 2, 2, 2, 2, 2} --- @@ -1490,7 +1490,7 @@ format[7] = {name = 'field7', type = 'unsigned'} -- Fail, the tuple {1, ... 1} is invalid for a new format. s:format(format) --- -- error: Tuple field count 6 is less than required by a defined index (expected 7) +- error: Tuple field count 6 is less than required (expected at least 7) ... s:drop() --- diff --git a/test/box/alter_limits.result b/test/box/alter_limits.result index cd9a28945537e8a01ef935e93a8e09c9d95d551d..5252cd9854203692eeb53226ee8a61d533245972 100644 --- a/test/box/alter_limits.result +++ b/test/box/alter_limits.result @@ -845,7 +845,7 @@ index = s:create_index('string', { type = 'tree', unique = false, parts = { 2, -- create index on a non-existing field index = s:create_index('nosuchfield', { type = 'tree', unique = true, parts = { 3, 'string'}}) --- -- error: Tuple field count 2 is less than required by a defined index (expected 3) +- error: Tuple field count 2 is less than required (expected at least 3) ... s.index.year:drop() --- @@ -866,7 +866,7 @@ s:replace{'Der Baader Meinhof Komplex'} ... index = s:create_index('year', { type = 'tree', unique = false, parts = { 2, 'unsigned'}}) --- -- error: Tuple field count 1 is less than required by a defined index (expected 2) +- error: Tuple field count 1 is less than required (expected at least 2) ... s:drop() --- diff --git a/test/box/ddl.result b/test/box/ddl.result index 3f086f9f67174e53e2d80376048c9cea0f837e66..20808c32c3e0951ac35c1d3aa1e17d7599564cc4 100644 --- a/test/box/ddl.result +++ b/test/box/ddl.result @@ -313,27 +313,27 @@ box.internal.collation.drop('test') ... box.space._collation:auto_increment{'test'} --- -- error: Tuple field count 2 is less than required by a defined index (expected 6) +- error: Tuple field count 2 is less than required (expected at least 6) ... box.space._collation:auto_increment{'test', 0, 'ICU'} --- -- error: Tuple field count 4 is less than required by a defined index (expected 6) +- error: Tuple field count 4 is less than required (expected at least 6) ... box.space._collation:auto_increment{'test', 'ADMIN', 'ICU', 'ru_RU'} --- -- error: Tuple field count 5 is less than required by a defined index (expected 6) +- error: Tuple field count 5 is less than required (expected at least 6) ... box.space._collation:auto_increment{42, 0, 'ICU', 'ru_RU'} --- -- error: Tuple field count 5 is less than required by a defined index (expected 6) +- error: Tuple field count 5 is less than required (expected at least 6) ... box.space._collation:auto_increment{'test', 0, 42, 'ru_RU'} --- -- error: Tuple field count 5 is less than required by a defined index (expected 6) +- error: Tuple field count 5 is less than required (expected at least 6) ... box.space._collation:auto_increment{'test', 0, 'ICU', 42} --- -- error: Tuple field count 5 is less than required by a defined index (expected 6) +- error: Tuple field count 5 is less than required (expected at least 6) ... box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} --ok --- diff --git a/test/box/sql.result b/test/box/sql.result index 3c4a11913ccdca1dde93f514568cd776621ca7a0..a82f6c68d3c71c05ba41dcbce709a9537e040d1a 100644 --- a/test/box/sql.result +++ b/test/box/sql.result @@ -296,7 +296,7 @@ s:truncate() -- get away with it. space:insert{'Britney'} --- -- error: Tuple field count 1 is less than required by a defined index (expected 2) +- error: Tuple field count 1 is less than required (expected at least 2) ... sorted(space.index.secondary:select('Anything')) --- @@ -304,7 +304,7 @@ sorted(space.index.secondary:select('Anything')) ... space:insert{'Stephanie'} --- -- error: Tuple field count 1 is less than required by a defined index (expected 2) +- error: Tuple field count 1 is less than required (expected at least 2) ... sorted(space.index.secondary:select('Anything')) --- @@ -633,7 +633,7 @@ sorted(space.index.secondary:select('Britney')) -- try to insert the incoplete tuple space:replace{'Spears'} --- -- error: Tuple field count 1 is less than required by a defined index (expected 2) +- error: Tuple field count 1 is less than required (expected at least 2) ... -- check that nothing has been updated space:select{'Spears'} diff --git a/test/box/tree_pk_multipart.result b/test/box/tree_pk_multipart.result index d248eac7f114c5cf4697a8980d08130242918d83..054ceed69aff1033d511b0cb7a7da299449130dc 100644 --- a/test/box/tree_pk_multipart.result +++ b/test/box/tree_pk_multipart.result @@ -490,11 +490,11 @@ i1 = space:create_index('primary', { type = 'tree', parts = {1, 'unsigned', 3, ' ... space:insert{1, 1} --- -- error: Tuple field count 2 is less than required by a defined index (expected 3) +- error: Tuple field count 2 is less than required (expected at least 3) ... space:replace{1, 1} --- -- error: Tuple field count 2 is less than required by a defined index (expected 3) +- error: Tuple field count 2 is less than required (expected at least 3) ... space:drop() --- diff --git a/test/engine/ddl.result b/test/engine/ddl.result index 5e3b43791629230104f025674d5d0b66bc113908..46bdff261f008c56dc9534ad800abdec04447f33 100644 --- a/test/engine/ddl.result +++ b/test/engine/ddl.result @@ -77,7 +77,7 @@ index = space:create_index('primary', {type = 'tree', parts = {1, 'unsigned', 2, ... space:insert({13}) --- -- error: Tuple field count 1 is less than required by a defined index (expected 2) +- error: Tuple field count 1 is less than required (expected at least 2) ... space:drop() --- diff --git a/test/engine/null.result b/test/engine/null.result index 0b78625b772225ceccde4560972e863f7166c4f4..dfe89b69c043b51eb19227b1cc1354f10213fa52 100644 --- a/test/engine/null.result +++ b/test/engine/null.result @@ -455,3 +455,162 @@ sk2:select{} s:drop() --- ... +-- +-- gh-2880: allow to store less field count than specified in a +-- format. +-- +format = {} +--- +... +format[1] = {name = 'field1', type = 'unsigned'} +--- +... +format[2] = {name = 'field2', type = 'unsigned'} +--- +... +format[3] = {name = 'field3'} +--- +... +format[4] = {name = 'field4', is_nullable = true} +--- +... +s = box.schema.create_space('test', {engine = engine, format = format}) +--- +... +pk = s:create_index('pk') +--- +... +sk = s:create_index('sk', {parts = {2, 'unsigned'}}) +--- +... +s:replace{1, 2} -- error +--- +- error: Tuple field count 2 is less than required (expected at least 3) +... +t1 = s:replace{2, 3, 4} +--- +... +t2 = s:replace{3, 4, 5, 6} +--- +... +t1.field1, t1.field2, t1.field3, t1.field4 +--- +- 2 +- 3 +- 4 +- null +... +t2.field1, t2.field2, t2.field3, t2.field4 +--- +- 3 +- 4 +- 5 +- 6 +... + -- Ensure the tuple is read ok from disk in a case of vinyl. +--- +... +if engine == 'vinyl' then box.snapshot() end +--- +... +s:select{2} +--- +- - [2, 3, 4] +... +s:drop() +--- +... +-- Check the case when not contiguous format tail is nullable. +format = {} +--- +... +format[1] = {name = 'field1', type = 'unsigned'} +--- +... +format[2] = {name = 'field2', type = 'unsigned'} +--- +... +format[3] = {name = 'field3'} +--- +... +format[4] = {name = 'field4', is_nullable = true} +--- +... +format[5] = {name = 'field5'} +--- +... +format[6] = {name = 'field6', is_nullable = true} +--- +... +format[7] = {name = 'field7', is_nullable = true} +--- +... +s = box.schema.create_space('test', {engine = engine, format = format}) +--- +... +pk = s:create_index('pk') +--- +... +sk = s:create_index('sk', {parts = {2, 'unsigned'}}) +--- +... +s:replace{1, 2} -- error +--- +- error: Tuple field count 2 is less than required (expected at least 5) +... +s:replace{2, 3, 4} -- error +--- +- error: Tuple field count 3 is less than required (expected at least 5) +... +s:replace{3, 4, 5, 6} -- error +--- +- error: Tuple field count 4 is less than required (expected at least 5) +... +t1 = s:replace{4, 5, 6, 7, 8} +--- +... +t2 = s:replace{5, 6, 7, 8, 9, 10} +--- +... +t3 = s:replace{6, 7, 8, 9, 10, 11, 12} +--- +... +t1.field1, t1.field2, t1.field3, t1.field4, t1.field5, t1.field6, t1.field7 +--- +- 4 +- 5 +- 6 +- 7 +- 8 +- null +- null +... +t2.field1, t2.field2, t2.field3, t2.field4, t2.field5, t2.field6, t2.field7 +--- +- 5 +- 6 +- 7 +- 8 +- 9 +- 10 +- null +... +t3.field1, t3.field2, t3.field3, t3.field4, t3.field5, t3.field6, t3.field7 +--- +- 6 +- 7 +- 8 +- 9 +- 10 +- 11 +- 12 +... +s:select{} +--- +- - [4, 5, 6, 7, 8] + - [5, 6, 7, 8, 9, 10] + - [6, 7, 8, 9, 10, 11, 12] +... +s:drop() +--- +... diff --git a/test/engine/null.test.lua b/test/engine/null.test.lua index 2755a8fc653e57871a6a45010c28f458ca95a851..7539d3bd6233267ebc02a5903e0f7fbe6dd0db5b 100644 --- a/test/engine/null.test.lua +++ b/test/engine/null.test.lua @@ -155,3 +155,53 @@ sk:select{} sk2:select{} s:drop() + +-- +-- gh-2880: allow to store less field count than specified in a +-- format. +-- +format = {} +format[1] = {name = 'field1', type = 'unsigned'} +format[2] = {name = 'field2', type = 'unsigned'} +format[3] = {name = 'field3'} +format[4] = {name = 'field4', is_nullable = true} +s = box.schema.create_space('test', {engine = engine, format = format}) +pk = s:create_index('pk') +sk = s:create_index('sk', {parts = {2, 'unsigned'}}) + +s:replace{1, 2} -- error +t1 = s:replace{2, 3, 4} +t2 = s:replace{3, 4, 5, 6} +t1.field1, t1.field2, t1.field3, t1.field4 +t2.field1, t2.field2, t2.field3, t2.field4 + -- Ensure the tuple is read ok from disk in a case of vinyl. +if engine == 'vinyl' then box.snapshot() end +s:select{2} + +s:drop() + +-- Check the case when not contiguous format tail is nullable. +format = {} +format[1] = {name = 'field1', type = 'unsigned'} +format[2] = {name = 'field2', type = 'unsigned'} +format[3] = {name = 'field3'} +format[4] = {name = 'field4', is_nullable = true} +format[5] = {name = 'field5'} +format[6] = {name = 'field6', is_nullable = true} +format[7] = {name = 'field7', is_nullable = true} +s = box.schema.create_space('test', {engine = engine, format = format}) +pk = s:create_index('pk') +sk = s:create_index('sk', {parts = {2, 'unsigned'}}) + +s:replace{1, 2} -- error +s:replace{2, 3, 4} -- error +s:replace{3, 4, 5, 6} -- error +t1 = s:replace{4, 5, 6, 7, 8} +t2 = s:replace{5, 6, 7, 8, 9, 10} +t3 = s:replace{6, 7, 8, 9, 10, 11, 12} +t1.field1, t1.field2, t1.field3, t1.field4, t1.field5, t1.field6, t1.field7 +t2.field1, t2.field2, t2.field3, t2.field4, t2.field5, t2.field6, t2.field7 +t3.field1, t3.field2, t3.field3, t3.field4, t3.field5, t3.field6, t3.field7 +s:select{} + +s:drop() diff --git a/test/vinyl/constraint.result b/test/vinyl/constraint.result index 0eda24ce07eebdc0ffd4bf09502e80bf1c9ecd4d..18b529691c615f5bb0c105eaca319f2fb24acbc8 100644 --- a/test/vinyl/constraint.result +++ b/test/vinyl/constraint.result @@ -83,11 +83,11 @@ index = space:create_index('primary', { type = 'tree', parts = {1,'unsigned',2,' ... space:insert{1} --- -- error: Tuple field count 1 is less than required by a defined index (expected 2) +- error: Tuple field count 1 is less than required (expected at least 2) ... space:replace{1} --- -- error: Tuple field count 1 is less than required by a defined index (expected 2) +- error: Tuple field count 1 is less than required (expected at least 2) ... space:delete{1} --- @@ -99,7 +99,7 @@ space:update(1, {{'=', 1, 101}}) ... space:upsert({1}, {{'+', 1, 10}}) --- -- error: Tuple field count 1 is less than required by a defined index (expected 2) +- error: Tuple field count 1 is less than required (expected at least 2) ... space:get{1} --- diff --git a/test/vinyl/savepoint.result b/test/vinyl/savepoint.result index ad3c5c1318e5065ce80ce8c693229a2d0d9d78df..c233913f7d6b2592bb5e247af62c4fc4b52069f4 100644 --- a/test/vinyl/savepoint.result +++ b/test/vinyl/savepoint.result @@ -124,7 +124,7 @@ index2 = space:create_index('secondary', { parts = {2, 'int', 3, 'str'} }) ... space:insert({1}) --- -- error: Tuple field count 1 is less than required by a defined index (expected 3) +- error: Tuple field count 1 is less than required (expected at least 3) ... space:insert({1, 1, 'a'}) --- @@ -622,7 +622,7 @@ space:insert({4, 2, 'b'}) ... space:upsert({2}, {{'=', 4, 1000}}) --- -- error: Tuple field count 1 is less than required by a defined index (expected 3) +- error: Tuple field count 1 is less than required (expected at least 3) ... index3:delete({3, 'a'}) ---