diff options
author | Jacob Evans <jacob.evans@10gen.com> | 2018-12-05 17:49:19 -0500 |
---|---|---|
committer | Jacob Evans <jacob.evans@10gen.com> | 2018-12-17 17:55:21 -0500 |
commit | 768a1a3c67dbe10936abf06f971bf6890b5047d9 (patch) | |
tree | 1cadc3078152c335ae5639b267284526cef29d11 /src/mongo/embedded/stitch_support | |
parent | 0b24c853fde845bfc4e77354e3ba05ce1a445276 (diff) | |
download | mongo-768a1a3c67dbe10936abf06f971bf6890b5047d9.tar.gz |
SERVER-37827 Add Stitch library matcher functions
Diffstat (limited to 'src/mongo/embedded/stitch_support')
-rw-r--r-- | src/mongo/embedded/stitch_support/SConscript | 4 | ||||
-rw-r--r-- | src/mongo/embedded/stitch_support/stitch_support.cpp | 110 | ||||
-rw-r--r-- | src/mongo/embedded/stitch_support/stitch_support.h | 90 | ||||
-rw-r--r-- | src/mongo/embedded/stitch_support/stitch_support_test.cpp | 82 |
4 files changed, 280 insertions, 6 deletions
diff --git a/src/mongo/embedded/stitch_support/SConscript b/src/mongo/embedded/stitch_support/SConscript index 9b6c071f182..d16a51efd76 100644 --- a/src/mongo/embedded/stitch_support/SConscript +++ b/src/mongo/embedded/stitch_support/SConscript @@ -51,7 +51,9 @@ stitchSupportTargets = stitchSupportEnv.Library( 'stitch_support.cpp', ], LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/db/service_context', + '$BUILD_DIR/mongo/db/matcher/expressions', + '$BUILD_DIR/mongo/db/query/collation/collator_factory_icu', + '$BUILD_DIR/mongo/db/query/collation/collator_factory_interface', ], INSTALL_ALIAS=[ 'stitch-support', diff --git a/src/mongo/embedded/stitch_support/stitch_support.cpp b/src/mongo/embedded/stitch_support/stitch_support.cpp index bb1245a4365..197daf8dc11 100644 --- a/src/mongo/embedded/stitch_support/stitch_support.cpp +++ b/src/mongo/embedded/stitch_support/stitch_support.cpp @@ -33,6 +33,10 @@ #include "stitch_support/stitch_support.h" #include "mongo/base/initializer.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/db/client.h" +#include "mongo/db/matcher/matcher.h" +#include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/service_context.h" #include "mongo/util/assert_util.h" @@ -80,12 +84,10 @@ private: stitch_support_v1_error _code; }; -stitch_support_v1_status translateException() try { - throw; +stitch_support_v1_status translateException() try { throw; } catch (const DBException& ex) { + return {STITCH_SUPPORT_V1_ERROR_EXCEPTION, ex.code(), ex.what()}; } catch (const StitchSupportException& ex) { return {ex.statusCode(), mongo::ErrorCodes::InternalError, ex.what()}; -} catch (const DBException& ex) { - return {STITCH_SUPPORT_V1_ERROR_EXCEPTION, ex.code(), ex.what()}; } catch (const std::bad_alloc& ex) { return {STITCH_SUPPORT_V1_ERROR_ENOMEM, mongo::ErrorCodes::InternalError, ex.what()}; } catch (const std::exception& ex) { @@ -161,6 +163,27 @@ struct stitch_support_v1_lib { mongo::EmbeddedServiceContextPtr serviceContext; }; +struct stitch_support_v1_collator { + stitch_support_v1_collator(std::unique_ptr<mongo::CollatorInterface> collator) + : collator(std::move(collator)) {} + std::unique_ptr<mongo::CollatorInterface> collator; +}; + +struct stitch_support_v1_matcher { + stitch_support_v1_matcher(mongo::ServiceContext::UniqueClient client, + const mongo::BSONObj& filterBSON, + stitch_support_v1_collator* collator) + : client(std::move(client)), + opCtx(this->client->makeOperationContext()), + matcher(filterBSON, + new mongo::ExpressionContext(opCtx.get(), + collator ? collator->collator.get() : nullptr)){}; + + mongo::ServiceContext::UniqueClient client; + mongo::ServiceContext::UniqueOperationContext opCtx; + mongo::Matcher matcher; +}; + namespace mongo { namespace { @@ -199,6 +222,45 @@ void stitch_lib_fini(stitch_support_v1_lib* const lib, stitch_support_v1_status& library.reset(); } +stitch_support_v1_collator* collator_create(stitch_support_v1_lib* const lib, + BSONObj collationSpecExpr) { + if (!library) { + throw StitchSupportException{STITCH_SUPPORT_V1_ERROR_LIBRARY_NOT_INITIALIZED, + "Cannot create a new collator when the Stitch Support Library " + "is not yet initialized."}; + } + + if (library.get() != lib) { + throw StitchSupportException{STITCH_SUPPORT_V1_ERROR_INVALID_LIB_HANDLE, + "Cannot create a new collator when the Stitch Support Library " + "is not yet initialized."}; + } + + auto statusWithCollator = + CollatorFactoryInterface::get(lib->serviceContext.get())->makeFromBSON(collationSpecExpr); + uassertStatusOK(statusWithCollator.getStatus()); + return new stitch_support_v1_collator(std::move(statusWithCollator.getValue())); +} + +stitch_support_v1_matcher* matcher_create(stitch_support_v1_lib* const lib, + BSONObj filter, + stitch_support_v1_collator* collator) { + if (!library) { + throw StitchSupportException{STITCH_SUPPORT_V1_ERROR_LIBRARY_NOT_INITIALIZED, + "Cannot create a new matcher when the Stitch Support Library " + "is not yet initialized."}; + } + + if (library.get() != lib) { + throw StitchSupportException{STITCH_SUPPORT_V1_ERROR_INVALID_LIB_HANDLE, + "Cannot create a new matcher when the Stitch Support Library " + "is not yet initialized."}; + } + + return new stitch_support_v1_matcher( + lib->serviceContext->makeClient("stitch_support"), filter.getOwned(), collator); +} + template <typename Function, typename ReturnType = decltype(std::declval<Function>()(*std::declval<stitch_support_v1_status*>()))> @@ -316,7 +378,45 @@ stitch_support_v1_status* MONGO_API_CALL stitch_support_v1_status_create(void) { } void MONGO_API_CALL stitch_support_v1_status_destroy(stitch_support_v1_status* const status) { - delete status; + static_cast<void>(enterCXX(nullptr, [=](stitch_support_v1_status&) { delete status; })); +} + +stitch_support_v1_collator* MONGO_API_CALL stitch_support_v1_collator_create( + stitch_support_v1_lib* lib, const char* collationBSON, stitch_support_v1_status* const status) { + return enterCXX(status, [&](stitch_support_v1_status& status) { + mongo::BSONObj collationSpecExpr(collationBSON); + return mongo::collator_create(lib, collationSpecExpr); + }); +} + +void MONGO_API_CALL stitch_support_v1_collator_destroy(stitch_support_v1_collator* const collator) { + static_cast<void>(enterCXX(nullptr, [=](stitch_support_v1_status&) { delete collator; })); +} + +stitch_support_v1_matcher* MONGO_API_CALL +stitch_support_v1_matcher_create(stitch_support_v1_lib* lib, + const char* filterBSON, + stitch_support_v1_collator* collator, + stitch_support_v1_status* const statusPtr) { + return enterCXX(statusPtr, [&](stitch_support_v1_status& status) { + mongo::BSONObj filter(filterBSON); + return mongo::matcher_create(lib, filter, collator); + }); +} + +void MONGO_API_CALL stitch_support_v1_matcher_destroy(stitch_support_v1_matcher* const matcher) { + static_cast<void>(enterCXX(nullptr, [=](stitch_support_v1_status&) { delete matcher; })); +} + +int MONGO_API_CALL +stitch_support_v1_check_match(stitch_support_v1_matcher* matcher, + const char* documentBSON, + bool* isMatch, + stitch_support_v1_status* statusPtr) { + return enterCXX(statusPtr, [&](stitch_support_v1_status& status) { + mongo::BSONObj document(documentBSON); + *isMatch = matcher->matcher.matches(document, nullptr); + }); } } // extern "C" diff --git a/src/mongo/embedded/stitch_support/stitch_support.h b/src/mongo/embedded/stitch_support/stitch_support.h index 701782231e0..3b993cd046c 100644 --- a/src/mongo/embedded/stitch_support/stitch_support.h +++ b/src/mongo/embedded/stitch_support/stitch_support.h @@ -229,6 +229,96 @@ stitch_support_v1_init(stitch_support_v1_status* status); STITCH_SUPPORT_API int MONGO_API_CALL stitch_support_v1_fini(stitch_support_v1_lib* const lib, stitch_support_v1_status* const status); +/** + * A collator object represents a parsed collation. A single collator can be used by multiple + * matcher, projection, and update objects. + * + * It is the client's responsibility to call stitch_support_v1_collator_destroy() to free up + * resources used by the collator. Once a collator is destroyed, it is not safe to call any + * functions on matcher, projection, and update objects that reference the collator, except for + * their destroy functions. + */ +typedef struct stitch_support_v1_collator stitch_support_v1_collator; + +/** + * Creates a stitch_support_v1_collator object, which stores a parsed collation. + * + * This function will fail if the collationBSON is invalid. On failure, it returns NULL and + * populates the 'status' object if it is not NULL. + */ +STITCH_SUPPORT_API stitch_support_v1_collator* MONGO_API_CALL stitch_support_v1_collator_create( + stitch_support_v1_lib* lib, const char* collationBSON, stitch_support_v1_status* const status); + +/** + * Destroys a valid stitch_support_v1_collator object. + * + * This function is not thread safe, and it must not execute concurrently with any other function + * that accesses the collation object being destroyed including those that access a matcher, + * projection or update object which reference the collation object. + * + * This function does not report failures. + */ +STITCH_SUPPORT_API void MONGO_API_CALL +stitch_support_v1_collator_destroy(stitch_support_v1_collator* collator); + +/** + * A matcher object is used to determine if a BSON document matches a predicate. + * + * A matcher can optionally use a collator. The client is responsible for ensuring that a matcher's + * collator continues to exist for the lifetime of the matcher and for ultimately destroying both + * the collator and the matcher. Multiple matcher, projection, and update objects can share the same + * collation object. + */ +typedef struct stitch_support_v1_matcher stitch_support_v1_matcher; + +/** + * Creates a stitch_support_v1_matcher object, which represents a predicate to match against. The + * predicate itself is represented as a BSON object, which is passed in the 'filterBSON' argument. + * + * This function will fail if the predicate is invalid, returning NULL and populating 'status' with + * information about the error. + * + * The 'collator' argument, a pointer to a stitch_support_v1_collator, will cause the matcher to use + * the given collator if provided but the pointer can be NULL to cause the matcher to use no + * collator. The newly created matcher does _not_ take ownership of its 'collator' object. + */ +STITCH_SUPPORT_API stitch_support_v1_matcher* MONGO_API_CALL +stitch_support_v1_matcher_create(stitch_support_v1_lib* lib, + const char* filterBSON, + stitch_support_v1_collator* collator, + stitch_support_v1_status* status); + +/** + * Destroys a valid stitch_support_v1_matcher object. + * + * This function does not destroy the collator associated with the destroyed matcher. When + * destroying a matcher and its associated collator together, it is safe to destroy them in either + * order. Although a matcher is no longer valid once its associated collator has been destroyed, it + * is still safe to call this destroy function on the matcher. + * + * This function is not thread safe, and it must not execute concurrently with any other function + * that accesses the matcher object being destroyed. + * + * This function does not report failures. + */ +STITCH_SUPPORT_API void MONGO_API_CALL +stitch_support_v1_matcher_destroy(stitch_support_v1_matcher* const matcher); + +/** + * Check if the 'documentBSON' input matches the predicate represented by the 'matcher' object. + * + * The 'matcher' and 'documentBSON' parameters must point to initialized objects of their respective + * types, and 'isMatch' must be non-NULL. + * + * When the check is successful, this function returns STITCH_SUPPORT_V1_SUCCESS, sets 'isMatch' to + * indicate whether the document matched. + */ +STITCH_SUPPORT_API int MONGO_API_CALL +stitch_support_v1_check_match(stitch_support_v1_matcher* matcher, + const char* documentBSON, + bool* isMatch, + stitch_support_v1_status* status); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/mongo/embedded/stitch_support/stitch_support_test.cpp b/src/mongo/embedded/stitch_support/stitch_support_test.cpp index 5acd7507dd6..97999c4e4f9 100644 --- a/src/mongo/embedded/stitch_support/stitch_support_test.cpp +++ b/src/mongo/embedded/stitch_support/stitch_support_test.cpp @@ -28,10 +28,16 @@ * it in the license file. */ +#include <string> +#include <utility> + +#include "mongo/bson/json.h" #include "mongo/unittest/unittest.h" #include "stitch_support/stitch_support.h" +using mongo::fromjson; + class StitchSupportTest : public mongo::unittest::Test { protected: void setUp() override { @@ -53,9 +59,85 @@ protected: stitch_support_v1_status* status = nullptr; stitch_support_v1_lib* lib = nullptr; + + auto checkMatch(const char* filterJSON, + std::vector<const char*> documentsJSON, + stitch_support_v1_collator* collator = nullptr) { + auto matcher = stitch_support_v1_matcher_create( + lib, fromjson(filterJSON).objdata(), collator, nullptr); + ASSERT(matcher); + bool isMatch = true; + for (const auto documentJSON : documentsJSON) { + stitch_support_v1_check_match( + matcher, fromjson(documentJSON).objdata(), &isMatch, nullptr); + } + stitch_support_v1_matcher_destroy(matcher); + return isMatch; + } + + auto checkMatchStatus(const char* filterJSON, + const char* documentJSON, + stitch_support_v1_collator* collator = nullptr) { + auto match_status = stitch_support_v1_status_create(); + auto matcher = stitch_support_v1_matcher_create( + lib, fromjson(filterJSON).objdata(), collator, match_status); + ASSERT(!matcher); + + ASSERT_EQ(STITCH_SUPPORT_V1_ERROR_EXCEPTION, + stitch_support_v1_status_get_error(match_status)); + // Make sure that we get a proper code back but don't worry about its exact value. + ASSERT_NE(0, stitch_support_v1_status_get_code(match_status)); + std::string explanation(stitch_support_v1_status_get_explanation(match_status)); + stitch_support_v1_status_destroy(match_status); + + return explanation; + } }; TEST_F(StitchSupportTest, InitializationIsSuccessful) { ASSERT_EQ(STITCH_SUPPORT_V1_SUCCESS, stitch_support_v1_status_get_error(status)); ASSERT(lib); } + +TEST_F(StitchSupportTest, CheckMatchWorksWithDefaults) { + ASSERT_TRUE(checkMatch("{a: 1}", {"{a: 1, b: 1}", "{a: [0, 1]}"})); + ASSERT_TRUE(checkMatch( + "{'a.b': 1}", + {"{a: {b: 1}}", "{a: [{b: 1}]}", "{a: {b: [0, 1]}}", "{a: [{b: [0, 1]}]}"})); + ASSERT_TRUE(checkMatch("{'a.0.b': 1}", + {"{a: [{b: 1}]}", + "{a: [{b: [0, 1]}]}"})); + ASSERT_TRUE(checkMatch("{'a.1.b': 1}", + {"{a: [{b: [0, 1]}, {b: [0, 1]}]}"})); + ASSERT_TRUE(checkMatch("{a: {$size: 1}}", {"{a: [100]}"})); + ASSERT_FALSE(checkMatch("{a: {$size: 1}}", {"{a: [[100], [101]]}"})); + ASSERT_TRUE(checkMatch("{'a.b': {$size: 1}}", {"{a: [0, {b: [100]}]}"})); + ASSERT_TRUE(checkMatch("{'a.1.0.b': 1}", {"{a: [123, [{b: [1]}, 456]]}"})); + ASSERT_TRUE(checkMatch("{'a.1.b': 1}", {"{a: [123, [{b: [1]}, 456]]}"})); + ASSERT_TRUE(checkMatch("{$expr: {$gt: ['$b', '$a']}}", {"{a: 123, b: 456}"})); + ASSERT_TRUE(checkMatch("{a: {$regex: 'lib$'}}", {"{a: 'stitchlib'}"})); +} + +TEST_F(StitchSupportTest, CheckMatchWorksWithStatus) { + ASSERT_EQ("bad query: BadValue: unknown operator: $bogus", + checkMatchStatus("{a: {$bogus: 1}}", "{a: 1}")); + ASSERT_EQ("bad query: BadValue: $where is not allowed in this context", + checkMatchStatus("{$where: 'this.a == 1'}", "{a: 1}")); + ASSERT_EQ("bad query: BadValue: $text is not allowed in this context", + checkMatchStatus("{$text: {$search: 'stitch'}}", "{a: 'stitch lib'}")); + ASSERT_EQ( + "bad query: BadValue: $geoNear, $near, and $nearSphere are not allowed in this context", + checkMatchStatus( + "{location: {$near: {$geometry: {type: 'Point', " + "coordinates: [ -73.9667, 40.78 ] }, $minDistance: 10, $maxDistance: 500}}}", + "{type: 'Point', 'coordinates': [100.0, 0.0]}")); + + // 'check_match' cannot actually fail so we do not test it with a status. +} + +TEST_F(StitchSupportTest, CheckMatchWorksWithCollation) { + auto collator = stitch_support_v1_collator_create( + lib, fromjson("{locale: 'en', strength: 2}").objdata(), nullptr); + ASSERT_TRUE(checkMatch("{a: 'word'}", {"{a: 'WORD', b: 'other'}"}, collator)); + stitch_support_v1_collator_destroy(collator); +} |