<commit_before>// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

#include <vespa/document/base/testdocman.h>
#include <vespa/document/datatype/tensor_data_type.h>
#include <vespa/document/fieldvalue/fieldvalues.h>
#include <vespa/document/repo/configbuilder.h>
#include <vespa/document/repo/documenttyperepo.h>
#include <vespa/document/serialization/vespadocumentserializer.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/documentupdate.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/tensor_add_update.h>
#include <vespa/document/update/tensor_modify_update.h>
#include <vespa/document/update/tensor_remove_update.h>
#include <vespa/document/update/valueupdate.h>
#include <vespa/document/util/bytebuffer.h>
#include <vespa/eval/tensor/default_tensor_engine.h>
#include <vespa/eval/tensor/tensor.h>
#include <vespa/vespalib/objects/nbostream.h>
#include <vespa/vespalib/util/exception.h>

#include <fcntl.h>
#include <gtest/gtest.h>
#include <unistd.h>

using namespace document::config_builder;
using vespalib::eval::TensorSpec;
using vespalib::tensor::DefaultTensorEngine;
using vespalib::tensor::Tensor;
using vespalib::nbostream;

namespace document {

namespace {

ByteBuffer::UP serializeHEAD(const DocumentUpdate & update)
{
    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)
{
    nbostream stream;
    VespaDocumentSerializer serializer(stream);
    serializer.write42(update);
    ByteBuffer::UP retVal(new ByteBuffer(stream.size()));
    retVal->putBytes(stream.peek(), stream.size());
    return retVal;
}

nbostream serialize(const ValueUpdate & update)
{
    nbostream stream;
    VespaDocumentSerializer serializer(stream);
    serializer.write(update);
    return stream;
}

nbostream serialize(const FieldUpdate & update)
{
    nbostream stream;
    VespaDocumentSerializer serializer(stream);
    serializer.write(update);
    return stream;
}

template<typename UpdateType>
void testRoundtripSerialize(const UpdateType& update, const DataType &type) {
    try{
        DocumentTypeRepo repo;
        nbostream stream = serialize(update);
        std::unique_ptr<UpdateType> copy(dynamic_cast<UpdateType*>(ValueUpdate::createInstance(repo, type, stream).release()));
        EXPECT_EQ(update, *copy);
    } catch (std::exception& e) {
            std::cerr << "Failed while processing update " << update << "\n";
    throw;
    }
}

}

TEST(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
    testRoundtripSerialize(ClearValueUpdate(), *DataType::INT);
    testRoundtripSerialize(AssignValueUpdate(IntFieldValue(1)), *DataType::INT);
    testRoundtripSerialize(ArithmeticValueUpdate(ArithmeticValueUpdate::Div, 4.3), *DataType::FLOAT);
    testRoundtripSerialize(AddValueUpdate(IntFieldValue(1), 4), *arrayType);
    testRoundtripSerialize(RemoveValueUpdate(IntFieldValue(1)), *arrayType);

    FieldUpdate fieldUpdate(docType->getField("intf"));
    fieldUpdate.addUpdate(AssignValueUpdate(IntFieldValue(1)));
    nbostream stream = serialize(fieldUpdate);
    FieldUpdate fieldUpdateCopy(repo, *docType, stream);
    EXPECT_EQ(fieldUpdate, fieldUpdateCopy);

        // Test that a document update can be serialized
    DocumentUpdate docUpdate(repo, *docType, DocumentId("doc::testdoc"));
    docUpdate.addUpdate(fieldUpdateCopy);
    ByteBuffer::UP docBuf = serializeHEAD(docUpdate);
    docBuf->flip();
    auto docUpdateCopy(DocumentUpdate::createHEAD(repo, nbostream(docBuf->getBufferAtPos(), docBuf->getRemaining())));

        // 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(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intf")).addUpdate(ClearValueUpdate()));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        EXPECT_FALSE(updated.getValue("intf"));
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intf")).addUpdate(AssignValueUpdate(IntFieldValue(15))));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        EXPECT_EQ(15, updated.getValue("intf")->getAsInt());
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intf")).addUpdate(ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 15)));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        EXPECT_EQ(20, updated.getValue("intf")->getAsInt());
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intarr")).addUpdate(AddValueUpdate(IntFieldValue(4))));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        std::unique_ptr<ArrayFieldValue> val(dynamic_cast<ArrayFieldValue*>(updated.getValue("intarr").release()));
        ASSERT_EQ(size_t(3), val->size());
        EXPECT_EQ(4, (*val)[2].getAsInt());
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intarr")).addUpdate(RemoveValueUpdate(IntFieldValue(3))));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        std::unique_ptr<ArrayFieldValue> val(dynamic_cast<ArrayFieldValue*>(updated.getValue("intarr").release()));
        ASSERT_EQ(size_t(1), val->size());
        EXPECT_EQ(7, (*val)[0].getAsInt());
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("bytef"))
                              .addUpdate(ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 15)));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        EXPECT_EQ(15, (int) updated.getValue("bytef")->getAsByte());
    }
}

TEST(DocumentUpdateTest, testClearField)
{
    // Create a document.
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    doc->setValue(doc->getField("headerval"), IntFieldValue(4));
    EXPECT_EQ(4, doc->getValue("headerval")->getAsInt());

    // Apply an update.
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(doc->getField("headerval")).addUpdate(AssignValueUpdate()))
        .applyTo(*doc);
    EXPECT_FALSE(doc->getValue("headerval"));
}

