1
0
Fork 0

added tests, fixed unique constraints

This commit is contained in:
jsteemann 2016-06-07 17:26:07 +02:00
parent a88b1adf8e
commit c1ff7d249f
2 changed files with 647 additions and 4 deletions

View File

@ -0,0 +1,579 @@
# coding: utf-8
require 'rspec'
require 'arangodb.rb'
describe ArangoDB do
prefix = "api-index-persistent"
################################################################################
## unique constraints during create
################################################################################
context "creating persistent index:" do
context "dealing with unique constraints violation:" do
before do
@cn = "UnitTestsCollectionIndexes"
ArangoDB.drop_collection(@cn)
@cid = ArangoDB.create_collection(@cn)
end
after do
ArangoDB.drop_collection(@cn)
end
it "does not create the index in case of violation" do
# create a document
cmd1 = "/_api/document?collection=#{@cn}"
body = "{ \"a\" : 1, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
# create another document
body = "{ \"a\" : 1, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
# try to create the index
cmd = "/_api/index?collection=#{@cn}"
body = "{ \"type\" : \"persistent\", \"unique\" : true, \"fields\" : [ \"a\" ] }"
doc = ArangoDB.log_post("#{prefix}-fail", cmd, :body => body)
doc.code.should eq(400)
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(1210)
end
it "does not create the index in case of violation, null attributes" do
# create a document
cmd1 = "/_api/document?collection=#{@cn}"
body = "{ \"a\" : null, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
# create another document
body = "{ \"a\" : null, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
# try to create the index
cmd = "/_api/index?collection=#{@cn}"
body = "{ \"type\" : \"persistent\", \"unique\" : true, \"fields\" : [ \"a\" ] }"
doc = ArangoDB.log_post("#{prefix}-fail", cmd, :body => body)
doc.code.should eq(400)
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(1210)
end
it "does not create the index in case of violation, sparse index" do
# create a document
cmd1 = "/_api/document?collection=#{@cn}"
body = "{ \"a\" : 1, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
# create another document
body = "{ \"a\" : 1, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
# try to create the index
cmd = "/_api/index?collection=#{@cn}"
body = "{ \"type\" : \"persistent\", \"unique\" : true, \"fields\" : [ \"a\" ], \"sparse\" : true }"
doc = ArangoDB.log_post("#{prefix}-fail", cmd, :body => body)
doc.code.should eq(400)
doc.parsed_response['error'].should eq(true)
doc.parsed_response['code'].should eq(400)
doc.parsed_response['errorNum'].should eq(1210)
end
it "creates the index in case of null attributes, sparse index" do
# create a document
cmd1 = "/_api/document?collection=#{@cn}"
body = "{ \"a\" : null, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
# create another document
body = "{ \"a\" : null, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
# try to create the index
cmd = "/_api/index?collection=#{@cn}"
body = "{ \"type\" : \"persistent\", \"unique\" : true, \"fields\" : [ \"a\" ], \"sparse\" : true }"
doc = ArangoDB.log_post("#{prefix}-fail", cmd, :body => body)
doc.code.should eq(201)
doc.parsed_response['error'].should eq(false)
end
end
end
################################################################################
## unique constraints during create
################################################################################
context "creating documents:" do
context "dealing with unique constraints:" do
before do
@cn = "UnitTestsCollectionIndexes"
ArangoDB.drop_collection(@cn)
@cid = ArangoDB.create_collection(@cn)
end
after do
ArangoDB.drop_collection(@cn)
end
it "rolls back in case of violation" do
cmd = "/_api/index?collection=#{@cn}"
body = "{ \"type\" : \"persistent\", \"unique\" : true, \"fields\" : [ \"a\" ] }"
doc = ArangoDB.log_post("#{prefix}-create1", cmd, :body => body)
doc.code.should eq(201)
doc.parsed_response['type'].should eq("persistent")
doc.parsed_response['unique'].should eq(true)
# create a document
cmd1 = "/_api/document?collection=#{@cn}"
body = "{ \"a\" : 1, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
id1 = doc.parsed_response['_id']
id1.should be_kind_of(String)
rev1 = doc.parsed_response['_rev']
rev1.should be_kind_of(String)
# check it
cmd2 = "/_api/document/#{id1}"
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
# create a unique constraint violation
body = "{ \"a\" : 1, \"b\" : 2 }"
doc = ArangoDB.log_post("#{prefix}-create3", cmd1, :body => body)
doc.code.should eq(409)
# check it again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
# third try (make sure the rollback has not destroyed anything)
body = "{ \"a\" : 1, \"b\" : 3 }"
doc = ArangoDB.log_post("#{prefix}-create4", cmd1, :body => body)
doc.code.should eq(409)
# check it again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
# unload collection
cmd3 = "/_api/collection/#{@cn}/unload"
doc = ArangoDB.log_put("#{prefix}", cmd3)
doc.code.should eq(200)
# flush wal
doc = ArangoDB.put("/_admin/wal/flush");
doc.code.should eq(200)
cmd3 = "/_api/collection/#{@cn}"
doc = ArangoDB.log_get("#{prefix}", cmd3)
doc.code.should eq(200)
while doc.parsed_response['status'] != 2
doc = ArangoDB.get(cmd3)
doc.code.should eq(200)
sleep 1
end
# check it again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
end
it "rolls back in case of violation, sparse index" do
cmd = "/_api/index?collection=#{@cn}"
body = "{ \"type\" : \"persistent\", \"unique\" : true, \"fields\" : [ \"a\" ], \"sparse\" : true }"
doc = ArangoDB.log_post("#{prefix}-create1", cmd, :body => body)
doc.code.should eq(201)
doc.parsed_response['type'].should eq("persistent")
doc.parsed_response['unique'].should eq(true)
# create a document
cmd1 = "/_api/document?collection=#{@cn}"
body = "{ \"a\" : 1, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-create2", cmd1, :body => body)
doc.code.should eq(201)
id1 = doc.parsed_response['_id']
id1.should be_kind_of(String)
rev1 = doc.parsed_response['_rev']
rev1.should be_kind_of(String)
# check it
cmd2 = "/_api/document/#{id1}"
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
# create a unique constraint violation
body = "{ \"a\" : 1, \"b\" : 2 }"
doc = ArangoDB.log_post("#{prefix}-create3", cmd1, :body => body)
doc.code.should eq(409)
# check it again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
# third try (make sure the rollback has not destroyed anything)
body = "{ \"a\" : 1, \"b\" : 3 }"
doc = ArangoDB.log_post("#{prefix}-create4", cmd1, :body => body)
doc.code.should eq(409)
# check it again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
# unload collection
cmd3 = "/_api/collection/#{@cn}/unload"
doc = ArangoDB.log_put("#{prefix}", cmd3)
doc.code.should eq(200)
# flush wal
doc = ArangoDB.put("/_admin/wal/flush");
doc.code.should eq(200)
cmd3 = "/_api/collection/#{@cn}"
doc = ArangoDB.log_get("#{prefix}", cmd3)
doc.code.should eq(200)
while doc.parsed_response['status'] != 2
doc = ArangoDB.get(cmd3)
doc.code.should eq(200)
sleep 1
end
# check it again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
end
end
end
################################################################################
## unique constraints during update
################################################################################
context "updating documents:" do
context "dealing with unique constraints:" do
before do
@cn = "UnitTestsCollectionIndexes"
ArangoDB.drop_collection(@cn)
@cid = ArangoDB.create_collection(@cn)
end
after do
ArangoDB.drop_collection(@cn)
end
it "rolls back in case of violation" do
cmd = "/_api/index?collection=#{@cn}"
body = "{ \"type\" : \"persistent\", \"unique\" : true, \"fields\" : [ \"a\" ] }"
doc = ArangoDB.log_post("#{prefix}-update1", cmd, :body => body)
doc.code.should eq(201)
doc.parsed_response['type'].should eq("persistent")
doc.parsed_response['unique'].should eq(true)
# create a document
cmd1 = "/_api/document?collection=#{@cn}"
body = "{ \"a\" : 1, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-update2", cmd1, :body => body)
doc.code.should eq(201)
id1 = doc.parsed_response['_id']
id1.should be_kind_of(String)
rev1 = doc.parsed_response['_rev']
rev1.should be_kind_of(String)
# check it
cmd2 = "/_api/document/#{id1}"
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
# create a second document
body = "{ \"a\" : 2, \"b\" : 2 }"
doc = ArangoDB.log_post("#{prefix}-update3", cmd1, :body => body)
doc.code.should eq(201)
id2 = doc.parsed_response['_id']
id2.should be_kind_of(String)
rev2 = doc.parsed_response['_rev']
rev2.should be_kind_of(String)
# create a unique constraint violation during update
body = "{ \"a\" : 2, \"b\" : 3 }"
doc = ArangoDB.log_put("#{prefix}", cmd2, :body => body)
doc.code.should eq(409)
# check first document again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
rev3 = doc.parsed_response['_rev']
rev3.should be_kind_of(String)
# check second document again
cmd3 = "/_api/document/#{id2}"
doc = ArangoDB.log_get("#{prefix}", cmd3)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(2)
doc.parsed_response['b'].should eq(2)
doc.parsed_response['_id'].should eq(id2)
doc.parsed_response['_rev'].should eq(rev2)
# third try (make sure the rollback has not destroyed anything)
body = "{ \"a\" : 2, \"b\" : 4 }"
doc = ArangoDB.log_put("#{prefix}", cmd2, :body => body)
doc.code.should eq(409)
# check the first document again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
doc.parsed_response['_rev'].should_not eq(rev2)
# unload collection
cmd4 = "/_api/collection/#{@cn}/unload"
doc = ArangoDB.log_put("#{prefix}", cmd4)
doc.code.should eq(200)
# flush wal
doc = ArangoDB.put("/_admin/wal/flush");
doc.code.should eq(200)
cmd4 = "/_api/collection/#{@cn}"
doc = ArangoDB.log_get("#{prefix}", cmd4)
doc.code.should eq(200)
while doc.parsed_response['status'] != 2
doc = ArangoDB.get(cmd4)
doc.code.should eq(200)
sleep 1
end
# check the first document again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
doc.parsed_response['_rev'].should_not eq(rev2)
end
it "rolls back in case of violation, sparse index" do
cmd = "/_api/index?collection=#{@cn}"
body = "{ \"type\" : \"persistent\", \"unique\" : true, \"fields\" : [ \"a\" ], \"sparse\" : true }"
doc = ArangoDB.log_post("#{prefix}-update1", cmd, :body => body)
doc.code.should eq(201)
doc.parsed_response['type'].should eq("persistent")
doc.parsed_response['unique'].should eq(true)
# create a document
cmd1 = "/_api/document?collection=#{@cn}"
body = "{ \"a\" : 1, \"b\" : 1 }"
doc = ArangoDB.log_post("#{prefix}-update2", cmd1, :body => body)
doc.code.should eq(201)
id1 = doc.parsed_response['_id']
id1.should be_kind_of(String)
rev1 = doc.parsed_response['_rev']
rev1.should be_kind_of(String)
# check it
cmd2 = "/_api/document/#{id1}"
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
# create a second document
body = "{ \"a\" : 2, \"b\" : 2 }"
doc = ArangoDB.log_post("#{prefix}-update3", cmd1, :body => body)
doc.code.should eq(201)
id2 = doc.parsed_response['_id']
id2.should be_kind_of(String)
rev2 = doc.parsed_response['_rev']
rev2.should be_kind_of(String)
# create a unique constraint violation during update
body = "{ \"a\" : 2, \"b\" : 3 }"
doc = ArangoDB.log_put("#{prefix}", cmd2, :body => body)
doc.code.should eq(409)
# check first document again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
rev3 = doc.parsed_response['_rev']
rev3.should be_kind_of(String)
# check second document again
cmd3 = "/_api/document/#{id2}"
doc = ArangoDB.log_get("#{prefix}", cmd3)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(2)
doc.parsed_response['b'].should eq(2)
doc.parsed_response['_id'].should eq(id2)
doc.parsed_response['_rev'].should eq(rev2)
# third try (make sure the rollback has not destroyed anything)
body = "{ \"a\" : 2, \"b\" : 4 }"
doc = ArangoDB.log_put("#{prefix}", cmd2, :body => body)
doc.code.should eq(409)
# check the first document again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
doc.parsed_response['_rev'].should_not eq(rev2)
# unload collection
cmd4 = "/_api/collection/#{@cn}/unload"
doc = ArangoDB.log_put("#{prefix}", cmd4)
doc.code.should eq(200)
# flush wal
doc = ArangoDB.put("/_admin/wal/flush");
doc.code.should eq(200)
cmd4 = "/_api/collection/#{@cn}"
doc = ArangoDB.log_get("#{prefix}", cmd4)
doc.code.should eq(200)
while doc.parsed_response['status'] != 2
doc = ArangoDB.get(cmd4)
doc.code.should eq(200)
sleep 1
end
# check the first document again
doc = ArangoDB.log_get("#{prefix}", cmd2)
doc.code.should eq(200)
doc.parsed_response['a'].should eq(1)
doc.parsed_response['b'].should eq(1)
doc.parsed_response['_id'].should eq(id1)
doc.parsed_response['_rev'].should eq(rev1)
doc.parsed_response['_rev'].should_not eq(rev2)
end
end
end
end

