From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp48.i.mail.ru (smtp48.i.mail.ru [94.100.177.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id F399B446445 for ; Tue, 13 Oct 2020 02:23:19 +0300 (MSK) From: Alexander Turenko Date: Tue, 13 Oct 2020 02:23:20 +0300 Message-Id: In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH v3 13/16] module api: expose box_key_def_merge() List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Vladislav Shpilevoy Cc: tarantool-patches@dev.tarantool.org, Alexander Turenko Part of #5273 --- src/box/key_def.c | 6 + src/box/key_def.h | 14 + src/exports.h | 1 + test/app-tap/module_api.c | 752 +++++++++++++++++++++++++++++++ test/app-tap/module_api.test.lua | 2 +- 5 files changed, 774 insertions(+), 1 deletion(-) diff --git a/src/box/key_def.c b/src/box/key_def.c index 7f134aa98..7226f2482 100644 --- a/src/box/key_def.c +++ b/src/box/key_def.c @@ -614,6 +614,12 @@ box_tuple_compare_with_key(box_tuple_t *tuple_a, const char *key_b, } +box_key_def_t * +box_key_def_merge(const box_key_def_t *first, const box_key_def_t *second) +{ + return key_def_merge(first, second); +} + /* }}} Module API functions */ int diff --git a/src/box/key_def.h b/src/box/key_def.h index 304deedff..fdf65dec6 100644 --- a/src/box/key_def.h +++ b/src/box/key_def.h @@ -511,6 +511,20 @@ API_EXPORT int box_tuple_compare_with_key(box_tuple_t *tuple_a, const char *key_b, box_key_def_t *key_def); +/** + * Allocate a new key_def with a set union of key parts from + * first and second key defs. + * + * Parts of the new key_def consist of the first key_def's parts + * and those parts of the second key_def that were not among the + * first parts. + * + * @retval not NULL Ok. + * @retval NULL Memory error. + */ +API_EXPORT box_key_def_t * +box_key_def_merge(const box_key_def_t *first, const box_key_def_t *second); + /** \endcond public */ /* diff --git a/src/exports.h b/src/exports.h index 71ec1a9b5..223390d52 100644 --- a/src/exports.h +++ b/src/exports.h @@ -31,6 +31,7 @@ EXPORT(box_iterator_free) EXPORT(box_iterator_next) EXPORT(box_key_def_delete) EXPORT(box_key_def_dump_parts) +EXPORT(box_key_def_merge) EXPORT(box_key_def_new) EXPORT(box_key_def_new_v2) EXPORT(box_key_def_validate_tuple) diff --git a/test/app-tap/module_api.c b/test/app-tap/module_api.c index 2f1ceefb6..175217ef9 100644 --- a/test/app-tap/module_api.c +++ b/test/app-tap/module_api.c @@ -540,6 +540,38 @@ key_part_def_check_equal(const box_key_part_def_t *a, string_check_equal(a->path, b->path); } +/** + * Check () result against expected one. + * + * Allocates temporary values on the box region (a caller should + * release them). + */ +static void +key_def_check_merge(box_key_part_def_t *a, uint32_t part_count_a, + box_key_part_def_t *b, uint32_t part_count_b, + box_key_part_def_t *exp, uint32_t part_count_exp) +{ + box_key_def_t *key_def_a = box_key_def_new_v2(a, part_count_a); + assert(key_def_a != NULL); + box_key_def_t *key_def_b = box_key_def_new_v2(b, part_count_b); + assert(key_def_b != NULL); + + box_key_def_t *key_def_res = box_key_def_merge(key_def_a, key_def_b); + uint32_t part_count_res; + box_key_part_def_t *res = box_key_def_dump_parts(key_def_res, + &part_count_res); + assert(res != NULL); + + assert(part_count_res == part_count_exp); + for (uint32_t i = 0; i < part_count_exp; ++i) { + key_part_def_check_equal(&res[i], &exp[i]); + } + + box_key_def_delete(key_def_res); + box_key_def_delete(key_def_b); + box_key_def_delete(key_def_a); +} + /** * Basic () and () * test. @@ -829,6 +861,725 @@ test_key_def_validate_tuple(struct lua_State *L) return 1; } +/** + * Basic () test. + */ +static int +test_key_def_merge(struct lua_State *L) +{ + /* + * What is the idea of ()? + * + * (In my humble understanding.) + * + * For any given kd1 and kd2, kd3 = merge(kd1, kd2) should + * impose the same order of tuples as if they would be + * ordered by kd1, but all kd1-equal tuples would be + * ordered by kd2. + * + * We could just add all key parts of kd2 to kd1 parts. + * However in some cases we can skip some of kd2 parts + * (the simplest case: when they are equal). That is what + * () doing in fact. + * + * Should we provide a guarantee that first len(kd1) parts + * of kd3 = merge(kd1, kd2) will be the same as in kd1? Or + * those key parts can be strengthen with turning off + * nullability, picking up more restrictive field type or + * choosing of a more restrictive collation if such + * restrictions are defined by kd2? + * + * The tuples ordering property is guaranteed by the + * implementation. In particular, it leans on the fact + * that a comparator for a more general type imposes the + * same ordering on a more restrictive type as if when a + * type-specific comparator is be used. E.g. an order of + * any two given unsigned integers is the same when we + * comparing them as unsigned integers, as integers, as + * numbers or as scalars (note: we don't have comparators + * for 'any' type). + * + * However provides not only comparator + * functions, but also validation and key extraction ones. + * + * Let's consider validation. It looks logical to expect + * that the following invariant is guaranteed: for any + * given kd1 and kd2, kd3 = merge(kd1, kd2) should accept + * only those tuples that both kd1 and kd2 accept (kd + * accepts a tuple when it is valid against kd). This is + * not so now. + * + * If the function would impose this guarantee, it must + * pay attention to field types compatibility (and which + * ones are more restrictive than others) and nullability. + * Not sure whether a collation may restrict a set of + * possible values (in theory it may be so; at least not + * any byte sequence forms a valid UTF-8 string). + * + * It also looks logical to expect that, when sets of + * tuples that are accepted by kd1 and that are accepted + * by kd2 have the empty intersection, the merge function + * will give an error. It is not so now too. + * + * If the function would impose this guarantee, it must + * handle the case, when the same field is marked with + * incompatible types and both key part definitions are + * non-nullable. Not sure that it is the only point that + * must be taken into account here. + * + * Now let's consider key extraction from a tuple. For + * given kd1 and kd2, a change of the merge algorithm may + * change parts count in kd3 = merge(kd1, kd2) and so + * parts count in a key extracted by it. It is hard to + * say, which guarantees we should provide here. So, + * maybe, if we'll touch the merge algorithm, we should + * leave the old function as is and expose _v2() function. + * + * On the other hand, having two implementations of the + * merge function with different guarantees, where only + * the older one will be used internally is somewhat + * strange and may lead to sudden inconsistencies. + * + * If we'll look at the () from the + * practical point of view, the only known usage of this + * function is to provide a comparator that gives exactly + * same order as a secondary index in tarantool (when it + * is not unique, secondary key parts are merged with the + * primary ones). So, it seems, if something should be + * changed, it should be changed in sync with internals. + * + * To sum up: current behaviour is the controversial topic + * and we may want to reconsider it in some way in a + * future. So let's look to some of the test cases below + * as on examples of current behaviour: not as on a + * commitment that it'll be the same forever (while the + * main property regarding tuples ordering is hold). + */ + + size_t region_svp = box_region_used(); + + /* + * Causion: Don't initialize directly + * in a real world code. Use (). + * + * The testing code is updated in sync with tarantool, so + * it may lean on the knowledge about particular set of + * fields and flags. + * + * In contrast a module should be able to be built against + * an older version of tarantool and correctly run on a + * newer one. It also should be able to build against the + * newer tarantool version without code changes. + * + * The structure may be updated in a + * future version of tarantool. The only permitted updates + * are adding new fields or flags, or update of a default + * value of a field or a flag. Let's show how it may break + * non-conventional code: + * + * 1. Case: a new field is added. + * + * As result, if brace initializer is used, + * -Wmissing-field-initializers (part of -Wextra) + * warning may be produced when building a module + * against the new tarantool version. Usage of -Werror + * for the Debug build is usual, so it may break + * compilation. + * + * 2. Case: a new field or flag is added with non-zero + * default value or a default value of some field or + * flag is changed. + * + * As result a module will initialize the new / changed + * fields or flags with values that are not default for + * given tarantool version, but may assume that + * everything that is not set explicitly is default. + */ + + /* Non-conventional prerequisite: no new fields. */ + size_t padding_offset = key_part_padding_offset(); + size_t path_field_end = offsetof(box_key_part_def_t, path) + + sizeof(const char *); + assert(padding_offset == path_field_end); + + /* Non-conventional prerequisite: list of known flags. */ + uint32_t known_flags = key_part_def_known_flags(); + assert(known_flags == BOX_KEY_PART_DEF_IS_NULLABLE); + + /* Non-conventional prerequisite: certain defaults. */ + box_key_part_def_t tmp; + box_key_part_def_create(&tmp); + assert((tmp.flags & BOX_KEY_PART_DEF_IS_NULLABLE) == 0); + assert(tmp.collation == NULL); + assert(tmp.path == NULL); + + /* + * The extra parentheses are necessary to initialize + * , because it is a union around an + * anonymous structure and padding, not a structure. + */ + + /* Case 1: all are different. */ + box_key_part_def_t a_1[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t b_1[] = { + {{0, 0, "unsigned", NULL, NULL}}, + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_1[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, + {{0, 0, "unsigned", NULL, NULL}}, + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_1, lengthof(a_1), b_1, lengthof(b_1), + exp_1, lengthof(exp_1)); + + /* Case 2: two key parts are the same. */ + box_key_part_def_t a_2[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, /* clash */ + }; + box_key_part_def_t b_2[] = { + {{1, 0, "unsigned", NULL, NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_2[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_2, lengthof(a_2), b_2, lengthof(b_2), + exp_2, lengthof(exp_2)); + + /* + * Case 3: more general field type + more restrictive one. + * + * Interpretation: when and have key parts that + * are point to the same field (considering and + * JSON paths) and collations are not present or don't + * impose any restrictions, the key part from is + * omitted without any care to and . + */ + box_key_part_def_t a_3[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "number", NULL, NULL}}, /* clash */ + }; + box_key_part_def_t b_3[] = { + {{1, 0, "unsigned", NULL, NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_3[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "number", NULL, NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_3, lengthof(a_3), b_3, lengthof(b_3), + exp_3, lengthof(exp_3)); + + /* + * Case 4: more restrictive field type + more general one. + * + * Interpretation: the same as for the case 3. + */ + box_key_part_def_t a_4[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, /* clash */ + }; + box_key_part_def_t b_4[] = { + {{1, 0, "number", NULL, NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_4[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_4, lengthof(a_4), b_4, lengthof(b_4), + exp_4, lengthof(exp_4)); + + /* + * Case 5: incompatible field types. + * + * Interpretation: the same as for the case 3. + */ + box_key_part_def_t a_5[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, /* clash */ + }; + box_key_part_def_t b_5[] = { + {{1, 0, "string", NULL, NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_5[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_5, lengthof(a_5), b_5, lengthof(b_5), + exp_5, lengthof(exp_5)); + + /* + * Case 6: nullable + non-nullable. + * + * Interpretation: the same as for the case 3. + */ + box_key_part_def_t a_6[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 1, "unsigned", NULL, NULL}}, /* clash */ + }; + box_key_part_def_t b_6[] = { + {{1, 0, "unsigned", NULL, NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_6[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 1, "unsigned", NULL, NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_6, lengthof(a_6), b_6, lengthof(b_6), + exp_6, lengthof(exp_6)); + + /* + * Case 7: non-nullable + nullable. + * + * Interpretation: the same as for the case 3. + */ + box_key_part_def_t a_7[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, /* clash */ + }; + box_key_part_def_t b_7[] = { + {{1, 1, "unsigned", NULL, NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_7[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "unsigned", NULL, NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_7, lengthof(a_7), b_7, lengthof(b_7), + exp_7, lengthof(exp_7)); + + /* + * Case 8: the same ICU collations. + * + * Interpretation: when and have key parts that + * are point to the same field (considering and + * JSON paths), the key part from is omitted, when one + * of the following conditions is true: + * + * 1. and have the same collation (or both lacks + * it). + * 2. has no collation. + * 3. has a non-ICU collation (those are 'none' and + * 'binary' now). + * 4. has an ICU collation with UCOL_DEFAULT strength + * (but I don't know what does it mean in practice and + * unable to interpret). + * + * Comments around () point the general + * idea: don't coalesce when 's collation may impose + * a strict order on keys equal in terms of the 's + * collation. (And I guess 'more strict' was meant by the + * word 'strict'.) + * + * The general rule is to don't coalesce when doubt. But + * under the conditions above we're sure that the order + * imposed by 's collation is already strict and hence + * we don't need 's collation at all. + * + * Beware! Tarantool-1.10 does not take collations into + * account at all when decide whether to coalesce a key + * part or not. See gh-3537. + * + * Aside of this, tarantool-1.10 only have 'unicode' and + * 'unicode_ci' collations. + */ + box_key_part_def_t a_8[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* clash */ + }; + box_key_part_def_t b_8[] = { + {{1, 0, "string", "unicode", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_8[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_8, lengthof(a_8), b_8, lengthof(b_8), + exp_8, lengthof(exp_8)); + + /* + * Case 9: no collation + ICU collation. + * + * Interpretation: see the case 8. + */ + box_key_part_def_t a_9[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", NULL, NULL}}, /* clash */ + }; + box_key_part_def_t b_9[] = { + {{1, 0, "string", "unicode", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_9[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", NULL, NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_9, lengthof(a_9), b_9, lengthof(b_9), + exp_9, lengthof(exp_9)); + + /* + * Case 10: ICU collation + no collation. + * + * Interpretation: see the case 8. + */ + box_key_part_def_t a_10[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* clash */ + }; + box_key_part_def_t b_10[] = { + {{1, 0, "string", NULL, NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_10[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* from */ + {{1, 0, "string", NULL, NULL}}, /* from */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_10, lengthof(a_10), b_10, lengthof(b_10), + exp_10, lengthof(exp_10)); + + /* + * Case 11: less strong ICU collation + more strong one, + * but with the same locale. + * + * 'Less strong' means 'have smaller strength' here. + * + * Interpretation: see the case 8. + */ + box_key_part_def_t a_11[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode_ci", NULL}}, /* clash */ + }; + box_key_part_def_t b_11[] = { + {{1, 0, "string", "unicode", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_11[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode_ci", NULL}}, /* from */ + {{1, 0, "string", "unicode", NULL}}, /* from */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_11, lengthof(a_11), b_11, lengthof(b_11), + exp_11, lengthof(exp_11)); + + /* + * Case 12: more strong ICU collation + less strong one, + * but with the same locale. + * + * 'More strong' means 'have bigger strength' here. + * + * Interpretation: see the case 8. + */ + box_key_part_def_t a_12[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* clash */ + }; + box_key_part_def_t b_12[] = { + {{1, 0, "string", "unicode_ci", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_12[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* from */ + {{1, 0, "string", "unicode_ci", NULL}}, /* from */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_12, lengthof(a_12), b_12, lengthof(b_12), + exp_12, lengthof(exp_12)); + + /* + * Case 13: ICU collations with different locales. + * + * Interpretation: see the case 8. + */ + box_key_part_def_t a_13[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode_am_s3", NULL}}, /* clash */ + }; + box_key_part_def_t b_13[] = { + {{1, 0, "string", "unicode_fi_s3", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_13[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode_am_s3", NULL}}, /* from */ + {{1, 0, "string", "unicode_fi_s3", NULL}}, /* from */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_13, lengthof(a_13), b_13, lengthof(b_13), + exp_13, lengthof(exp_13)); + + /* + * Case 14: 'none' collation + ICU collation. + * + * Interpretation: see the case 8. + * + * Note: 'none' collation is the same as lack of a + * collation from key_def point of view. So after + * dump to key parts it becomes NULL. + */ + box_key_part_def_t a_14[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "none", NULL}}, /* clash */ + }; + box_key_part_def_t b_14[] = { + {{1, 0, "string", "unicode", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_14[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", NULL, NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_14, lengthof(a_14), b_14, lengthof(b_14), + exp_14, lengthof(exp_14)); + + /* + * Case 15: ICU collation + 'none' collation. + * + * Interpretation: see the case 8. + * + * Note: 'none' collation is the same as lack of a + * collation from key_def point of view. So after + * dump to key parts it becomes NULL. + */ + box_key_part_def_t a_15[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* clash */ + }; + box_key_part_def_t b_15[] = { + {{1, 0, "string", "none", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_15[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* from */ + {{1, 0, "string", NULL, NULL}}, /* from */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_15, lengthof(a_15), b_15, lengthof(b_15), + exp_15, lengthof(exp_15)); + + /* + * Case 16: 'binary' collation + ICU collation. + * + * Interpretation: see the case 8. + */ + box_key_part_def_t a_16[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "binary", NULL}}, /* clash */ + }; + box_key_part_def_t b_16[] = { + {{1, 0, "string", "unicode", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_16[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "binary", NULL}}, /* coalesced */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_16, lengthof(a_16), b_16, lengthof(b_16), + exp_16, lengthof(exp_16)); + + /* + * Case 17: ICU collation + 'binary' collation. + * + * Interpretation: see the case 8. + */ + box_key_part_def_t a_17[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* clash */ + }; + box_key_part_def_t b_17[] = { + {{1, 0, "string", "binary", NULL}}, /* clash */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + box_key_part_def_t exp_17[] = { + {{3, 0, "unsigned", NULL, NULL}}, + {{1, 0, "string", "unicode", NULL}}, /* from */ + {{1, 0, "string", "binary", NULL}}, /* from */ + {{2, 0, "unsigned", NULL, NULL}}, + }; + key_def_check_merge(a_17, lengthof(a_17), b_17, lengthof(b_17), + exp_17, lengthof(exp_17)); + + /* + * Case 18: the same JSON paths. + * + * Interpretation: and are considered as + * a 'pointer' to a field. JSON path are compared by its + * meaning, not just byte-to-byte. See also the case 3. + */ + box_key_part_def_t a_18[] = { + {{0, 0, "unsigned", NULL, "moo"}}, + }; + box_key_part_def_t b_18[] = { + {{0, 0, "unsigned", NULL, "moo"}}, + }; + box_key_part_def_t exp_18[] = { + {{0, 0, "unsigned", NULL, "moo"}}, /* coalesced */ + }; + key_def_check_merge(a_18, lengthof(a_18), b_18, lengthof(b_18), + exp_18, lengthof(exp_18)); + + /* + * Case 19: the same JSON paths, but different . + * + * Interpretation: see the case 18. + */ + box_key_part_def_t a_19[] = { + {{0, 0, "unsigned", NULL, "moo"}}, + }; + box_key_part_def_t b_19[] = { + {{1, 0, "unsigned", NULL, "moo"}}, + }; + box_key_part_def_t exp_19[] = { + {{0, 0, "unsigned", NULL, "moo"}}, + {{1, 0, "unsigned", NULL, "moo"}}, + }; + key_def_check_merge(a_19, lengthof(a_19), b_19, lengthof(b_19), + exp_19, lengthof(exp_19)); + + /* + * Case 20: equivalent JSON paths. + * + * Interpretation: see the case 18. A key part from + * is omitted in the case, so the JSON path from is + * present in the result. + */ + box_key_part_def_t a_20[] = { + {{0, 0, "unsigned", NULL, ".moo"}}, + }; + box_key_part_def_t b_20[] = { + {{0, 0, "unsigned", NULL, "moo" }}, + }; + box_key_part_def_t exp_20[] = { + {{0, 0, "unsigned", NULL, ".moo"}}, /* coalesced */ + }; + key_def_check_merge(a_20, lengthof(a_20), b_20, lengthof(b_20), + exp_20, lengthof(exp_20)); + + /* + * Case 21: no JSON path + JSON path. + * + * Interpretation: see the case 18. + */ + box_key_part_def_t a_21[] = { + {{0, 0, "unsigned", NULL, NULL }}, + }; + box_key_part_def_t b_21[] = { + {{0, 0, "unsigned", NULL, "moo"}}, + }; + box_key_part_def_t exp_21[] = { + {{0, 0, "unsigned", NULL, NULL }}, + {{0, 0, "unsigned", NULL, "moo"}}, + }; + key_def_check_merge(a_21, lengthof(a_21), b_21, lengthof(b_21), + exp_21, lengthof(exp_21)); + + /* + * Case 22: JSON path + no JSON path. + * + * Interpretation: see the case 18. + */ + box_key_part_def_t a_22[] = { + {{0, 0, "unsigned", NULL, "moo"}}, + }; + box_key_part_def_t b_22[] = { + {{0, 0, "unsigned", NULL, NULL }}, + }; + box_key_part_def_t exp_22[] = { + {{0, 0, "unsigned", NULL, "moo"}}, + {{0, 0, "unsigned", NULL, NULL }}, + }; + key_def_check_merge(a_22, lengthof(a_22), b_22, lengthof(b_22), + exp_22, lengthof(exp_22)); + + /* + * Case 23: different JSON paths. + * + * Interpretation: see the case 18. + */ + box_key_part_def_t a_23[] = { + {{0, 0, "unsigned", NULL, "foo"}}, + }; + box_key_part_def_t b_23[] = { + {{0, 0, "unsigned", NULL, "bar"}}, + }; + box_key_part_def_t exp_23[] = { + {{0, 0, "unsigned", NULL, "foo"}}, + {{0, 0, "unsigned", NULL, "bar"}}, + }; + key_def_check_merge(a_23, lengthof(a_23), b_23, lengthof(b_23), + exp_23, lengthof(exp_23)); + + /* + * Case 24: a shorter JSON path + a longer JSON path, but + * with the same prefix. + * + * Interpretation: see the case 18. Those JSON paths are + * not equivalent. + */ + box_key_part_def_t a_24[] = { + {{0, 0, "unsigned", NULL, "foo" }}, + }; + box_key_part_def_t b_24[] = { + {{0, 0, "unsigned", NULL, "foo.bar"}}, + }; + box_key_part_def_t exp_24[] = { + {{0, 0, "unsigned", NULL, "foo" }}, + {{0, 0, "unsigned", NULL, "foo.bar"}}, + }; + key_def_check_merge(a_24, lengthof(a_24), b_24, lengthof(b_24), + exp_24, lengthof(exp_24)); + + /* + * Case 25: a longer JSON path + a shorter JSON path, but + * with the same prefix. + * + * Interpretation: see the case 18. Those JSON paths are + * not equivalent. + */ + box_key_part_def_t a_25[] = { + {{0, 0, "unsigned", NULL, "foo.bar"}}, + }; + box_key_part_def_t b_25[] = { + {{0, 0, "unsigned", NULL, "foo" }}, + }; + box_key_part_def_t exp_25[] = { + {{0, 0, "unsigned", NULL, "foo.bar"}}, + {{0, 0, "unsigned", NULL, "foo" }}, + }; + key_def_check_merge(a_25, lengthof(a_25), b_25, lengthof(b_25), + exp_25, lengthof(exp_25)); + + /* Clean up. */ + box_region_truncate(region_svp); + + lua_pushboolean(L, 1); + return 1; +} + /* }}} key_def api v2 */ static int @@ -1209,6 +1960,7 @@ luaopen_module_api(lua_State *L) {"test_key_def_new_v2", test_key_def_new_v2}, {"test_key_def_dump_parts", test_key_def_dump_parts}, {"test_key_def_validate_tuple", test_key_def_validate_tuple}, + {"test_key_def_merge", test_key_def_merge}, {NULL, NULL} }; luaL_register(L, "module_api", lib); diff --git a/test/app-tap/module_api.test.lua b/test/app-tap/module_api.test.lua index dbea9343b..6d045f8ce 100755 --- a/test/app-tap/module_api.test.lua +++ b/test/app-tap/module_api.test.lua @@ -177,7 +177,7 @@ local function test_iscdata(test, module) end local test = require('tap').test("module_api", function(test) - test:plan(31) + test:plan(32) local status, module = pcall(require, 'module_api') test:is(status, true, "module") test:ok(status, "module is loaded") -- 2.25.0