diff options
9 files changed, 112 insertions, 36 deletions
diff --git a/persistence/src/vespa/persistence/conformancetest/conformancetest.cpp b/persistence/src/vespa/persistence/conformancetest/conformancetest.cpp index c1373a391f0..4d9d48c4926 100644 --- a/persistence/src/vespa/persistence/conformancetest/conformancetest.cpp +++ b/persistence/src/vespa/persistence/conformancetest/conformancetest.cpp @@ -948,7 +948,6 @@ void ConformanceTest::testUpdate() { CPPUNIT_ASSERT(!result.hasDocument()); } - { UpdateResult result = spi->update(bucket, Timestamp(6), update, context); @@ -957,6 +956,32 @@ void ConformanceTest::testUpdate() { CPPUNIT_ASSERT_EQUAL(Result::NONE, result.getErrorCode()); CPPUNIT_ASSERT_EQUAL(Timestamp(0), result.getExistingTimestamp()); } + + { + GetResult result = spi->get(bucket, document::AllFields(), doc1->getId(), context); + CPPUNIT_ASSERT_EQUAL(Result::NONE, result.getErrorCode()); + CPPUNIT_ASSERT_EQUAL(Timestamp(0), result.getTimestamp()); + CPPUNIT_ASSERT(!result.hasDocument()); + } + + update->setCreateIfNonExistent(true); + { + // Document does not exist (and therefore its condition cannot match by definition), + // but since CreateIfNonExistent is set it should be auto-created anyway. + UpdateResult result = spi->update(bucket, Timestamp(7), update, context); + spi->flush(bucket, context); + CPPUNIT_ASSERT_EQUAL(Result::NONE, result.getErrorCode()); + CPPUNIT_ASSERT_EQUAL(Timestamp(7), result.getExistingTimestamp()); + } + + { + GetResult result = spi->get(bucket, document::AllFields(), doc1->getId(), context); + CPPUNIT_ASSERT_EQUAL(Result::NONE, result.getErrorCode()); + CPPUNIT_ASSERT_EQUAL(Timestamp(7), result.getTimestamp()); + CPPUNIT_ASSERT_EQUAL(document::IntFieldValue(42), + reinterpret_cast<document::IntFieldValue&>( + *result.getDocument().getValue("headerval"))); + } } void ConformanceTest::testGet() { diff --git a/persistence/src/vespa/persistence/spi/abstractpersistenceprovider.cpp b/persistence/src/vespa/persistence/spi/abstractpersistenceprovider.cpp index 5e6e908e042..7f6bd835cd5 100644 --- a/persistence/src/vespa/persistence/spi/abstractpersistenceprovider.cpp +++ b/persistence/src/vespa/persistence/spi/abstractpersistenceprovider.cpp @@ -1,9 +1,10 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "abstractpersistenceprovider.h" +#include <vespa/document/datatype/documenttype.h> #include <vespa/document/update/documentupdate.h> #include <vespa/document/fieldset/fieldsets.h> - +#include <vespa/document/fieldvalue/document.h> namespace storage { @@ -19,20 +20,27 @@ AbstractPersistenceProvider::update(const Bucket& bucket, Timestamp ts, return UpdateResult(getResult.getErrorCode(), getResult.getErrorMessage()); } - if (!getResult.hasDocument()) { - return UpdateResult(); + auto docToUpdate = getResult.getDocumentPtr(); + Timestamp updatedTs = getResult.getTimestamp(); + if (!docToUpdate) { + if (!upd->getCreateIfNonExistent()) { + return UpdateResult(); + } else { + docToUpdate = std::make_shared<document::Document>(upd->getType(), upd->getId()); + updatedTs = ts; + } } - upd->applyTo(getResult.getDocument()); + upd->applyTo(*docToUpdate); - Result putResult = put(bucket, ts, getResult.getDocumentPtr(), context); + Result putResult = put(bucket, ts, docToUpdate, context); if (putResult.hasError()) { return UpdateResult(putResult.getErrorCode(), putResult.getErrorMessage()); } - return UpdateResult(getResult.getTimestamp()); + return UpdateResult(updatedTs); } RemoveResult diff --git a/storage/src/tests/distributor/twophaseupdateoperationtest.cpp b/storage/src/tests/distributor/twophaseupdateoperationtest.cpp index 3c82931467e..a8771ddc28a 100644 --- a/storage/src/tests/distributor/twophaseupdateoperationtest.cpp +++ b/storage/src/tests/distributor/twophaseupdateoperationtest.cpp @@ -61,7 +61,8 @@ class TwoPhaseUpdateOperationTest : public CppUnit::TestFixture, CPPUNIT_TEST(testSafePathConditionMatchSendsPutsWithUpdatedDoc); CPPUNIT_TEST(testSafePathConditionParseFailureFailsWithIllegalParamsError); CPPUNIT_TEST(testSafePathConditonUnknownDocTypeFailsWithIllegalParamsError); - CPPUNIT_TEST(testSafePathConditionWithMissingDocFailsWithTasError); + CPPUNIT_TEST(safe_path_condition_with_missing_doc_and_no_auto_create_fails_with_tas_error); + CPPUNIT_TEST(safe_path_condition_with_missing_doc_and_auto_create_sends_puts); CPPUNIT_TEST(testFastPathCloseEdgeSendsCorrectReply); CPPUNIT_TEST(testSafePathCloseEdgeSendsCorrectReply); CPPUNIT_TEST_SUITE_END(); @@ -97,7 +98,8 @@ protected: void testSafePathConditionMatchSendsPutsWithUpdatedDoc(); void testSafePathConditionParseFailureFailsWithIllegalParamsError(); void testSafePathConditonUnknownDocTypeFailsWithIllegalParamsError(); - void testSafePathConditionWithMissingDocFailsWithTasError(); + void safe_path_condition_with_missing_doc_and_no_auto_create_fails_with_tas_error(); + void safe_path_condition_with_missing_doc_and_auto_create_sends_puts(); void testFastPathCloseEdgeSendsCorrectReply(); void testSafePathCloseEdgeSendsCorrectReply(); @@ -1096,7 +1098,7 @@ TwoPhaseUpdateOperationTest::testSafePathConditonUnknownDocTypeFailsWithIllegalP } void -TwoPhaseUpdateOperationTest::testSafePathConditionWithMissingDocFailsWithTasError() +TwoPhaseUpdateOperationTest::safe_path_condition_with_missing_doc_and_no_auto_create_fails_with_tas_error() { setupDistributor(2, 2, "storage:2 distributor:1"); std::shared_ptr<TwoPhaseUpdateOperation> cb( @@ -1118,6 +1120,22 @@ TwoPhaseUpdateOperationTest::testSafePathConditionWithMissingDocFailsWithTasErro } void +TwoPhaseUpdateOperationTest::safe_path_condition_with_missing_doc_and_auto_create_sends_puts() +{ + setupDistributor(2, 2, "storage:2 distributor:1"); + std::shared_ptr<TwoPhaseUpdateOperation> cb( + sendUpdate("0=1/2/3,1=2/3/4", UpdateOptions() + .condition("testdoctype1.headerval==120") + .createIfNonExistent(true))); + + MessageSenderStub sender; + cb->start(sender, framework::MilliSecTime(0)); + replyToGet(*cb, sender, 0, 100, false); + replyToGet(*cb, sender, 1, 110, false); + CPPUNIT_ASSERT_EQUAL("Put => 1,Put => 0"s, sender.getCommands(true, false, 2)); +} + +void TwoPhaseUpdateOperationTest::assertAbortedUpdateReplyWithContextPresent( const MessageSenderStub& closeSender) const { @@ -1177,9 +1195,6 @@ TwoPhaseUpdateOperationTest::testSafePathCloseEdgeSendsCorrectReply() // document IDs without explicit doctypes will _not_ be auto-failed on the // distributor. -// XXX shouldn't be necessary to have any special handling of create-if... and -// test-and-set right? They appear fully mutually exclusive. - // XXX: test case where update reply has been sent but callback still // has pending messages (e.g. n-of-m case). diff --git a/storage/src/tests/persistence/testandsettest.cpp b/storage/src/tests/persistence/testandsettest.cpp index 686e10ba5ef..197aa95fc22 100644 --- a/storage/src/tests/persistence/testandsettest.cpp +++ b/storage/src/tests/persistence/testandsettest.cpp @@ -66,8 +66,10 @@ public: void conditional_remove_executed_on_condition_match(); void conditional_update_not_executed_on_condition_mismatch(); void conditional_update_executed_on_condition_match(); + void conditional_update_not_executed_when_no_document_and_no_auto_create(); + void conditional_update_executed_when_no_document_but_auto_create_is_enabled(); void invalid_document_selection_should_fail(); - void non_existing_document_should_fail(); + void conditional_put_to_non_existing_document_should_fail(); void document_with_no_type_should_fail(); CPPUNIT_TEST_SUITE(TestAndSetTest); @@ -77,16 +79,17 @@ public: CPPUNIT_TEST(conditional_remove_executed_on_condition_match); CPPUNIT_TEST(conditional_update_not_executed_on_condition_mismatch); CPPUNIT_TEST(conditional_update_executed_on_condition_match); + CPPUNIT_TEST(conditional_update_not_executed_when_no_document_and_no_auto_create); + CPPUNIT_TEST(conditional_update_executed_when_no_document_but_auto_create_is_enabled); CPPUNIT_TEST(invalid_document_selection_should_fail); - CPPUNIT_TEST(non_existing_document_should_fail); + CPPUNIT_TEST(conditional_put_to_non_existing_document_should_fail); CPPUNIT_TEST(document_with_no_type_should_fail); CPPUNIT_TEST_SUITE_END(); protected: std::unique_ptr<api::UpdateCommand> conditional_update_test( - bool matchingHeader, - api::Timestamp timestampOne, - api::Timestamp timestampTwo); + bool createIfMissing, + api::Timestamp updateTimestamp); document::Document::SP createTestDocument(); document::Document::SP retrieveTestDocument(); @@ -183,18 +186,16 @@ void TestAndSetTest::conditional_remove_executed_on_condition_match() } std::unique_ptr<api::UpdateCommand> TestAndSetTest::conditional_update_test( - bool matchingHeader, - api::Timestamp timestampOne, - api::Timestamp timestampTwo) + bool createIfMissing, + api::Timestamp updateTimestamp) { - putTestDocument(matchingHeader, timestampOne); - auto docUpdate = std::make_shared<document::DocumentUpdate>(_env->_testDocMan.getTypeRepo(), testDoc->getType(), testDocId); auto fieldUpdate = document::FieldUpdate(testDoc->getField("content")); fieldUpdate.addUpdate(document::AssignValueUpdate(NEW_CONTENT)); docUpdate->addUpdate(fieldUpdate); + docUpdate->setCreateIfNonExistent(createIfMissing); - auto updateUp = std::make_unique<api::UpdateCommand>(makeDocumentBucket(BUCKET_ID), docUpdate, timestampTwo); + auto updateUp = std::make_unique<api::UpdateCommand>(makeDocumentBucket(BUCKET_ID), docUpdate, updateTimestamp); setTestCondition(*updateUp); return updateUp; } @@ -203,11 +204,12 @@ void TestAndSetTest::conditional_update_not_executed_on_condition_mismatch() { api::Timestamp timestampOne = 0; api::Timestamp timestampTwo = 1; - auto updateUp = conditional_update_test(false, timestampOne, timestampTwo); + putTestDocument(false, timestampOne); + auto updateUp = conditional_update_test(false, timestampTwo); CPPUNIT_ASSERT(thread->handleUpdate(*updateUp)->getResult() == api::ReturnCode::Result::TEST_AND_SET_CONDITION_FAILED); CPPUNIT_ASSERT_EQUAL(expectedDocEntryString(timestampOne, testDocId), - dumpBucket(BUCKET_ID)); + dumpBucket(BUCKET_ID)); assertTestDocumentFoundAndMatchesContent(OLD_CONTENT); } @@ -216,7 +218,8 @@ void TestAndSetTest::conditional_update_executed_on_condition_match() { api::Timestamp timestampOne = 0; api::Timestamp timestampTwo = 1; - auto updateUp = conditional_update_test(true, timestampOne, timestampTwo); + putTestDocument(true, timestampOne); + auto updateUp = conditional_update_test(false, timestampTwo); CPPUNIT_ASSERT(thread->handleUpdate(*updateUp)->getResult() == api::ReturnCode::Result::OK); CPPUNIT_ASSERT_EQUAL(expectedDocEntryString(timestampOne, testDocId) + @@ -226,6 +229,23 @@ void TestAndSetTest::conditional_update_executed_on_condition_match() assertTestDocumentFoundAndMatchesContent(NEW_CONTENT); } +void TestAndSetTest::conditional_update_not_executed_when_no_document_and_no_auto_create() { + api::Timestamp updateTimestamp = 200; + auto updateUp = conditional_update_test(false, updateTimestamp); + + CPPUNIT_ASSERT(thread->handleUpdate(*updateUp)->getResult() == api::ReturnCode::Result::TEST_AND_SET_CONDITION_FAILED); + CPPUNIT_ASSERT_EQUAL(""s, dumpBucket(BUCKET_ID)); +} + +void TestAndSetTest::conditional_update_executed_when_no_document_but_auto_create_is_enabled() { + api::Timestamp updateTimestamp = 200; + auto updateUp = conditional_update_test(true, updateTimestamp); + + CPPUNIT_ASSERT(thread->handleUpdate(*updateUp)->getResult() == api::ReturnCode::Result::OK); + CPPUNIT_ASSERT_EQUAL(expectedDocEntryString(updateTimestamp, testDocId), dumpBucket(BUCKET_ID)); + assertTestDocumentFoundAndMatchesContent(NEW_CONTENT); +} + void TestAndSetTest::invalid_document_selection_should_fail() { // Conditionally replace nonexisting document @@ -238,7 +258,7 @@ void TestAndSetTest::invalid_document_selection_should_fail() CPPUNIT_ASSERT_EQUAL(""s, dumpBucket(BUCKET_ID)); } -void TestAndSetTest::non_existing_document_should_fail() +void TestAndSetTest::conditional_put_to_non_existing_document_should_fail() { // Conditionally replace nonexisting document // Fail since no document exists to match with test and set diff --git a/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp b/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp index c652f787b2e..b7ebafc114c 100644 --- a/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp +++ b/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp @@ -378,7 +378,7 @@ TwoPhaseUpdateOperation::handleSafePathReceivedGet(DistributorMessageSender& sen } docToUpdate = reply.getDocument(); setUpdatedForTimestamp(receivedTimestamp); - } else if (hasTasCondition()) { + } else if (hasTasCondition() && !shouldCreateIfNonExistent()) { replyWithTasFailure(sender, "Document did not exist"); return; } else if (shouldCreateIfNonExistent()) { diff --git a/storage/src/vespa/storage/persistence/persistencethread.cpp b/storage/src/vespa/storage/persistence/persistencethread.cpp index 1b221ea0b7c..55f7c076244 100644 --- a/storage/src/vespa/storage/persistence/persistencethread.cpp +++ b/storage/src/vespa/storage/persistence/persistencethread.cpp @@ -85,9 +85,10 @@ bool PersistenceThread::tasConditionExists(const api::TestAndSetCommand & cmd) { return cmd.getCondition().isPresent(); } -bool PersistenceThread::tasConditionMatches(const api::TestAndSetCommand & cmd, MessageTracker & tracker) { +bool PersistenceThread::tasConditionMatches(const api::TestAndSetCommand & cmd, MessageTracker & tracker, + bool missingDocumentImpliesMatch) { try { - TestAndSetHelper helper(*this, cmd); + TestAndSetHelper helper(*this, cmd, missingDocumentImpliesMatch); auto code = helper.retrieveAndMatch(); if (code.failed()) { @@ -149,7 +150,7 @@ PersistenceThread::handleUpdate(api::UpdateCommand& cmd) auto tracker = std::make_unique<MessageTracker>(metrics, _env._component.getClock()); metrics.request_size.addValue(cmd.getApproxByteSize()); - if (tasConditionExists(cmd) && !tasConditionMatches(cmd, *tracker)) { + if (tasConditionExists(cmd) && !tasConditionMatches(cmd, *tracker, cmd.getUpdate()->getCreateIfNonExistent())) { return tracker; } diff --git a/storage/src/vespa/storage/persistence/persistencethread.h b/storage/src/vespa/storage/persistence/persistencethread.h index 05ca34f623d..ed27a759e8b 100644 --- a/storage/src/vespa/storage/persistence/persistencethread.h +++ b/storage/src/vespa/storage/persistence/persistencethread.h @@ -87,7 +87,8 @@ private: friend class TestAndSetHelper; bool tasConditionExists(const api::TestAndSetCommand & cmd); - bool tasConditionMatches(const api::TestAndSetCommand & cmd, MessageTracker & tracker); + bool tasConditionMatches(const api::TestAndSetCommand & cmd, MessageTracker & tracker, + bool missingDocumentImpliesMatch = false); }; } // storage diff --git a/storage/src/vespa/storage/persistence/testandsethelper.cpp b/storage/src/vespa/storage/persistence/testandsethelper.cpp index 70a6bdf21db..511d44ad331 100644 --- a/storage/src/vespa/storage/persistence/testandsethelper.cpp +++ b/storage/src/vespa/storage/persistence/testandsethelper.cpp @@ -39,11 +39,13 @@ spi::GetResult TestAndSetHelper::retrieveDocument(const document::FieldSet & fie _thread._context); } -TestAndSetHelper::TestAndSetHelper(PersistenceThread & thread, const api::TestAndSetCommand & cmd) +TestAndSetHelper::TestAndSetHelper(PersistenceThread & thread, const api::TestAndSetCommand & cmd, + bool missingDocumentImpliesMatch) : _thread(thread), _component(thread._env._component), _cmd(cmd), - _docId(cmd.getDocumentId()) + _docId(cmd.getDocumentId()), + _missingDocumentImpliesMatch(missingDocumentImpliesMatch) { getDocumentType(); parseDocumentSelection(); @@ -69,6 +71,8 @@ api::ReturnCode TestAndSetHelper::retrieveAndMatch() { // Document matches return api::ReturnCode(); + } else if (_missingDocumentImpliesMatch) { + return api::ReturnCode(); } return api::ReturnCode(api::ReturnCode::TEST_AND_SET_CONDITION_FAILED, "Document does not exist"); diff --git a/storage/src/vespa/storage/persistence/testandsethelper.h b/storage/src/vespa/storage/persistence/testandsethelper.h index 4e48259bc79..21c111c712f 100644 --- a/storage/src/vespa/storage/persistence/testandsethelper.h +++ b/storage/src/vespa/storage/persistence/testandsethelper.h @@ -26,13 +26,15 @@ class TestAndSetHelper { const document::DocumentId _docId; const document::DocumentType * _docTypePtr; std::unique_ptr<document::select::Node> _docSelectionUp; + bool _missingDocumentImpliesMatch; void getDocumentType(); void parseDocumentSelection(); spi::GetResult retrieveDocument(const document::FieldSet & fieldSet); public: - TestAndSetHelper(PersistenceThread & thread, const api::TestAndSetCommand & cmd); + TestAndSetHelper(PersistenceThread & thread, const api::TestAndSetCommand & cmd, + bool missingDocumentImpliesMatch = false); ~TestAndSetHelper(); api::ReturnCode retrieveAndMatch(); }; |