summaryrefslogtreecommitdiff
path: root/src/mongo/embedded/stitch_support
diff options
context:
space:
mode:
authorJacob Evans <jacob.evans@10gen.com>2018-12-05 17:49:19 -0500
committerJacob Evans <jacob.evans@10gen.com>2018-12-17 17:55:21 -0500
commit768a1a3c67dbe10936abf06f971bf6890b5047d9 (patch)
tree1cadc3078152c335ae5639b267284526cef29d11 /src/mongo/embedded/stitch_support
parent0b24c853fde845bfc4e77354e3ba05ce1a445276 (diff)
downloadmongo-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/SConscript4
-rw-r--r--src/mongo/embedded/stitch_support/stitch_support.cpp110
-rw-r--r--src/mongo/embedded/stitch_support/stitch_support.h90
-rw-r--r--src/mongo/embedded/stitch_support/stitch_support_test.cpp82
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);
+}