TEST(DocumentUpdateTest, testUpdateApplySingleValue)
{
    // Create a document.
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    doc->setValue(doc->getField("headerval"), IntFieldValue(4));
    EXPECT_EQ(4, doc->getValue("headerval")->getAsInt());

    // Apply an update.
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(doc->getField("headerval")).addUpdate(AssignValueUpdate(IntFieldValue(9))))
        .applyTo(*doc);
    EXPECT_EQ(9, doc->getValue("headerval")->getAsInt());
}

TEST(DocumentUpdateTest, testUpdateArray)
{
    // Create a document.
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    EXPECT_EQ((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(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(doc->getField("tags")).addUpdate(AssignValueUpdate(myarray)))
        .applyTo(*doc);
    auto fval1(doc->getAs<ArrayFieldValue>(doc->getField("tags")));
    ASSERT_EQ((size_t) 2, fval1->size());
    EXPECT_EQ(std::string("foo"), std::string((*fval1)[0].getAsString()));
    EXPECT_EQ(std::string("bar"), std::string((*fval1)[1].getAsString()));

    // Append array field
    DocumentUpdate(docMan.getTypeRepo(), *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")));
    ASSERT_EQ((size_t) 4, fval2->size());
    EXPECT_EQ(std::string("foo"), std::string((*fval2)[0].getAsString()));
    EXPECT_EQ(std::string("bar"), std::string((*fval2)[1].getAsString()));
    EXPECT_EQ(std::string("another"), std::string((*fval2)[2].getAsString()));
    EXPECT_EQ(std::string("tag"), std::string((*fval2)[3].getAsString()));

    // Append single value.
    ASSERT_THROW(
        DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
            .addUpdate(FieldUpdate(doc->getField("tags"))
                       .addUpdate(AssignValueUpdate(StringFieldValue("THROW MEH!"))))
            .applyTo(*doc),
        std::exception) << "Expected exception when assigning a string value to an array field.";

    // Remove array field.
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(doc->getField("tags"))
                   .addUpdate(RemoveValueUpdate(StringFieldValue("foo")))
                   .addUpdate(RemoveValueUpdate(StringFieldValue("tag"))))
        .applyTo(*doc);
    auto fval3(doc->getAs<ArrayFieldValue>(doc->getField("tags")));
    ASSERT_EQ((size_t) 2, fval3->size());
    EXPECT_EQ(std::string("bar"), std::string((*fval3)[0].getAsString()));
    EXPECT_EQ(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"));
    ASSERT_THROW(
        DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
            .addUpdate(FieldUpdate(doc->getField("tags"))
                       .addUpdate(RemoveValueUpdate(myarray2)))
            .applyTo(*doc),
        std::exception) << "Expected exception when removing an array from a string array.";
}

TEST(DocumentUpdateTest, testUpdateWeightedSet)
{
    // Create a test document
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field& field(doc->getType().getField("stringweightedset"));
    EXPECT_EQ((FieldValue*) 0, doc->getValue(field).get());
	
    // Assign weightedset field
    WeightedSetFieldValue wset(field.getDataType());
    wset.add(StringFieldValue("foo"), 3);
    wset.add(StringFieldValue("bar"), 14);
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(field).addUpdate(AssignValueUpdate(wset)))
        .applyTo(*doc);
    auto fval1(doc->getAs<WeightedSetFieldValue>(field));
    ASSERT_EQ((size_t) 2, fval1->size());
    EXPECT_TRUE(fval1->contains(StringFieldValue("foo")));
    EXPECT_NE(fval1->find(StringFieldValue("foo")), fval1->end());
    EXPECT_EQ(3, fval1->get(StringFieldValue("foo"), 0));
    EXPECT_TRUE(fval1->contains(StringFieldValue("bar")));
    EXPECT_NE(fval1->find(StringFieldValue("bar")), fval1->end());
    EXPECT_EQ(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(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(field)
                   .addUpdate(AssignValueUpdate(wset2)))
        .applyTo(*doc);
    auto fval2(doc->getAs<WeightedSetFieldValue>(field));
    ASSERT_EQ((size_t) 2, fval2->size());
    EXPECT_TRUE(fval2->contains(StringFieldValue("foo")));
    EXPECT_NE(fval2->find(StringFieldValue("foo")), fval1->end());
    EXPECT_EQ(16, fval2->get(StringFieldValue("foo"), 0));
    EXPECT_TRUE(fval2->contains(StringFieldValue("bar")));
    EXPECT_NE(fval2->find(StringFieldValue("bar")), fval1->end());
    EXPECT_EQ(24, fval2->get(StringFieldValue("bar"), 0));

    // Append weighted field
    DocumentUpdate(docMan.getTypeRepo(), *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));
    ASSERT_EQ((size_t) 3, fval3->size());
    EXPECT_TRUE(fval3->contains(StringFieldValue("foo")));
    EXPECT_EQ(3, fval3->get(StringFieldValue("foo"), 0));
    EXPECT_TRUE(fval3->contains(StringFieldValue("bar")));
    EXPECT_EQ(24, fval3->get(StringFieldValue("bar"), 0));
    EXPECT_TRUE(fval3->contains(StringFieldValue("too")));
    EXPECT_EQ(14, fval3->get(StringFieldValue("too"), 0));

    // Remove weighted field
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(field)
                   .addUpdate(RemoveValueUpdate(StringFieldValue("foo")))
                   .addUpdate(RemoveValueUpdate(StringFieldValue("too"))))
        .applyTo(*doc);
    auto fval4(doc->getAs<WeightedSetFieldValue>(field));
    ASSERT_EQ((size_t) 1, fval4->size());
    EXPECT_FALSE(fval4->contains(StringFieldValue("foo")));
    EXPECT_TRUE(fval4->contains(StringFieldValue("bar")));
    EXPECT_EQ(24, fval4->get(StringFieldValue("bar"), 0));
    EXPECT_FALSE(fval4->contains(StringFieldValue("too")));
}

namespace {

struct WeightedSetAutoCreateFixture
{
    DocumentTypeRepo repo;
    const DocumentType* docType;
    Document doc;
    const Field& field;
    DocumentUpdate update;

    ~WeightedSetAutoCreateFixture();
    WeightedSetAutoCreateFixture();

    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();
    }
};

WeightedSetAutoCreateFixture::~WeightedSetAutoCreateFixture() = default;
WeightedSetAutoCreateFixture::WeightedSetAutoCreateFixture()
    : repo(makeConfig()),
      docType(repo.getDocumentType("test")),
      doc(*docType, DocumentId("doc::testdoc")),
      field(docType->getField("strwset")),
      update(repo, *docType, DocumentId("doc::testdoc"))
{
    update.addUpdate(FieldUpdate(field)
                             .addUpdate(MapValueUpdate(StringFieldValue("foo"),
                                                       ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 1))));
}
} // anon ns