View File

@ -258,6 +258,7 @@ void RocksDBIndex::toVelocyPackFigures(VPackBuilder& builder) const {
int RocksDBIndex::insert(arangodb::Transaction* trx, TRI_doc_mptr_t const* doc,
bool) {
auto comparator = RocksDBFeature::instance()->comparator();
std::vector<TRI_index_element_t*> elements;
int res;
@ -281,9 +282,18 @@ int RocksDBIndex::insert(arangodb::Transaction* trx, TRI_doc_mptr_t const* doc,
}
VPackSlice const key = Transaction::extractKeyFromDocument(VPackSlice(doc->vpack()));
std::string const prefix = buildPrefix(trx->vocbase()->_id, _collection->_info.id(), _iid);
VPackBuilder builder;
std::vector<std::string> values;
values.reserve(elements.size());
// lower and upper bounds, only required if the index is unique
std::vector<std::pair<std::string, std::string>> bounds;
if (_unique) {
bounds.reserve(elements.size());
}
for (auto& it : elements) {
builder.clear();
builder.openArray();
@ -296,9 +306,44 @@ int RocksDBIndex::insert(arangodb::Transaction* trx, TRI_doc_mptr_t const* doc,
VPackSlice const s = builder.slice();
std::string value;
value.reserve(keyPrefixSize() + s.byteSize());
value += buildPrefix(trx->vocbase()->_id, _collection->_info.id(), _iid);
value += prefix;
value.append(s.startAs<char const>(), s.byteSize());
values.emplace_back(std::move(value));
if (_unique) {
builder.clear();
builder.openArray();
for (size_t i = 0; i < _fields.size(); ++i) {
builder.add(it->subObjects()[i].slice(doc));
}
builder.add(VPackSlice::minKeySlice());
builder.close();
VPackSlice s = builder.slice();
std::string value;
value.reserve(keyPrefixSize() + s.byteSize());
value += prefix;
value.append(s.startAs<char const>(), s.byteSize());
std::pair<std::string, std::string> p;
p.first = value;
builder.clear();
builder.openArray();
for (size_t i = 0; i < _fields.size(); ++i) {
builder.add(it->subObjects()[i].slice(doc));
}
builder.add(VPackSlice::maxKeySlice());
builder.close();
s = builder.slice();
value.clear();
value += prefix;
value.append(s.startAs<char const>(), s.byteSize());
p.second = value;
bounds.emplace_back(std::move(p));
}
}
auto rocksTransaction = trx->rocksTransaction();
@ -309,10 +354,28 @@ int RocksDBIndex::insert(arangodb::Transaction* trx, TRI_doc_mptr_t const* doc,
size_t const count = elements.size();
for (size_t i = 0; i < count; ++i) {
if (_unique) {
std::string existing;
auto status = rocksTransaction->Get(readOptions, values[i], &existing);
bool uniqueConstraintViolated = false;
auto iterator = rocksTransaction->GetIterator(readOptions);
if (status.ok()) {
if (iterator != nullptr) {
auto& bound = bounds[i];
iterator->Seek(rocksdb::Slice(bound.first.c_str(), bound.first.size()));
while (iterator->Valid()) {
int res = comparator->Compare(iterator->key(), rocksdb::Slice(bound.second.c_str(), bound.second.size()));
if (res > 0) {
break;
}
uniqueConstraintViolated = true;
break;
}
delete iterator;
}
if (uniqueConstraintViolated) {
// duplicate key
res = TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED;
if (!_collection->useSecondaryIndexes()) {
@ -324,6 +387,7 @@ int RocksDBIndex::insert(arangodb::Transaction* trx, TRI_doc_mptr_t const* doc,
if (res == TRI_ERROR_NO_ERROR) {
auto status = rocksTransaction->Put(values[i], std::string());
if (! status.ok()) {
res = TRI_ERROR_INTERNAL;
}