summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/exec/projection_exec.cpp2
-rw-r--r--src/mongo/db/exec/projection_exec.h16
-rw-r--r--src/mongo/embedded/stitch_support/stitch_support.cpp120
-rw-r--r--src/mongo/embedded/stitch_support/stitch_support.h290
-rw-r--r--src/mongo/embedded/stitch_support/stitch_support_test.cpp208
5 files changed, 504 insertions, 132 deletions
diff --git a/src/mongo/db/exec/projection_exec.cpp b/src/mongo/db/exec/projection_exec.cpp
index e1240539f38..84f7fb0fc56 100644
--- a/src/mongo/db/exec/projection_exec.cpp
+++ b/src/mongo/db/exec/projection_exec.cpp
@@ -226,7 +226,7 @@ StatusWith<BSONObj> ProjectionExec::project(const BSONObj& in,
MatchDetails matchDetails;
// If it's a positional projection we need a MatchDetails.
- if (transformRequiresDetails()) {
+ if (projectRequiresQueryExpression()) {
matchDetails.requestElemMatchKey();
invariant(nullptr != _queryExpression);
invariant(_queryExpression->matchesBSON(in, &matchDetails));
diff --git a/src/mongo/db/exec/projection_exec.h b/src/mongo/db/exec/projection_exec.h
index 74676d6d1fe..4d79808b0d7 100644
--- a/src/mongo/db/exec/projection_exec.h
+++ b/src/mongo/db/exec/projection_exec.h
@@ -153,6 +153,15 @@ public:
const boost::optional<const double> textScore = boost::none,
const int64_t recordId = 0) const;
+ /**
+ * Determines if calls to the project method require that this object was created with the full
+ * query expression. We may need it for MatchDetails.
+ */
+ bool projectRequiresQueryExpression() const {
+ return ARRAY_OP_POSITIONAL == _arrayOpType;
+ }
+
+
private:
/**
* Adds meta fields to the end of a projection.
@@ -198,13 +207,6 @@ private:
const MatchDetails* details = nullptr) const;
/**
- * See transform(...) above.
- */
- bool transformRequiresDetails() const {
- return ARRAY_OP_POSITIONAL == _arrayOpType;
- }
-
- /**
* Appends the element 'e' to the builder 'bob', possibly descending into sub-fields of 'e'
* if needed.
*/
diff --git a/src/mongo/embedded/stitch_support/stitch_support.cpp b/src/mongo/embedded/stitch_support/stitch_support.cpp
index daa16a8289a..c907c4a9d20 100644
--- a/src/mongo/embedded/stitch_support/stitch_support.cpp
+++ b/src/mongo/embedded/stitch_support/stitch_support.cpp
@@ -36,6 +36,7 @@
#include "mongo/base/initializer.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/client.h"
+#include "mongo/db/exec/projection_exec.h"
#include "mongo/db/matcher/matcher.h"
#include "mongo/db/ops/parsed_update.h"
#include "mongo/db/query/collation/collator_factory_interface.h"
@@ -43,7 +44,7 @@
#include "mongo/db/update/update_driver.h"
#include "mongo/util/assert_util.h"
-#include <iostream>
+#include <algorithm>
#include <string>
#if defined(_WIN32)
@@ -162,7 +163,7 @@ struct stitch_support_v1_matcher {
stitch_support_v1_collator* collator)
: client(std::move(client)),
opCtx(this->client->makeOperationContext()),
- matcher(filterBSON,
+ matcher(filterBSON.getOwned(),
new mongo::ExpressionContext(opCtx.get(),
collator ? collator->collator.get() : nullptr)){};
@@ -171,6 +172,34 @@ struct stitch_support_v1_matcher {
mongo::Matcher matcher;
};
+struct stitch_support_v1_projection {
+ stitch_support_v1_projection(mongo::ServiceContext::UniqueClient client,
+ const mongo::BSONObj& pattern,
+ stitch_support_v1_matcher* matcher,
+ stitch_support_v1_collator* collator)
+ : client(std::move(client)),
+ opCtx(this->client->makeOperationContext()),
+ projectionExec(opCtx.get(),
+ pattern.getOwned(),
+ matcher ? matcher->matcher.getMatchExpression() : nullptr,
+ collator ? collator->collator.get() : nullptr),
+ matcher(matcher) {
+ uassert(51050,
+ "Projections with a positional operator require a matcher",
+ matcher || !projectionExec.projectRequiresQueryExpression());
+ uassert(51051,
+ "$textScore, $sortKey, $recordId, $geoNear and $returnKey are not allowed in this "
+ "context",
+ !projectionExec.hasMetaFields() && !projectionExec.returnKey());
+ }
+
+ mongo::ServiceContext::UniqueClient client;
+ mongo::ServiceContext::UniqueOperationContext opCtx;
+ mongo::ProjectionExec projectionExec;
+
+ stitch_support_v1_matcher* matcher;
+};
+
struct stitch_support_v1_update_details {
std::vector<std::string> modifiedPaths;
};
@@ -290,7 +319,28 @@ stitch_support_v1_matcher* matcher_create(stitch_support_v1_lib* const lib,
}
return new stitch_support_v1_matcher(
- lib->serviceContext->makeClient("stitch_support"), filter.getOwned(), collator);
+ lib->serviceContext->makeClient("stitch_support"), filter, collator);
+}
+
+stitch_support_v1_projection* projection_create(stitch_support_v1_lib* const lib,
+ BSONObj spec,
+ stitch_support_v1_matcher* matcher,
+ stitch_support_v1_collator* collator) {
+ if (!library) {
+ throw StitchSupportException{STITCH_SUPPORT_V1_ERROR_LIBRARY_NOT_INITIALIZED,
+ "Cannot create a new projection 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 projection when the Stitch Support "
+ "Library is not yet initialized."};
+ }
+
+
+ return new stitch_support_v1_projection(
+ lib->serviceContext->makeClient("stitch_support"), spec, matcher, collator);
}
stitch_support_v1_update* update_create(stitch_support_v1_lib* const lib,
@@ -385,8 +435,8 @@ 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(mongo::getStatusImpl(statusPtr), [&]() {
+ stitch_support_v1_status* const status) {
+ return enterCXX(mongo::getStatusImpl(status), [&]() {
mongo::BSONObj filter(filterBSON);
return mongo::matcher_create(lib, filter, collator);
});
@@ -397,16 +447,55 @@ void MONGO_API_CALL stitch_support_v1_matcher_destroy(stitch_support_v1_matcher*
static_cast<void>(enterCXX(nullStatus, [=]() { delete matcher; }));
}
+stitch_support_v1_projection* MONGO_API_CALL
+stitch_support_v1_projection_create(stitch_support_v1_lib* lib,
+ const char* specBSON,
+ stitch_support_v1_matcher* matcher,
+ stitch_support_v1_collator* collator,
+ stitch_support_v1_status* const status) {
+ return enterCXX(mongo::getStatusImpl(status), [&]() {
+ mongo::BSONObj spec(specBSON);
+ return mongo::projection_create(lib, spec, matcher, collator);
+ });
+}
+
+void MONGO_API_CALL
+stitch_support_v1_projection_destroy(stitch_support_v1_projection* const projection) {
+ mongo::StitchSupportStatusImpl* nullStatus = nullptr;
+ static_cast<void>(enterCXX(nullStatus, [=]() { delete projection; }));
+}
+
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(mongo::getStatusImpl(statusPtr), [&]() {
+ stitch_support_v1_status* status) {
+ return enterCXX(mongo::getStatusImpl(status), [&]() {
mongo::BSONObj document(documentBSON);
*isMatch = matcher->matcher.matches(document, nullptr);
});
}
+char* MONGO_API_CALL
+stitch_support_v1_projection_apply(stitch_support_v1_projection* const projection,
+ const char* documentBSON,
+ stitch_support_v1_status* status) {
+ return enterCXX(mongo::getStatusImpl(status), [&]() {
+ mongo::BSONObj document(documentBSON);
+
+ auto outputResult = projection->projectionExec.project(document);
+ auto outputObj = uassertStatusOK(outputResult);
+ auto outputSize = static_cast<size_t>(outputObj.objsize());
+ auto output = new (std::nothrow) char[outputSize];
+
+ uassert(mongo::ErrorCodes::ExceededMemoryLimit,
+ "Failed to allocate memory for projection",
+ output);
+
+ static_cast<void>(std::copy_n(outputObj.objdata(), outputSize, output));
+ return output;
+ });
+}
+
stitch_support_v1_update* MONGO_API_CALL
stitch_support_v1_update_create(stitch_support_v1_lib* lib,
const char* updateExprBSON,
@@ -462,19 +551,18 @@ stitch_support_v1_update_apply(stitch_support_v1_update* const update,
&mutableDoc,
false /* validateForStorage */,
immutablePaths,
- NULL /* logOpRec*/,
+ nullptr /* logOpRec*/,
&docWasModified,
&modifiedPaths));
auto outputObj = mutableDoc.getObject();
- size_t output_size = static_cast<size_t>(outputObj.objsize());
- char* output = static_cast<char*>(malloc(output_size));
+ size_t outputSize = static_cast<size_t>(outputObj.objsize());
+ auto output = new (std::nothrow) char[outputSize];
uassert(
mongo::ErrorCodes::ExceededMemoryLimit, "Failed to allocate memory for update", output);
- memcpy(
- static_cast<void*>(output), static_cast<const void*>(outputObj.objdata()), output_size);
+ static_cast<void>(std::copy_n(outputObj.objdata(), outputSize, output));
if (update_details) {
update_details->modifiedPaths = modifiedPaths.serialize();
@@ -490,7 +578,8 @@ stitch_support_v1_update_details* MONGO_API_CALL stitch_support_v1_update_detail
void MONGO_API_CALL
stitch_support_v1_update_details_destroy(stitch_support_v1_update_details* update_details) {
- delete update_details;
+ mongo::StitchSupportStatusImpl* nullStatus = nullptr;
+ static_cast<void>(enterCXX(nullStatus, [=]() { delete update_details; }));
};
size_t MONGO_API_CALL stitch_support_v1_update_details_num_modified_paths(
@@ -504,4 +593,9 @@ const char* MONGO_API_CALL stitch_support_v1_update_details_path(
return update_details->modifiedPaths[path_index].c_str();
}
+void MONGO_API_CALL stitch_support_v1_bson_free(char* bson) {
+ mongo::StitchSupportStatusImpl* nullStatus = nullptr;
+ static_cast<void>(enterCXX(nullStatus, [=]() { delete[](bson); }));
+}
+
} // extern "C"
diff --git a/src/mongo/embedded/stitch_support/stitch_support.h b/src/mongo/embedded/stitch_support/stitch_support.h
index 2e52cbb381a..c75dc732820 100644
--- a/src/mongo/embedded/stitch_support/stitch_support.h
+++ b/src/mongo/embedded/stitch_support/stitch_support.h
@@ -190,36 +190,6 @@ stitch_support_v1_status_get_explanation(const stitch_support_v1_status* status)
STITCH_SUPPORT_API int MONGO_API_CALL
stitch_support_v1_status_get_code(const stitch_support_v1_status* status);
-typedef struct stitch_support_v1_update_details stitch_support_v1_update_details;
-
-/**
- * Create an "update details" object to pass to stitch_support_v1_update_apply(), which will
- * populate the update details with a list of paths modified by the update.
- *
- * Clients can reuse the same update details object for multiple calls to
- * stitch_support_v1_update_apply().
- */
-STITCH_SUPPORT_API stitch_support_v1_update_details* MONGO_API_CALL
-stitch_support_v1_update_details_create(void);
-
-STITCH_SUPPORT_API void MONGO_API_CALL
-stitch_support_v1_update_details_destroy(stitch_support_v1_update_details* update_details);
-
-/**
- * The number of modified paths in an update details object. Always call this function to ensure an
- * index is in bounds before calling stitch_support_v1_update_details_path().
- */
-STITCH_SUPPORT_API size_t MONGO_API_CALL stitch_support_v1_update_details_num_modified_paths(
- stitch_support_v1_update_details* update_details);
-
-/**
- * Return a dotted-path string from the given index of the modified paths in the update details
- * object. The above note about distinguishing field names from array indexes in the documentation
- * of stitch_support_v1_match_details_elem_match_path_component() also applies here.
- */
-STITCH_SUPPORT_API const char* MONGO_API_CALL stitch_support_v1_update_details_path(
- stitch_support_v1_update_details* update_details, size_t path_index);
-
/**
* An object which describes the runtime state of the Stitch Support Library.
*
@@ -236,71 +206,6 @@ STITCH_SUPPORT_API const char* MONGO_API_CALL stitch_support_v1_update_details_p
typedef struct stitch_support_v1_lib stitch_support_v1_lib;
/**
- * An update object used to apply an update to a BSON document, which may modify particular
- * fields (e.g.: {$set: {a: 1}}) or replace the entire document with a new one.
- */
-typedef struct stitch_support_v1_update stitch_support_v1_update;
-
-typedef struct stitch_support_v1_matcher stitch_support_v1_matcher;
-typedef struct stitch_support_v1_collator stitch_support_v1_collator;
-/**
- * Creates a stitch_support_v1_update object by parsing the given update expression.
- *
- * If the update expression includes a positional ($) operator, then the caller must pass a
- * stitch_support_v1_matcher, which is used to determine which array element matches the positional.
- * The 'matcher' argument is not used when the update expression has no positional operator, and it
- * can be NULL.
- *
- * The caller can optionally provide a collator, which is used when evaluating arrayFilters match
- * expressions. The 'collator' parameter must match the collator in 'matcher'. A mismatch will raise
- * an invariant violation. Multiple matcher, projection, and update objects can share the same
- * collation object.
- *
- * The newly created update object does _not_ take ownership of its 'matcher' or 'collators'
- * objects. The client is responsible for ensuring that the matcher and collator continue to exist
- * for the lifetime of the update and for ultimately destroying all three of the update, matcher,
- * and collator.
- */
-STITCH_SUPPORT_API stitch_support_v1_update* MONGO_API_CALL
-stitch_support_v1_update_create(stitch_support_v1_lib* lib,
- const char* updateBSON,
- const char* arrayFiltersBSON,
- stitch_support_v1_matcher* matcher,
- stitch_support_v1_collator* collator,
- stitch_support_v1_status* status);
-
-/**
- * Destroys a valid stitch_support_v1_update object.
- *
- * This function does not destroy the collator associated with the destroyed update. When
- * destroying a update and its associated collator together, it is safe to destroy them in either
- * order. Although a update is no longer valid once its associated collator has been destroyed, it
- * is still safe to call this destroy function on the update.
- *
- * 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_update_destroy(stitch_support_v1_update* const update);
-
-/**
- * Apply an update to an input document, writing the resulting BSON to the 'output' buffer. Returns
- * a pointer to the output buffer on success or NULL on error. The caller is responsible for
- * destroying the result buffer with free().
- *
- * If the update includes a positional ($) operator, the caller should verify before applying it
- * that the associated matcher matches the input document. A non-matching input document will
- * trigger an assertion failure.
- */
-STITCH_SUPPORT_API char* MONGO_API_CALL
-stitch_support_v1_update_apply(stitch_support_v1_update* const update,
- const char* documentBSON,
- stitch_support_v1_update_details* update_details,
- stitch_support_v1_status* status);
-
-/**
* Creates a stitch_support_v1_lib object, which stores context for the Stitch Support library. A
* process should only ever have one stitch_support_v1_lib instance.
*
@@ -415,6 +320,201 @@ stitch_support_v1_check_match(stitch_support_v1_matcher* matcher,
bool* isMatch,
stitch_support_v1_status* status);
+/**
+ * A projection object is used to apply a projection to a BSON document which may include or exclude
+ * particular fields (e.g.: {_id: 0, a: 1}) or produce new columns through an expression made of
+ * projection operators.
+ *
+ * A projection requires a matcher if it uses the positional ($) operator. This matcher is used to
+ * determine which array element matches the specified position. The matcher is optional and unused
+ * when there is no positional operator.
+ *
+ * A projection can optionally use a collator for evaluating $elemMatch operators. Projection
+ * objects without collators will use the default collation for $elemMatch, even if their matcher
+ * has a collator object. Multiple matcher, projection, and update objects can share the same
+ * collation object.
+ */
+typedef struct stitch_support_v1_projection stitch_support_v1_projection;
+
+/**
+ * Creates a stitch_support_v1_projection object, which is used to apply a projection to a BSON
+ * document. The projection specification is also represented as a BSON document, which is passed in
+ * the 'specBSON' argument. The syntax used for projection is the same as a MongoDB "find" command
+ * (i.e., not an aggregation $project stage).
+ *
+ * This function will fail if the projection specification is invalid, returning NULL and populating
+ * 'status' with information about the error.
+ *
+ * If the projection specification includes a positional ($) operator, then the caller must pass a
+ * mongo_embedded_v1_matcher. The 'matcher' argument is unnecessary if the specification has no
+ * positional operator and it can be NULL.
+ *
+ * The 'collator' argument, a pointer to a stitch_support_v1_collator, will cause the projection to
+ * use the given collator if provided, The pointer can be NULL to cause the projection to use no
+ * collator.
+ *
+ * The newly created projection object does _not_ take ownership of its 'matcher' or 'collator'
+ * objects. The client is responsible for ensuring that the matcher and collator continue to exist
+ * for the lifetime of the projection and for ultimately destroying all three of the projection,
+ * matcher and collator.
+ */
+STITCH_SUPPORT_API stitch_support_v1_projection* MONGO_API_CALL
+stitch_support_v1_projection_create(stitch_support_v1_lib* lib,
+ const char* specBSON,
+ stitch_support_v1_matcher* matcher,
+ stitch_support_v1_collator* collator,
+ stitch_support_v1_status* status);
+
+/**
+ * Destroys a valid stitch_support_v1_projection object.
+ *
+ * This function does not destroy the collator or matcher associated with the destroyed projection.
+ * When destroying a projection and its associated collator and/or matcher together, it is safe to
+ * destroy them in any order. Although a projection is no longer valid once its associated collator
+ * or matcher has been destroyed, it is still safe to call this destroy function on the projection.
+ *
+ * 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_projection_destroy(stitch_support_v1_projection* const projection);
+
+/**
+ * Apply a projection to an input document, writing the resulting BSON to a newly allocated 'output'
+ * buffer. Returns a pointer to the output buffer on success or NULL on error. 'status' is populated
+ * in the error case. The caller is responsible for destroying the result buffer with
+ * stitch_support_v1_bson_free().
+ *
+ * If the projection includes a positional ($) operator, the caller should verify before applying it
+ * that the associated matcher matches the input document. A non-matching input document will
+ * trigger an assertion failure.
+ */
+STITCH_SUPPORT_API char* MONGO_API_CALL
+stitch_support_v1_projection_apply(stitch_support_v1_projection* const projection,
+ const char* documentBSON,
+ stitch_support_v1_status* status);
+
+/**
+ * An update details object stores the list of paths modified by a call to
+ * stitch_support_v1_update_apply().
+ */
+typedef struct stitch_support_v1_update_details stitch_support_v1_update_details;
+
+/**
+ * Create an "update details" object to pass to stitch_support_v1_update_apply(), which will
+ * populate the update details with a list of paths modified by the update.
+ *
+ * Clients can reuse the same update details object for multiple calls to
+ * stitch_support_v1_update_apply().
+ */
+STITCH_SUPPORT_API stitch_support_v1_update_details* MONGO_API_CALL
+stitch_support_v1_update_details_create(void);
+
+/**
+ * Destroys an update details object.
+ *
+ * This function is not thread safe, and it must not execute concurrently with any other function
+ * that accesses the update details object being destroyed.
+ *
+ * This function does not report failures.
+ */
+STITCH_SUPPORT_API void MONGO_API_CALL
+stitch_support_v1_update_details_destroy(stitch_support_v1_update_details* update_details);
+
+/**
+ * The number of modified paths in an update details object. Always call this function to ensure an
+ * index is in bounds before calling stitch_support_v1_update_details_path().
+ */
+STITCH_SUPPORT_API size_t MONGO_API_CALL stitch_support_v1_update_details_num_modified_paths(
+ stitch_support_v1_update_details* update_details);
+
+/**
+ * Return a dotted-path string from the given index of the modified paths in the update details
+ * object.
+ */
+STITCH_SUPPORT_API const char* MONGO_API_CALL stitch_support_v1_update_details_path(
+ stitch_support_v1_update_details* update_details, size_t path_index);
+
+/**
+ * An update object used to apply an update to a BSON document, which may modify particular
+ * fields (e.g.: {$set: {a: 1}}) or replace the entire document with a new one.
+ */
+typedef struct stitch_support_v1_update stitch_support_v1_update;
+
+/**
+ * Creates a stitch_support_v1_update object by parsing the update expression provided in the
+ * 'updateBSON' argument.
+ *
+ * The optional 'arrayFiltersBSON' argument must be a BSON array where each element of the array
+ * represents an array filter with the field name storing the placeholder name and the value storing
+ * the assoicated match expression. This argument is permitted to be NULL in the case where there
+ * are no array filters. If 'updateBSON' contains a placeholder ($[*]) that does not have a
+ * corresponding 'arrayFiltersBSON' entry this function will fail to parse.
+ *
+ * This function will fail if either 'updateBSON' or 'arrayFiltersBSON fails to parse, returning
+ * NULL and populating 'status' with information about the error.
+ *
+ * The caller can optionally provide a collator, which is used when evaluating arrayFilters match
+ * expressions. The 'collator' parameter must match the collator in 'matcher'. A mismatch will raise
+ * an invariant violation if 'matcher' is non-NULL. Multiple matcher, projection, and update objects
+ * can share the same collation object.
+ *
+ * The newly created update object does _not_ take ownership of its 'matcher' or 'collator'
+ * objects. The client is responsible for ensuring that the matcher and collator continue to exist
+ * for the lifetime of the update and for ultimately destroying all three of the update, matcher,
+ * and collator.
+ */
+STITCH_SUPPORT_API stitch_support_v1_update* MONGO_API_CALL
+stitch_support_v1_update_create(stitch_support_v1_lib* lib,
+ const char* updateBSON,
+ const char* arrayFiltersBSON,
+ stitch_support_v1_matcher* matcher,
+ stitch_support_v1_collator* collator,
+ stitch_support_v1_status* status);
+
+/**
+ * Destroys a valid stitch_support_v1_update object.
+ *
+ * This function does not destroy the collator associated with the destroyed update. When
+ * destroying an update and its associated collator together, it is safe to destroy them in either
+ * order. Although an update is no longer valid once its associated collator has been destroyed, it
+ * is still safe to call this destroy function on the update.
+ *
+ * This function is not thread safe, and it must not execute concurrently with any other function
+ * that accesses the update object being destroyed.
+ *
+ * This function does not report failures.
+ */
+STITCH_SUPPORT_API void MONGO_API_CALL
+stitch_support_v1_update_destroy(stitch_support_v1_update* const update);
+
+/**
+ * Apply an update to an input document writing the resulting BSON to a newly allocated output
+ * buffer. Returns a pointer to the output buffer on success or NULL on error. 'status' is populated
+ * in the error case. The caller is responsible for destroying the result buffer with
+ * stitch_support_v1_bson_free().
+ *
+ * If the update includes a positional ($) operator, the caller should verify before applying it
+ * that the associated matcher matches the input document. A non-matching input document will
+ * trigger an assertion failure.
+ */
+STITCH_SUPPORT_API char* MONGO_API_CALL
+stitch_support_v1_update_apply(stitch_support_v1_update* const update,
+ const char* documentBSON,
+ stitch_support_v1_update_details* update_details,
+ stitch_support_v1_status* status);
+
+/**
+ * Free the memory of a BSON buffer returned by stitch_support_v1_projection_apply() or
+ * stitch_support_v1_update_apply(). This function can be safely called on a NULL pointer.
+ *
+ * This function can be called at any time to deallocate a BSON buffer and will not invalidate any
+ * library object.
+ */
+STITCH_SUPPORT_API void MONGO_API_CALL stitch_support_v1_bson_free(char* bson);
+
#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 ba43a9267c1..456a080799c 100644
--- a/src/mongo/embedded/stitch_support/stitch_support_test.cpp
+++ b/src/mongo/embedded/stitch_support/stitch_support_test.cpp
@@ -28,6 +28,7 @@
* it in the license file.
*/
+#include <algorithm>
#include <string>
#include <utility>
@@ -43,6 +44,7 @@ namespace {
using mongo::fromjson;
using mongo::makeGuard;
using mongo::ScopeGuard;
+using mongo::tojson;
class StitchSupportTest : public mongo::unittest::Test {
protected:
@@ -75,33 +77,104 @@ protected:
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;
+ ON_BLOCK_EXIT([matcher] { stitch_support_v1_matcher_destroy(matcher); });
+ return std::all_of(
+ documentsJSON.begin(), documentsJSON.end(), [=](const char* documentJSON) {
+ bool isMatch;
+ stitch_support_v1_check_match(
+ matcher, fromjson(documentJSON).objdata(), &isMatch, nullptr);
+ 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 matchStatus = stitch_support_v1_status_create();
+ ON_BLOCK_EXIT([matchStatus] { stitch_support_v1_status_destroy(matchStatus); });
auto matcher = stitch_support_v1_matcher_create(
- lib, fromjson(filterJSON).objdata(), collator, match_status);
- ASSERT(!matcher);
+ lib, fromjson(filterJSON).objdata(), collator, matchStatus);
+ if (matcher) {
+ stitch_support_v1_matcher_destroy(matcher);
+ FAIL("Expected stich_support_v1_matcher_create to fail");
+ }
ASSERT_EQ(STITCH_SUPPORT_V1_ERROR_EXCEPTION,
- stitch_support_v1_status_get_error(match_status));
+ stitch_support_v1_status_get_error(matchStatus));
// 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);
+ ASSERT_NE(0, stitch_support_v1_status_get_code(matchStatus));
+ std::string explanation(stitch_support_v1_status_get_explanation(matchStatus));
return explanation;
}
+ auto checkProjection(const char* specJSON,
+ std::vector<const char*> documentsJSON,
+ const char* filterJSON = nullptr,
+ stitch_support_v1_collator* collator = nullptr,
+ bool denyProjectionCollator = false) {
+ stitch_support_v1_matcher* matcher = nullptr;
+ if (filterJSON) {
+ matcher = stitch_support_v1_matcher_create(
+ lib, fromjson(filterJSON).objdata(), collator, nullptr);
+ ASSERT(matcher);
+ }
+ ON_BLOCK_EXIT([matcher] { stitch_support_v1_matcher_destroy(matcher); });
+
+ auto projection =
+ stitch_support_v1_projection_create(lib,
+ fromjson(specJSON).objdata(),
+ matcher,
+ denyProjectionCollator ? nullptr : collator,
+ nullptr);
+ ASSERT(projection);
+ ON_BLOCK_EXIT([projection] { stitch_support_v1_projection_destroy(projection); });
+
+ std::vector<std::string> results;
+ std::transform(documentsJSON.begin(),
+ documentsJSON.end(),
+ std::back_inserter(results),
+ [=](const char* documentJSON) {
+ auto bson = stitch_support_v1_projection_apply(
+ projection, fromjson(documentJSON).objdata(), nullptr);
+ auto result = tojson(mongo::BSONObj(bson));
+ stitch_support_v1_bson_free(bson);
+ return result;
+ });
+
+ return results;
+ }
+
+ auto checkProjectionStatus(const char* specJSON,
+ const char* documentJSON,
+ const char* filterJSON = nullptr,
+ stitch_support_v1_collator* collator = nullptr) {
+ auto projectionStatus = stitch_support_v1_status_create();
+ ON_BLOCK_EXIT([projectionStatus] { stitch_support_v1_status_destroy(projectionStatus); });
+
+ stitch_support_v1_matcher* matcher = nullptr;
+ if (filterJSON) {
+ matcher = stitch_support_v1_matcher_create(
+ lib, fromjson(filterJSON).objdata(), collator, nullptr);
+ ASSERT(matcher);
+ }
+ ON_BLOCK_EXIT([matcher] { stitch_support_v1_matcher_destroy(matcher); });
+
+ auto projection = stitch_support_v1_projection_create(
+ lib, fromjson(specJSON).objdata(), matcher, collator, projectionStatus);
+ if (projection) {
+ stitch_support_v1_projection_destroy(projection);
+ FAIL("Expected stich_support_v1_projection_create to fail");
+ }
+
+ ASSERT_EQ(STITCH_SUPPORT_V1_ERROR_EXCEPTION,
+ stitch_support_v1_status_get_error(projectionStatus));
+ // 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(projectionStatus));
+
+ return std::string(stitch_support_v1_status_get_explanation(projectionStatus));
+ }
+
void checkUpdate(const char* expr,
const char* document,
mongo::BSONObj expectedResult,
@@ -139,11 +212,56 @@ protected:
<< stitch_support_v1_status_get_error(status) << ":"
<< stitch_support_v1_status_get_explanation(status);
ASSERT(updateResult);
- ON_BLOCK_EXIT([updateResult] { free(updateResult); });
+ ON_BLOCK_EXIT([updateResult] { stitch_support_v1_bson_free(updateResult); });
ASSERT_BSONOBJ_EQ(mongo::BSONObj(updateResult), expectedResult);
}
+ auto checkUpdateStatus(const char* expr,
+ const char* document,
+ const char* match = nullptr,
+ const char* arrayFilters = nullptr,
+ const char* collatorObj = nullptr) {
+ auto updateStatus = stitch_support_v1_status_create();
+ ON_BLOCK_EXIT([updateStatus] { stitch_support_v1_status_destroy(updateStatus); });
+
+ stitch_support_v1_collator* collator = nullptr;
+ if (collatorObj) {
+ collator =
+ stitch_support_v1_collator_create(lib, fromjson(collatorObj).objdata(), nullptr);
+ }
+ ON_BLOCK_EXIT([collator] { stitch_support_v1_collator_destroy(collator); });
+
+ stitch_support_v1_matcher* matcher = nullptr;
+ if (match) {
+ matcher =
+ stitch_support_v1_matcher_create(lib, fromjson(match).objdata(), collator, nullptr);
+ ASSERT(matcher);
+ }
+ ON_BLOCK_EXIT([matcher] { stitch_support_v1_matcher_destroy(matcher); });
+
+ stitch_support_v1_update* update = stitch_support_v1_update_create(
+ lib,
+ fromjson(expr).objdata(),
+ arrayFilters ? fromjson(arrayFilters).objdata() : nullptr,
+ matcher,
+ collator,
+ updateStatus);
+ if (!update) {
+ ASSERT_EQ(STITCH_SUPPORT_V1_ERROR_EXCEPTION,
+ stitch_support_v1_status_get_error(updateStatus));
+ // 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(updateStatus));
+ } else {
+ ON_BLOCK_EXIT([update] { stitch_support_v1_update_destroy(update); });
+ char* updateResult = stitch_support_v1_update_apply(
+ update, fromjson(document).objdata(), updateDetails, updateStatus);
+ ASSERT_NE(0, stitch_support_v1_status_get_code(updateStatus));
+ ASSERT(!updateResult);
+ }
+ return std::string(stitch_support_v1_status_get_explanation(updateStatus));
+ }
+
const std::string getModifiedPaths() {
ASSERT(updateDetails);
@@ -213,8 +331,53 @@ TEST_F(StitchSupportTest, CheckMatchWorksWithStatus) {
TEST_F(StitchSupportTest, CheckMatchWorksWithCollation) {
auto collator = stitch_support_v1_collator_create(
lib, fromjson("{locale: 'en', strength: 2}").objdata(), nullptr);
+ ON_BLOCK_EXIT([collator] { stitch_support_v1_collator_destroy(collator); });
ASSERT_TRUE(checkMatch("{a: 'word'}", {"{a: 'WORD', b: 'other'}"}, collator));
- stitch_support_v1_collator_destroy(collator);
+}
+
+TEST_F(StitchSupportTest, CheckProjectionWorkDefaults) {
+ auto results =
+ checkProjection("{a: 1}", {"{_id: 1, a: 100, b: 200}", "{_id: 1, a: 200, b: 300}"});
+ ASSERT_EQ("{ \"_id\" : 1, \"a\" : 100 }", results[0]);
+ ASSERT_EQ("{ \"_id\" : 1, \"a\" : 200 }", results[1]);
+ results = checkProjection("{'a.$.c': 1}",
+ {"{_id: 1, a: [{b: 2, c: 100}, {b: 1, c: 200}]}",
+ "{_id: 1, a: [{b: 1, c: 100, d: 45}, {b: 2, c: 200}]}"},
+ "{'a.b': 1}");
+ ASSERT_EQ("{ \"_id\" : 1, \"a\" : [ { \"b\" : 1, \"c\" : 200 } ] }", results[0]);
+ ASSERT_EQ("{ \"_id\" : 1, \"a\" : [ { \"b\" : 1, \"c\" : 100, \"d\" : 45 } ] }", results[1]);
+ ASSERT_EQ(
+ "{ \"a\" : [ { \"b\" : 2, \"c\" : 2 } ] }",
+ checkProjection("{a: {$elemMatch: {b: 2}}}", {"{a: [{b: 1, c: 1}, {b: 2, c: 2}]}"})[0]);
+ ASSERT_EQ("{ \"a\" : [ 2, 3 ] }",
+ checkProjection("{a: {$slice: [1, 2]}}", {"{a: [1, 2, 3, 4]}"})[0]);
+}
+
+TEST_F(StitchSupportTest, CheckProjectionProducesExpectedStatus) {
+ ASSERT_EQ(
+ "Projections with a positional operator require a matcher",
+ checkProjectionStatus("{'a.$.c': 1}", "{_id: 1, a: [{b: 2, c: 100}, {b: 1, c: 200}]}"));
+ ASSERT_EQ(
+ "$textScore, $sortKey, $recordId, $geoNear and $returnKey are not allowed in this context",
+ checkProjectionStatus("{a: {$meta: 'textScore'}}", "{_id: 1, a: 100, b: 200}"));
+}
+
+TEST_F(StitchSupportTest, CheckProjectionCollatesRespectfully) {
+ auto collator = stitch_support_v1_collator_create(
+ lib, fromjson("{locale: 'en', strength: 2}").objdata(), nullptr);
+ ON_BLOCK_EXIT([collator] { stitch_support_v1_collator_destroy(collator); });
+ ASSERT_EQ("{ \"_id\" : 1, \"a\" : [ \"mixEdCaSe\" ] }",
+ checkProjection("{a: {$elemMatch: {$eq: 'MiXedcAse'}}}",
+ {"{_id: 1, a: ['lowercase', 'mixEdCaSe', 'UPPERCASE']}"},
+ nullptr,
+ collator)[0]);
+ // Ignore a matcher's collator.
+ ASSERT_EQ("{ \"_id\" : 1 }",
+ checkProjection("{a: {$elemMatch: {$eq: 'MiXedcAse'}}}",
+ {"{_id: 1, a: ['lowercase', 'mixEdCaSe', 'UPPERCASE']}"},
+ "{_id: 1}",
+ collator,
+ true)[0]);
}
TEST_F(StitchSupportTest, TestUpdateSingleElement) {
@@ -295,6 +458,19 @@ TEST_F(StitchSupportTest, TestUpdateRespectsTheCollation) {
ASSERT_EQ(getModifiedPaths(), "[a]");
}
+TEST_F(StitchSupportTest, TestUpdateProducesProperStatus) {
+ ASSERT_EQ("Unknown modifier: $bogus", checkUpdateStatus("{$bogus: {a: 2}}", "{a: 1}"));
+ ASSERT_EQ("Updating the path 'a' would create a conflict at 'a'",
+ checkUpdateStatus("{$set: {a: 2, a: 3}}", "{a: 1}"));
+ ASSERT_EQ("No array filter found for identifier 'i' in path 'a.$[i]'",
+ checkUpdateStatus("{$set: {'a.$[i]': 3}}", "{a: [1, 2]}"));
+ ASSERT_EQ("No array filter found for identifier 'i' in path 'a.$[i]'",
+ checkUpdateStatus("{$set: {'a.$[i]': 3}}", "{a: [1, 2]}", nullptr, "[{'j': 2}]"));
+ ASSERT_EQ("Update created a conflict at 'a.0'",
+ checkUpdateStatus(
+ "{$set: {'a.$[i]': 2, 'a.$[j]': 3}}", "{a: [0]}", nullptr, " [{i: 0}, {j:0}]"));
+}
+
} // namespace
// Define main function as an entry to these tests.