diff options
Diffstat (limited to 'document/src/tests/documentupdatetestcase.cpp')
-rw-r--r-- | document/src/tests/documentupdatetestcase.cpp | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/document/src/tests/documentupdatetestcase.cpp b/document/src/tests/documentupdatetestcase.cpp new file mode 100644 index 00000000000..be619b0f35e --- /dev/null +++ b/document/src/tests/documentupdatetestcase.cpp @@ -0,0 +1,1030 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/fastos/fastos.h> +#include <vespa/document/fieldvalue/fieldvalues.h> +#include <vespa/document/update/documentupdate.h> +#include <vespa/document/base/testdocman.h> + +#include <vespa/document/update/addvalueupdate.h> +#include <vespa/document/update/arithmeticvalueupdate.h> +#include <vespa/document/update/assignvalueupdate.h> +#include <vespa/document/update/clearvalueupdate.h> +#include <vespa/document/update/documentupdateflags.h> +#include <vespa/document/update/fieldupdate.h> +#include <vespa/document/update/mapvalueupdate.h> +#include <vespa/document/update/removevalueupdate.h> +#include <vespa/document/update/valueupdate.h> +#include <vespa/document/serialization/vespadocumentserializer.h> + +#include <vespa/document/repo/configbuilder.h> +#include <vespa/document/repo/documenttyperepo.h> +#include <vespa/vdstestlib/cppunit/macros.h> + +using namespace document::config_builder; + +namespace document { + +struct DocumentUpdateTest : public CppUnit::TestFixture { + void setUp() {} + void tearDown() {} + + void testSimpleUsage(); + void testUpdateApplySingleValue(); + void testClearField(); + void testUpdateArray(); + void testUpdateWeightedSet(); + void testIncrementNonExistingAutoCreateWSetField(); + void testIncrementExistingWSetField(); + void testIncrementWithZeroResultWeightIsRemoved(); + void testReadSerializedFile(); + void testGenerateSerializedFile(); + void testSetBadFieldTypes(); + void testUpdateApplyNoParams(); + void testUpdateApplyNoArrayValues(); + void testUpdateArrayEmptyParamValue(); + void testUpdateWeightedSetEmptyParamValue(); + void testUpdateArrayWrongSubtype(); + void testUpdateWeightedSetWrongSubtype(); + void testMapValueUpdate(); + void testThatDocumentUpdateFlagsIsWorking(); + void testThatCreateIfNonExistentFlagIsSerialized50AndDeserialized50(); + void testThatCreateIfNonExistentFlagIsSerializedAndDeserialized(); + + CPPUNIT_TEST_SUITE(DocumentUpdateTest); + CPPUNIT_TEST(testSimpleUsage); + CPPUNIT_TEST(testUpdateApplySingleValue); + CPPUNIT_TEST(testClearField); + CPPUNIT_TEST(testUpdateArray); + CPPUNIT_TEST(testUpdateWeightedSet); + CPPUNIT_TEST(testIncrementNonExistingAutoCreateWSetField); + CPPUNIT_TEST(testIncrementExistingWSetField); + CPPUNIT_TEST(testIncrementWithZeroResultWeightIsRemoved); + CPPUNIT_TEST(testReadSerializedFile); + CPPUNIT_TEST(testGenerateSerializedFile); + CPPUNIT_TEST(testSetBadFieldTypes); + CPPUNIT_TEST(testUpdateApplyNoParams); + CPPUNIT_TEST(testUpdateApplyNoArrayValues); + CPPUNIT_TEST(testUpdateArrayEmptyParamValue); + CPPUNIT_TEST(testUpdateWeightedSetEmptyParamValue); + CPPUNIT_TEST(testUpdateArrayWrongSubtype); + CPPUNIT_TEST(testUpdateWeightedSetWrongSubtype); + CPPUNIT_TEST(testMapValueUpdate); + CPPUNIT_TEST(testThatDocumentUpdateFlagsIsWorking); + CPPUNIT_TEST(testThatCreateIfNonExistentFlagIsSerialized50AndDeserialized50); + CPPUNIT_TEST(testThatCreateIfNonExistentFlagIsSerializedAndDeserialized); + CPPUNIT_TEST_SUITE_END(); + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DocumentUpdateTest); + +namespace { + +ByteBuffer::UP serializeHEAD(const DocumentUpdate & update) +{ + vespalib::nbostream stream; + VespaDocumentSerializer serializer(stream); + serializer.writeHEAD(update); + ByteBuffer::UP retVal(new ByteBuffer(stream.size())); + retVal->putBytes(stream.peek(), stream.size()); + return retVal; +} + +ByteBuffer::UP serialize42(const DocumentUpdate & update) +{ + vespalib::nbostream stream; + VespaDocumentSerializer serializer(stream); + serializer.write42(update); + ByteBuffer::UP retVal(new ByteBuffer(stream.size())); + retVal->putBytes(stream.peek(), stream.size()); + return retVal; +} + +ByteBuffer::UP serialize(const ValueUpdate & update) +{ + vespalib::nbostream stream; + VespaDocumentSerializer serializer(stream); + serializer.write(update); + ByteBuffer::UP retVal(new ByteBuffer(stream.size())); + retVal->putBytes(stream.peek(), stream.size()); + return retVal; +} + +ByteBuffer::UP serialize(const FieldUpdate & update) +{ + vespalib::nbostream stream; + VespaDocumentSerializer serializer(stream); + serializer.write(update); + ByteBuffer::UP retVal(new ByteBuffer(stream.size())); + retVal->putBytes(stream.peek(), stream.size()); + return retVal; +} + +template<typename UpdateType> +void testValueUpdate(const UpdateType& update, const DataType &type) { + try{ + DocumentTypeRepo repo; + ByteBuffer::UP buf = serialize(update); + buf->flip(); + typename UpdateType::UP copy(dynamic_cast<UpdateType*>( + ValueUpdate::createInstance(repo, type, *buf, + Document::getNewestSerializationVersion()) + .release())); + CPPUNIT_ASSERT_EQUAL(update, *copy); + } catch (std::exception& e) { + std::cerr << "Failed while processing update " << update << "\n"; + throw; + } +} + + +} // namespace + +void +DocumentUpdateTest::testSimpleUsage() { + DocumenttypesConfigBuilderHelper builder; + builder.document(42, "test", + Struct("test.header") + .addField("bytef", DataType::T_BYTE) + .addField("intf", DataType::T_INT), + Struct("test.body") + .addField("intarr", Array(DataType::T_INT))); + DocumentTypeRepo repo(builder.config()); + const DocumentType* docType(repo.getDocumentType("test")); + const DataType *arrayType = repo.getDataType(*docType, "Array<Int>"); + + // Test that primitive value updates can be serialized + testValueUpdate(ClearValueUpdate(), *DataType::INT); + testValueUpdate(AssignValueUpdate(IntFieldValue(1)), *DataType::INT); + testValueUpdate(ArithmeticValueUpdate(ArithmeticValueUpdate::Div, 4.3), + *DataType::FLOAT); + testValueUpdate(AddValueUpdate(IntFieldValue(1), 4), *arrayType); + testValueUpdate(RemoveValueUpdate(IntFieldValue(1)), *arrayType); + + FieldUpdate fieldUpdate(docType->getField("intf")); + fieldUpdate.addUpdate(AssignValueUpdate(IntFieldValue(1))); + ByteBuffer::UP fieldBuf = serialize(fieldUpdate); + fieldBuf->flip(); + FieldUpdate fieldUpdateCopy(repo, *docType, *fieldBuf, + Document::getNewestSerializationVersion()); + CPPUNIT_ASSERT_EQUAL(fieldUpdate, fieldUpdateCopy); + + // Test that a document update can be serialized + DocumentUpdate docUpdate(*docType, DocumentId("doc::testdoc")); + docUpdate.addUpdate(fieldUpdateCopy); + ByteBuffer::UP docBuf = serialize42(docUpdate); + docBuf->flip(); + DocumentUpdate::UP docUpdateCopy(DocumentUpdate::create42(repo, *docBuf)); + CPPUNIT_ASSERT(!docUpdate.affectsDocumentBody()); + CPPUNIT_ASSERT(!docUpdateCopy->affectsDocumentBody()); + + // Create a test document + Document doc(*docType, DocumentId("doc::testdoc")); + doc.set("bytef", 0); + doc.set("intf", 5); + ArrayFieldValue array(*arrayType); + array.add(IntFieldValue(3)); + array.add(IntFieldValue(7)); + doc.setValue("intarr", array); + + // Verify that we can apply simple updates to it + { + Document updated(doc); + DocumentUpdate upd(*docType, DocumentId("doc::testdoc")); + upd.addUpdate(FieldUpdate(docType->getField("intf")) + .addUpdate(ClearValueUpdate())); + upd.applyTo(updated); + CPPUNIT_ASSERT(doc != updated); + CPPUNIT_ASSERT(! updated.getValue("intf")); + } + { + Document updated(doc); + DocumentUpdate upd(*docType, DocumentId("doc::testdoc")); + upd.addUpdate(FieldUpdate(docType->getField("intf")) + .addUpdate(AssignValueUpdate(IntFieldValue(15)))); + upd.applyTo(updated); + CPPUNIT_ASSERT(doc != updated); + CPPUNIT_ASSERT_EQUAL(15, updated.getValue("intf")->getAsInt()); + } + { + Document updated(doc); + DocumentUpdate upd(*docType, DocumentId("doc::testdoc")); + upd.addUpdate(FieldUpdate(docType->getField("intf")) + .addUpdate(ArithmeticValueUpdate( + ArithmeticValueUpdate::Add, 15))); + upd.applyTo(updated); + CPPUNIT_ASSERT(doc != updated); + CPPUNIT_ASSERT_EQUAL(20, updated.getValue("intf")->getAsInt()); + } + { + Document updated(doc); + DocumentUpdate upd(*docType, DocumentId("doc::testdoc")); + upd.addUpdate(FieldUpdate(docType->getField("intarr")) + .addUpdate(AddValueUpdate(IntFieldValue(4)))); + upd.applyTo(updated); + CPPUNIT_ASSERT(doc != updated); + std::unique_ptr<ArrayFieldValue> val(dynamic_cast<ArrayFieldValue*>( + updated.getValue("intarr").release())); + CPPUNIT_ASSERT_EQUAL(size_t(3), val->size()); + CPPUNIT_ASSERT_EQUAL(4, (*val)[2].getAsInt()); + CPPUNIT_ASSERT(upd.affectsDocumentBody()); + } + { + Document updated(doc); + DocumentUpdate upd(*docType, DocumentId("doc::testdoc")); + upd.addUpdate(FieldUpdate(docType->getField("intarr")) + .addUpdate(RemoveValueUpdate(IntFieldValue(3)))); + upd.applyTo(updated); + CPPUNIT_ASSERT(doc != updated); + std::unique_ptr<ArrayFieldValue> val(dynamic_cast<ArrayFieldValue*>( + updated.getValue("intarr").release())); + CPPUNIT_ASSERT_EQUAL(size_t(1), val->size()); + CPPUNIT_ASSERT_EQUAL(7, (*val)[0].getAsInt()); + } + { + Document updated(doc); + DocumentUpdate upd(*docType, DocumentId("doc::testdoc")); + upd.addUpdate(FieldUpdate(docType->getField("bytef")) + .addUpdate(ArithmeticValueUpdate( + ArithmeticValueUpdate::Add, 15))); + upd.applyTo(updated); + CPPUNIT_ASSERT(doc != updated); + CPPUNIT_ASSERT_EQUAL(15, (int) updated.getValue("bytef")->getAsByte()); + } +} + +void +DocumentUpdateTest::testClearField() +{ + // Create a document. + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + doc->setValue(doc->getField("headerval"), IntFieldValue(4)); + CPPUNIT_ASSERT_EQUAL(4, doc->getValue("headerval")->getAsInt()); + + // Apply an update. + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(doc->getField("headerval")) + .addUpdate(AssignValueUpdate())) + .applyTo(*doc); + CPPUNIT_ASSERT(!doc->getValue("headerval")); +} + +void +DocumentUpdateTest::testUpdateApplySingleValue() +{ + // Create a document. + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + doc->setValue(doc->getField("headerval"), IntFieldValue(4)); + CPPUNIT_ASSERT_EQUAL(4, doc->getValue("headerval")->getAsInt()); + + // Apply an update. + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(doc->getField("headerval")) + .addUpdate(AssignValueUpdate(IntFieldValue(9)))) + .applyTo(*doc); + CPPUNIT_ASSERT_EQUAL(9, doc->getValue("headerval")->getAsInt()); +} + +void +DocumentUpdateTest::testUpdateArray() +{ + // Create a document. + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*)NULL, + doc->getValue(doc->getField("tags")).get()); + + // Assign array field. + ArrayFieldValue myarray(doc->getType().getField("tags").getDataType()); + myarray.add(StringFieldValue("foo")); + myarray.add(StringFieldValue("bar")); + + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(doc->getField("tags")) + .addUpdate(AssignValueUpdate(myarray))) + .applyTo(*doc); + std::unique_ptr<ArrayFieldValue> + fval1(doc->getAs<ArrayFieldValue>(doc->getField("tags"))); + CPPUNIT_ASSERT_EQUAL((size_t) 2, fval1->size()); + CPPUNIT_ASSERT_EQUAL(std::string("foo"), + std::string((*fval1)[0].getAsString())); + CPPUNIT_ASSERT_EQUAL(std::string("bar"), + std::string((*fval1)[1].getAsString())); + + // Append array field + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(doc->getField("tags")) + .addUpdate(AddValueUpdate(StringFieldValue("another"))) + .addUpdate(AddValueUpdate(StringFieldValue("tag")))) + .applyTo(*doc); + std::unique_ptr<ArrayFieldValue> + fval2(doc->getAs<ArrayFieldValue>(doc->getField("tags"))); + CPPUNIT_ASSERT_EQUAL((size_t) 4, fval2->size()); + CPPUNIT_ASSERT_EQUAL(std::string("foo"), + std::string((*fval2)[0].getAsString())); + CPPUNIT_ASSERT_EQUAL(std::string("bar"), + std::string((*fval2)[1].getAsString())); + CPPUNIT_ASSERT_EQUAL(std::string("another"), + std::string((*fval2)[2].getAsString())); + CPPUNIT_ASSERT_EQUAL(std::string("tag"), + std::string((*fval2)[3].getAsString())); + + // Append single value. + try { + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(doc->getField("tags")) + .addUpdate(AssignValueUpdate( + StringFieldValue("THROW MEH!")))) + .applyTo(*doc); + CPPUNIT_FAIL("Expected exception when assinging a string value to an " + "array field."); + } + catch (std::exception& e) { + ; // fprintf(stderr, "Got exception => OK: %s\n", e.what()); + } + + // Remove array field. + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(doc->getField("tags")) + .addUpdate(RemoveValueUpdate(StringFieldValue("foo"))) + .addUpdate(RemoveValueUpdate(StringFieldValue("tag")))) + .applyTo(*doc); + std::unique_ptr<ArrayFieldValue> + fval3(doc->getAs<ArrayFieldValue>(doc->getField("tags"))); + CPPUNIT_ASSERT_EQUAL((size_t) 2, fval3->size()); + CPPUNIT_ASSERT_EQUAL(std::string("bar"), + std::string((*fval3)[0].getAsString())); + CPPUNIT_ASSERT_EQUAL(std::string("another"), + std::string((*fval3)[1].getAsString())); + + // Remove array from array. + ArrayFieldValue myarray2(doc->getType().getField("tags").getDataType()); + myarray2.add(StringFieldValue("foo")); + myarray2.add(StringFieldValue("bar")); + try { + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(doc->getField("tags")) + .addUpdate(RemoveValueUpdate(myarray2))) + .applyTo(*doc); + CPPUNIT_FAIL("Expected exception when removing an array from a " + "string array."); + } + catch (std::exception& e) { + ; // fprintf(stderr, "Got exception => OK: %s\n", e.what()); + } +} + +void +DocumentUpdateTest::testUpdateWeightedSet() +{ + // Create a test document + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + const Field& field(doc->getType().getField("stringweightedset")); + CPPUNIT_ASSERT_EQUAL((FieldValue*) 0, doc->getValue(field).get()); + + // Assign weightedset field + WeightedSetFieldValue wset(field.getDataType()); + wset.add(StringFieldValue("foo"), 3); + wset.add(StringFieldValue("bar"), 14); + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field) + .addUpdate(AssignValueUpdate(wset))) + .applyTo(*doc); + std::unique_ptr<WeightedSetFieldValue> + fval1(doc->getAs<WeightedSetFieldValue>(field)); + CPPUNIT_ASSERT_EQUAL((size_t) 2, fval1->size()); + CPPUNIT_ASSERT(fval1->contains(StringFieldValue("foo"))); + CPPUNIT_ASSERT(fval1->find(StringFieldValue("foo")) != fval1->end()); + CPPUNIT_ASSERT_EQUAL(3, fval1->get(StringFieldValue("foo"), 0)); + CPPUNIT_ASSERT(fval1->contains(StringFieldValue("bar"))); + CPPUNIT_ASSERT(fval1->find(StringFieldValue("bar")) != fval1->end()); + CPPUNIT_ASSERT_EQUAL(14, fval1->get(StringFieldValue("bar"), 0)); + + // Do a second assign + WeightedSetFieldValue wset2(field.getDataType()); + wset2.add(StringFieldValue("foo"), 16); + wset2.add(StringFieldValue("bar"), 24); + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field) + .addUpdate(AssignValueUpdate(wset2))) + .applyTo(*doc); + std::unique_ptr<WeightedSetFieldValue> + fval2(doc->getAs<WeightedSetFieldValue>(field)); + CPPUNIT_ASSERT_EQUAL((size_t) 2, fval2->size()); + CPPUNIT_ASSERT(fval2->contains(StringFieldValue("foo"))); + CPPUNIT_ASSERT(fval2->find(StringFieldValue("foo")) != fval1->end()); + CPPUNIT_ASSERT_EQUAL(16, fval2->get(StringFieldValue("foo"), 0)); + CPPUNIT_ASSERT(fval2->contains(StringFieldValue("bar"))); + CPPUNIT_ASSERT(fval2->find(StringFieldValue("bar")) != fval1->end()); + CPPUNIT_ASSERT_EQUAL(24, fval2->get(StringFieldValue("bar"), 0)); + + // Append weighted field + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field) + .addUpdate(AddValueUpdate(StringFieldValue("foo")) + .setWeight(3)) + .addUpdate(AddValueUpdate(StringFieldValue("too")) + .setWeight(14))) + .applyTo(*doc); + std::unique_ptr<WeightedSetFieldValue> + fval3(doc->getAs<WeightedSetFieldValue>(field)); + CPPUNIT_ASSERT_EQUAL((size_t) 3, fval3->size()); + CPPUNIT_ASSERT(fval3->contains(StringFieldValue("foo"))); + CPPUNIT_ASSERT_EQUAL(3, fval3->get(StringFieldValue("foo"), 0)); + CPPUNIT_ASSERT(fval3->contains(StringFieldValue("bar"))); + CPPUNIT_ASSERT_EQUAL(24, fval3->get(StringFieldValue("bar"), 0)); + CPPUNIT_ASSERT(fval3->contains(StringFieldValue("too"))); + CPPUNIT_ASSERT_EQUAL(14, fval3->get(StringFieldValue("too"), 0)); + + // Remove weighted field + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field) + .addUpdate(RemoveValueUpdate(StringFieldValue("foo"))) + .addUpdate(RemoveValueUpdate(StringFieldValue("too")))) + .applyTo(*doc); + std::unique_ptr<WeightedSetFieldValue> + fval4(doc->getAs<WeightedSetFieldValue>(field)); + CPPUNIT_ASSERT_EQUAL((size_t) 1, fval4->size()); + CPPUNIT_ASSERT(!fval4->contains(StringFieldValue("foo"))); + CPPUNIT_ASSERT(fval4->contains(StringFieldValue("bar"))); + CPPUNIT_ASSERT_EQUAL(24, fval4->get(StringFieldValue("bar"), 0)); + CPPUNIT_ASSERT(!fval4->contains(StringFieldValue("too"))); +} + +namespace { + +struct WeightedSetAutoCreateFixture +{ + DocumentTypeRepo repo; + const DocumentType* docType; + Document doc; + const Field& field; + DocumentUpdate update; + + WeightedSetAutoCreateFixture() + : repo(makeConfig()), + docType(repo.getDocumentType("test")), + doc(*docType, DocumentId("doc::testdoc")), + field(docType->getField("strwset")), + update(*docType, DocumentId("doc::testdoc")) + { + update.addUpdate(FieldUpdate(field) + .addUpdate(MapValueUpdate(StringFieldValue("foo"), + ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 1)))); + } + + void applyUpdateToDocument() { + update.applyTo(doc); + } + + static DocumenttypesConfig makeConfig() { + DocumenttypesConfigBuilderHelper builder; + // T_TAG is an alias for a weighted set with create-if-non-existing + // and remove-if-zero attributes set. Attempting to explicitly create + // a field matching those characteristics will in fact fail with a + // redefinition error. + builder.document(42, "test", + Struct("test.header") + .addField("strwset", DataType::T_TAG), + Struct("test.body")); + return builder.config(); + } +}; + +} // anon ns + +void +DocumentUpdateTest::testIncrementNonExistingAutoCreateWSetField() +{ + WeightedSetAutoCreateFixture fixture; + + fixture.applyUpdateToDocument(); + + std::unique_ptr<WeightedSetFieldValue> ws( + fixture.doc.getAs<WeightedSetFieldValue>(fixture.field)); + CPPUNIT_ASSERT_EQUAL(size_t(1), ws->size()); + CPPUNIT_ASSERT(ws->contains(StringFieldValue("foo"))); + CPPUNIT_ASSERT_EQUAL(1, ws->get(StringFieldValue("foo"), 0)); +} + +void +DocumentUpdateTest::testIncrementExistingWSetField() +{ + WeightedSetAutoCreateFixture fixture; + { + WeightedSetFieldValue wset(fixture.field.getDataType()); + wset.add(StringFieldValue("bar"), 14); + fixture.doc.setValue(fixture.field, wset); + } + fixture.applyUpdateToDocument(); + + std::unique_ptr<WeightedSetFieldValue> ws( + fixture.doc.getAs<WeightedSetFieldValue>(fixture.field)); + CPPUNIT_ASSERT_EQUAL(size_t(2), ws->size()); + CPPUNIT_ASSERT(ws->contains(StringFieldValue("foo"))); + CPPUNIT_ASSERT_EQUAL(1, ws->get(StringFieldValue("foo"), 0)); +} + +void +DocumentUpdateTest::testIncrementWithZeroResultWeightIsRemoved() +{ + WeightedSetAutoCreateFixture fixture; + fixture.update.addUpdate(FieldUpdate(fixture.field) + .addUpdate(MapValueUpdate(StringFieldValue("baz"), + ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 0)))); + + fixture.applyUpdateToDocument(); + + std::unique_ptr<WeightedSetFieldValue> ws( + fixture.doc.getAs<WeightedSetFieldValue>(fixture.field)); + CPPUNIT_ASSERT_EQUAL(size_t(1), ws->size()); + CPPUNIT_ASSERT(ws->contains(StringFieldValue("foo"))); + CPPUNIT_ASSERT(!ws->contains(StringFieldValue("baz"))); +} + +void DocumentUpdateTest::testReadSerializedFile() +{ + // Reads a file serialized from java + const char file_name[] = "data/crossplatform-java-cpp-doctypes.cfg"; + DocumentTypeRepo repo(readDocumenttypesConfig(file_name)); + + int fd = open("data/serializeupdatejava.dat", O_RDONLY); + + int len = lseek(fd,0,SEEK_END); + ByteBuffer buf(len); + lseek(fd,0,SEEK_SET); + read(fd, buf.getBuffer(), len); + + close(fd); + + DocumentUpdate::UP updp(DocumentUpdate::create42(repo, buf)); + DocumentUpdate& upd(*updp); + + const DocumentType *type = repo.getDocumentType("serializetest"); + CPPUNIT_ASSERT_EQUAL(DocumentId(DocIdString("update", "test")), + upd.getId()); + CPPUNIT_ASSERT_EQUAL(*type, upd.getType()); + + // Verify assign value update. + FieldUpdate serField = upd[0]; + CPPUNIT_ASSERT_EQUAL(serField.getField().getId(Document::getNewestSerializationVersion()), + type->getField("intfield").getId(Document::getNewestSerializationVersion())); + + const ValueUpdate* serValue = &serField[0]; + CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Assign); + + const AssignValueUpdate* assign( + static_cast<const AssignValueUpdate*>(serValue)); + CPPUNIT_ASSERT_EQUAL(IntFieldValue(4), + static_cast<const IntFieldValue&>(assign->getValue())); + + // Verify clear field update. + serField = upd[1]; + CPPUNIT_ASSERT_EQUAL(serField.getField().getId(Document::getNewestSerializationVersion()), + type->getField("floatfield").getId(Document::getNewestSerializationVersion())); + + serValue = &serField[0]; + CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Clear); + CPPUNIT_ASSERT(serValue->inherits(ClearValueUpdate::classId)); + + // Verify add value update. + serField = upd[2]; + CPPUNIT_ASSERT_EQUAL(serField.getField().getId(Document::getNewestSerializationVersion()), + type->getField("arrayoffloatfield").getId(Document::getNewestSerializationVersion())); + + serValue = &serField[0]; + CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Add); + + const AddValueUpdate* add = static_cast<const AddValueUpdate*>(serValue); + const FieldValue* value = &add->getValue(); + CPPUNIT_ASSERT(value->inherits(FloatFieldValue::classId)); + CPPUNIT_ASSERT_EQUAL(value->getAsFloat(), 5.00f); + + serValue = &serField[1]; + CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Add); + + add = static_cast<const AddValueUpdate*>(serValue); + value = &add->getValue(); + CPPUNIT_ASSERT(value->inherits(FloatFieldValue::classId)); + CPPUNIT_ASSERT_EQUAL(value->getAsFloat(), 4.23f); + + serValue = &serField[2]; + CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Add); + + add = static_cast<const AddValueUpdate*>(serValue); + value = &add->getValue(); + CPPUNIT_ASSERT(value->inherits(FloatFieldValue::classId)); + CPPUNIT_ASSERT_EQUAL(value->getAsFloat(), -1.00f); + +} + +void DocumentUpdateTest::testGenerateSerializedFile() +{ + // Tests nothing, only generates a file for java test + const char file_name[] = "data/crossplatform-java-cpp-doctypes.cfg"; + DocumentTypeRepo repo(readDocumenttypesConfig(file_name)); + + const DocumentType *type(repo.getDocumentType("serializetest")); + DocumentUpdate upd(*type, DocumentId(DocIdString("update", "test"))); + upd.addUpdate(FieldUpdate(type->getField("intfield")) + .addUpdate(AssignValueUpdate(IntFieldValue(4)))); + upd.addUpdate(FieldUpdate(type->getField("floatfield")) + .addUpdate(AssignValueUpdate(FloatFieldValue(1.00f)))); + upd.addUpdate(FieldUpdate(type->getField("arrayoffloatfield")) + .addUpdate(AddValueUpdate(FloatFieldValue(5.00f))) + .addUpdate(AddValueUpdate(FloatFieldValue(4.23f))) + .addUpdate(AddValueUpdate(FloatFieldValue(-1.00f)))); + upd.addUpdate(FieldUpdate(type->getField("intfield")) + .addUpdate(ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 3))); + upd.addUpdate(FieldUpdate(type->getField("wsfield")) + .addUpdate(MapValueUpdate(StringFieldValue("foo"), + ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 2))) + .addUpdate(MapValueUpdate(StringFieldValue("foo"), + ArithmeticValueUpdate(ArithmeticValueUpdate::Mul, 2)))); + ByteBuffer::UP buf(serialize42(upd)); + + int fd = open("data/serializeupdatecpp.dat", + O_WRONLY | O_TRUNC | O_CREAT, 0644); + write(fd, buf->getBuffer(), buf->getPos()); + close(fd); +} + + +void DocumentUpdateTest::testSetBadFieldTypes() +{ + // Create a test document + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*)NULL, + doc->getValue(doc->getField("headerval")).get()); + + // Assign a float value to an int field. + DocumentUpdate update(*doc->getDataType(), doc->getId()); + try { + update.addUpdate(FieldUpdate(doc->getField("headerval")) + .addUpdate(AssignValueUpdate(FloatFieldValue(4.00f)))); + CPPUNIT_FAIL("Expected exception when adding a float to an int field."); + } catch (std::exception& e) { + ; // fprintf(stderr, "Got exception => OK: %s\n", e.what()); + } + + // Apply update + update.applyTo(*doc); + + // Verify that the field is NOT set in the document. + CPPUNIT_ASSERT_EQUAL((document::FieldValue*)NULL, + doc->getValue(doc->getField("headerval")).get()); +} + +void +DocumentUpdateTest::testUpdateApplyNoParams() +{ + // Create a test document + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*)NULL, + doc->getValue(doc->getField("tags")).get()); + + // Assign array field with no parameters - illegal. + DocumentUpdate update(*doc->getDataType(), doc->getId()); + try { + update.addUpdate(FieldUpdate(doc->getField("tags")) + .addUpdate(AssignValueUpdate())); + CPPUNIT_FAIL("Expected exception when assign a NULL value."); + } catch (std::exception& e) { + ; // fprintf(stderr, "Got exception => OK: %s\n", e.what()); + } + + // Apply update + update.applyTo(*doc); + + // Verify that the field was cleared in the document. + CPPUNIT_ASSERT(!doc->hasValue(doc->getField("tags"))); +} + +void +DocumentUpdateTest::testUpdateApplyNoArrayValues() +{ + // Create a test document + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + const Field &field(doc->getType().getField("tags")); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, + doc->getValue(field).get()); + + // Assign array field with no array values = empty array + DocumentUpdate update(*doc->getDataType(), doc->getId()); + update.addUpdate(FieldUpdate(field) + .addUpdate(AssignValueUpdate( + ArrayFieldValue(field.getDataType())))); + + // Apply update + update.applyTo(*doc); + + // Verify that the field was set in the document + std::unique_ptr<ArrayFieldValue> fval(doc->getAs<ArrayFieldValue>(field)); + CPPUNIT_ASSERT(fval.get()); + CPPUNIT_ASSERT_EQUAL((size_t) 0, fval->size()); +} + +void +DocumentUpdateTest::testUpdateArrayEmptyParamValue() +{ + // Create a test document. + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + const Field &field(doc->getType().getField("tags")); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, + doc->getValue(field).get()); + + // Assign array field with no array values = empty array. + DocumentUpdate update(*doc->getDataType(), doc->getId()); + update.addUpdate(FieldUpdate(field) + .addUpdate(AssignValueUpdate( + ArrayFieldValue(field.getDataType())))); + update.applyTo(*doc); + + // Verify that the field was set in the document. + std::unique_ptr<ArrayFieldValue> fval1(doc->getAs<ArrayFieldValue>(field)); + CPPUNIT_ASSERT(fval1.get()); + CPPUNIT_ASSERT_EQUAL((size_t) 0, fval1->size()); + + // Remove array field. + update.clear(); + update.addUpdate(FieldUpdate(field) + .addUpdate(ClearValueUpdate())); + update.applyTo(*doc); + + // Verify that the field was cleared in the document. + std::unique_ptr<ArrayFieldValue> fval2(doc->getAs<ArrayFieldValue>(field)); + CPPUNIT_ASSERT(!fval2); +} + +void +DocumentUpdateTest::testUpdateWeightedSetEmptyParamValue() +{ + // Create a test document + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + const Field &field(doc->getType().getField("stringweightedset")); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, + doc->getValue(field).get()); + + // Assign weighted set with no items = empty set. + DocumentUpdate update(*doc->getDataType(), doc->getId()); + update.addUpdate(FieldUpdate(field) + .addUpdate(AssignValueUpdate( + WeightedSetFieldValue(field.getDataType())))); + update.applyTo(*doc); + + // Verify that the field was set in the document. + std::unique_ptr<WeightedSetFieldValue> + fval1(doc->getAs<WeightedSetFieldValue>(field)); + CPPUNIT_ASSERT(fval1.get()); + CPPUNIT_ASSERT_EQUAL((size_t) 0, fval1->size()); + + // Remove weighted set field. + update.clear(); + update.addUpdate(FieldUpdate(field) + .addUpdate(ClearValueUpdate())); + update.applyTo(*doc); + + // Verify that the field was cleared in the document. + std::unique_ptr<WeightedSetFieldValue> + fval2(doc->getAs<WeightedSetFieldValue>(field)); + CPPUNIT_ASSERT(!fval2); +} + +void +DocumentUpdateTest::testUpdateArrayWrongSubtype() +{ + // Create a test document + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + const Field &field(doc->getType().getField("tags")); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, + doc->getValue(field).get()); + + // Assign int values to string array. + DocumentUpdate update(*doc->getDataType(), doc->getId()); + try { + update.addUpdate(FieldUpdate(field) + .addUpdate(AddValueUpdate(IntFieldValue(123))) + .addUpdate(AddValueUpdate(IntFieldValue(456)))); + CPPUNIT_FAIL("Expected exception when adding wrong type."); + } catch (std::exception& e) { + ; // fprintf(stderr, "Got exception => OK: %s\n", e.what()); + } + + // Apply update + update.applyTo(*doc); + + // Verify that the field was NOT set in the document + FieldValue::UP fval(doc->getValue(field)); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, fval.get()); +} + +void +DocumentUpdateTest::testUpdateWeightedSetWrongSubtype() +{ + // Create a test document + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + const Field &field(doc->getType().getField("stringweightedset")); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, + doc->getValue(field).get()); + + // Assign int values to string array. + DocumentUpdate update(*doc->getDataType(), doc->getId()); + try { + update.addUpdate(FieldUpdate(field) + .addUpdate(AddValueUpdate(IntFieldValue(123)) + .setWeight(1000)) + .addUpdate(AddValueUpdate(IntFieldValue(456)) + .setWeight(2000))); + CPPUNIT_FAIL("Expected exception when adding wrong type."); + } catch (std::exception& e) { + ; // fprintf(stderr, "Got exception => OK: %s\n", e.what()); + } + + // Apply update + update.applyTo(*doc); + + // Verify that the field was NOT set in the document + FieldValue::UP fval(doc->getValue(field)); + CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, fval.get()); +} + +void +DocumentUpdateTest::testMapValueUpdate() +{ + // Create a test document + TestDocMan docMan; + Document::UP doc(docMan.createDocument()); + const Field &field1 = doc->getField("stringweightedset"); + const Field &field2 = doc->getField("stringweightedset2"); + WeightedSetFieldValue wsval1(field1.getDataType()); + WeightedSetFieldValue wsval2(field2.getDataType()); + doc->setValue(field1, wsval1); + doc->setValue(field2, wsval2); + + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field1) + .addUpdate(MapValueUpdate( + StringFieldValue("banana"), + ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 1.0) + ))) + .applyTo(*doc); + std::unique_ptr<WeightedSetFieldValue> fv1 = + doc->getAs<WeightedSetFieldValue>(field1); + CPPUNIT_ASSERT(fv1->size() == 0); + + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field2) + .addUpdate(MapValueUpdate( + StringFieldValue("banana"), + ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 1.0) + ))) + .applyTo(*doc); + std::unique_ptr<WeightedSetFieldValue> fv2 = + doc->getAs<WeightedSetFieldValue>(field2); + CPPUNIT_ASSERT(fv2->size() == 1); + + CPPUNIT_ASSERT(fv1->find(StringFieldValue("apple")) == fv1->end()); + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field1) + .addUpdate(ClearValueUpdate())) + .applyTo(*doc); + + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field1) + .addUpdate(AddValueUpdate(StringFieldValue("apple")) + .setWeight(1))) + .applyTo(*doc); + + std::unique_ptr<WeightedSetFieldValue> + fval3(doc->getAs<WeightedSetFieldValue>(field1)); + CPPUNIT_ASSERT(fval3->find(StringFieldValue("apple")) != fval3->end()); + CPPUNIT_ASSERT_EQUAL(1, fval3->get(StringFieldValue("apple"))); + + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field2) + .addUpdate(AddValueUpdate(StringFieldValue("apple")) + .setWeight(1))) + .applyTo(*doc); + + std::unique_ptr<WeightedSetFieldValue> + fval3b(doc->getAs<WeightedSetFieldValue>(field2)); + CPPUNIT_ASSERT(fval3b->find(StringFieldValue("apple")) != fval3b->end()); + CPPUNIT_ASSERT_EQUAL(1, fval3b->get(StringFieldValue("apple"))); + + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field1) + .addUpdate(MapValueUpdate( + StringFieldValue("apple"), + ArithmeticValueUpdate(ArithmeticValueUpdate::Sub, 1.0) + ))) + .applyTo(*doc); + + std::unique_ptr<WeightedSetFieldValue> fv3 = + doc->getAs<WeightedSetFieldValue>(field1); + CPPUNIT_ASSERT(fv3->find(StringFieldValue("apple")) != fv3->end()); + CPPUNIT_ASSERT_EQUAL(0, fv3->get(StringFieldValue("apple"))); + + DocumentUpdate(*doc->getDataType(), doc->getId()) + .addUpdate(FieldUpdate(field2) + .addUpdate(MapValueUpdate( + StringFieldValue("apple"), + ArithmeticValueUpdate(ArithmeticValueUpdate::Sub, 1.0) + ))) + .applyTo(*doc); + + std::unique_ptr<WeightedSetFieldValue> fv4 = + doc->getAs<WeightedSetFieldValue>(field2); + CPPUNIT_ASSERT(fv4->find(StringFieldValue("apple")) == fv4->end()); +} + + +void +assertDocumentUpdateFlag(bool createIfNonExistent, int value) +{ + DocumentUpdateFlags f1; + f1.setCreateIfNonExistent(createIfNonExistent); + CPPUNIT_ASSERT_EQUAL(createIfNonExistent, f1.getCreateIfNonExistent()); + int combined = f1.injectInto(value); + std::cout << "createIfNonExistent=" << createIfNonExistent << ", value=" << value << ", combined=" << combined << std::endl; + + DocumentUpdateFlags f2 = DocumentUpdateFlags::extractFlags(combined); + int extractedValue = DocumentUpdateFlags::extractValue(combined); + CPPUNIT_ASSERT_EQUAL(createIfNonExistent, f2.getCreateIfNonExistent()); + CPPUNIT_ASSERT_EQUAL(value, extractedValue); +} + +void +DocumentUpdateTest::testThatDocumentUpdateFlagsIsWorking() +{ + { // create-if-non-existent = true + assertDocumentUpdateFlag(true, 0); + assertDocumentUpdateFlag(true, 1); + assertDocumentUpdateFlag(true, 2); + assertDocumentUpdateFlag(true, 9999); + assertDocumentUpdateFlag(true, 0xFFFFFFE); + assertDocumentUpdateFlag(true, 0xFFFFFFF); + } + { // create-if-non-existent = false + assertDocumentUpdateFlag(false, 0); + assertDocumentUpdateFlag(false, 1); + assertDocumentUpdateFlag(false, 2); + assertDocumentUpdateFlag(false, 9999); + assertDocumentUpdateFlag(false, 0xFFFFFFE); + assertDocumentUpdateFlag(false, 0xFFFFFFF); + } +} + +struct CreateIfNonExistentFixture +{ + TestDocMan docMan; + Document::UP document; + DocumentUpdate::UP update; + CreateIfNonExistentFixture() + : docMan(), + document(docMan.createDocument()), + update(new DocumentUpdate(*document->getDataType(), + document->getId())) + { + update->addUpdate(FieldUpdate(document->getField("headerval")) + .addUpdate(AssignValueUpdate(IntFieldValue(1)))); + update->setCreateIfNonExistent(true); + } +}; + +void +DocumentUpdateTest::testThatCreateIfNonExistentFlagIsSerialized50AndDeserialized50() +{ + CreateIfNonExistentFixture f; + + ByteBuffer::UP buf(serializeHEAD(*f.update)); + buf->flip(); + + DocumentUpdate::UP deserialized = DocumentUpdate::createHEAD(f.docMan.getTypeRepo(), *buf); + CPPUNIT_ASSERT_EQUAL(*f.update, *deserialized); + CPPUNIT_ASSERT(deserialized->getCreateIfNonExistent()); +} + +void +DocumentUpdateTest::testThatCreateIfNonExistentFlagIsSerializedAndDeserialized() +{ + CreateIfNonExistentFixture f; + + ByteBuffer::UP buf(serialize42(*f.update)); + buf->flip(); + + DocumentUpdate::UP deserialized = DocumentUpdate::create42(f.docMan.getTypeRepo(), *buf); + CPPUNIT_ASSERT_EQUAL(*f.update, *deserialized); + CPPUNIT_ASSERT(deserialized->getCreateIfNonExistent()); +} + +} // namespace document |