diff --git a/cfg/tarantool_silverbox_cfg.c b/cfg/tarantool_silverbox_cfg.c index 415a6fd25fcca7b10c62f80f24f5c43e9cdae850..caf93de716273b012481883fad04077c67807b05 100644 --- a/cfg/tarantool_silverbox_cfg.c +++ b/cfg/tarantool_silverbox_cfg.c @@ -78,8 +78,9 @@ acceptDefault_name__namespace(tarantool_cfg_namespace *c) { static int acceptDefault_name__namespace__index(tarantool_cfg_namespace_index *c) { - c->type = strdup("HASH"); + c->type = strdup(""); if (c->type == NULL) return CNF_NOMEMORY; + c->unique = -1; c->key_field = NULL; return 0; } @@ -87,7 +88,7 @@ acceptDefault_name__namespace__index(tarantool_cfg_namespace_index *c) { static int acceptDefault_name__namespace__index__key_field(tarantool_cfg_namespace_index_key_field *c) { c->fieldno = -1; - c->type = strdup("NUM"); + c->type = strdup(""); if (c->type == NULL) return CNF_NOMEMORY; return 0; } @@ -208,6 +209,11 @@ static NameAtom _name__namespace__index__type[] = { { "index", -1, _name__namespace__index__type + 2 }, { "type", -1, NULL } }; +static NameAtom _name__namespace__index__unique[] = { + { "namespace", -1, _name__namespace__index__unique + 1 }, + { "index", -1, _name__namespace__index__unique + 2 }, + { "unique", -1, NULL } +}; static NameAtom _name__namespace__index__key_field__fieldno[] = { { "namespace", -1, _name__namespace__index__key_field__fieldno + 1 }, { "index", -1, _name__namespace__index__key_field__fieldno + 2 }, @@ -617,6 +623,19 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) { if (opt->paramValue.stringval && c->namespace[opt->name->index]->index[opt->name->next->index]->type == NULL) return CNF_NOMEMORY; } + else if ( cmpNameAtoms( opt->name, _name__namespace__index__unique) ) { + if (opt->paramType != numberType ) + return CNF_WRONGTYPE; + ARRAYALLOC(c->namespace, opt->name->index + 1, _name__namespace); + ARRAYALLOC(c->namespace[opt->name->index]->index, opt->name->next->index + 1, _name__namespace__index); + errno = 0; + long int i32 = strtol(opt->paramValue.numberval, NULL, 10); + if (i32 == 0 && errno == EINVAL) + return CNF_WRONGINT; + if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE) + return CNF_WRONGRANGE; + c->namespace[opt->name->index]->index[opt->name->next->index]->unique = i32; + } else if ( cmpNameAtoms( opt->name, _name__namespace__index__key_field__fieldno) ) { if (opt->paramType != numberType ) return CNF_WRONGTYPE; @@ -777,6 +796,7 @@ typedef enum IteratorState { S_name__namespace__estimated_rows, S_name__namespace__index, S_name__namespace__index__type, + S_name__namespace__index__unique, S_name__namespace__index__key_field, S_name__namespace__index__key_field__fieldno, S_name__namespace__index__key_field__type, @@ -1169,6 +1189,7 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char case S_name__namespace__estimated_rows: case S_name__namespace__index: case S_name__namespace__index__type: + case S_name__namespace__index__unique: case S_name__namespace__index__key_field: case S_name__namespace__index__key_field__fieldno: case S_name__namespace__index__key_field__type: @@ -1211,6 +1232,7 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char case S_name__namespace__index: i->state = S_name__namespace__index; case S_name__namespace__index__type: + case S_name__namespace__index__unique: case S_name__namespace__index__key_field: case S_name__namespace__index__key_field__fieldno: case S_name__namespace__index__key_field__type: @@ -1225,6 +1247,17 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char return NULL; } snprintf(buf, PRINTBUFLEN-1, "namespace[%d].index[%d].type", i->idx_name__namespace, i->idx_name__namespace__index); + i->state = S_name__namespace__index__unique; + return buf; + case S_name__namespace__index__unique: + *v = malloc(32); + if (*v == NULL) { + free(i); + out_warning(CNF_NOMEMORY, "No memory to output value"); + return NULL; + } + sprintf(*v, "%"PRId32, c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->unique); + snprintf(buf, PRINTBUFLEN-1, "namespace[%d].index[%d].unique", i->idx_name__namespace, i->idx_name__namespace__index); i->state = S_name__namespace__index__key_field; return buf; case S_name__namespace__index__key_field: diff --git a/cfg/tarantool_silverbox_cfg.cfg b/cfg/tarantool_silverbox_cfg.cfg index d64f2b1249ac3f187f47a58bbc38fb8900a9b5bc..8a3668cdc5940f96f0a7727c0b2e2732d162c9a3 100644 --- a/cfg/tarantool_silverbox_cfg.cfg +++ b/cfg/tarantool_silverbox_cfg.cfg @@ -108,11 +108,12 @@ namespace = [ estimated_rows = 0 index = [ { - type = "HASH" + type = "" + unique = -1 key_field = [ { fieldno = -1 - type = "NUM" + type = "" } ] } diff --git a/cfg/tarantool_silverbox_cfg.cfg_tmpl b/cfg/tarantool_silverbox_cfg.cfg_tmpl index 22ced2c366626b15e2327312b72ec3db847d6c64..264a62d751a3ac9f7acd952d9559e93a789b72e8 100644 --- a/cfg/tarantool_silverbox_cfg.cfg_tmpl +++ b/cfg/tarantool_silverbox_cfg.cfg_tmpl @@ -107,10 +107,11 @@ namespace = [ cardinality = -1 estimated_rows = 0 index = [ - type = "HASH" + type = "" + unique = -1 key_field = [ fieldno = -1 - type = "NUM" + type = "" ] ] ] diff --git a/cfg/tarantool_silverbox_cfg.h b/cfg/tarantool_silverbox_cfg.h index 9513e73a99b39298236438e94f3891428516e245..b548b064cce9a9516066f82b2cf2916e93e6d90b 100644 --- a/cfg/tarantool_silverbox_cfg.h +++ b/cfg/tarantool_silverbox_cfg.h @@ -15,6 +15,7 @@ typedef struct tarantool_cfg_namespace_index_key_field { typedef struct tarantool_cfg_namespace_index { char* type; + int32_t unique; tarantool_cfg_namespace_index_key_field** key_field; } tarantool_cfg_namespace_index; diff --git a/mod/silverbox/box.c b/mod/silverbox/box.c index 2c601f687d90760c03ab216cdf2be855ce20d56a..f65dd87702635652528e3e905c27e64d1f46d621 100644 --- a/mod/silverbox/box.c +++ b/mod/silverbox/box.c @@ -205,10 +205,12 @@ tree_index_member_compare(struct tree_index_member *member_a, struct tree_index_ if (member_b->tuple == NULL) return 2; - if (member_a->tuple > member_b->tuple) - return 3; - else if (member_a->tuple < member_b->tuple) - return -3; + if (index->unique == false) { + if (member_a->tuple > member_b->tuple) + return 3; + else if (member_a->tuple < member_b->tuple) + return -3; + } return 0; } @@ -325,7 +327,7 @@ tuple_txn_ref(struct box_txn *txn, struct box_tuple *tuple) } static struct box_tuple * -uniq_find_by_tuple(struct index *self, struct box_tuple *tuple) +index_find_hash_by_tuple(struct index *self, struct box_tuple *tuple) { void *key = tuple_field(tuple, self->key_field->fieldno); if (key == NULL) @@ -432,6 +434,22 @@ alloc_search_pattern(struct index *index, int key_cardinality, void *key) return pattern; } +static struct box_tuple * +index_find_tree(struct index *self, void *key) +{ + struct tree_index_member *member = (struct tree_index_member *)key; + + return sptree_str_t_find(self->idx.tree, member); +} + +static struct box_tuple * +index_find_tree_by_tuple(struct index *self, struct box_tuple *tuple) +{ + struct tree_index_member *member = tuple2tree_index_member(self, tuple, NULL); + + return self->find(self, member); +} + static void index_remove_hash_num(struct index *self, struct box_tuple *tuple) { @@ -554,7 +572,7 @@ validate_indeces(struct box_txn *txn) if (!field_is_num(field)) box_raise(ERR_CODE_ILLEGAL_PARAMS, "field must be NUM"); } - if (index->type == TREE) + if (index->type == TREE && index->unique == false) /* Don't check non unique indexes */ continue; @@ -1496,6 +1514,14 @@ custom_init(void) index->search_pattern = palloc(eter_pool, SIZEOF_TREE_INDEX_MEMBER(index)); + if (cfg.namespace[i]->index[j]->unique == 0) + index->unique = false; + else if (cfg.namespace[i]->index[j]->unique == 1) + index->unique = true; + else + panic("(namespace = %" PRIu32 " index = %" PRIu32 ") " + "unique property is undefined", i, j); + if (strcmp(cfg.namespace[i]->index[j]->type, "HASH") == 0) { if (index->key_cardinality != 1) panic("(namespace = %" PRIu32 " index = %" PRIu32 ") " @@ -1504,9 +1530,13 @@ custom_init(void) index->enabled = true; index->type = HASH; + if (index->unique == false) + panic("(namespace = %" PRIu32 " index = %" PRIu32 ") " + "hash index must be unique", i, j); + if (index->key_field->type == NUM) { index->find = index_find_hash_num; - index->find_by_tuple = uniq_find_by_tuple; + index->find_by_tuple = index_find_hash_by_tuple; index->remove = index_remove_hash_num; index->replace = index_replace_hash_num; index->namespace = &namespace[i]; @@ -1517,7 +1547,7 @@ custom_init(void) estimated_rows); } else { index->find = index_find_hash_str; - index->find_by_tuple = uniq_find_by_tuple; + index->find_by_tuple = index_find_hash_by_tuple; index->remove = index_remove_hash_str; index->replace = index_replace_hash_str; index->namespace = &namespace[i]; @@ -1531,16 +1561,15 @@ custom_init(void) index->enabled = false; index->type = TREE; - index->find = NULL; - index->find_by_tuple = NULL; + index->find = index_find_tree; + index->find_by_tuple = index_find_tree_by_tuple; index->remove = index_remove_tree_str; index->replace = index_replace_tree_str; index->iterator_init = index_iterator_init_tree_str; index->iterator_next = index_iterator_next_tree_str; index->namespace = &namespace[i]; - index->idx.tree = - palloc(eter_pool, sizeof(*index->idx.tree)); + index->idx.tree = palloc(eter_pool, sizeof(*index->idx.tree)); } else panic("namespace = %" PRIu32 " index = %" PRIu32 ") " "unknown index type `%s'", @@ -1666,7 +1695,7 @@ build_indexes(void) } m = (struct tree_index_member *) - ((char *)member + i * SIZEOF_TREE_INDEX_MEMBER(index)); + ((char *)member + i * SIZEOF_TREE_INDEX_MEMBER(index)); tuple2tree_index_member(index, kh_value(namespace[n].index[0].idx.hash, diff --git a/mod/silverbox/box.h b/mod/silverbox/box.h index 7b5228cb3f06f1109d1b0b4b9b55d405d3716a27..2c3a51ce4ca4dc9416cab649ee96c809b567fb48 100644 --- a/mod/silverbox/box.h +++ b/mod/silverbox/box.h @@ -64,6 +64,8 @@ SPTREE_DEF(str_t, realloc); struct index { bool enabled; + bool unique; + struct box_tuple *(*find) (struct index * index, void *key); /* only for unique lookups */ struct box_tuple *(*find_by_tuple) (struct index * index, struct box_tuple * pattern); void (*remove) (struct index * index, struct box_tuple *); diff --git a/mod/silverbox/box_cfg.cfg_tmpl b/mod/silverbox/box_cfg.cfg_tmpl index a5200a53ce7758c773a710493c097a12dad1e57a..54ca6ca3eeca6bad879afedf460f059f98f45907 100644 --- a/mod/silverbox/box_cfg.cfg_tmpl +++ b/mod/silverbox/box_cfg.cfg_tmpl @@ -56,10 +56,11 @@ namespace = [ cardinality = -1 estimated_rows = 0 index = [ - type = "HASH" + type = "" + unique = -1 key_field = [ fieldno = -1 - type = "NUM" + type = "" ] ] ] diff --git a/mod/silverbox/t/box.pl b/mod/silverbox/t/box.pl index 367edf79d3ed6b379a1ce29725aab33537f4baeb..29a16aad64841609eeeed5a7af525498fc481481 100644 --- a/mod/silverbox/t/box.pl +++ b/mod/silverbox/t/box.pl @@ -10,7 +10,7 @@ use lib "$Bin"; use TBox (); use Carp qw/confess/; -use Test::More tests => 182; +use Test::More tests => 201; use Test::Exception; local $SIG{__DIE__} = \&confess; @@ -459,3 +459,57 @@ my @tuple_bad = (13, 'mail.ru', '123'); cleanup $tuple_bad[0]; throws_ok sub { $box->Insert(@tuple_bad) }, qr/Illegal parametrs/, "index_constains/bad_field_type"; + +## Check unique tree index +sub def_param_unique { + my $format = 'l&&&'; + return { servers => $server, + namespaces => [ { + indexes => [ { + index_name => 'id', + keys => [0], + }, { + index_name => 'email', + keys => [1], + }, { + index_name => 'firstname', + keys => [2], + }, { + index_name => 'lastname', + keys => [3], + } , { + index_name => 'fullname', + keys => [2, 3] + } ], + namespace => 27, + format => $format, + default_index => 'id', + } ]} +} + +$box = MR::SilverBox->new(def_param_unique); +ok $box->isa('MR::SilverBox'), 'connect'; + +my $tuples = [ [1, 'rtokarev@corp.mail.ru', 'Roman', 'Tokarev'], + [2, 'vostrikov@corp.mail.ru', 'Yuri', 'Vostrikov'], + [3, 'aleinikov@corp.mail.ru', 'Roman', 'Aleinikov'], + [4, 'roman.s.tokarev@gmail.com', 'Roman', 'Tokarev'], + [5, 'vostrikov@corp.mail.ru', 'delamon', 'delamon'] ]; + +foreach my $tuple (@$tuples) { + cleanup $tuple->[0]; +} + +foreach my $tuple (@$tuples) { + if ($tuple == $tuples->[-1] || $tuple == $tuples->[-2]) { + throws_ok sub { $box->Insert(@$tuple) }, qr/Index violation/, "unique_tree_index/insert \'$tuple->[0]\'"; + } else { + ok $box->Insert(@$tuple), "unique_tree_index/insert \'$tuple->[0]\'"; + } +} + +my @res = $box->Select([map $_->[0], @$tuples], { limit => 100 }); +foreach my $r (@res) { + ok sub { return $r != $tuples->[-1] && $r != $tuples->[-2] }; +} + diff --git a/scripts/run_test.sh b/scripts/run_test.sh index b6d124f7e367ae77b8962234a61b7fa7a73f3072..ce6abef662c744e6f6d43c99f69b8f81009efa96 100755 --- a/scripts/run_test.sh +++ b/scripts/run_test.sh @@ -90,47 +90,57 @@ write_config() { namespace[0].enabled = 1 namespace[0].index[0].type = "HASH" + namespace[0].index[0].unique = 1 namespace[0].index[0].key_field[0].fieldno = 0 namespace[0].index[0].key_field[0].type = "NUM" namespace[0].index[1].type = "HASH" + namespace[0].index[1].unique = 1 namespace[0].index[1].key_field[0].fieldno = 1 namespace[0].index[1].key_field[0].type = "STR" namespace[3].enabled = 1 namespace[3].index[0].type = "HASH" + namespace[3].index[0].unique = 1 namespace[3].index[0].key_field[0].fieldno = 0 namespace[3].index[0].key_field[0].type = "NUM" namespace[5].enabled = 1 namespace[5].index[0].type = "HASH" + namespace[5].index[0].unique = 1 namespace[5].index[0].key_field[0].fieldno = 0 namespace[5].index[0].key_field[0].type = "NUM" namespace[11].enabled = 1 namespace[11].index[0].type = "HASH" + namespace[11].index[0].unique = 1 namespace[11].index[0].key_field[0].fieldno = 0 namespace[11].index[0].key_field[0].type = "NUM" namespace[19].enabled = 1 namespace[19].index[0].type = "HASH" + namespace[19].index[0].unique = 1 namespace[19].index[0].key_field[0].fieldno = 0 namespace[19].index[0].key_field[0].type = "STR" namespace[22].enabled = 1 namespace[22].index[0].type = "HASH" + namespace[22].index[0].unique = 1 namespace[22].index[0].key_field[0].fieldno = 0 namespace[22].index[0].key_field[0].type = "NUM" namespace[23].enabled = 1 namespace[23].index[0].type = "HASH" + namespace[23].index[0].unique = 1 namespace[23].index[0].key_field[0].fieldno = 0 namespace[23].index[0].key_field[0].type = "NUM" namespace[24].enabled = 1 namespace[24].index[0].type = "HASH" + namespace[24].index[0].unique = 1 namespace[24].index[0].key_field[0].fieldno = 0 namespace[24].index[0].key_field[0].type = "NUM" namespace[24].index[1].type = "HASH" + namespace[24].index[1].unique = 1 namespace[24].index[1].key_field[0].fieldno = 1 namespace[24].index[1].key_field[0].type = "STR" @@ -142,17 +152,78 @@ write_config() { namespace[26].enabled = 1 namespace[26].index[0].type = "HASH" + namespace[26].index[0].unique = 1 namespace[26].index[0].key_field[0].fieldno = 0 namespace[26].index[0].key_field[0].type = "NUM" namespace[26].index[1].type = "TREE" + namespace[26].index[1].unique = 0 namespace[26].index[1].key_field[0].fieldno = 1 namespace[26].index[1].key_field[0].type = "STR" namespace[26].index[2].type = "TREE" + namespace[26].index[2].unique = 1 namespace[26].index[2].key_field[0].fieldno = 1 namespace[26].index[2].key_field[0].type = "STR" namespace[26].index[2].key_field[1].fieldno = 2 namespace[26].index[2].key_field[1].type = "NUM" + namespace[27] = { + enabled = 1 + + # Email + index[0] = { + type = "HASH" + unique = 1 + + key_field[0] = { + type = "NUM" + fieldno = 0 + } + } + index[1] = { + type = "TREE" + unique = 1 + + key_field[0] = { + type = "STR" + fieldno = 1 + } + } + # FirstName + index[2] = { + type = "TREE" + unique = 0 + + key_field[0] = { + type = "STR" + fieldno = 2 + } + } + # LastName + index[3] = { + type = "TREE" + unique = 0 + + key_field[0] = { + type = "STR" + fieldno = 3 + } + } + # FullName + index[4] = { + type = "TREE" + unique = 1 + + key_field[0] = { + type = "STR" + fieldno = 2 + } + key_field[1] = { + type = "STR" + fieldno = 3 + } + } + } + EOF }