diff options
author | Tor Brede Vekterli <vekterli@yahoo-inc.com> | 2017-01-30 16:11:03 +0100 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@yahoo-inc.com> | 2017-02-02 15:18:36 +0100 |
commit | a689ebd4f603357473d5587aecd3bb2d9f1b1270 (patch) | |
tree | 448cb7ba625ffe90acf951207ab2e7d886b2996f /document | |
parent | c42033757432562d303f59b76be3ed43efd26939 (diff) |
Add C++ reference field data type and value
Diffstat (limited to 'document')
12 files changed, 471 insertions, 6 deletions
diff --git a/document/src/tests/datatype/CMakeLists.txt b/document/src/tests/datatype/CMakeLists.txt index 1061b115be9..7b46ca675b3 100644 --- a/document/src/tests/datatype/CMakeLists.txt +++ b/document/src/tests/datatype/CMakeLists.txt @@ -8,3 +8,13 @@ vespa_add_executable(document_datatype_test_app TEST document_documentconfig ) vespa_add_test(NAME document_datatype_test_app COMMAND document_datatype_test_app) + +vespa_add_executable(document_referencedatatype_test_app TEST + SOURCES + referencedatatype_test.cpp + DEPENDS + document + AFTER + document_documentconfig +) +vespa_add_test(NAME document_referencedatatype_test_app COMMAND document_referencedatatype_test_app) diff --git a/document/src/tests/datatype/referencedatatype_test.cpp b/document/src/tests/datatype/referencedatatype_test.cpp new file mode 100644 index 00000000000..2844a84b6cc --- /dev/null +++ b/document/src/tests/datatype/referencedatatype_test.cpp @@ -0,0 +1,71 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/document/base/field.h> +#include <vespa/document/datatype/referencedatatype.h> +#include <vespa/document/fieldvalue/referencefieldvalue.h> +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/util/exceptions.h> +#include <ostream> +#include <sstream> + +using namespace document; + +struct Fixture { + DocumentType docType{"foo"}; + ReferenceDataType refType{docType, 12345}; +}; + +TEST_F("Constructor generates type-parameterized name and sets type ID", Fixture) { + EXPECT_EQUAL("Reference<foo>", f.refType.getName()); + EXPECT_EQUAL(12345, f.refType.getId()); +} + +TEST_F("Target document type is accessible via data type", Fixture) { + EXPECT_EQUAL(f.docType, f.refType.getTargetType()); +} + +TEST_F("Empty ReferenceFieldValue instances can be created from type", Fixture) { + auto fv = f.refType.createFieldValue(); + ASSERT_TRUE(fv.get() != nullptr); + ASSERT_TRUE(dynamic_cast<ReferenceFieldValue*>(fv.get()) != nullptr); + EXPECT_EQUAL(&f.refType, fv->getDataType()); +} + +TEST_F("operator== checks document type and type ID", Fixture) { + EXPECT_NOT_EQUAL(f.refType, *DataType::STRING); + EXPECT_EQUAL(f.refType, f.refType); + + DocumentType otherDocType("bar"); + ReferenceDataType refWithDifferentType(otherDocType, 12345); + ReferenceDataType refWithSameTypeDifferentId(f.docType, 56789); + + EXPECT_NOT_EQUAL(f.refType, refWithDifferentType); + EXPECT_NOT_EQUAL(f.refType, refWithSameTypeDifferentId); +} + +TEST_F("clone() creates new type instance equal to old instance", Fixture) { + std::unique_ptr<ReferenceDataType> cloned(f.refType.clone()); + ASSERT_TRUE(cloned.get() != nullptr); + EXPECT_EQUAL(f.refType, *cloned); +} + +TEST_F("print() emits type name and id", Fixture) { + std::ostringstream ss; + f.refType.print(ss, true, ""); + EXPECT_EQUAL("ReferenceDataType(foo, id 12345)", ss.str()); +} + +TEST_F("buildFieldPath returns empty path for empty input", Fixture) { + auto fp = f.refType.buildFieldPath(""); + ASSERT_TRUE(fp.get() != nullptr); + EXPECT_TRUE(fp->empty()); +} + +TEST_F("buildFieldPath throws IllegalArgumentException for non-empty input", Fixture) { + EXPECT_EXCEPTION(f.refType.buildFieldPath("herebedragons"), + vespalib::IllegalArgumentException, + "Reference data type does not support further field " + "recursion: 'herebedragons'"); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/document/src/tests/fieldvalue/CMakeLists.txt b/document/src/tests/fieldvalue/CMakeLists.txt index c44cca690b7..852ff35ea52 100644 --- a/document/src/tests/fieldvalue/CMakeLists.txt +++ b/document/src/tests/fieldvalue/CMakeLists.txt @@ -26,3 +26,13 @@ vespa_add_executable(document_predicatefieldvalue_test_app TEST document_documentconfig ) vespa_add_test(NAME document_predicatefieldvalue_test_app COMMAND document_predicatefieldvalue_test_app) + +vespa_add_executable(document_referencefieldvalue_test_app TEST + SOURCES + referencefieldvalue_test.cpp + DEPENDS + document + AFTER + document_documentconfig +) +vespa_add_test(NAME document_referencefieldvalue_test_app COMMAND document_referencefieldvalue_test_app) diff --git a/document/src/tests/fieldvalue/referencefieldvalue_test.cpp b/document/src/tests/fieldvalue/referencefieldvalue_test.cpp new file mode 100644 index 00000000000..8ad76ceec7e --- /dev/null +++ b/document/src/tests/fieldvalue/referencefieldvalue_test.cpp @@ -0,0 +1,134 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/document/base/field.h> +#include <vespa/document/datatype/referencedatatype.h> +#include <vespa/document/fieldvalue/referencefieldvalue.h> +#include <vespa/document/fieldvalue/stringfieldvalue.h> +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/util/exceptions.h> +#include <ostream> +#include <sstream> + +using namespace document; + +namespace { + +struct Fixture { + DocumentType docType{"foo"}; + ReferenceDataType refType{docType, 12345}; + + DocumentType otherDocType{"bar"}; + ReferenceDataType otherRefType{otherDocType, 54321}; +}; + +} + +using vespalib::IllegalArgumentException; + +TEST_F("Default-constructed reference is empty and bound to type", Fixture) { + ReferenceFieldValue fv(f.refType); + ASSERT_TRUE(fv.getDataType() != nullptr); + EXPECT_EQUAL(f.refType, *fv.getDataType()); + ASSERT_FALSE(fv.hasValidDocumentId()); +} + +TEST_F("Reference can be constructed with document ID", Fixture) { + ReferenceFieldValue fv(f.refType, DocumentId("id:ns:foo::itsa-me")); + ASSERT_TRUE(fv.getDataType() != nullptr); + EXPECT_EQUAL(f.refType, *fv.getDataType()); + ASSERT_TRUE(fv.hasValidDocumentId()); + EXPECT_EQUAL(DocumentId("id:ns:foo::itsa-me"), fv.getDocumentId()); +} + +TEST_F("Newly constructed reference is marked as changed", Fixture) { + ReferenceFieldValue fv(f.refType); + EXPECT_TRUE(fv.hasChanged()); + + ReferenceFieldValue fv2(f.refType, DocumentId("id:ns:foo::itsa-me")); + EXPECT_TRUE(fv2.hasChanged()); +} + +TEST_F("Exception is thrown if doc ID type does not match referenced document type", Fixture) { + EXPECT_EXCEPTION( + ReferenceFieldValue(f.refType, DocumentId("id:ns:bar::wario-time")), + IllegalArgumentException, + "Can't assign document ID 'id:ns:bar::wario-time' (of type 'bar') " + "to reference of document type 'foo'"); +} + +TEST_F("Exception is thrown if doc ID does not have a type", Fixture) { + // Could have had a special cased message for this, but type-less IDs are + // not expected to be allowed through the feed pipeline at all. We just + // want to ensure it fails in a controlled fashion if encountered. + EXPECT_EXCEPTION( + ReferenceFieldValue(f.refType, DocumentId("doc:foo:bario")), + IllegalArgumentException, + "Can't assign document ID 'doc:foo:bario' (of type '') " + "to reference of document type 'foo'"); +} + +TEST_F("assign()ing another reference field value assigns doc ID and type", Fixture) { + ReferenceFieldValue src(f.refType, DocumentId("id:ns:foo::yoshi")); + ReferenceFieldValue dest(f.otherRefType); + + dest.assign(src); + ASSERT_TRUE(dest.hasValidDocumentId()); + EXPECT_EQUAL(src.getDocumentId(), dest.getDocumentId()); + EXPECT_EQUAL(src.getDataType(), dest.getDataType()); +} + +TEST_F("assign()ing a non-reference field value throws exception", Fixture) { + ReferenceFieldValue fv(f.refType); + EXPECT_EXCEPTION(fv.assign(StringFieldValue("waluigi time!!")), + IllegalArgumentException, + "Can't assign field value of type String to a " + "ReferenceFieldValue"); +} + +TEST_F("clone()ing creates new instance with same ID and type", Fixture) { + ReferenceFieldValue src(f.refType, DocumentId("id:ns:foo::yoshi")); + + std::unique_ptr<ReferenceFieldValue> cloned(src.clone()); + ASSERT_TRUE(cloned.get() != nullptr); + EXPECT_EQUAL(src.getDocumentId(), cloned->getDocumentId()); + EXPECT_EQUAL(src.getDataType(), cloned->getDataType()); + EXPECT_TRUE(cloned->hasChanged()); +} + +TEST_F("compare() orders first on type ID, then on document ID", Fixture) { + // foo type has id 12345 + ReferenceFieldValue fvType1Id1(f.refType, DocumentId("id:ns:foo::AA")); + ReferenceFieldValue fvType1Id2(f.refType, DocumentId("id:ns:foo::AB")); + // bar type has id 54321 + ReferenceFieldValue fvType2Id1(f.otherRefType, DocumentId("id:ns:bar::AA")); + ReferenceFieldValue fvType2Id2(f.otherRefType, DocumentId("id:ns:bar::AB")); + + // Different types + EXPECT_TRUE(fvType1Id1.compare(fvType2Id1) < 0); + EXPECT_TRUE(fvType2Id1.compare(fvType1Id1) > 0); + + // Same types, different IDs + EXPECT_TRUE(fvType1Id1.compare(fvType1Id2) < 0); + EXPECT_TRUE(fvType1Id2.compare(fvType1Id1) > 0); + + // Equal types and ID + EXPECT_EQUAL(0, fvType1Id1.compare(fvType1Id1)); + EXPECT_EQUAL(0, fvType1Id2.compare(fvType1Id2)); + EXPECT_EQUAL(0, fvType2Id1.compare(fvType2Id1)); +} + +TEST_F("print() includes reference type and document ID", Fixture) { + ReferenceFieldValue src(f.refType, DocumentId("id:ns:foo::yoshi")); + std::ostringstream ss; + src.print(ss, false, ""); + EXPECT_EQUAL("ReferenceFieldValue(ReferenceDataType(foo, id 12345), " + "DocumentId(id:ns:foo::yoshi))", ss.str()); +} + +// TODO test +// - modified flag +// - false after (de)serialization(?) +// - (de)serialization + +TEST_MAIN() { TEST_RUN_ALL(); } + diff --git a/document/src/vespa/document/datatype/CMakeLists.txt b/document/src/vespa/document/datatype/CMakeLists.txt index e6826075762..2f46ae29166 100644 --- a/document/src/vespa/document/datatype/CMakeLists.txt +++ b/document/src/vespa/document/datatype/CMakeLists.txt @@ -15,6 +15,7 @@ vespa_add_library(document_datatypes OBJECT structureddatatype.cpp urldatatype.cpp weightedsetdatatype.cpp + referencedatatype.cpp DEPENDS AFTER document_documentconfig diff --git a/document/src/vespa/document/datatype/referencedatatype.cpp b/document/src/vespa/document/datatype/referencedatatype.cpp new file mode 100644 index 00000000000..c1258e1a1c7 --- /dev/null +++ b/document/src/vespa/document/datatype/referencedatatype.cpp @@ -0,0 +1,46 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "referencedatatype.h" +#include "../fieldvalue/referencefieldvalue.h" +#include <vespa/vespalib/util/exceptions.h> +#include <vespa/vespalib/util/stringfmt.h> +#include <ostream> + +namespace document { + +ReferenceDataType::ReferenceDataType(const DocumentType& targetDocType, int id) + : DataType(vespalib::make_string("Reference<%s>", targetDocType.getName().c_str()), id), + _targetDocType(targetDocType) +{ +} + +ReferenceDataType::~ReferenceDataType() { +} + +std::unique_ptr<FieldValue> ReferenceDataType::createFieldValue() const { + return std::make_unique<ReferenceFieldValue>(*this); +} + +void ReferenceDataType::print(std::ostream& os, bool verbose, const std::string& indent) const { + (void) verbose; + (void) indent; + os << "ReferenceDataType(" << _targetDocType.getName() + << ", id " << getId() << ')'; +} + +ReferenceDataType* ReferenceDataType::clone() const { + return new ReferenceDataType(_targetDocType, getId()); +} + +std::unique_ptr<FieldPath> ReferenceDataType::onBuildFieldPath( + const vespalib::stringref& remainingFieldName) const { + if (!remainingFieldName.empty()) { + throw vespalib::IllegalArgumentException( + vespalib::make_string("Reference data type does not support " + "further field recursion: '%s'", + remainingFieldName.c_str()), VESPA_STRLOC); + } + return std::make_unique<FieldPath>(); +} + +} // document diff --git a/document/src/vespa/document/datatype/referencedatatype.h b/document/src/vespa/document/datatype/referencedatatype.h new file mode 100644 index 00000000000..9fbd9c70002 --- /dev/null +++ b/document/src/vespa/document/datatype/referencedatatype.h @@ -0,0 +1,26 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "documenttype.h" + +namespace document { + +class ReferenceDataType : public DataType { + const DocumentType& _targetDocType; +public: + ReferenceDataType(const DocumentType& targetDocType, int id); + ~ReferenceDataType(); + + const DocumentType& getTargetType() const noexcept { + return _targetDocType; + } + + std::unique_ptr<FieldValue> createFieldValue() const override; + void print(std::ostream&, bool verbose, const std::string& indent) const override; + ReferenceDataType* clone() const override; + std::unique_ptr<FieldPath> onBuildFieldPath( + const vespalib::stringref& remainingFieldName) const override; +}; + +} // document diff --git a/document/src/vespa/document/fieldvalue/CMakeLists.txt b/document/src/vespa/document/fieldvalue/CMakeLists.txt index 759d4d42b40..8c76db54d5f 100644 --- a/document/src/vespa/document/fieldvalue/CMakeLists.txt +++ b/document/src/vespa/document/fieldvalue/CMakeLists.txt @@ -23,6 +23,7 @@ vespa_add_library(document_fieldvalues OBJECT structuredfieldvalue.cpp tensorfieldvalue.cpp weightedsetfieldvalue.cpp + referencefieldvalue.cpp DEPENDS AFTER document_documentconfig diff --git a/document/src/vespa/document/fieldvalue/referencefieldvalue.cpp b/document/src/vespa/document/fieldvalue/referencefieldvalue.cpp new file mode 100644 index 00000000000..ffda9fbfe28 --- /dev/null +++ b/document/src/vespa/document/fieldvalue/referencefieldvalue.cpp @@ -0,0 +1,109 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "referencefieldvalue.h" +#include <vespa/vespalib/util/exceptions.h> +#include <vespa/vespalib/util/stringfmt.h> +#include <cassert> + +using vespalib::IllegalArgumentException; +using vespalib::make_string; + +namespace document { + +IMPLEMENT_IDENTIFIABLE(ReferenceFieldValue, FieldValue); + +ReferenceFieldValue::ReferenceFieldValue() + : _dataType(nullptr), + _documentId() +{ +} + +ReferenceFieldValue::ReferenceFieldValue(const ReferenceDataType& dataType) + : _dataType(&dataType), + _documentId() +{ +} + +ReferenceFieldValue::ReferenceFieldValue( + const ReferenceDataType& dataType, + const DocumentId& documentId) + : _dataType(&dataType), + _documentId(documentId) +{ + requireIdOfMatchingType(_documentId, _dataType->getTargetType()); +} + +ReferenceFieldValue::~ReferenceFieldValue() { +} + +void ReferenceFieldValue::requireIdOfMatchingType( + const DocumentId& id, const DocumentType& type) +{ + if (id.getDocType() != type.getName()) { + throw IllegalArgumentException( + make_string("Can't assign document ID '%s' (of type '%s') to " + "reference of document type '%s'", + id.toString().c_str(), + id.getDocType().c_str(), + type.getName().c_str()), + VESPA_STRLOC); + } +} + +FieldValue& ReferenceFieldValue::assign(const FieldValue& rhs) { + const auto* refValueRhs(dynamic_cast<const ReferenceFieldValue*>(&rhs)); + if (refValueRhs != nullptr) { + if (refValueRhs == this) { + return *this; + } + _documentId = refValueRhs->_documentId; + _dataType = refValueRhs->_dataType; + } else { + throw IllegalArgumentException( + make_string("Can't assign field value of type %s to " + "a ReferenceFieldValue", + rhs.getDataType()->getName().c_str()), + VESPA_STRLOC); + } + return *this; +} + +ReferenceFieldValue* ReferenceFieldValue::clone() const { + assert(_dataType != nullptr); + return new ReferenceFieldValue(*_dataType, _documentId); +} + +int ReferenceFieldValue::compare(const FieldValue& rhs) const { + const int parentCompare = FieldValue::compare(rhs); + if (parentCompare != 0) { + return parentCompare; + } + // Type equality is checked by the parent. + const auto& refValueRhs(dynamic_cast<const ReferenceFieldValue&>(rhs)); + // TODO PERF: DocumentId does currently _not_ expose any methods that + // cheaply allow an ordering to be established. Only (in)equality operators. + // IdString::operator== is already implemented in the same way as this, so + // don't put this code in your inner loops, kids! + return _documentId.toString().compare(refValueRhs._documentId.toString()); +} + +void ReferenceFieldValue::print(std::ostream& os, bool verbose, const std::string& indent) const { + (void) verbose; + (void) indent; + assert(_dataType != nullptr); + os << "ReferenceFieldValue(" << *_dataType << ", DocumentId("; + _documentId.print(os, false, ""); + os << "))"; +} + +bool ReferenceFieldValue::hasChanged() const { + return true; // TODO +} + +void ReferenceFieldValue::accept(FieldValueVisitor&) { +} + +void ReferenceFieldValue::accept(ConstFieldValueVisitor&) const { +} + +} // document diff --git a/document/src/vespa/document/fieldvalue/referencefieldvalue.h b/document/src/vespa/document/fieldvalue/referencefieldvalue.h new file mode 100644 index 00000000000..46f3c79b391 --- /dev/null +++ b/document/src/vespa/document/fieldvalue/referencefieldvalue.h @@ -0,0 +1,55 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "fieldvalue.h" +#include "../datatype/referencedatatype.h" +#include "../base/documentid.h" + +namespace document { + +class ReferenceFieldValue : public FieldValue { + const ReferenceDataType* _dataType; + DocumentId _documentId; +public: + // Empty constructor required for Identifiable. + ReferenceFieldValue(); + + explicit ReferenceFieldValue(const ReferenceDataType& dataType); + + ReferenceFieldValue(const ReferenceDataType& dataType, + const DocumentId& documentId); + + ~ReferenceFieldValue(); + + ReferenceFieldValue(const ReferenceFieldValue&) = default; + ReferenceFieldValue& operator=(const ReferenceFieldValue&) = default; + + bool hasValidDocumentId() const noexcept { + return _documentId.hasDocType(); + } + + // Returned value is only well-defined if hasValidDocumentId() == true. + const DocumentId& getDocumentId() const noexcept { + return _documentId; + } + + const DataType* getDataType() const override { return _dataType; } + FieldValue& assign(const FieldValue&) override; + ReferenceFieldValue* clone() const override; + int compare(const FieldValue&) const override; + void printXml(XmlOutputStream&) const override { /* Not implemented */ } + void print(std::ostream&, bool, const std::string&) const override; + bool hasChanged() const override; + void accept(FieldValueVisitor&) override; + void accept(ConstFieldValueVisitor&) const override; + + DECLARE_IDENTIFIABLE(ReferenceFieldValue); +private: + // Throws vespalib::IllegalArgumentException if doc type of `id` does not + // match the name of `type`. + static void requireIdOfMatchingType( + const DocumentId& id, const DocumentType& type); +}; + +} // document diff --git a/document/src/vespa/document/repo/documenttyperepo.cpp b/document/src/vespa/document/repo/documenttyperepo.cpp index d0b72fee2f0..49a3bd32fe9 100644 --- a/document/src/vespa/document/repo/documenttyperepo.cpp +++ b/document/src/vespa/document/repo/documenttyperepo.cpp @@ -199,7 +199,7 @@ struct DataTypeRepo { Repo repo; AnnotationTypeRepo annotations; - DataTypeRepo() : doc_type(0) {} + DataTypeRepo() : doc_type(nullptr) {} ~DataTypeRepo() { delete doc_type; } }; @@ -534,7 +534,7 @@ DocumentTypeRepo::~DocumentTypeRepo() { const DocumentType *DocumentTypeRepo::getDocumentType(int32_t type_id) const { const DataTypeRepo *repo = FindPtr(_doc_types, type_id); - return repo ? repo->doc_type : 0; + return repo ? repo->doc_type : nullptr; } const DocumentType *DocumentTypeRepo::getDocumentType(const stringref &name) const { @@ -549,26 +549,26 @@ const DocumentType *DocumentTypeRepo::getDocumentType(const stringref &name) con return it->second->doc_type; } } - return 0; + return nullptr; } const DataType * DocumentTypeRepo::getDataType(const DocumentType &doc_type, int32_t id) const { const DataTypeRepo *dt_repo = FindPtr(_doc_types, doc_type.getId()); - return dt_repo ? dt_repo->repo.lookup(id) : 0; + return dt_repo ? dt_repo->repo.lookup(id) : nullptr; } const DataType * DocumentTypeRepo::getDataType( const DocumentType &doc_type, const stringref &name) const { const DataTypeRepo *dt_repo = FindPtr(_doc_types, doc_type.getId()); - return dt_repo ? dt_repo->repo.lookup(name) : 0; + return dt_repo ? dt_repo->repo.lookup(name) : nullptr; } const AnnotationType *DocumentTypeRepo::getAnnotationType( const DocumentType &doc_type, int32_t id) const { const DataTypeRepo *dt_repo = FindPtr(_doc_types, doc_type.getId()); - return dt_repo ? dt_repo->annotations.lookup(id) : 0; + return dt_repo ? dt_repo->annotations.lookup(id) : nullptr; } void DocumentTypeRepo::forEachDocumentType( diff --git a/document/src/vespa/document/util/identifiableid.h b/document/src/vespa/document/util/identifiableid.h index 75651281e83..4bbbb38294f 100644 --- a/document/src/vespa/document/util/identifiableid.h +++ b/document/src/vespa/document/util/identifiableid.h @@ -40,6 +40,7 @@ #define CID_MapFieldValue DOCUMENT_CID(36) #define CID_PredicateFieldValue DOCUMENT_CID(37) #define CID_TensorFieldValue DOCUMENT_CID(38) +#define CID_ReferenceFieldValue DOCUMENT_CID(39) #define CID_DataType DOCUMENT_CID(50) #define CID_PrimitiveDataType DOCUMENT_CID(51) @@ -58,6 +59,7 @@ #define CID_MapDataType DOCUMENT_CID(65) #define CID_AnnotationReferenceDataType DOCUMENT_CID(66) #define CID_TensorDataType DOCUMENT_CID(67) +#define CID_ReferenceDataType DOCUMENT_CID(68) #define CID_document_FieldPathEntry DOCUMENT_CID(80) |