TEST(DocumentUpdateTest, testIncrementNonExistingAutoCreateWSetField)
{
    WeightedSetAutoCreateFixture fixture;

    fixture.applyUpdateToDocument();

    std::unique_ptr<WeightedSetFieldValue> ws(
            fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
    ASSERT_EQ(size_t(1), ws->size());
    EXPECT_TRUE(ws->contains(StringFieldValue("foo")));
    EXPECT_EQ(1, ws->get(StringFieldValue("foo"), 0));
}

TEST(DocumentUpdateTest, testIncrementExistingWSetField)
{
    WeightedSetAutoCreateFixture fixture;
    {
        WeightedSetFieldValue wset(fixture.field.getDataType());
        wset.add(StringFieldValue("bar"), 14);
        fixture.doc.setValue(fixture.field, wset);
    }
    fixture.applyUpdateToDocument();

    auto ws(fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
    ASSERT_EQ(size_t(2), ws->size());
    EXPECT_TRUE(ws->contains(StringFieldValue("foo")));
    EXPECT_EQ(1, ws->get(StringFieldValue("foo"), 0));
}

TEST(DocumentUpdateTest, testIncrementWithZeroResultWeightIsRemoved)
{
    WeightedSetAutoCreateFixture fixture;
    fixture.update.addUpdate(FieldUpdate(fixture.field)
            .addUpdate(MapValueUpdate(StringFieldValue("baz"),
                                      ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 0))));

    fixture.applyUpdateToDocument();

    auto ws(fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
    ASSERT_EQ(size_t(1), ws->size());
    EXPECT_TRUE(ws->contains(StringFieldValue("foo")));
    EXPECT_FALSE(ws->contains(StringFieldValue("baz")));
}

TEST(DocumentUpdateTest, testReadSerializedFile)
{
    // Reads a file serialized from java
    const std::string 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);
    if (read(fd, buf.getBuffer(), len) != len) {
    	throw vespalib::Exception("read failed");
    }
    close(fd);

    nbostream is(buf.getBufferAtPos(), buf.getRemaining());
    DocumentUpdate::UP updp(DocumentUpdate::create42(repo, is));
    DocumentUpdate& upd(*updp);

    const DocumentType *type = repo.getDocumentType("serializetest");
    EXPECT_EQ(DocumentId(DocIdString("update", "test")), upd.getId());
    EXPECT_EQ(*type, upd.getType());

    // Verify assign value update.
    FieldUpdate serField = upd.getUpdates()[1];
    EXPECT_EQ(serField.getField().getId(), type->getField("intfield").getId());

    const ValueUpdate* serValue = &serField[0];
    EXPECT_EQ(serValue->getType(), ValueUpdate::Assign);

    const AssignValueUpdate* assign(static_cast<const AssignValueUpdate*>(serValue));
    EXPECT_EQ(IntFieldValue(4), static_cast<const IntFieldValue&>(assign->getValue()));

    // Verify clear field update.
    serField = upd.getUpdates()[2];
    EXPECT_EQ(serField.getField().getId(), type->getField("floatfield").getId());

    serValue = &serField[0];
    EXPECT_EQ(serValue->getType(), ValueUpdate::Clear);
    EXPECT_TRUE(serValue->inherits(ClearValueUpdate::classId));

    // Verify add value update.
    serField = upd.getUpdates()[0];
    EXPECT_EQ(serField.getField().getId(), type->getField("arrayoffloatfield").getId());

    serValue = &serField[0];
    EXPECT_EQ(serValue->getType(), ValueUpdate::Add);

    const AddValueUpdate* add = static_cast<const AddValueUpdate*>(serValue);
    const FieldValue* value = &add->getValue();
    EXPECT_TRUE(value->inherits(FloatFieldValue::classId));
    EXPECT_FLOAT_EQ(value->getAsFloat(), 5.00f);

    serValue = &serField[1];
    EXPECT_EQ(serValue->getType(), ValueUpdate::Add);

    add = static_cast<const AddValueUpdate*>(serValue);
    value = &add->getValue();
    EXPECT_TRUE(value->inherits(FloatFieldValue::classId));
    EXPECT_FLOAT_EQ(value->getAsFloat(), 4.23f);

    serValue = &serField[2];
    EXPECT_EQ(serValue->getType(), ValueUpdate::Add);

    add = static_cast<const AddValueUpdate*>(serValue);
    value = &add->getValue();
    EXPECT_TRUE(value->inherits(FloatFieldValue::classId));
    EXPECT_FLOAT_EQ(value->getAsFloat(), -1.00f);

}

TEST(DocumentUpdateTest, testGenerateSerializedFile)
{
    // Tests nothing, only generates a file for java test
    const std::string file_name = "data/crossplatform-java-cpp-doctypes.cfg";
    DocumentTypeRepo repo(readDocumenttypesConfig(file_name));

    const DocumentType *type(repo.getDocumentType("serializetest"));
    DocumentUpdate upd(repo, *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);
    if (write(fd, buf->getBuffer(), buf->getPos()) != (ssize_t)buf->getPos()) {
	    throw vespalib::Exception("read failed");
    }
    close(fd);
}


TEST(DocumentUpdateTest, testSetBadFieldTypes)
{
    // Create a test document
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    EXPECT_EQ((document::FieldValue*)NULL, doc->getValue(doc->getField("headerval")).get());

    // Assign a float value to an int field.
    DocumentUpdate update(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
    ASSERT_THROW(
        update.addUpdate(FieldUpdate(doc->getField("headerval"))
                 .addUpdate(AssignValueUpdate(FloatFieldValue(4.00f)))),
        std::exception) << "Expected exception when adding a float to an int field.";

    update.applyTo(*doc);

    // Verify that the field is NOT set in the document.
    EXPECT_EQ((document::FieldValue*)NULL,
			 doc->getValue(doc->getField("headerval")).get());
}

TEST(DocumentUpdateTest, testUpdateApplyNoParams)
{
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    EXPECT_EQ((document::FieldValue*)NULL, doc->getValue(doc->getField("tags")).get());

    DocumentUpdate update(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
    update.addUpdate(FieldUpdate(doc->getField("tags")).addUpdate(AssignValueUpdate()));

    update.applyTo(*doc);

    // Verify that the field was cleared in the document.
    EXPECT_FALSE(doc->hasValue(doc->getField("tags")));
}

TEST(DocumentUpdateTest, testUpdateApplyNoArrayValues)
{
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field &field(doc->getType().getField("tags"));
    EXPECT_EQ((document::FieldValue*) 0, doc->getValue(field).get());

    // Assign array field with no array values = empty array
    DocumentUpdate update(docMan.getTypeRepo(), *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> fval(doc->getAs<ArrayFieldValue>(field));
    EXPECT_TRUE(fval.get());
    EXPECT_EQ((size_t) 0, fval->size());
}

TEST(DocumentUpdateTest, testUpdateArrayEmptyParamValue)
{
    // Create a test document.
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field &field(doc->getType().getField("tags"));
    EXPECT_EQ((document::FieldValue*) 0, doc->getValue(field).get());

    // Assign array field with no array values = empty array.
    DocumentUpdate update(docMan.getTypeRepo(), *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));
    EXPECT_TRUE(fval1.get());
    EXPECT_EQ((size_t) 0, fval1->size());

    // Remove array field.
    DocumentUpdate update2(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
    update2.addUpdate(FieldUpdate(field).addUpdate(ClearValueUpdate()));
    update2.applyTo(*doc);

    // Verify that the field was cleared in the document.
    std::unique_ptr<ArrayFieldValue> fval2(doc->getAs<ArrayFieldValue>(field));
    EXPECT_FALSE(fval2);
}

TEST(DocumentUpdateTest, testUpdateWeightedSetEmptyParamValue)
{
    // Create a test document
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field &field(doc->getType().getField("stringweightedset"));
    EXPECT_EQ((document::FieldValue*) 0, doc->getValue(field).get());

    // Assign weighted set with no items = empty set.
    DocumentUpdate update(docMan.getTypeRepo(), *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.
    auto fval1(doc->getAs<WeightedSetFieldValue>(field));
    EXPECT_TRUE(fval1.get());
    EXPECT_EQ((size_t) 0, fval1->size());

    // Remove weighted set field.
    DocumentUpdate update2(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
    update2.addUpdate(FieldUpdate(field).addUpdate(ClearValueUpdate()));
    update2.applyTo(*doc);

    // Verify that the field was cleared in the document.
    auto fval2(doc->getAs<WeightedSetFieldValue>(field));
    EXPECT_FALSE(fval2);
}

TEST(DocumentUpdateTest, testUpdateArrayWrongSubtype)
{
    // Create a test document
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field &field(doc->getType().getField("tags"));
    EXPECT_EQ((document::FieldValue*) 0, doc->getValue(field).get());

    // Assign int values to string array.
    DocumentUpdate update(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());<commit_msg>Change some EXPECT_ to ASSERT_.<commit_after>// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

#include <vespa/document/base/testdocman.h>
#include <vespa/document/datatype/tensor_data_type.h>
#include <vespa/document/fieldvalue/fieldvalues.h>
#include <vespa/document/repo/configbuilder.h>
#include <vespa/document/repo/documenttyperepo.h>
#include <vespa/document/serialization/vespadocumentserializer.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/documentupdate.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/tensor_add_update.h>
#include <vespa/document/update/tensor_modify_update.h>
#include <vespa/document/update/tensor_remove_update.h>
#include <vespa/document/update/valueupdate.h>
#include <vespa/document/util/bytebuffer.h>
#include <vespa/eval/tensor/default_tensor_engine.h>
#include <vespa/eval/tensor/tensor.h>
#include <vespa/vespalib/objects/nbostream.h>
#include <vespa/vespalib/util/exception.h>

#include <fcntl.h>
#include <gtest/gtest.h>
#include <unistd.h>

using namespace document::config_builder;
using vespalib::eval::TensorSpec;
using vespalib::tensor::DefaultTensorEngine;
using vespalib::tensor::Tensor;
using vespalib::nbostream;

namespace document {

namespace {

ByteBuffer::UP serializeHEAD(const DocumentUpdate & update)
{
    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)
{
    nbostream stream;
    VespaDocumentSerializer serializer(stream);
    serializer.write42(update);
    ByteBuffer::UP retVal(new ByteBuffer(stream.size()));
    retVal->putBytes(stream.peek(), stream.size());
    return retVal;
}

nbostream serialize(const ValueUpdate & update)
{
    nbostream stream;
    VespaDocumentSerializer serializer(stream);
    serializer.write(update);
    return stream;
}

nbostream serialize(const FieldUpdate & update)
{
    nbostream stream;
    VespaDocumentSerializer serializer(stream);
    serializer.write(update);
    return stream;
}

template<typename UpdateType>
void testRoundtripSerialize(const UpdateType& update, const DataType &type) {
    try{
        DocumentTypeRepo repo;
        nbostream stream = serialize(update);
        std::unique_ptr<UpdateType> copy(dynamic_cast<UpdateType*>(ValueUpdate::createInstance(repo, type, stream).release()));
        EXPECT_EQ(update, *copy);
    } catch (std::exception& e) {
            std::cerr << "Failed while processing update " << update << "\n";
    throw;
    }
}

}

TEST(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
    testRoundtripSerialize(ClearValueUpdate(), *DataType::INT);
    testRoundtripSerialize(AssignValueUpdate(IntFieldValue(1)), *DataType::INT);
    testRoundtripSerialize(ArithmeticValueUpdate(ArithmeticValueUpdate::Div, 4.3), *DataType::FLOAT);
    testRoundtripSerialize(AddValueUpdate(IntFieldValue(1), 4), *arrayType);
    testRoundtripSerialize(RemoveValueUpdate(IntFieldValue(1)), *arrayType);

    FieldUpdate fieldUpdate(docType->getField("intf"));
    fieldUpdate.addUpdate(AssignValueUpdate(IntFieldValue(1)));
    nbostream stream = serialize(fieldUpdate);
    FieldUpdate fieldUpdateCopy(repo, *docType, stream);
    EXPECT_EQ(fieldUpdate, fieldUpdateCopy);

        // Test that a document update can be serialized
    DocumentUpdate docUpdate(repo, *docType, DocumentId("doc::testdoc"));
    docUpdate.addUpdate(fieldUpdateCopy);
    ByteBuffer::UP docBuf = serializeHEAD(docUpdate);
    docBuf->flip();
    auto docUpdateCopy(DocumentUpdate::createHEAD(repo, nbostream(docBuf->getBufferAtPos(), docBuf->getRemaining())));

        // 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(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intf")).addUpdate(ClearValueUpdate()));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        EXPECT_FALSE(updated.getValue("intf"));
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intf")).addUpdate(AssignValueUpdate(IntFieldValue(15))));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        EXPECT_EQ(15, updated.getValue("intf")->getAsInt());
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intf")).addUpdate(ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 15)));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        EXPECT_EQ(20, updated.getValue("intf")->getAsInt());
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intarr")).addUpdate(AddValueUpdate(IntFieldValue(4))));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        std::unique_ptr<ArrayFieldValue> val(dynamic_cast<ArrayFieldValue*>(updated.getValue("intarr").release()));
        ASSERT_EQ(size_t(3), val->size());
        EXPECT_EQ(4, (*val)[2].getAsInt());
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("intarr")).addUpdate(RemoveValueUpdate(IntFieldValue(3))));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        std::unique_ptr<ArrayFieldValue> val(dynamic_cast<ArrayFieldValue*>(updated.getValue("intarr").release()));
        ASSERT_EQ(size_t(1), val->size());
        EXPECT_EQ(7, (*val)[0].getAsInt());
    }
    {
        Document updated(doc);
        DocumentUpdate upd(repo, *docType, DocumentId("doc::testdoc"));
        upd.addUpdate(FieldUpdate(docType->getField("bytef"))
                              .addUpdate(ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 15)));
        upd.applyTo(updated);
        EXPECT_NE(doc, updated);
        EXPECT_EQ(15, (int) updated.getValue("bytef")->getAsByte());
    }
}

TEST(DocumentUpdateTest, testClearField)
{
    // Create a document.
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    doc->setValue(doc->getField("headerval"), IntFieldValue(4));
    EXPECT_EQ(4, doc->getValue("headerval")->getAsInt());

    // Apply an update.
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(doc->getField("headerval")).addUpdate(AssignValueUpdate()))
        .applyTo(*doc);
    EXPECT_FALSE(doc->getValue("headerval"));
}

TEST(DocumentUpdateTest, testUpdateApplySingleValue)
{
    // Create a document.
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    doc->setValue(doc->getField("headerval"), IntFieldValue(4));
    EXPECT_EQ(4, doc->getValue("headerval")->getAsInt());

    // Apply an update.
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(doc->getField("headerval")).addUpdate(AssignValueUpdate(IntFieldValue(9))))
        .applyTo(*doc);
    EXPECT_EQ(9, doc->getValue("headerval")->getAsInt());
}

TEST(DocumentUpdateTest, testUpdateArray)
{
    // Create a document.
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    EXPECT_EQ((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(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(doc->getField("tags")).addUpdate(AssignValueUpdate(myarray)))
        .applyTo(*doc);
    auto fval1(doc->getAs<ArrayFieldValue>(doc->getField("tags")));
    ASSERT_EQ((size_t) 2, fval1->size());
    EXPECT_EQ(std::string("foo"), std::string((*fval1)[0].getAsString()));
    EXPECT_EQ(std::string("bar"), std::string((*fval1)[1].getAsString()));

    // Append array field
    DocumentUpdate(docMan.getTypeRepo(), *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")));
    ASSERT_EQ((size_t) 4, fval2->size());
    EXPECT_EQ(std::string("foo"), std::string((*fval2)[0].getAsString()));
    EXPECT_EQ(std::string("bar"), std::string((*fval2)[1].getAsString()));
    EXPECT_EQ(std::string("another"), std::string((*fval2)[2].getAsString()));
    EXPECT_EQ(std::string("tag"), std::string((*fval2)[3].getAsString()));

    // Append single value.
    ASSERT_THROW(
        DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
            .addUpdate(FieldUpdate(doc->getField("tags"))
                       .addUpdate(AssignValueUpdate(StringFieldValue("THROW MEH!"))))
            .applyTo(*doc),
        std::exception) << "Expected exception when assigning a string value to an array field.";

    // Remove array field.
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(doc->getField("tags"))
                   .addUpdate(RemoveValueUpdate(StringFieldValue("foo")))
                   .addUpdate(RemoveValueUpdate(StringFieldValue("tag"))))
        .applyTo(*doc);
    auto fval3(doc->getAs<ArrayFieldValue>(doc->getField("tags")));
    ASSERT_EQ((size_t) 2, fval3->size());
    EXPECT_EQ(std::string("bar"), std::string((*fval3)[0].getAsString()));
    EXPECT_EQ(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"));
    ASSERT_THROW(
        DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
            .addUpdate(FieldUpdate(doc->getField("tags"))
                       .addUpdate(RemoveValueUpdate(myarray2)))
            .applyTo(*doc),
        std::exception) << "Expected exception when removing an array from a string array.";
}

TEST(DocumentUpdateTest, testUpdateWeightedSet)
{
    // Create a test document
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field& field(doc->getType().getField("stringweightedset"));
    EXPECT_EQ((FieldValue*) 0, doc->getValue(field).get());
	
    // Assign weightedset field
    WeightedSetFieldValue wset(field.getDataType());
    wset.add(StringFieldValue("foo"), 3);
    wset.add(StringFieldValue("bar"), 14);
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(field).addUpdate(AssignValueUpdate(wset)))
        .applyTo(*doc);
    auto fval1(doc->getAs<WeightedSetFieldValue>(field));
    ASSERT_EQ((size_t) 2, fval1->size());
    EXPECT_TRUE(fval1->contains(StringFieldValue("foo")));
    EXPECT_NE(fval1->find(StringFieldValue("foo")), fval1->end());
    EXPECT_EQ(3, fval1->get(StringFieldValue("foo"), 0));
    EXPECT_TRUE(fval1->contains(StringFieldValue("bar")));
    EXPECT_NE(fval1->find(StringFieldValue("bar")), fval1->end());
    EXPECT_EQ(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(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(field)
                   .addUpdate(AssignValueUpdate(wset2)))
        .applyTo(*doc);
    auto fval2(doc->getAs<WeightedSetFieldValue>(field));
    ASSERT_EQ((size_t) 2, fval2->size());
    EXPECT_TRUE(fval2->contains(StringFieldValue("foo")));
    EXPECT_NE(fval2->find(StringFieldValue("foo")), fval1->end());
    EXPECT_EQ(16, fval2->get(StringFieldValue("foo"), 0));
    EXPECT_TRUE(fval2->contains(StringFieldValue("bar")));
    EXPECT_NE(fval2->find(StringFieldValue("bar")), fval1->end());
    EXPECT_EQ(24, fval2->get(StringFieldValue("bar"), 0));

    // Append weighted field
    DocumentUpdate(docMan.getTypeRepo(), *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));
    ASSERT_EQ((size_t) 3, fval3->size());
    EXPECT_TRUE(fval3->contains(StringFieldValue("foo")));
    EXPECT_EQ(3, fval3->get(StringFieldValue("foo"), 0));
    EXPECT_TRUE(fval3->contains(StringFieldValue("bar")));
    EXPECT_EQ(24, fval3->get(StringFieldValue("bar"), 0));
    EXPECT_TRUE(fval3->contains(StringFieldValue("too")));
    EXPECT_EQ(14, fval3->get(StringFieldValue("too"), 0));

    // Remove weighted field
    DocumentUpdate(docMan.getTypeRepo(), *doc->getDataType(), doc->getId())
        .addUpdate(FieldUpdate(field)
                   .addUpdate(RemoveValueUpdate(StringFieldValue("foo")))
                   .addUpdate(RemoveValueUpdate(StringFieldValue("too"))))
        .applyTo(*doc);
    auto fval4(doc->getAs<WeightedSetFieldValue>(field));
    ASSERT_EQ((size_t) 1, fval4->size());
    EXPECT_FALSE(fval4->contains(StringFieldValue("foo")));
    EXPECT_TRUE(fval4->contains(StringFieldValue("bar")));
    EXPECT_EQ(24, fval4->get(StringFieldValue("bar"), 0));
    EXPECT_FALSE(fval4->contains(StringFieldValue("too")));
}

namespace {

struct WeightedSetAutoCreateFixture
{
    DocumentTypeRepo repo;
    const DocumentType* docType;
    Document doc;
    const Field& field;
    DocumentUpdate update;

    ~WeightedSetAutoCreateFixture();
    WeightedSetAutoCreateFixture();

    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();
    }
};

WeightedSetAutoCreateFixture::~WeightedSetAutoCreateFixture() = default;
WeightedSetAutoCreateFixture::WeightedSetAutoCreateFixture()
    : repo(makeConfig()),
      docType(repo.getDocumentType("test")),
      doc(*docType, DocumentId("doc::testdoc")),
      field(docType->getField("strwset")),
      update(repo, *docType, DocumentId("doc::testdoc"))
{
    update.addUpdate(FieldUpdate(field)
                             .addUpdate(MapValueUpdate(StringFieldValue("foo"),
                                                       ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 1))));
}
} // anon ns

TEST(DocumentUpdateTest, testIncrementNonExistingAutoCreateWSetField)
{
    WeightedSetAutoCreateFixture fixture;

    fixture.applyUpdateToDocument();

    std::unique_ptr<WeightedSetFieldValue> ws(
            fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
    ASSERT_EQ(size_t(1), ws->size());
    EXPECT_TRUE(ws->contains(StringFieldValue("foo")));
    EXPECT_EQ(1, ws->get(StringFieldValue("foo"), 0));
}

TEST(DocumentUpdateTest, testIncrementExistingWSetField)
{
    WeightedSetAutoCreateFixture fixture;
    {
        WeightedSetFieldValue wset(fixture.field.getDataType());
        wset.add(StringFieldValue("bar"), 14);
        fixture.doc.setValue(fixture.field, wset);
    }
    fixture.applyUpdateToDocument();

    auto ws(fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
    ASSERT_EQ(size_t(2), ws->size());
    EXPECT_TRUE(ws->contains(StringFieldValue("foo")));
    EXPECT_EQ(1, ws->get(StringFieldValue("foo"), 0));
}

TEST(DocumentUpdateTest, testIncrementWithZeroResultWeightIsRemoved)
{
    WeightedSetAutoCreateFixture fixture;
    fixture.update.addUpdate(FieldUpdate(fixture.field)
            .addUpdate(MapValueUpdate(StringFieldValue("baz"),
                                      ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 0))));

    fixture.applyUpdateToDocument();

    auto ws(fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
    ASSERT_EQ(size_t(1), ws->size());
    EXPECT_TRUE(ws->contains(StringFieldValue("foo")));
    EXPECT_FALSE(ws->contains(StringFieldValue("baz")));
}

TEST(DocumentUpdateTest, testReadSerializedFile)
{
    // Reads a file serialized from java
    const std::string 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);
    if (read(fd, buf.getBuffer(), len) != len) {
    	throw vespalib::Exception("read failed");
    }
    close(fd);

    nbostream is(buf.getBufferAtPos(), buf.getRemaining());
    DocumentUpdate::UP updp(DocumentUpdate::create42(repo, is));
    DocumentUpdate& upd(*updp);

    const DocumentType *type = repo.getDocumentType("serializetest");
    EXPECT_EQ(DocumentId(DocIdString("update", "test")), upd.getId());
    EXPECT_EQ(*type, upd.getType());

    // Verify assign value update.
    FieldUpdate serField = upd.getUpdates()[1];
    EXPECT_EQ(serField.getField().getId(), type->getField("intfield").getId());

    const ValueUpdate* serValue = &serField[0];
    ASSERT_EQ(serValue->getType(), ValueUpdate::Assign);

    const AssignValueUpdate* assign(static_cast<const AssignValueUpdate*>(serValue));
    EXPECT_EQ(IntFieldValue(4), static_cast<const IntFieldValue&>(assign->getValue()));

    // Verify clear field update.
    serField = upd.getUpdates()[2];
    EXPECT_EQ(serField.getField().getId(), type->getField("floatfield").getId());

    serValue = &serField[0];
    EXPECT_EQ(serValue->getType(), ValueUpdate::Clear);
    EXPECT_TRUE(serValue->inherits(ClearValueUpdate::classId));

    // Verify add value update.
    serField = upd.getUpdates()[0];
    EXPECT_EQ(serField.getField().getId(), type->getField("arrayoffloatfield").getId());

    serValue = &serField[0];
    ASSERT_EQ(serValue->getType(), ValueUpdate::Add);

    const AddValueUpdate* add = static_cast<const AddValueUpdate*>(serValue);
    const FieldValue* value = &add->getValue();
    EXPECT_TRUE(value->inherits(FloatFieldValue::classId));
    EXPECT_FLOAT_EQ(value->getAsFloat(), 5.00f);

    serValue = &serField[1];
    ASSERT_EQ(serValue->getType(), ValueUpdate::Add);

    add = static_cast<const AddValueUpdate*>(serValue);
    value = &add->getValue();
    EXPECT_TRUE(value->inherits(FloatFieldValue::classId));
    EXPECT_FLOAT_EQ(value->getAsFloat(), 4.23f);

    serValue = &serField[2];
    ASSERT_EQ(serValue->getType(), ValueUpdate::Add);

    add = static_cast<const AddValueUpdate*>(serValue);
    value = &add->getValue();
    EXPECT_TRUE(value->inherits(FloatFieldValue::classId));
    EXPECT_FLOAT_EQ(value->getAsFloat(), -1.00f);

}

TEST(DocumentUpdateTest, testGenerateSerializedFile)
{
    // Tests nothing, only generates a file for java test
    const std::string file_name = "data/crossplatform-java-cpp-doctypes.cfg";
    DocumentTypeRepo repo(readDocumenttypesConfig(file_name));

    const DocumentType *type(repo.getDocumentType("serializetest"));
    DocumentUpdate upd(repo, *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);
    if (write(fd, buf->getBuffer(), buf->getPos()) != (ssize_t)buf->getPos()) {
	    throw vespalib::Exception("read failed");
    }
    close(fd);
}


TEST(DocumentUpdateTest, testSetBadFieldTypes)
{
    // Create a test document
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    EXPECT_EQ((document::FieldValue*)NULL, doc->getValue(doc->getField("headerval")).get());

    // Assign a float value to an int field.
    DocumentUpdate update(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
    ASSERT_THROW(
        update.addUpdate(FieldUpdate(doc->getField("headerval"))
                 .addUpdate(AssignValueUpdate(FloatFieldValue(4.00f)))),
        std::exception) << "Expected exception when adding a float to an int field.";

    update.applyTo(*doc);

    // Verify that the field is NOT set in the document.
    EXPECT_EQ((document::FieldValue*)NULL,
			 doc->getValue(doc->getField("headerval")).get());
}

TEST(DocumentUpdateTest, testUpdateApplyNoParams)
{
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    EXPECT_EQ((document::FieldValue*)NULL, doc->getValue(doc->getField("tags")).get());

    DocumentUpdate update(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
    update.addUpdate(FieldUpdate(doc->getField("tags")).addUpdate(AssignValueUpdate()));

    update.applyTo(*doc);

    // Verify that the field was cleared in the document.
    EXPECT_FALSE(doc->hasValue(doc->getField("tags")));
}

TEST(DocumentUpdateTest, testUpdateApplyNoArrayValues)
{
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field &field(doc->getType().getField("tags"));
    EXPECT_EQ((document::FieldValue*) 0, doc->getValue(field).get());

    // Assign array field with no array values = empty array
    DocumentUpdate update(docMan.getTypeRepo(), *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> fval(doc->getAs<ArrayFieldValue>(field));
    ASSERT_TRUE(fval.get());
    EXPECT_EQ((size_t) 0, fval->size());
}

TEST(DocumentUpdateTest, testUpdateArrayEmptyParamValue)
{
    // Create a test document.
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field &field(doc->getType().getField("tags"));
    EXPECT_EQ((document::FieldValue*) 0, doc->getValue(field).get());

    // Assign array field with no array values = empty array.
    DocumentUpdate update(docMan.getTypeRepo(), *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));
    ASSERT_TRUE(fval1.get());
    EXPECT_EQ((size_t) 0, fval1->size());

    // Remove array field.
    DocumentUpdate update2(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
    update2.addUpdate(FieldUpdate(field).addUpdate(ClearValueUpdate()));
    update2.applyTo(*doc);

    // Verify that the field was cleared in the document.
    std::unique_ptr<ArrayFieldValue> fval2(doc->getAs<ArrayFieldValue>(field));
    EXPECT_FALSE(fval2);
}

TEST(DocumentUpdateTest, testUpdateWeightedSetEmptyParamValue)
{
    // Create a test document
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field &field(doc->getType().getField("stringweightedset"));
    EXPECT_EQ((document::FieldValue*) 0, doc->getValue(field).get());

    // Assign weighted set with no items = empty set.
    DocumentUpdate update(docMan.getTypeRepo(), *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.
    auto fval1(doc->getAs<WeightedSetFieldValue>(field));
    ASSERT_TRUE(fval1.get());
    EXPECT_EQ((size_t) 0, fval1->size());

    // Remove weighted set field.
    DocumentUpdate update2(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
    update2.addUpdate(FieldUpdate(field).addUpdate(ClearValueUpdate()));
    update2.applyTo(*doc);

    // Verify that the field was cleared in the document.
    auto fval2(doc->getAs<WeightedSetFieldValue>(field));
    EXPECT_FALSE(fval2);
}

TEST(DocumentUpdateTest, testUpdateArrayWrongSubtype)
{
    // Create a test document
    TestDocMan docMan;
    Document::UP doc(docMan.createDocument());
    const Field &field(doc->getType().getField("tags"));
    EXPECT_EQ((document::FieldValue*) 0, doc->getValue(field).get());

    // Assign int values to string array.
    DocumentUpdate update(docMan.getTypeRepo(), *doc->getDataType(), doc->getId());
{'fraction_non_alphanumeric': 0.1550868030013241, 'fraction_numerical': 0.008680300132411358, 'mean_word_length': 4.538500712976166, 'pattern_counts': {'":': 0, '<': 149, '<?xml version=': 0, '>': 495, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1681635', 'n_tokens_mistral': 16819, 'n_tokens_neox': 16799, 'n_words': 2509}