From 3929480271b3e2936a6c8538f0f6aea3c30707f5 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 16 Apr 2025 03:13:31 -0400 Subject: [PATCH 1/2] PHPC-2478: Implement API for bulkWrite command (#1790) * PHPC-2495, PHPC-2490, PHPC-2491, PHPC-2492: BulkWriteCommand ctor and ops * PHPC-2494: BulkWriteCommandResult and BulkWriteCommandException It was possible to reuse WriteConcernError and WriteError with slight changes to their init functions. * Include missing header in WriteResult.h This was not necessary for compilation, but it makes the header internally consistent. * PHPC-2493: Manager and Server::executeBulkWriteCommand() * PHPC-2494: Handle null error fields in BulkWriteCommandResult Always return arrays for writeErrors and writeConcernErrors * PHPC-2494: Return writeErrors as an associative array * PHPC-2493: Revise error handling for mongoc_bulkwrite_execute * Consistent WriteError and WriteConcernError parsing for bulk write results * BulkWriteCommand tests * clang-format * Remove BulkWriteCommandResult::getServer() Per CDRIVER-5843, libmongoc does not consistently populate this field. It also isn't required by the spec, so omit it for now. * Check for _id extraction before appending insert * Regenerate BulkWriteCommandException arginfo * PHPC-2493: Relocate writeConcern option to executeBulkWriteCommand() * Parse sort option for replaceOne and updateOne * BulkWriteCommand::replaceOne() need not accept root-level arrays * Preserve empty documents for verbose results and error reply Result maps should only be null if verboseResults=false. Additionally, the error reply document can default to an empty document on error (as it is in libmongoc). * PHPC-2494: Relocate error fields to BulkWriteCommandException Revise the stubs to reflect that executeBulkWriteCommand() always returns a BulkWriteCommandResult. If the result is unacknowledged, that is reported via isAcknowledged() and other methods can throw, which is consistent with WriteResult for the legacy bulk write API. If the result is empty on error (i.e. no writes were successful), BulkWriteCommandException::$partialResult is left unset. * Throw BulkWriteCommandException directly for server errors This also ensures that BulkWriteCommandException uses the original server-side error code and message (if available) for top-level errors. * Check for empty error_reply instead of NULL mongoc_bulkwriteexception_errorreply() always returns an initialized document, but it may be empty. This ensures that an InvalidArgumentException is not unnecessarily proxied behind a BulkWriteCommandException. --- config.m4 | 3 + config.w32 | 4 +- php_phongo.c | 3 + src/MongoDB/BulkWriteCommand.c | 846 ++++++++++++++++++ src/MongoDB/BulkWriteCommand.h | 26 + src/MongoDB/BulkWriteCommand.stub.php | 28 + src/MongoDB/BulkWriteCommandResult.c | 284 ++++++ src/MongoDB/BulkWriteCommandResult.h | 28 + src/MongoDB/BulkWriteCommandResult.stub.php | 32 + src/MongoDB/BulkWriteCommandResult_arginfo.h | 64 ++ src/MongoDB/BulkWriteCommand_arginfo.h | 73 ++ .../Exception/BulkWriteCommandException.c | 191 ++++ .../Exception/BulkWriteCommandException.h | 26 + .../BulkWriteCommandException.stub.php | 27 + .../BulkWriteCommandException_arginfo.h | 65 ++ src/MongoDB/Manager.c | 36 + src/MongoDB/Manager.stub.php | 2 + src/MongoDB/Manager_arginfo.h | 9 +- src/MongoDB/Server.c | 24 + src/MongoDB/Server.stub.php | 2 + src/MongoDB/Server_arginfo.h | 9 +- src/MongoDB/WriteConcernError.c | 22 +- src/MongoDB/WriteConcernError.h | 2 +- src/MongoDB/WriteError.c | 29 +- src/MongoDB/WriteError.h | 3 +- src/MongoDB/WriteResult.c | 21 +- src/MongoDB/WriteResult.h | 2 + src/phongo_classes.h | 18 + src/phongo_error.c | 2 +- src/phongo_execute.c | 130 +++ src/phongo_execute.h | 3 + src/phongo_structs.h | 30 + ...and-ctor-bypassDocumentValidation-001.phpt | 56 ++ ...and-ctor-bypassDocumentValidation-002.phpt | 106 +++ .../bulkwritecommand-ctor-comment-001.phpt | 61 ++ ...lkwritecommand-ctor-comment_error-001.phpt | 27 + .../bulkwritecommand-ctor-let-001.phpt | 60 ++ .../bulkwritecommand-ctor-let_error-001.phpt | 34 + .../bulkwritecommand-ctor-ordered-001.phpt | 67 ++ .../bulkwritecommand-ctor-ordered-002.phpt | 67 ++ ...kwritecommand-ctor-verboseresults-001.phpt | 98 ++ ...kwritecommand-ctor-verboseresults-002.phpt | 48 + .../bulkwritecommand-debug-001.phpt | 39 + .../bulkwritecommand-debug-002.phpt | 53 ++ .../bulkwritecommand-insertone-001.phpt | 25 + .../bulkwritecommandresult-debug-001.phpt | 46 + ...writecommandresult-isAcknowledged-001.phpt | 76 ++ .../manager-executeBulkWriteCommand-001.phpt | 179 ++++ ...ger-executeBulkWriteCommand_error-001.phpt | 31 + ...ger-executeBulkWriteCommand_error-002.phpt | 30 + .../server-executeBulkWriteCommand-001.phpt | 180 ++++ ...ver-executeBulkWriteCommand_error-001.phpt | 32 + ...ver-executeBulkWriteCommand_error-002.phpt | 31 + 53 files changed, 3365 insertions(+), 25 deletions(-) create mode 100644 src/MongoDB/BulkWriteCommand.c create mode 100644 src/MongoDB/BulkWriteCommand.h create mode 100644 src/MongoDB/BulkWriteCommand.stub.php create mode 100644 src/MongoDB/BulkWriteCommandResult.c create mode 100644 src/MongoDB/BulkWriteCommandResult.h create mode 100644 src/MongoDB/BulkWriteCommandResult.stub.php create mode 100644 src/MongoDB/BulkWriteCommandResult_arginfo.h create mode 100644 src/MongoDB/BulkWriteCommand_arginfo.h create mode 100644 src/MongoDB/Exception/BulkWriteCommandException.c create mode 100644 src/MongoDB/Exception/BulkWriteCommandException.h create mode 100644 src/MongoDB/Exception/BulkWriteCommandException.stub.php create mode 100644 src/MongoDB/Exception/BulkWriteCommandException_arginfo.h create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-comment-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-comment_error-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-let_error-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-debug-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-debug-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommandresult-debug-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommandresult-isAcknowledged-001.phpt create mode 100644 tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt create mode 100644 tests/bulkwritecommand/manager-executeBulkWriteCommand_error-001.phpt create mode 100644 tests/bulkwritecommand/manager-executeBulkWriteCommand_error-002.phpt create mode 100644 tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt create mode 100644 tests/bulkwritecommand/server-executeBulkWriteCommand_error-001.phpt create mode 100644 tests/bulkwritecommand/server-executeBulkWriteCommand_error-002.phpt diff --git a/config.m4 b/config.m4 index 1e2afabf0..834ce559f 100644 --- a/config.m4 +++ b/config.m4 @@ -167,6 +167,8 @@ if test "$PHP_MONGODB" != "no"; then src/BSON/UTCDateTime.c \ src/BSON/UTCDateTimeInterface.c \ src/MongoDB/BulkWrite.c \ + src/MongoDB/BulkWriteCommand.c \ + src/MongoDB/BulkWriteCommandResult.c \ src/MongoDB/ClientEncryption.c \ src/MongoDB/Command.c \ src/MongoDB/Cursor.c \ @@ -186,6 +188,7 @@ if test "$PHP_MONGODB" != "no"; then src/MongoDB/WriteResult.c \ src/MongoDB/Exception/AuthenticationException.c \ src/MongoDB/Exception/BulkWriteException.c \ + src/MongoDB/Exception/BulkWriteCommandException.c \ src/MongoDB/Exception/CommandException.c \ src/MongoDB/Exception/ConnectionException.c \ src/MongoDB/Exception/ConnectionTimeoutException.c \ diff --git a/config.w32 b/config.w32 index 22979021a..fb1f23639 100644 --- a/config.w32 +++ b/config.w32 @@ -116,8 +116,8 @@ if (PHP_MONGODB != "no") { EXTENSION("mongodb", "php_phongo.c", null, PHP_MONGODB_CFLAGS); MONGODB_ADD_SOURCES("/src", "phongo_apm.c phongo_atomic.c phongo_bson.c phongo_bson_encode.c phongo_client.c phongo_compat.c phongo_error.c phongo_execute.c phongo_ini.c phongo_log.c phongo_util.c"); MONGODB_ADD_SOURCES("/src/BSON", "Binary.c BinaryInterface.c Document.c Iterator.c DBPointer.c Decimal128.c Decimal128Interface.c Int64.c Javascript.c JavascriptInterface.c MaxKey.c MaxKeyInterface.c MinKey.c MinKeyInterface.c ObjectId.c ObjectIdInterface.c PackedArray.c Persistable.c Regex.c RegexInterface.c Serializable.c Symbol.c Timestamp.c TimestampInterface.c Type.c Undefined.c Unserializable.c UTCDateTime.c UTCDateTimeInterface.c"); - MONGODB_ADD_SOURCES("/src/MongoDB", "BulkWrite.c ClientEncryption.c Command.c Cursor.c CursorInterface.c Manager.c Query.c ReadConcern.c ReadPreference.c Server.c ServerApi.c ServerDescription.c Session.c TopologyDescription.c WriteConcern.c WriteConcernError.c WriteError.c WriteResult.c"); - MONGODB_ADD_SOURCES("/src/MongoDB/Exception", "AuthenticationException.c BulkWriteException.c CommandException.c ConnectionException.c ConnectionTimeoutException.c EncryptionException.c Exception.c ExecutionTimeoutException.c InvalidArgumentException.c LogicException.c RuntimeException.c ServerException.c UnexpectedValueException.c"); + MONGODB_ADD_SOURCES("/src/MongoDB", "BulkWrite.c BulkWriteCommand.c BulkWriteCommandResult.c ClientEncryption.c Command.c Cursor.c CursorInterface.c Manager.c Query.c ReadConcern.c ReadPreference.c Server.c ServerApi.c ServerDescription.c Session.c TopologyDescription.c WriteConcern.c WriteConcernError.c WriteError.c WriteResult.c"); + MONGODB_ADD_SOURCES("/src/MongoDB/Exception", "AuthenticationException.c BulkWriteException.c BulkWriteCommandException.c CommandException.c ConnectionException.c ConnectionTimeoutException.c EncryptionException.c Exception.c ExecutionTimeoutException.c InvalidArgumentException.c LogicException.c RuntimeException.c ServerException.c UnexpectedValueException.c"); MONGODB_ADD_SOURCES("/src/MongoDB/Monitoring", "CommandFailedEvent.c CommandStartedEvent.c CommandSubscriber.c CommandSucceededEvent.c LogSubscriber.c SDAMSubscriber.c Subscriber.c ServerChangedEvent.c ServerClosedEvent.c ServerHeartbeatFailedEvent.c ServerHeartbeatStartedEvent.c ServerHeartbeatSucceededEvent.c ServerOpeningEvent.c TopologyChangedEvent.c TopologyClosedEvent.c TopologyOpeningEvent.c functions.c"); MONGODB_ADD_SOURCES("/src/libmongoc/src/common/src", PHP_MONGODB_COMMON_SOURCES); MONGODB_ADD_SOURCES("/src/libmongoc/src/libbson/src/bson", PHP_MONGODB_BSON_SOURCES); diff --git a/php_phongo.c b/php_phongo.c index f947d3942..98a0b6d0e 100644 --- a/php_phongo.c +++ b/php_phongo.c @@ -252,6 +252,8 @@ PHP_MINIT_FUNCTION(mongodb) /* {{{ */ php_phongo_cursor_interface_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_bulkwrite_init_ce(INIT_FUNC_ARGS_PASSTHRU); + php_phongo_bulkwritecommand_init_ce(INIT_FUNC_ARGS_PASSTHRU); + php_phongo_bulkwritecommandresult_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_clientencryption_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_command_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_cursor_init_ce(INIT_FUNC_ARGS_PASSTHRU); @@ -277,6 +279,7 @@ PHP_MINIT_FUNCTION(mongodb) /* {{{ */ php_phongo_authenticationexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_bulkwriteexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); + php_phongo_bulkwritecommandexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_commandexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_connectiontimeoutexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_encryptionexception_init_ce(INIT_FUNC_ARGS_PASSTHRU); diff --git a/src/MongoDB/BulkWriteCommand.c b/src/MongoDB/BulkWriteCommand.c new file mode 100644 index 000000000..22fc35778 --- /dev/null +++ b/src/MongoDB/BulkWriteCommand.c @@ -0,0 +1,846 @@ +/* + * Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bson/bson.h" +#include "mongoc/mongoc.h" + +#include +#include + +#include "php_array_api.h" + +#include "php_phongo.h" +#include "phongo_bson_encode.h" +#include "phongo_error.h" +#include "phongo_util.h" +#include "BulkWriteCommand_arginfo.h" + +#include "MongoDB/WriteConcern.h" + +#define PHONGO_BULKWRITECOMMAND_BYPASS_UNSET -1 + +zend_class_entry* php_phongo_bulkwritecommand_ce; + +/* Creates a mongoc_bulkwriteopts_t from internal options, which should be freed + * by the caller. */ +mongoc_bulkwriteopts_t* phongo_bwc_assemble_opts(php_phongo_bulkwritecommand_t* intern) +{ + mongoc_bulkwriteopts_t* opts = mongoc_bulkwriteopts_new(); + + if (intern->bypass != PHONGO_BULKWRITECOMMAND_BYPASS_UNSET) { + mongoc_bulkwriteopts_set_bypassdocumentvalidation(opts, intern->bypass); + } + + if (intern->comment) { + mongoc_bulkwriteopts_set_comment(opts, intern->comment); + } + + if (intern->let) { + mongoc_bulkwriteopts_set_let(opts, intern->let); + } + + mongoc_bulkwriteopts_set_ordered(opts, intern->ordered); + mongoc_bulkwriteopts_set_verboseresults(opts, intern->verbose); + + return opts; +} + +// TODO: Make this a common utility function to share with BulkWrite.c +/* Extracts the "_id" field of a BSON document into a return value. */ +static void phongo_bwc_extract_id(bson_t* doc, zval** return_value) +{ + zval* id = NULL; + php_phongo_bson_state state; + + PHONGO_BSON_INIT_STATE(state); + state.map.root.type = PHONGO_TYPEMAP_NATIVE_ARRAY; + + /* TODO: Instead of converting the entire document, iterate BSON to obtain + * the bson_value_t of the _id field and then use phongo_bson_value_to_zval + * or phongo_bson_value_to_zval_legacy to populate the return value. */ + if (!php_phongo_bson_to_zval_ex(doc, &state)) { + goto cleanup; + } + + id = php_array_fetchc(&state.zchild, "_id"); + + if (id) { + ZVAL_ZVAL(*return_value, id, 1, 0); + } + +cleanup: + zval_ptr_dtor(&state.zchild); +} + +// TODO: Make this a common utility function to share with BulkWrite.c +/* Returns whether the BSON array's keys are a sequence of integer strings + * starting with "0". */ +static inline bool phongo_bwc_bson_array_has_valid_keys(bson_t* array) +{ + bson_iter_t iter; + + if (bson_empty(array)) { + return true; + } + + if (bson_iter_init(&iter, array)) { + char key[12]; + int count = 0; + + while (bson_iter_next(&iter)) { + bson_snprintf(key, sizeof(key), "%d", count); + + if (0 != strcmp(key, bson_iter_key(&iter))) { + return false; + } + + count++; + } + } + + return true; +} + +/* Constructs a new BulkWriteCommand */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, __construct) +{ + php_phongo_bulkwritecommand_t* intern; + zval* zoptions = NULL; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(zoptions) + PHONGO_PARSE_PARAMETERS_END(); + + intern->bw = mongoc_bulkwrite_new(); + intern->bypass = PHONGO_BULKWRITECOMMAND_BYPASS_UNSET; + intern->comment = NULL; + intern->let = NULL; + intern->num_ops = 0; + intern->ordered = true; + intern->verbose = false; + + if (!zoptions) { + return; + } + + if (php_array_existsc(zoptions, "bypassDocumentValidation")) { + intern->bypass = php_array_fetchc_bool(zoptions, "bypassDocumentValidation") ? 1 : 0; + } + + if (php_array_existsc(zoptions, "comment")) { + zval* value = php_array_fetchc_deref(zoptions, "comment"); + + intern->comment = ecalloc(1, sizeof(bson_value_t)); + phongo_zval_to_bson_value(value, intern->comment); + + if (EG(exception)) { + return; + } + } + + if (php_array_existsc(zoptions, "let")) { + zval* value = php_array_fetchc_deref(zoptions, "let"); + + if (Z_TYPE_P(value) != IS_OBJECT && Z_TYPE_P(value) != IS_ARRAY) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"let\" option to be array or object, %s given", zend_get_type_by_const(Z_TYPE_P(value))); + return; + } + + intern->let = bson_new(); + php_phongo_zval_to_bson(value, PHONGO_BSON_NONE, intern->let, NULL); + + if (EG(exception)) { + return; + } + } + + if (php_array_existsc(zoptions, "ordered")) { + intern->ordered = php_array_fetchc_bool(zoptions, "ordered"); + } + + if (php_array_existsc(zoptions, "verboseResults")) { + intern->verbose = php_array_fetchc_bool(zoptions, "verboseResults"); + } +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, count) +{ + php_phongo_bulkwritecommand_t* intern; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + RETURN_LONG(intern->num_ops); +} + +static bool phongo_bwc_parse_hint(zval* zhint, bson_value_t* bhint) +{ + if (Z_TYPE_P(zhint) != IS_STRING && Z_TYPE_P(zhint) != IS_OBJECT && Z_TYPE_P(zhint) != IS_ARRAY) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"hint\" option to be string, array, or object, %s given", zend_get_type_by_const(Z_TYPE_P(zhint))); + return false; + } + + phongo_zval_to_bson_value(zhint, bhint); + + if (EG(exception)) { + return false; + } + + // Catch the edge case where the option yields a BSON array + if (bhint->value_type != BSON_TYPE_UTF8 && bhint->value_type != BSON_TYPE_DOCUMENT) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"hint\" option to yield string or document but got \"%s\"", php_phongo_bson_type_to_string(bhint->value_type)); + return false; + } + + return true; +} + +static bool phongo_bwc_parse_document(zval* zdocument, bson_t* bdocument, const char* key) +{ + if (Z_TYPE_P(zdocument) != IS_OBJECT && Z_TYPE_P(zdocument) != IS_ARRAY) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"%s\" option to be array or object, %s given", key, zend_get_type_by_const(Z_TYPE_P(zdocument))); + return false; + } + + php_phongo_zval_to_bson(zdocument, PHONGO_BSON_NONE, bdocument, NULL); + + if (EG(exception)) { + return false; + } + + return true; +} + +static bool phongo_bwc_parse_array(zval* zarray, bson_t* barray, const char* key) +{ + if (Z_TYPE_P(zarray) != IS_OBJECT && Z_TYPE_P(zarray) != IS_ARRAY) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"%s\" option to be array or object, %s given", key, zend_get_type_by_const(Z_TYPE_P(zarray))); + return false; + } + + // Explicitly allow MongoDB\BSON\PackedArray for array values + php_phongo_zval_to_bson(zarray, PHONGO_BSON_ALLOW_ROOT_ARRAY, barray, NULL); + + if (EG(exception)) { + return false; + } + + if (!phongo_bwc_bson_array_has_valid_keys(barray)) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"%s\" option to yield array but got non-sequential keys", key); + return false; + } + + return true; +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, deleteMany) +{ + php_phongo_bulkwritecommand_t* intern; + char* ns; + size_t ns_len; + zval* zfilter; + zval* zoptions = NULL; + bson_t bfilter = BSON_INITIALIZER; + mongoc_bulkwrite_deletemanyopts_t* opts = NULL; + bson_error_t error = { 0 }; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(ns, ns_len) + Z_PARAM_ARRAY_OR_OBJECT(zfilter) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(zoptions) + PHONGO_PARSE_PARAMETERS_END(); + + if (strlen(ns) != ns_len) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Namespace string should not contain null bytes"); + return; + } + + php_phongo_zval_to_bson(zfilter, PHONGO_BSON_NONE, &bfilter, NULL); + + if (EG(exception)) { + goto cleanup; + } + + if (zoptions) { + opts = mongoc_bulkwrite_deletemanyopts_new(); + + if (php_array_existsc(zoptions, "hint")) { + bson_value_t bhint = { 0 }; + + if (!phongo_bwc_parse_hint(php_array_fetchc_deref(zoptions, "hint"), &bhint)) { + bson_value_destroy(&bhint); + goto cleanup; + } + + mongoc_bulkwrite_deletemanyopts_set_hint(opts, &bhint); + bson_value_destroy(&bhint); + } + + if (php_array_existsc(zoptions, "collation")) { + bson_t bcollation = BSON_INITIALIZER; + + if (!phongo_bwc_parse_document(php_array_fetchc_deref(zoptions, "collation"), &bcollation, "collation")) { + bson_destroy(&bcollation); + goto cleanup; + } + + mongoc_bulkwrite_deletemanyopts_set_collation(opts, &bcollation); + bson_destroy(&bcollation); + } + } + + if (!mongoc_bulkwrite_append_deletemany(intern->bw, ns, &bfilter, opts, &error)) { + phongo_throw_exception_from_bson_error_t(&error); + goto cleanup; + } + + intern->num_ops++; + +cleanup: + bson_destroy(&bfilter); + mongoc_bulkwrite_deletemanyopts_destroy(opts); +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, deleteOne) +{ + php_phongo_bulkwritecommand_t* intern; + char* ns; + size_t ns_len; + zval* zfilter; + zval* zoptions = NULL; + bson_t bfilter = BSON_INITIALIZER; + mongoc_bulkwrite_deleteoneopts_t* opts = NULL; + bson_error_t error = { 0 }; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STRING(ns, ns_len) + Z_PARAM_ARRAY_OR_OBJECT(zfilter) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(zoptions) + PHONGO_PARSE_PARAMETERS_END(); + + if (strlen(ns) != ns_len) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Namespace string should not contain null bytes"); + return; + } + + php_phongo_zval_to_bson(zfilter, PHONGO_BSON_NONE, &bfilter, NULL); + + if (EG(exception)) { + goto cleanup; + } + + if (zoptions) { + opts = mongoc_bulkwrite_deleteoneopts_new(); + + if (php_array_existsc(zoptions, "hint")) { + bson_value_t bhint = { 0 }; + + if (!phongo_bwc_parse_hint(php_array_fetchc_deref(zoptions, "hint"), &bhint)) { + bson_value_destroy(&bhint); + goto cleanup; + } + + mongoc_bulkwrite_deleteoneopts_set_hint(opts, &bhint); + bson_value_destroy(&bhint); + } + + if (php_array_existsc(zoptions, "collation")) { + bson_t bcollation = BSON_INITIALIZER; + + if (!phongo_bwc_parse_document(php_array_fetchc_deref(zoptions, "collation"), &bcollation, "collation")) { + bson_destroy(&bcollation); + goto cleanup; + } + + mongoc_bulkwrite_deleteoneopts_set_collation(opts, &bcollation); + bson_destroy(&bcollation); + } + } + + if (!mongoc_bulkwrite_append_deleteone(intern->bw, ns, &bfilter, opts, &error)) { + phongo_throw_exception_from_bson_error_t(&error); + goto cleanup; + } + + intern->num_ops++; + +cleanup: + bson_destroy(&bfilter); + mongoc_bulkwrite_deleteoneopts_destroy(opts); +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, insertOne) +{ + php_phongo_bulkwritecommand_t* intern; + char* ns; + size_t ns_len; + zval* zdocument; + bson_t bdocument = BSON_INITIALIZER; + bson_t* bson_out = NULL; + bson_error_t error = { 0 }; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STRING(ns, ns_len) + Z_PARAM_ARRAY_OR_OBJECT(zdocument) + PHONGO_PARSE_PARAMETERS_END(); + + if (strlen(ns) != ns_len) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Namespace string should not contain null bytes"); + return; + } + + php_phongo_zval_to_bson(zdocument, (PHONGO_BSON_ADD_ID | PHONGO_BSON_RETURN_ID), &bdocument, &bson_out); + + if (EG(exception)) { + goto cleanup; + } + + if (!bson_out) { + phongo_throw_exception(PHONGO_ERROR_LOGIC, "php_phongo_zval_to_bson() did not return an _id. Please file a bug report."); + goto cleanup; + } + + if (!mongoc_bulkwrite_append_insertone(intern->bw, ns, &bdocument, NULL, &error)) { + phongo_throw_exception_from_bson_error_t(&error); + goto cleanup; + } + + intern->num_ops++; + + phongo_bwc_extract_id(bson_out, &return_value); + +cleanup: + bson_destroy(&bdocument); + bson_clear(&bson_out); +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, replaceOne) +{ + php_phongo_bulkwritecommand_t* intern; + char* ns; + size_t ns_len; + zval* zfilter; + zval* zreplacement; + zval* zoptions = NULL; + bson_t bfilter = BSON_INITIALIZER; + bson_t breplacement = BSON_INITIALIZER; + mongoc_bulkwrite_replaceoneopts_t* opts = NULL; + bson_error_t error = { 0 }; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_STRING(ns, ns_len) + Z_PARAM_ARRAY_OR_OBJECT(zfilter) + Z_PARAM_ARRAY_OR_OBJECT(zreplacement) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(zoptions) + PHONGO_PARSE_PARAMETERS_END(); + + if (strlen(ns) != ns_len) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Namespace string should not contain null bytes"); + return; + } + + php_phongo_zval_to_bson(zfilter, PHONGO_BSON_NONE, &bfilter, NULL); + + if (EG(exception)) { + goto cleanup; + } + + php_phongo_zval_to_bson(zreplacement, PHONGO_BSON_NONE, &breplacement, NULL); + + if (EG(exception)) { + goto cleanup; + } + + if (zoptions) { + opts = mongoc_bulkwrite_replaceoneopts_new(); + + if (php_array_existsc(zoptions, "collation")) { + bson_t bcollation = BSON_INITIALIZER; + + if (!phongo_bwc_parse_document(php_array_fetchc_deref(zoptions, "collation"), &bcollation, "collation")) { + bson_destroy(&bcollation); + goto cleanup; + } + + mongoc_bulkwrite_replaceoneopts_set_collation(opts, &bcollation); + bson_destroy(&bcollation); + } + + if (php_array_existsc(zoptions, "hint")) { + bson_value_t bhint = { 0 }; + + if (!phongo_bwc_parse_hint(php_array_fetchc_deref(zoptions, "hint"), &bhint)) { + bson_value_destroy(&bhint); + goto cleanup; + } + + mongoc_bulkwrite_replaceoneopts_set_hint(opts, &bhint); + bson_value_destroy(&bhint); + } + + if (php_array_existsc(zoptions, "sort")) { + bson_t bsort = BSON_INITIALIZER; + + if (!phongo_bwc_parse_document(php_array_fetchc_deref(zoptions, "sort"), &bsort, "sort")) { + bson_destroy(&bsort); + goto cleanup; + } + + mongoc_bulkwrite_replaceoneopts_set_sort(opts, &bsort); + bson_destroy(&bsort); + } + + if (php_array_existsc(zoptions, "upsert")) { + mongoc_bulkwrite_replaceoneopts_set_upsert(opts, php_array_fetchc_bool(zoptions, "upsert")); + } + } + + if (!mongoc_bulkwrite_append_replaceone(intern->bw, ns, &bfilter, &breplacement, opts, &error)) { + phongo_throw_exception_from_bson_error_t(&error); + goto cleanup; + } + + intern->num_ops++; + +cleanup: + bson_destroy(&bfilter); + bson_destroy(&breplacement); + mongoc_bulkwrite_replaceoneopts_destroy(opts); +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, updateMany) +{ + php_phongo_bulkwritecommand_t* intern; + char* ns; + size_t ns_len; + zval* zfilter; + zval* zupdate; + zval* zoptions = NULL; + bson_t bfilter = BSON_INITIALIZER; + bson_t bupdate = BSON_INITIALIZER; + mongoc_bulkwrite_updatemanyopts_t* opts = NULL; + bson_error_t error = { 0 }; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_STRING(ns, ns_len) + Z_PARAM_ARRAY_OR_OBJECT(zfilter) + Z_PARAM_ARRAY_OR_OBJECT(zupdate) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(zoptions) + PHONGO_PARSE_PARAMETERS_END(); + + if (strlen(ns) != ns_len) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Namespace string should not contain null bytes"); + return; + } + + php_phongo_zval_to_bson(zfilter, PHONGO_BSON_NONE, &bfilter, NULL); + + if (EG(exception)) { + goto cleanup; + } + + // Explicitly allow MongoDB\BSON\PackedArray for update pipelines + php_phongo_zval_to_bson(zupdate, PHONGO_BSON_ALLOW_ROOT_ARRAY, &bupdate, NULL); + + if (EG(exception)) { + goto cleanup; + } + + if (zoptions) { + opts = mongoc_bulkwrite_updatemanyopts_new(); + + if (php_array_existsc(zoptions, "arrayFilters")) { + bson_t barrayfilters = BSON_INITIALIZER; + + if (!phongo_bwc_parse_array(php_array_fetchc_deref(zoptions, "arrayFilters"), &barrayfilters, "arrayFilters")) { + bson_destroy(&barrayfilters); + goto cleanup; + } + + mongoc_bulkwrite_updatemanyopts_set_arrayfilters(opts, &barrayfilters); + bson_destroy(&barrayfilters); + } + + if (php_array_existsc(zoptions, "collation")) { + bson_t bcollation = BSON_INITIALIZER; + + if (!phongo_bwc_parse_document(php_array_fetchc_deref(zoptions, "collation"), &bcollation, "collation")) { + bson_destroy(&bcollation); + goto cleanup; + } + + mongoc_bulkwrite_updatemanyopts_set_collation(opts, &bcollation); + bson_destroy(&bcollation); + } + + if (php_array_existsc(zoptions, "hint")) { + bson_value_t bhint = { 0 }; + + if (!phongo_bwc_parse_hint(php_array_fetchc_deref(zoptions, "hint"), &bhint)) { + bson_value_destroy(&bhint); + goto cleanup; + } + + mongoc_bulkwrite_updatemanyopts_set_hint(opts, &bhint); + bson_value_destroy(&bhint); + } + + if (php_array_existsc(zoptions, "upsert")) { + mongoc_bulkwrite_updatemanyopts_set_upsert(opts, php_array_fetchc_bool(zoptions, "upsert")); + } + } + + if (!mongoc_bulkwrite_append_updatemany(intern->bw, ns, &bfilter, &bupdate, opts, &error)) { + phongo_throw_exception_from_bson_error_t(&error); + goto cleanup; + } + + intern->num_ops++; + +cleanup: + bson_destroy(&bfilter); + bson_destroy(&bupdate); + mongoc_bulkwrite_updatemanyopts_destroy(opts); +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, updateOne) +{ + php_phongo_bulkwritecommand_t* intern; + char* ns; + size_t ns_len; + zval* zfilter; + zval* zupdate; + zval* zoptions = NULL; + bson_t bfilter = BSON_INITIALIZER; + bson_t bupdate = BSON_INITIALIZER; + mongoc_bulkwrite_updateoneopts_t* opts = NULL; + bson_error_t error = { 0 }; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_STRING(ns, ns_len) + Z_PARAM_ARRAY_OR_OBJECT(zfilter) + Z_PARAM_ARRAY_OR_OBJECT(zupdate) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(zoptions) + PHONGO_PARSE_PARAMETERS_END(); + + if (strlen(ns) != ns_len) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Namespace string should not contain null bytes"); + return; + } + + php_phongo_zval_to_bson(zfilter, PHONGO_BSON_NONE, &bfilter, NULL); + + if (EG(exception)) { + goto cleanup; + } + + // Explicitly allow MongoDB\BSON\PackedArray for update pipelines + php_phongo_zval_to_bson(zupdate, PHONGO_BSON_ALLOW_ROOT_ARRAY, &bupdate, NULL); + + if (EG(exception)) { + goto cleanup; + } + + if (zoptions) { + opts = mongoc_bulkwrite_updateoneopts_new(); + + if (php_array_existsc(zoptions, "arrayFilters")) { + bson_t barrayfilters = BSON_INITIALIZER; + + if (!phongo_bwc_parse_array(php_array_fetchc_deref(zoptions, "arrayFilters"), &barrayfilters, "arrayFilters")) { + bson_destroy(&barrayfilters); + goto cleanup; + } + + mongoc_bulkwrite_updateoneopts_set_arrayfilters(opts, &barrayfilters); + bson_destroy(&barrayfilters); + } + + if (php_array_existsc(zoptions, "collation")) { + bson_t bcollation = BSON_INITIALIZER; + + if (!phongo_bwc_parse_document(php_array_fetchc_deref(zoptions, "collation"), &bcollation, "collation")) { + bson_destroy(&bcollation); + goto cleanup; + } + + mongoc_bulkwrite_updateoneopts_set_collation(opts, &bcollation); + bson_destroy(&bcollation); + } + + if (php_array_existsc(zoptions, "hint")) { + bson_value_t bhint = { 0 }; + + if (!phongo_bwc_parse_hint(php_array_fetchc_deref(zoptions, "hint"), &bhint)) { + bson_value_destroy(&bhint); + goto cleanup; + } + + mongoc_bulkwrite_updateoneopts_set_hint(opts, &bhint); + bson_value_destroy(&bhint); + } + + if (php_array_existsc(zoptions, "sort")) { + bson_t bsort = BSON_INITIALIZER; + + if (!phongo_bwc_parse_document(php_array_fetchc_deref(zoptions, "sort"), &bsort, "sort")) { + bson_destroy(&bsort); + goto cleanup; + } + + mongoc_bulkwrite_updateoneopts_set_sort(opts, &bsort); + bson_destroy(&bsort); + } + + if (php_array_existsc(zoptions, "upsert")) { + mongoc_bulkwrite_updateoneopts_set_upsert(opts, php_array_fetchc_bool(zoptions, "upsert")); + } + } + + if (!mongoc_bulkwrite_append_updateone(intern->bw, ns, &bfilter, &bupdate, opts, &error)) { + phongo_throw_exception_from_bson_error_t(&error); + goto cleanup; + } + + intern->num_ops++; + +cleanup: + bson_destroy(&bfilter); + bson_destroy(&bupdate); + mongoc_bulkwrite_updateoneopts_destroy(opts); +} + +/* MongoDB\Driver\BulkWriteCommand object handlers */ +static zend_object_handlers php_phongo_handler_bulkwritecommand; + +static void php_phongo_bulkwritecommand_free_object(zend_object* object) +{ + php_phongo_bulkwritecommand_t* intern = Z_OBJ_BULKWRITECOMMAND(object); + + zend_object_std_dtor(&intern->std); + + if (intern->bw) { + mongoc_bulkwrite_destroy(intern->bw); + } + + if (intern->let) { + bson_clear(&intern->let); + } + + if (intern->comment) { + bson_value_destroy(intern->comment); + efree(intern->comment); + } + + if (!Z_ISUNDEF(intern->session)) { + zval_ptr_dtor(&intern->session); + } +} + +static zend_object* php_phongo_bulkwritecommand_create_object(zend_class_entry* class_type) +{ + php_phongo_bulkwritecommand_t* intern = zend_object_alloc(sizeof(php_phongo_bulkwritecommand_t), class_type); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + + intern->std.handlers = &php_phongo_handler_bulkwritecommand; + + return &intern->std; +} + +static HashTable* php_phongo_bulkwritecommand_get_debug_info(zend_object* object, int* is_temp) +{ + zval retval = ZVAL_STATIC_INIT; + php_phongo_bulkwritecommand_t* intern = NULL; + + *is_temp = 1; + intern = Z_OBJ_BULKWRITECOMMAND(object); + array_init(&retval); + + if (intern->bypass != PHONGO_BULKWRITECOMMAND_BYPASS_UNSET) { + ADD_ASSOC_BOOL_EX(&retval, "bypassDocumentValidation", intern->bypass); + } else { + ADD_ASSOC_NULL_EX(&retval, "bypassDocumentValidation"); + } + + if (intern->comment) { + zval zv; + + if (!phongo_bson_value_to_zval_legacy(intern->comment, &zv)) { + zval_ptr_dtor(&zv); + goto done; + } + + ADD_ASSOC_ZVAL_EX(&retval, "comment", &zv); + } + + if (intern->let) { + zval zv; + + if (!php_phongo_bson_to_zval(intern->let, &zv)) { + zval_ptr_dtor(&zv); + goto done; + } + + ADD_ASSOC_ZVAL_EX(&retval, "let", &zv); + } + + ADD_ASSOC_BOOL_EX(&retval, "ordered", intern->ordered); + ADD_ASSOC_BOOL_EX(&retval, "verboseResults", intern->verbose); + + if (!Z_ISUNDEF(intern->session)) { + ADD_ASSOC_ZVAL_EX(&retval, "session", &intern->session); + Z_ADDREF(intern->session); + } else { + ADD_ASSOC_NULL_EX(&retval, "session"); + } + +done: + return Z_ARRVAL(retval); +} + +void php_phongo_bulkwritecommand_init_ce(INIT_FUNC_ARGS) +{ + php_phongo_bulkwritecommand_ce = register_class_MongoDB_Driver_BulkWriteCommand(zend_ce_countable); + php_phongo_bulkwritecommand_ce->create_object = php_phongo_bulkwritecommand_create_object; + + memcpy(&php_phongo_handler_bulkwritecommand, phongo_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_phongo_handler_bulkwritecommand.get_debug_info = php_phongo_bulkwritecommand_get_debug_info; + php_phongo_handler_bulkwritecommand.free_obj = php_phongo_bulkwritecommand_free_object; + php_phongo_handler_bulkwritecommand.offset = XtOffsetOf(php_phongo_bulkwritecommand_t, std); +} diff --git a/src/MongoDB/BulkWriteCommand.h b/src/MongoDB/BulkWriteCommand.h new file mode 100644 index 000000000..d8c2cabfa --- /dev/null +++ b/src/MongoDB/BulkWriteCommand.h @@ -0,0 +1,26 @@ +/* + * Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PHONGO_BULKWRITECOMMAND_H +#define PHONGO_BULKWRITECOMMAND_H + +#include "mongoc/mongoc.h" + +#include "phongo_structs.h" + +mongoc_bulkwriteopts_t* phongo_bwc_assemble_opts(php_phongo_bulkwritecommand_t* intern); + +#endif /* PHONGO_BULKWRITECOMMAND_H */ diff --git a/src/MongoDB/BulkWriteCommand.stub.php b/src/MongoDB/BulkWriteCommand.stub.php new file mode 100644 index 000000000..c8554917f --- /dev/null +++ b/src/MongoDB/BulkWriteCommand.stub.php @@ -0,0 +1,28 @@ + +#include +#include + +#include "php_array_api.h" + +#include "php_phongo.h" +#include "phongo_error.h" + +#include "BSON/Document.h" +#include "MongoDB/WriteConcernError.h" +#include "MongoDB/WriteError.h" +#include "BulkWriteCommandResult_arginfo.h" + +#define PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED(method) \ + if (!intern->is_acknowledged) { \ + phongo_throw_exception(PHONGO_ERROR_LOGIC, "MongoDB\\Driver\\BulkWriteCommandResult::" method "() should not be called for an unacknowledged write result"); \ + return; \ + } + +zend_class_entry* php_phongo_bulkwritecommandresult_ce; + +PHONGO_DISABLED_CONSTRUCTOR(MongoDB_Driver_BulkWriteCommandResult) + +/* Returns the number of documents that were inserted */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getInsertedCount) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getInsertedCount"); + + RETURN_LONG(intern->inserted_count); +} + +/* Returns the number of documents that matched the update criteria */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getMatchedCount) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getMatchedCount"); + + RETURN_LONG(intern->matched_count); +} + +/* Returns the number of documents that were actually modified by an update */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getModifiedCount) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getModifiedCount"); + + RETURN_LONG(intern->modified_count); +} + +/* Returns the number of documents that were deleted */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getDeletedCount) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getDeletedCount"); + + RETURN_LONG(intern->deleted_count); +} + +/* Returns the number of documents that were upserted */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getUpsertedCount) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getUpsertedCount"); + + RETURN_LONG(intern->upserted_count); +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getInsertResults) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getInsertResults"); + + if (intern->insert_results) { + phongo_document_new(return_value, intern->insert_results, true); + } +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getUpdateResults) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getUpdateResults"); + + if (intern->update_results) { + phongo_document_new(return_value, intern->update_results, true); + } +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getDeleteResults) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getDeleteResults"); + + if (intern->delete_results) { + phongo_document_new(return_value, intern->delete_results, true); + } +} + +/* Returns whether the write operation was acknowledged (based on the write + concern). */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, isAcknowledged) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + RETURN_BOOL(intern->is_acknowledged); +} + +/* MongoDB\Driver\BulkWriteCommandResult object handlers */ +static zend_object_handlers php_phongo_handler_bulkwritecommandresult; + +static void php_phongo_bulkwritecommandresult_free_object(zend_object* object) +{ + php_phongo_bulkwritecommandresult_t* intern = Z_OBJ_BULKWRITECOMMANDRESULT(object); + + zend_object_std_dtor(&intern->std); + + bson_destroy(intern->insert_results); + bson_destroy(intern->update_results); + bson_destroy(intern->delete_results); +} + +static zend_object* php_phongo_bulkwritecommandresult_create_object(zend_class_entry* class_type) +{ + php_phongo_bulkwritecommandresult_t* intern = zend_object_alloc(sizeof(php_phongo_bulkwritecommandresult_t), class_type); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + + intern->std.handlers = &php_phongo_handler_bulkwritecommandresult; + + return &intern->std; +} + +static HashTable* php_phongo_bulkwritecommandresult_get_debug_info(zend_object* object, int* is_temp) +{ + php_phongo_bulkwritecommandresult_t* intern; + zval retval = ZVAL_STATIC_INIT; + + intern = Z_OBJ_BULKWRITECOMMANDRESULT(object); + *is_temp = 1; + array_init_size(&retval, 12); + + ADD_ASSOC_BOOL_EX(&retval, "isAcknowledged", intern->is_acknowledged); + ADD_ASSOC_LONG_EX(&retval, "insertedCount", intern->inserted_count); + ADD_ASSOC_LONG_EX(&retval, "matchedCount", intern->matched_count); + ADD_ASSOC_LONG_EX(&retval, "modifiedCount", intern->modified_count); + ADD_ASSOC_LONG_EX(&retval, "upsertedCount", intern->upserted_count); + ADD_ASSOC_LONG_EX(&retval, "deletedCount", intern->deleted_count); + + if (intern->insert_results) { + zval insert_results; + + phongo_document_new(&insert_results, intern->insert_results, true); + ADD_ASSOC_ZVAL_EX(&retval, "insertResults", &insert_results); + } else { + ADD_ASSOC_NULL_EX(&retval, "insertResults"); + } + + if (intern->update_results) { + zval update_results; + + phongo_document_new(&update_results, intern->update_results, true); + ADD_ASSOC_ZVAL_EX(&retval, "updateResults", &update_results); + } else { + ADD_ASSOC_NULL_EX(&retval, "updateResults"); + } + + if (intern->delete_results) { + zval delete_results; + + phongo_document_new(&delete_results, intern->delete_results, true); + ADD_ASSOC_ZVAL_EX(&retval, "deleteResults", &delete_results); + } else { + ADD_ASSOC_NULL_EX(&retval, "deleteResults"); + } + + return Z_ARRVAL(retval); +} + +void php_phongo_bulkwritecommandresult_init_ce(INIT_FUNC_ARGS) +{ + php_phongo_bulkwritecommandresult_ce = register_class_MongoDB_Driver_BulkWriteCommandResult(); + php_phongo_bulkwritecommandresult_ce->create_object = php_phongo_bulkwritecommandresult_create_object; + + memcpy(&php_phongo_handler_bulkwritecommandresult, phongo_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_phongo_handler_bulkwritecommandresult.get_debug_info = php_phongo_bulkwritecommandresult_get_debug_info; + php_phongo_handler_bulkwritecommandresult.free_obj = php_phongo_bulkwritecommandresult_free_object; + php_phongo_handler_bulkwritecommandresult.offset = XtOffsetOf(php_phongo_bulkwritecommandresult_t, std); +} + +static inline bson_t* _bson_copy_or_null(const bson_t* bson) +{ + return bson ? bson_copy(bson) : NULL; +} + +php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* return_value, mongoc_bulkwriteresult_t* bw_res) +{ + php_phongo_bulkwritecommandresult_t* intern; + + object_init_ex(return_value, php_phongo_bulkwritecommandresult_ce); + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(return_value); + intern->is_acknowledged = (bw_res != NULL); + + // Copy mongoc_bulkwriteresult_t fields + if (bw_res) { + intern->inserted_count = mongoc_bulkwriteresult_insertedcount(bw_res); + intern->upserted_count = mongoc_bulkwriteresult_upsertedcount(bw_res); + intern->matched_count = mongoc_bulkwriteresult_matchedcount(bw_res); + intern->modified_count = mongoc_bulkwriteresult_modifiedcount(bw_res); + intern->deleted_count = mongoc_bulkwriteresult_deletedcount(bw_res); + + // Result documents will null if verboseResults=false + intern->insert_results = _bson_copy_or_null(mongoc_bulkwriteresult_insertresults(bw_res)); + intern->update_results = _bson_copy_or_null(mongoc_bulkwriteresult_updateresults(bw_res)); + intern->delete_results = _bson_copy_or_null(mongoc_bulkwriteresult_deleteresults(bw_res)); + } + + return intern; +} diff --git a/src/MongoDB/BulkWriteCommandResult.h b/src/MongoDB/BulkWriteCommandResult.h new file mode 100644 index 000000000..a69adb7c1 --- /dev/null +++ b/src/MongoDB/BulkWriteCommandResult.h @@ -0,0 +1,28 @@ +/* + * Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PHONGO_BULKWRITECOMMANDRESULT_H +#define PHONGO_BULKWRITECOMMANDRESULT_H + +#include "mongoc/mongoc.h" + +#include + +#include "phongo_structs.h" + +php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* return_value, mongoc_bulkwriteresult_t* bw_res); + +#endif /* PHONGO_BULKWRITECOMMANDRESULT_H */ diff --git a/src/MongoDB/BulkWriteCommandResult.stub.php b/src/MongoDB/BulkWriteCommandResult.stub.php new file mode 100644 index 000000000..a6f60e7f2 --- /dev/null +++ b/src/MongoDB/BulkWriteCommandResult.stub.php @@ -0,0 +1,32 @@ +ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NOT_SERIALIZABLE; + + return class_entry; +} diff --git a/src/MongoDB/BulkWriteCommand_arginfo.h b/src/MongoDB/BulkWriteCommand_arginfo.h new file mode 100644 index 000000000..1dde4d410 --- /dev/null +++ b/src/MongoDB/BulkWriteCommand_arginfo.h @@ -0,0 +1,73 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 2af15e87d1d8095137d315218e688221ac2b5983 */ + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommand___construct, 0, 0, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommand_count, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommand_deleteOne, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, namespace, IS_STRING, 0) + ZEND_ARG_TYPE_MASK(0, filter, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_MongoDB_Driver_BulkWriteCommand_deleteMany arginfo_class_MongoDB_Driver_BulkWriteCommand_deleteOne + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommand_insertOne, 0, 2, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, namespace, IS_STRING, 0) + ZEND_ARG_TYPE_MASK(0, document, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommand_replaceOne, 0, 3, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, namespace, IS_STRING, 0) + ZEND_ARG_TYPE_MASK(0, filter, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) + ZEND_ARG_TYPE_MASK(0, replacement, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommand_updateOne, 0, 3, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, namespace, IS_STRING, 0) + ZEND_ARG_TYPE_MASK(0, filter, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) + ZEND_ARG_TYPE_MASK(0, update, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_MongoDB_Driver_BulkWriteCommand_updateMany arginfo_class_MongoDB_Driver_BulkWriteCommand_updateOne + + +static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, __construct); +static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, count); +static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, deleteOne); +static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, deleteMany); +static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, insertOne); +static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, replaceOne); +static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, updateOne); +static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, updateMany); + + +static const zend_function_entry class_MongoDB_Driver_BulkWriteCommand_methods[] = { + ZEND_ME(MongoDB_Driver_BulkWriteCommand, __construct, arginfo_class_MongoDB_Driver_BulkWriteCommand___construct, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_BulkWriteCommand, count, arginfo_class_MongoDB_Driver_BulkWriteCommand_count, ZEND_ACC_PUBLIC) + ZEND_ME(MongoDB_Driver_BulkWriteCommand, deleteOne, arginfo_class_MongoDB_Driver_BulkWriteCommand_deleteOne, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_BulkWriteCommand, deleteMany, arginfo_class_MongoDB_Driver_BulkWriteCommand_deleteMany, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_BulkWriteCommand, insertOne, arginfo_class_MongoDB_Driver_BulkWriteCommand_insertOne, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_BulkWriteCommand, replaceOne, arginfo_class_MongoDB_Driver_BulkWriteCommand_replaceOne, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_BulkWriteCommand, updateOne, arginfo_class_MongoDB_Driver_BulkWriteCommand_updateOne, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_BulkWriteCommand, updateMany, arginfo_class_MongoDB_Driver_BulkWriteCommand_updateMany, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_FE_END +}; + +static zend_class_entry *register_class_MongoDB_Driver_BulkWriteCommand(zend_class_entry *class_entry_Countable) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "MongoDB\\Driver", "BulkWriteCommand", class_MongoDB_Driver_BulkWriteCommand_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NOT_SERIALIZABLE; + zend_class_implements(class_entry, 1, class_entry_Countable); + + return class_entry; +} diff --git a/src/MongoDB/Exception/BulkWriteCommandException.c b/src/MongoDB/Exception/BulkWriteCommandException.c new file mode 100644 index 000000000..986b70263 --- /dev/null +++ b/src/MongoDB/Exception/BulkWriteCommandException.c @@ -0,0 +1,191 @@ +/* + * Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "php_phongo.h" +#include "phongo_error.h" + +#include "BSON/Document.h" +#include "BulkWriteCommandException.h" +#include "BulkWriteCommandException_arginfo.h" +#include "MongoDB/WriteConcernError.h" +#include "MongoDB/WriteError.h" + +zend_class_entry* php_phongo_bulkwritecommandexception_ce; + +/* Returns the error reply document (if any) from the failed bulk write */ +static PHP_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getErrorReply) +{ + PHONGO_PARSE_PARAMETERS_NONE(); + + zval rv; + zval* result = zend_read_property(php_phongo_bulkwritecommandexception_ce, Z_OBJ_P(getThis()), ZEND_STRL("errorReply"), 0, &rv); + + RETURN_ZVAL(result, 1, 0); +} + +/* Returns the partial BulkWriteCommandResult (if any) from the failed bulk write. */ +static PHP_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getPartialResult) +{ + PHONGO_PARSE_PARAMETERS_NONE(); + + zval rv; + zval* result = zend_read_property(php_phongo_bulkwritecommandexception_ce, Z_OBJ_P(getThis()), ZEND_STRL("partialResult"), 0, &rv); + + RETURN_ZVAL(result, 1, 0); +} + +/* Returns a map of write errors from the failed bulk write. */ +static PHP_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getWriteErrors) +{ + PHONGO_PARSE_PARAMETERS_NONE(); + + zval rv; + zval* result = zend_read_property(php_phongo_bulkwritecommandexception_ce, Z_OBJ_P(getThis()), ZEND_STRL("writeErrors"), 0, &rv); + + RETURN_ZVAL(result, 1, 0); +} + +/* Returns a list of write concern errors from the failed bulk write. */ +static PHP_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getWriteConcernErrors) +{ + PHONGO_PARSE_PARAMETERS_NONE(); + + zval rv; + zval* result = zend_read_property(php_phongo_bulkwritecommandexception_ce, Z_OBJ_P(getThis()), ZEND_STRL("writeConcernErrors"), 0, &rv); + + RETURN_ZVAL(result, 1, 0); +} + +void php_phongo_bulkwritecommandexception_init_ce(INIT_FUNC_ARGS) +{ + php_phongo_bulkwritecommandexception_ce = register_class_MongoDB_Driver_Exception_BulkWriteCommandException(php_phongo_serverexception_ce); +} + +/* Populates return_value with a list of WriteConcernError objects. Returns true + * on success; otherwise, false is returned and an exception is thrown. */ +static bool phongo_bulkwritecommandexception_get_writeconcernerrors(const bson_t* write_concern_errors, zval* return_value) +{ + bson_iter_t iter; + + array_init(return_value); + + if (bson_iter_init(&iter, write_concern_errors)) { + while (bson_iter_next(&iter)) { + bson_t bson; + uint32_t len; + const uint8_t* data; + zval write_concern_error; + + if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { + continue; + } + + bson_iter_document(&iter, &len, &data); + + if (!bson_init_static(&bson, data, len)) { + continue; + } + + if (!phongo_writeconcernerror_init(&write_concern_error, &bson)) { + /* Exception already thrown */ + zval_ptr_dtor(&write_concern_error); + return false; + } + + add_next_index_zval(return_value, &write_concern_error); + } + } + + return true; +} + +/* Populates return_value with a map of WriteError objects indexed by the offset + * of the corresponding operation. Returns true on success; otherwise, false is + * returned and an exception is thrown. */ +static bool phongo_bulkwritecommandexception_get_writeerrors(const bson_t* write_errors, zval* return_value) +{ + bson_iter_t iter; + + array_init(return_value); + + if (bson_iter_init(&iter, write_errors)) { + while (bson_iter_next(&iter)) { + bson_t bson; + uint32_t len; + const uint8_t* data; + zval write_error; + zend_ulong index; + + if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { + continue; + } + + bson_iter_document(&iter, &len, &data); + + if (!bson_init_static(&bson, data, len)) { + continue; + } + + index = (zend_ulong) ZEND_STRTOUL(bson_iter_key(&iter), NULL, 10); + + if (!phongo_writeerror_init_ex(&write_error, &bson, (int32_t) index)) { + /* Exception already thrown */ + zval_ptr_dtor(&write_error); + return false; + } + + add_index_zval(return_value, index, &write_error); + } + } + + return true; +} + +void php_phongo_bulkwritecommandexception_init_props(zend_object* object, const mongoc_bulkwriteexception_t* bw_exc, zval* result) +{ + const bson_t* errorreply = mongoc_bulkwriteexception_errorreply(bw_exc); + zval zwriteconcernerrors, zwriteerrors; + + if (!bson_empty(errorreply)) { + zval zerrorreply; + + /* Manually copy the bson_t to satisfy phongo_document_new. This can be + * changed once PHPC-2535 is addressed. */ + phongo_document_new(&zerrorreply, bson_copy(errorreply), false); + zend_update_property(php_phongo_bulkwritecommandexception_ce, object, ZEND_STRL("errorReply"), &zerrorreply); + zval_ptr_dtor(&zerrorreply); + } + + if (result && Z_TYPE_P(result) == IS_OBJECT && instanceof_function(Z_OBJCE_P(result), php_phongo_bulkwritecommandresult_ce)) { + zend_update_property(php_phongo_bulkwritecommandexception_ce, object, ZEND_STRL("partialResult"), result); + } + + /* Note: get_writeconcernerrors and get_writeerrors could throw if BSON + * decoding fails, but that risk similarly exists for decoding a command + * result in phongo_throw_exception_from_bson_error_t_and_reply. */ + if (phongo_bulkwritecommandexception_get_writeconcernerrors(mongoc_bulkwriteexception_writeconcernerrors(bw_exc), &zwriteconcernerrors)) { + zend_update_property(php_phongo_bulkwritecommandexception_ce, object, ZEND_STRL("writeConcernErrors"), &zwriteconcernerrors); + } + + if (phongo_bulkwritecommandexception_get_writeerrors(mongoc_bulkwriteexception_writeerrors(bw_exc), &zwriteerrors)) { + zend_update_property(php_phongo_bulkwritecommandexception_ce, object, ZEND_STRL("writeErrors"), &zwriteerrors); + } + + zval_ptr_dtor(&zwriteconcernerrors); + zval_ptr_dtor(&zwriteerrors); +} diff --git a/src/MongoDB/Exception/BulkWriteCommandException.h b/src/MongoDB/Exception/BulkWriteCommandException.h new file mode 100644 index 000000000..b0fc71b33 --- /dev/null +++ b/src/MongoDB/Exception/BulkWriteCommandException.h @@ -0,0 +1,26 @@ +/* +* Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PHONGO_BULKWRITECOMMANDEXCEPTION_H +#define PHONGO_BULKWRITECOMMANDEXCEPTION_H + +#include "mongoc/mongoc.h" + +#include + +void php_phongo_bulkwritecommandexception_init_props(zend_object* object, const mongoc_bulkwriteexception_t* bw_exc, zval* result); + +#endif /* PHONGO_BULKWRITECOMMANDEXCEPTION_H */ diff --git a/src/MongoDB/Exception/BulkWriteCommandException.stub.php b/src/MongoDB/Exception/BulkWriteCommandException.stub.php new file mode 100644 index 000000000..8c8b46e0d --- /dev/null +++ b/src/MongoDB/Exception/BulkWriteCommandException.stub.php @@ -0,0 +1,27 @@ +ce_flags |= ZEND_ACC_FINAL; + + zend_string *property_errorReply_class_MongoDB_BSON_Document = zend_string_init("MongoDB\\BSON\\Document", sizeof("MongoDB\\BSON\\Document")-1, 1); + zval property_errorReply_default_value; + ZVAL_NULL(&property_errorReply_default_value); + zend_string *property_errorReply_name = zend_string_init("errorReply", sizeof("errorReply") - 1, 1); + zend_declare_typed_property(class_entry, property_errorReply_name, &property_errorReply_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_errorReply_class_MongoDB_BSON_Document, 0, MAY_BE_NULL)); + zend_string_release(property_errorReply_name); + + zend_string *property_partialResult_class_MongoDB_Driver_BulkWriteCommandResult = zend_string_init("MongoDB\\Driver\\BulkWriteCommandResult", sizeof("MongoDB\\Driver\\BulkWriteCommandResult")-1, 1); + zval property_partialResult_default_value; + ZVAL_NULL(&property_partialResult_default_value); + zend_string *property_partialResult_name = zend_string_init("partialResult", sizeof("partialResult") - 1, 1); + zend_declare_typed_property(class_entry, property_partialResult_name, &property_partialResult_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_partialResult_class_MongoDB_Driver_BulkWriteCommandResult, 0, MAY_BE_NULL)); + zend_string_release(property_partialResult_name); + + zval property_writeErrors_default_value; + ZVAL_EMPTY_ARRAY(&property_writeErrors_default_value); + zend_string *property_writeErrors_name = zend_string_init("writeErrors", sizeof("writeErrors") - 1, 1); + zend_declare_typed_property(class_entry, property_writeErrors_name, &property_writeErrors_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); + zend_string_release(property_writeErrors_name); + + zval property_writeConcernErrors_default_value; + ZVAL_EMPTY_ARRAY(&property_writeConcernErrors_default_value); + zend_string *property_writeConcernErrors_name = zend_string_init("writeConcernErrors", sizeof("writeConcernErrors") - 1, 1); + zend_declare_typed_property(class_entry, property_writeConcernErrors_name, &property_writeConcernErrors_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); + zend_string_release(property_writeConcernErrors_name); + + return class_entry; +} diff --git a/src/MongoDB/Manager.c b/src/MongoDB/Manager.c index f24a383fe..86d693dd5 100644 --- a/src/MongoDB/Manager.c +++ b/src/MongoDB/Manager.c @@ -519,6 +519,42 @@ static PHP_METHOD(MongoDB_Driver_Manager, executeBulkWrite) phongo_execute_bulk_write(getThis(), namespace, bulk, options, server_id, return_value); } +/* Executes a BulkWriteCommand (i.e. bulkWrite command for MongoDB 8.0+) */ +static PHP_METHOD(MongoDB_Driver_Manager, executeBulkWriteCommand) +{ + php_phongo_manager_t* intern; + zval* zbwc; + php_phongo_bulkwritecommand_t* bwc; + zval* zoptions = NULL; + uint32_t server_id = 0; + zval* zsession = NULL; + + PHONGO_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(zbwc, php_phongo_bulkwritecommand_ce) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_OR_NULL(zoptions) + PHONGO_PARSE_PARAMETERS_END(); + + intern = Z_MANAGER_OBJ_P(getThis()); + bwc = Z_BULKWRITECOMMAND_OBJ_P(zbwc); + + if (!phongo_parse_session(zoptions, intern->client, NULL, &zsession)) { + /* Exception should already have been thrown */ + return; + } + + if (!php_phongo_manager_select_server(true, false, NULL, zsession, intern->client, &server_id)) { + /* Exception should already have been thrown */ + return; + } + + /* If the Server was created in a different process, reset the client so + * that its session pool is cleared. */ + PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern, intern); + + phongo_execute_bulkwritecommand(getThis(), bwc, zoptions, server_id, return_value); +} + /* Returns the autoEncryption.encryptedFieldsMap driver option */ static PHP_METHOD(MongoDB_Driver_Manager, getEncryptedFieldsMap) { diff --git a/src/MongoDB/Manager.stub.php b/src/MongoDB/Manager.stub.php index b421e8bae..300bbdb16 100644 --- a/src/MongoDB/Manager.stub.php +++ b/src/MongoDB/Manager.stub.php @@ -18,6 +18,8 @@ final public function createClientEncryption(array $options): ClientEncryption { final public function executeBulkWrite(string $namespace, BulkWrite $bulk, array|null $options = null): WriteResult {} + final public function executeBulkWriteCommand(BulkWriteCommand $bulkWriteCommand, ?array $options = null): BulkWriteCommandResult {} + final public function executeCommand(string $db, Command $command, array|null $options = null): CursorInterface {} final public function executeQuery(string $namespace, Query $query, array|null $options = null): CursorInterface {} diff --git a/src/MongoDB/Manager_arginfo.h b/src/MongoDB/Manager_arginfo.h index 5c1532250..a2962670a 100644 --- a/src/MongoDB/Manager_arginfo.h +++ b/src/MongoDB/Manager_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 0e0470a95648b0188bcb59270f976b298596c1a4 */ + * Stub hash: 94e08d9aa9d6b2f361f14207a86881c54ee3ff67 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_MongoDB_Driver_Manager___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, uri, IS_STRING, 1, "null") @@ -21,6 +21,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Manager_exec ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Manager_executeBulkWriteCommand, 0, 1, MongoDB\\Driver\\BulkWriteCommandResult, 0) + ZEND_ARG_OBJ_INFO(0, bulkWriteCommand, MongoDB\\Driver\\BulkWriteCommand, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Manager_executeCommand, 0, 2, MongoDB\\Driver\\CursorInterface, 0) ZEND_ARG_TYPE_INFO(0, db, IS_STRING, 0) ZEND_ARG_OBJ_INFO(0, command, MongoDB\\Driver\\Command, 0) @@ -69,6 +74,7 @@ static ZEND_METHOD(MongoDB_Driver_Manager, __construct); static ZEND_METHOD(MongoDB_Driver_Manager, addSubscriber); static ZEND_METHOD(MongoDB_Driver_Manager, createClientEncryption); static ZEND_METHOD(MongoDB_Driver_Manager, executeBulkWrite); +static ZEND_METHOD(MongoDB_Driver_Manager, executeBulkWriteCommand); static ZEND_METHOD(MongoDB_Driver_Manager, executeCommand); static ZEND_METHOD(MongoDB_Driver_Manager, executeQuery); static ZEND_METHOD(MongoDB_Driver_Manager, executeReadCommand); @@ -89,6 +95,7 @@ static const zend_function_entry class_MongoDB_Driver_Manager_methods[] = { ZEND_ME(MongoDB_Driver_Manager, addSubscriber, arginfo_class_MongoDB_Driver_Manager_addSubscriber, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Manager, createClientEncryption, arginfo_class_MongoDB_Driver_Manager_createClientEncryption, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Manager, executeBulkWrite, arginfo_class_MongoDB_Driver_Manager_executeBulkWrite, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_Manager, executeBulkWriteCommand, arginfo_class_MongoDB_Driver_Manager_executeBulkWriteCommand, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Manager, executeCommand, arginfo_class_MongoDB_Driver_Manager_executeCommand, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Manager, executeQuery, arginfo_class_MongoDB_Driver_Manager_executeQuery, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Manager, executeReadCommand, arginfo_class_MongoDB_Driver_Manager_executeReadCommand, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) diff --git a/src/MongoDB/Server.c b/src/MongoDB/Server.c index c318c3112..24c53a12a 100644 --- a/src/MongoDB/Server.c +++ b/src/MongoDB/Server.c @@ -194,6 +194,30 @@ static PHP_METHOD(MongoDB_Driver_Server, executeBulkWrite) phongo_execute_bulk_write(&intern->manager, namespace, bulk, options, intern->server_id, return_value); } +/* Executes a BulkWriteCommand (i.e. bulkWrite command for MongoDB 8.0+) */ +static PHP_METHOD(MongoDB_Driver_Server, executeBulkWriteCommand) +{ + php_phongo_server_t* intern; + zval* zbwc; + php_phongo_bulkwritecommand_t* bwc; + zval* zoptions = NULL; + + PHONGO_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(zbwc, php_phongo_bulkwritecommand_ce) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_OR_NULL(zoptions) + PHONGO_PARSE_PARAMETERS_END(); + + intern = Z_SERVER_OBJ_P(getThis()); + bwc = Z_BULKWRITECOMMAND_OBJ_P(zbwc); + + /* If the Server was created in a different process, reset the client so + * that its session pool is cleared. */ + PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern, Z_MANAGER_OBJ_P(&intern->manager)); + + phongo_execute_bulkwritecommand(&intern->manager, bwc, zoptions, intern->server_id, return_value); +} + /* Returns the hostname for this Server */ static PHP_METHOD(MongoDB_Driver_Server, getHost) { diff --git a/src/MongoDB/Server.stub.php b/src/MongoDB/Server.stub.php index 9f2bb134c..3aee834e2 100644 --- a/src/MongoDB/Server.stub.php +++ b/src/MongoDB/Server.stub.php @@ -74,6 +74,8 @@ final private function __construct() {} final public function executeBulkWrite(string $namespace, BulkWrite $bulkWrite, array|null $options = null): WriteResult {} + final public function executeBulkWriteCommand(BulkWriteCommand $bulkWriteCommand, ?array $options = null): BulkWriteCommandResult {} + final public function executeCommand(string $db, Command $command, array|null $options = null): CursorInterface {} final public function executeQuery(string $namespace, Query $query, array|null $options = null): CursorInterface {} diff --git a/src/MongoDB/Server_arginfo.h b/src/MongoDB/Server_arginfo.h index 03c68fed1..3d64e2780 100644 --- a/src/MongoDB/Server_arginfo.h +++ b/src/MongoDB/Server_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 4b3c108555c3548cf604852b1f0f334ada52a276 */ + * Stub hash: ed6e371d10ddbcfd3ffed6aeb48758d0e88d717f */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_MongoDB_Driver_Server___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -10,6 +10,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Server_execu ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Server_executeBulkWriteCommand, 0, 1, MongoDB\\Driver\\BulkWriteCommandResult, 0) + ZEND_ARG_OBJ_INFO(0, bulkWriteCommand, MongoDB\\Driver\\BulkWriteCommand, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Server_executeCommand, 0, 2, MongoDB\\Driver\\CursorInterface, 0) ZEND_ARG_TYPE_INFO(0, db, IS_STRING, 0) ZEND_ARG_OBJ_INFO(0, command, MongoDB\\Driver\\Command, 0) @@ -61,6 +66,7 @@ ZEND_END_ARG_INFO() static ZEND_METHOD(MongoDB_Driver_Server, __construct); static ZEND_METHOD(MongoDB_Driver_Server, executeBulkWrite); +static ZEND_METHOD(MongoDB_Driver_Server, executeBulkWriteCommand); static ZEND_METHOD(MongoDB_Driver_Server, executeCommand); static ZEND_METHOD(MongoDB_Driver_Server, executeQuery); static ZEND_METHOD(MongoDB_Driver_Server, executeReadCommand); @@ -83,6 +89,7 @@ static ZEND_METHOD(MongoDB_Driver_Server, isSecondary); static const zend_function_entry class_MongoDB_Driver_Server_methods[] = { ZEND_ME(MongoDB_Driver_Server, __construct, arginfo_class_MongoDB_Driver_Server___construct, ZEND_ACC_PRIVATE|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Server, executeBulkWrite, arginfo_class_MongoDB_Driver_Server_executeBulkWrite, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_Server, executeBulkWriteCommand, arginfo_class_MongoDB_Driver_Server_executeBulkWriteCommand, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Server, executeCommand, arginfo_class_MongoDB_Driver_Server_executeCommand, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Server, executeQuery, arginfo_class_MongoDB_Driver_Server_executeQuery, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_Server, executeReadCommand, arginfo_class_MongoDB_Driver_Server_executeReadCommand, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) diff --git a/src/MongoDB/WriteConcernError.c b/src/MongoDB/WriteConcernError.c index 9a9acd007..edf81dd45 100644 --- a/src/MongoDB/WriteConcernError.c +++ b/src/MongoDB/WriteConcernError.c @@ -133,7 +133,13 @@ void php_phongo_writeconcernerror_init_ce(INIT_FUNC_ARGS) php_phongo_handler_writeconcernerror.offset = XtOffsetOf(php_phongo_writeconcernerror_t, std); } -zend_bool phongo_writeconcernerror_init(zval* return_value, bson_t* bson) +/* Initializes a new WriteConcernError in return_value using the BSON document. + * Returns true on success; otherwise, false is returned and an exception is + * thrown. + * + * This function supports documents from both mongoc_bulk_operation_execute and + * mongoc_bulkwriteexception_t (returned by mongoc_bulkwrite_execute). */ +bool phongo_writeconcernerror_init(zval* return_value, const bson_t* bson) { bson_iter_t iter; php_phongo_writeconcernerror_t* intern; @@ -147,14 +153,18 @@ zend_bool phongo_writeconcernerror_init(zval* return_value, bson_t* bson) intern->code = bson_iter_int32(&iter); } - if (bson_iter_init_find(&iter, bson, "errmsg") && BSON_ITER_HOLDS_UTF8(&iter)) { - uint32_t errmsg_len; - const char* err_msg = bson_iter_utf8(&iter, &errmsg_len); + // Additionally check for field name used by mongoc_bulkwriteexception_t + if ((bson_iter_init_find(&iter, bson, "errmsg") && BSON_ITER_HOLDS_UTF8(&iter)) || + (bson_iter_init_find(&iter, bson, "message") && BSON_ITER_HOLDS_UTF8(&iter))) { + uint32_t len; + const char* message = bson_iter_utf8(&iter, &len); - intern->message = estrndup(err_msg, errmsg_len); + intern->message = estrndup(message, len); } - if (bson_iter_init_find(&iter, bson, "errInfo") && BSON_ITER_HOLDS_DOCUMENT(&iter)) { + // Additionally check for field name used by mongoc_bulkwriteexception_t + if ((bson_iter_init_find(&iter, bson, "errInfo") && BSON_ITER_HOLDS_DOCUMENT(&iter)) || + (bson_iter_init_find(&iter, bson, "details") && BSON_ITER_HOLDS_DOCUMENT(&iter))) { uint32_t len; const uint8_t* data = NULL; diff --git a/src/MongoDB/WriteConcernError.h b/src/MongoDB/WriteConcernError.h index f5f632bfb..0801543de 100644 --- a/src/MongoDB/WriteConcernError.h +++ b/src/MongoDB/WriteConcernError.h @@ -21,6 +21,6 @@ #include -zend_bool phongo_writeconcernerror_init(zval* return_value, bson_t* bson); +bool phongo_writeconcernerror_init(zval* return_value, const bson_t* bson); #endif /* PHONGO_WRITECONCERNERROR_H */ diff --git a/src/MongoDB/WriteError.c b/src/MongoDB/WriteError.c index 5db082d20..148a90c0d 100644 --- a/src/MongoDB/WriteError.c +++ b/src/MongoDB/WriteError.c @@ -143,7 +143,19 @@ void php_phongo_writeerror_init_ce(INIT_FUNC_ARGS) php_phongo_handler_writeerror.offset = XtOffsetOf(php_phongo_writeerror_t, std); } -zend_bool phongo_writeerror_init(zval* return_value, bson_t* bson) +bool phongo_writeerror_init(zval* return_value, const bson_t* bson) +{ + return phongo_writeerror_init_ex(return_value, bson, 0); +} + +/* Initializes a new WriteError in return_value using the BSON document. Returns + * true on success; otherwise, false is returned and an exception is thrown. + * + * This function supports documents from both mongoc_bulk_operation_execute and + * mongoc_bulkwriteexception_t (returned by mongoc_bulkwrite_execute). When + * initializing from mongoc_bulkwriteexception_t, an index should be explicitly + * provided since the BSON document will not have an "index" field. */ +bool phongo_writeerror_init_ex(zval* return_value, const bson_t* bson, int32_t index) { bson_iter_t iter; php_phongo_writeerror_t* intern; @@ -152,26 +164,31 @@ zend_bool phongo_writeerror_init(zval* return_value, bson_t* bson) intern = Z_WRITEERROR_OBJ_P(return_value); intern->code = 0; - intern->index = 0; + intern->index = index; if (bson_iter_init_find(&iter, bson, "code") && BSON_ITER_HOLDS_INT32(&iter)) { intern->code = bson_iter_int32(&iter); } - if (bson_iter_init_find(&iter, bson, "errmsg") && BSON_ITER_HOLDS_UTF8(&iter)) { + // Additionally check for field name used by mongoc_bulkwriteexception_t + if ((bson_iter_init_find(&iter, bson, "errmsg") && BSON_ITER_HOLDS_UTF8(&iter)) || + (bson_iter_init_find(&iter, bson, "message") && BSON_ITER_HOLDS_UTF8(&iter))) { uint32_t errmsg_len; const char* err_msg = bson_iter_utf8(&iter, &errmsg_len); intern->message = estrndup(err_msg, errmsg_len); } - if (bson_iter_init_find(&iter, bson, "errInfo") && BSON_ITER_HOLDS_DOCUMENT(&iter)) { + // Additionally check for field name used by mongoc_bulkwriteexception_t + if ((bson_iter_init_find(&iter, bson, "errInfo") && BSON_ITER_HOLDS_DOCUMENT(&iter)) || + (bson_iter_init_find(&iter, bson, "details") && BSON_ITER_HOLDS_DOCUMENT(&iter))) { uint32_t len; const uint8_t* data = NULL; bson_iter_document(&iter, &len, &data); if (!php_phongo_bson_data_to_zval(data, len, &intern->info)) { + /* Exception already thrown */ zval_ptr_dtor(&intern->info); ZVAL_UNDEF(&intern->info); @@ -179,7 +196,9 @@ zend_bool phongo_writeerror_init(zval* return_value, bson_t* bson) } } - if (bson_iter_init_find(&iter, bson, "index") && BSON_ITER_HOLDS_INT32(&iter)) { + /* If the WriteError is initialized from mongoc_bulkwriteexception_t, an + * index should already have been specified. */ + if (!intern->index && bson_iter_init_find(&iter, bson, "index") && BSON_ITER_HOLDS_INT32(&iter)) { intern->index = bson_iter_int32(&iter); } diff --git a/src/MongoDB/WriteError.h b/src/MongoDB/WriteError.h index b2334f98e..69347a1e5 100644 --- a/src/MongoDB/WriteError.h +++ b/src/MongoDB/WriteError.h @@ -21,6 +21,7 @@ #include -zend_bool phongo_writeerror_init(zval* return_value, bson_t* bson); +bool phongo_writeerror_init(zval* return_value, const bson_t* bson); +bool phongo_writeerror_init_ex(zval* return_value, const bson_t* bson, int32_t index); #endif /* PHONGO_WRITEERROR_H */ diff --git a/src/MongoDB/WriteResult.c b/src/MongoDB/WriteResult.c index be0cb66c3..c7280ee89 100644 --- a/src/MongoDB/WriteResult.c +++ b/src/MongoDB/WriteResult.c @@ -44,7 +44,10 @@ zend_class_entry* php_phongo_writeresult_ce; -static bool php_phongo_writeresult_get_writeconcernerror(php_phongo_writeresult_t* intern, zval* return_value) +/* Populates return_value with a WriteConcernError object (if available). + * Returns true on success; otherwise, false is returned and an exception is + * thrown. */ +static bool phongo_writeresult_get_writeconcernerror(php_phongo_writeresult_t* intern, zval* return_value) { bson_iter_t iter, child; zval writeconcernerror; @@ -68,6 +71,7 @@ static bool php_phongo_writeresult_get_writeconcernerror(php_phongo_writeresult_ } if (!phongo_writeconcernerror_init(&writeconcernerror, &cbson)) { + /* Exception already thrown */ zval_ptr_dtor(&writeconcernerror); return false; } @@ -81,7 +85,9 @@ static bool php_phongo_writeresult_get_writeconcernerror(php_phongo_writeresult_ return true; } -static bool php_phongo_writeresult_get_writeerrors(php_phongo_writeresult_t* intern, zval* return_value) +/* Populates return_value with a list of WriteError objects. Returns true on + * success; otherwise, false is returned and an exception is thrown. */ +static bool phongo_writeresult_get_writeerrors(php_phongo_writeresult_t* intern, zval* return_value) { bson_iter_t iter, child; @@ -105,8 +111,9 @@ static bool php_phongo_writeresult_get_writeerrors(php_phongo_writeresult_t* int } if (!phongo_writeerror_init(&writeerror, &cbson)) { + /* Exception already thrown */ zval_ptr_dtor(&writeerror); - continue; + return false; } add_next_index_zval(return_value, &writeerror); @@ -282,7 +289,7 @@ static PHP_METHOD(MongoDB_Driver_WriteResult, getWriteConcernError) PHONGO_PARSE_PARAMETERS_NONE(); - php_phongo_writeresult_get_writeconcernerror(intern, return_value); + phongo_writeresult_get_writeconcernerror(intern, return_value); } /* Returns any write errors that occurred */ @@ -294,7 +301,7 @@ static PHP_METHOD(MongoDB_Driver_WriteResult, getWriteErrors) PHONGO_PARSE_PARAMETERS_NONE(); - php_phongo_writeresult_get_writeerrors(intern, return_value); + phongo_writeresult_get_writeerrors(intern, return_value); } static PHP_METHOD(MongoDB_Driver_WriteResult, getErrorReplies) @@ -401,14 +408,14 @@ static HashTable* php_phongo_writeresult_get_debug_info(zend_object* object, int { zval writeerrors; - php_phongo_writeresult_get_writeerrors(intern, &writeerrors); + phongo_writeresult_get_writeerrors(intern, &writeerrors); ADD_ASSOC_ZVAL_EX(&retval, "writeErrors", &writeerrors); } { zval writeconcernerror; - php_phongo_writeresult_get_writeconcernerror(intern, &writeconcernerror); + phongo_writeresult_get_writeconcernerror(intern, &writeconcernerror); ADD_ASSOC_ZVAL_EX(&retval, "writeConcernError", &writeconcernerror); } diff --git a/src/MongoDB/WriteResult.h b/src/MongoDB/WriteResult.h index 1a7acd9f7..50125ef7e 100644 --- a/src/MongoDB/WriteResult.h +++ b/src/MongoDB/WriteResult.h @@ -21,6 +21,8 @@ #include +#include "phongo_structs.h" + php_phongo_writeresult_t* phongo_writeresult_init(zval* return_value, bson_t* reply, zval* manager, uint32_t server_id); #endif /* PHONGO_WRITERESULT_H */ diff --git a/src/phongo_classes.h b/src/phongo_classes.h index b0b9f01a4..1bdb0fafb 100644 --- a/src/phongo_classes.h +++ b/src/phongo_classes.h @@ -26,6 +26,14 @@ static inline php_phongo_bulkwrite_t* php_bulkwrite_fetch_object(zend_object* ob { return (php_phongo_bulkwrite_t*) ((char*) obj - XtOffsetOf(php_phongo_bulkwrite_t, std)); } +static inline php_phongo_bulkwritecommand_t* php_bulkwritecommand_fetch_object(zend_object* obj) +{ + return (php_phongo_bulkwritecommand_t*) ((char*) obj - XtOffsetOf(php_phongo_bulkwritecommand_t, std)); +} +static inline php_phongo_bulkwritecommandresult_t* php_bulkwritecommandresult_fetch_object(zend_object* obj) +{ + return (php_phongo_bulkwritecommandresult_t*) ((char*) obj - XtOffsetOf(php_phongo_bulkwritecommandresult_t, std)); +} static inline php_phongo_clientencryption_t* php_clientencryption_fetch_object(zend_object* obj) { return (php_phongo_clientencryption_t*) ((char*) obj - XtOffsetOf(php_phongo_clientencryption_t, std)); @@ -216,6 +224,8 @@ static inline php_phongo_topologyopeningevent_t* php_topologyopeningevent_fetch_ #define Z_SESSION_OBJ_P(zv) (php_session_fetch_object(Z_OBJ_P(zv))) #define Z_TOPOLOGYDESCRIPTION_OBJ_P(zv) (php_topologydescription_fetch_object(Z_OBJ_P(zv))) #define Z_BULKWRITE_OBJ_P(zv) (php_bulkwrite_fetch_object(Z_OBJ_P(zv))) +#define Z_BULKWRITECOMMAND_OBJ_P(zv) (php_bulkwritecommand_fetch_object(Z_OBJ_P(zv))) +#define Z_BULKWRITECOMMANDRESULT_OBJ_P(zv) (php_bulkwritecommandresult_fetch_object(Z_OBJ_P(zv))) #define Z_WRITECONCERN_OBJ_P(zv) (php_writeconcern_fetch_object(Z_OBJ_P(zv))) #define Z_WRITECONCERNERROR_OBJ_P(zv) (php_writeconcernerror_fetch_object(Z_OBJ_P(zv))) #define Z_WRITEERROR_OBJ_P(zv) (php_writeerror_fetch_object(Z_OBJ_P(zv))) @@ -262,6 +272,8 @@ static inline php_phongo_topologyopeningevent_t* php_topologyopeningevent_fetch_ #define Z_OBJ_SESSION(zo) (php_session_fetch_object(zo)) #define Z_OBJ_TOPOLOGYDESCRIPTION(zo) (php_topologydescription_fetch_object(zo)) #define Z_OBJ_BULKWRITE(zo) (php_bulkwrite_fetch_object(zo)) +#define Z_OBJ_BULKWRITECOMMAND(zo) (php_bulkwritecommand_fetch_object(zo)) +#define Z_OBJ_BULKWRITECOMMANDRESULT(zo) (php_bulkwritecommandresult_fetch_object(zo)) #define Z_OBJ_WRITECONCERN(zo) (php_writeconcern_fetch_object(zo)) #define Z_OBJ_WRITECONCERNERROR(zo) (php_writeconcernerror_fetch_object(zo)) #define Z_OBJ_WRITEERROR(zo) (php_writeerror_fetch_object(zo)) @@ -308,6 +320,8 @@ extern zend_class_entry* php_phongo_serverdescription_ce; extern zend_class_entry* php_phongo_session_ce; extern zend_class_entry* php_phongo_topologydescription_ce; extern zend_class_entry* php_phongo_bulkwrite_ce; +extern zend_class_entry* php_phongo_bulkwritecommand_ce; +extern zend_class_entry* php_phongo_bulkwritecommandresult_ce; extern zend_class_entry* php_phongo_writeconcern_ce; extern zend_class_entry* php_phongo_writeconcernerror_ce; extern zend_class_entry* php_phongo_writeerror_ce; @@ -328,6 +342,7 @@ extern zend_class_entry* php_phongo_encryptionexception_ce; extern zend_class_entry* php_phongo_executiontimeoutexception_ce; extern zend_class_entry* php_phongo_connectiontimeoutexception_ce; extern zend_class_entry* php_phongo_bulkwriteexception_ce; +extern zend_class_entry* php_phongo_bulkwritecommandexception_ce; extern zend_class_entry* php_phongo_type_ce; extern zend_class_entry* php_phongo_persistable_ce; @@ -409,6 +424,8 @@ extern void php_phongo_timestamp_interface_init_ce(INIT_FUNC_ARGS); extern void php_phongo_utcdatetime_interface_init_ce(INIT_FUNC_ARGS); extern void php_phongo_bulkwrite_init_ce(INIT_FUNC_ARGS); +extern void php_phongo_bulkwritecommand_init_ce(INIT_FUNC_ARGS); +extern void php_phongo_bulkwritecommandresult_init_ce(INIT_FUNC_ARGS); extern void php_phongo_clientencryption_init_ce(INIT_FUNC_ARGS); extern void php_phongo_command_init_ce(INIT_FUNC_ARGS); extern void php_phongo_cursor_init_ce(INIT_FUNC_ARGS); @@ -430,6 +447,7 @@ extern void php_phongo_cursor_interface_init_ce(INIT_FUNC_ARGS); extern void php_phongo_authenticationexception_init_ce(INIT_FUNC_ARGS); extern void php_phongo_bulkwriteexception_init_ce(INIT_FUNC_ARGS); +extern void php_phongo_bulkwritecommandexception_init_ce(INIT_FUNC_ARGS); extern void php_phongo_commandexception_init_ce(INIT_FUNC_ARGS); extern void php_phongo_connectionexception_init_ce(INIT_FUNC_ARGS); extern void php_phongo_connectiontimeoutexception_init_ce(INIT_FUNC_ARGS); diff --git a/src/phongo_error.c b/src/phongo_error.c index d50e10d86..f2d78f17c 100644 --- a/src/phongo_error.c +++ b/src/phongo_error.c @@ -189,7 +189,7 @@ void phongo_throw_exception_from_bson_error_t_and_reply(bson_error_t* error, con /* Server errors (other than ExceededTimeLimit) and write concern errors * may use CommandException and report the result document for the * failed command. For BC, ExceededTimeLimit errors will continue to use - * ExcecutionTimeoutException and omit the result document. */ + * ExecutionTimeoutException and omit the result document. */ if (reply && ((error->domain == MONGOC_ERROR_SERVER && error->code != PHONGO_SERVER_ERROR_EXCEEDED_TIME_LIMIT) || error->domain == MONGOC_ERROR_WRITE_CONCERN)) { zval zv; diff --git a/src/phongo_execute.c b/src/phongo_execute.c index 848b69edd..03aeca794 100644 --- a/src/phongo_execute.c +++ b/src/phongo_execute.c @@ -27,7 +27,10 @@ #include "phongo_execute.h" #include "phongo_util.h" +#include "MongoDB/BulkWriteCommand.h" +#include "MongoDB/BulkWriteCommandResult.h" #include "MongoDB/Cursor.h" +#include "MongoDB/Exception/BulkWriteCommandException.h" #include "MongoDB/ReadPreference.h" #include "MongoDB/Session.h" #include "MongoDB/WriteResult.h" @@ -333,6 +336,133 @@ bool phongo_execute_bulk_write(zval* manager, const char* namespace, php_phongo_ return success; } +bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_t* bwc, zval* zoptions, uint32_t server_id, zval* return_value) +{ + mongoc_client_t* client = NULL; + mongoc_bulkwrite_t* bw = bwc->bw; + mongoc_bulkwriteopts_t* bw_opts = NULL; + mongoc_bulkwritereturn_t bw_ret = { 0 }; + zval* zsession = NULL; + zval* zwriteConcern = NULL; + const mongoc_write_concern_t* write_concern = NULL; + bool success = true; + + client = Z_MANAGER_OBJ_P(manager)->client; + + if (!phongo_parse_session(zoptions, client, NULL, &zsession)) { + /* Exception should already have been thrown */ + return false; + } + + if (!phongo_parse_write_concern(zoptions, NULL, &zwriteConcern)) { + /* Exception should already have been thrown */ + return false; + } + + /* If a write concern was not specified, libmongoc will use the client's + * write concern. Check if an unacknowledged write concern would conflict + * with an explicit session. */ + write_concern = zwriteConcern ? Z_WRITECONCERN_OBJ_P(zwriteConcern)->write_concern : mongoc_client_get_write_concern(client); + + if (zsession && !mongoc_write_concern_is_acknowledged(write_concern)) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Cannot combine \"session\" option with an unacknowledged write concern"); + return false; + } + + mongoc_bulkwrite_set_client(bw, client); + + bw_opts = phongo_bwc_assemble_opts(bwc); + mongoc_bulkwriteopts_set_serverid(bw_opts, server_id); + + if (zsession) { + /* Save a reference to the session on the class struct to avoid leaving + * a dangling pointer within mongoc_bulkwrite_t. */ + ZVAL_ZVAL(&bwc->session, zsession, 1, 0); + mongoc_bulkwrite_set_session(bw, Z_SESSION_OBJ_P(zsession)->client_session); + } + + if (zwriteConcern) { + mongoc_bulkwriteopts_set_writeconcern(bw_opts, Z_WRITECONCERN_OBJ_P(zwriteConcern)->write_concern); + } + + bw_ret = mongoc_bulkwrite_execute(bw, bw_opts); + + phongo_bulkwritecommandresult_init(return_value, bw_ret.res); + + /* Error handling for mongoc_bulkwrite_execute differs significantly from + * mongoc_bulk_operation_execute. + * + * - There may or may not be a top-level error. Top-level errors include + * both logical errors (invalid arguments) and runtime errors (e.g. server + * selection failure). A bulk write failing due to write or write concern + * errors will typically not have a top-level error. + * + * - There may or may not be an error reply document. This document could be + * the response of a failed bulkWrite command, but it may also originate + * from libmongoc (e.g. server selection, appending a session, iterating + * BSON). This function only uses it to extrapolate error labels and it is + * otherwise accessible to the user through BulkWriteCommandException. + * + * - InvalidArgumentException may be thrown directly for a basic top-level + * error if there is no partial write result or error reply. Otherwise, + * BulkWriteCommandException is thrown. + */ + if (bw_ret.exc) { + success = false; + bson_error_t error = { 0 }; + bool has_top_level_error = mongoc_bulkwriteexception_error(bw_ret.exc, &error); + const bson_t* error_reply = mongoc_bulkwriteexception_errorreply(bw_ret.exc); + + /* Throw an exception if there is a top-level error and it does not + * originate from the server. Assuming we do not return early for an + * InvalidArgumentException, this first exception will be accessible + * via Exception::getPrevious(). + * + * TODO: MONGOC_ERROR_WRITE_CONCERN should never be reported as a + * top-level error by mongoc_bulkwrite_execute, so consider removing. + */ + if (has_top_level_error && error.domain != MONGOC_ERROR_SERVER && error.domain != MONGOC_ERROR_WRITE_CONCERN) { + phongo_throw_exception_from_bson_error_t_and_reply(&error, error_reply); + } + + /* Unlike mongoc_bulk_operation_execute, mongoc_bulkwrite_execute may + * report MONGOC_ERROR_COMMAND_INVALID_ARG alongside a partial result + * (CDRIVER-5842). Throw InvalidArgumentException directly iff there is + * neither a partial write result nor an error reply (we can assume + * there are no write or write concern errors for this case). */ + if (EG(exception) && EG(exception)->ce == php_phongo_invalidargumentexception_ce && !bw_ret.res && bson_empty(error_reply)) { + goto cleanup; + } + + if (EG(exception)) { + char* message; + + (void) spprintf(&message, 0, "Bulk write failed due to previous %s: %s", PHONGO_ZVAL_EXCEPTION_NAME(EG(exception)), error.message); + zend_throw_exception(php_phongo_bulkwritecommandexception_ce, message, 0); + efree(message); + } else { + zend_throw_exception(php_phongo_bulkwritecommandexception_ce, has_top_level_error ? error.message : "Bulk write failed", error.code); + } + + /* Initialize BulkWriteCommandException properties. Although a + * BulkWriteCommandResult is always returned on success, the partial + * result reported via the exception may be null. */ + php_phongo_bulkwritecommandexception_init_props(EG(exception), bw_ret.exc, bw_ret.res ? return_value : NULL); + + /* Ensure error labels are added to the final BulkWriteCommandException. + * If RuntimeException was previously thrown, labels may also have been + * added to it by phongo_throw_exception_from_bson_error_t_and_reply. */ + phongo_exception_add_error_labels(error_reply); + } + +cleanup: + mongoc_bulkwriteopts_destroy(bw_opts); + mongoc_bulkwriteresult_destroy(bw_ret.res); + mongoc_bulkwriteexception_destroy(bw_ret.exc); + + return success; +} + bool phongo_execute_command(zval* manager, php_phongo_command_type_t type, const char* db, zval* zcommand, zval* options, uint32_t server_id, zval* return_value) { mongoc_client_t* client; diff --git a/src/phongo_execute.h b/src/phongo_execute.h index 47312b9e7..b7307828b 100644 --- a/src/phongo_execute.h +++ b/src/phongo_execute.h @@ -22,6 +22,8 @@ #include +#include "phongo_structs.h" + /* This enum is used for processing options and selecting a libmongoc function * to use in phongo_execute_command. The values are important, as READ and WRITE * are also used as a bit field to determine whether readPreference, @@ -37,6 +39,7 @@ typedef enum { } php_phongo_command_type_t; bool phongo_execute_bulk_write(zval* manager, const char* namespace, php_phongo_bulkwrite_t* bulk_write, zval* zwriteConcern, uint32_t server_id, zval* return_value); +bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_t* bwc, zval* zoptions, uint32_t server_id, zval* return_value); bool phongo_execute_command(zval* manager, php_phongo_command_type_t type, const char* db, zval* zcommand, zval* zreadPreference, uint32_t server_id, zval* return_value); bool phongo_execute_query(zval* manager, const char* namespace, zval* zquery, zval* zreadPreference, uint32_t server_id, zval* return_value); diff --git a/src/phongo_structs.h b/src/phongo_structs.h index 5a22cfc59..7b083e612 100644 --- a/src/phongo_structs.h +++ b/src/phongo_structs.h @@ -38,6 +38,36 @@ typedef struct { zend_object std; } php_phongo_bulkwrite_t; +typedef struct { + mongoc_bulkwrite_t* bw; + int bypass; + bson_value_t* comment; + bson_t* let; + size_t num_ops; + bool ordered; + zval session; + bool verbose; + mongoc_write_concern_t* write_concern; + zend_object std; +} php_phongo_bulkwritecommand_t; + +typedef struct { + bool is_acknowledged; + int64_t inserted_count; + int64_t matched_count; + int64_t modified_count; + int64_t upserted_count; + int64_t deleted_count; + bson_t* insert_results; + bson_t* update_results; + bson_t* delete_results; + bson_t* write_errors; + bson_t* write_concern_errors; + bson_t* error_reply; + zval manager; + zend_object std; +} php_phongo_bulkwritecommandresult_t; + typedef struct { mongoc_client_encryption_t* client_encryption; zval key_vault_client_manager; diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt new file mode 100644 index 000000000..171781e28 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt @@ -0,0 +1,56 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() bypassDocumentValidation=true +--SKIPIF-- + + + + +--FILE-- +executeWriteCommand(DATABASE_NAME, new MongoDB\Driver\Command([ + 'create' => COLLECTION_NAME, + 'validator' => [ + '$jsonSchema' => [ + 'bsonType' => 'object', + 'required' => ['x'], + ], + ], +])); + +$bulk = new MongoDB\Driver\BulkWriteCommand(['bypassDocumentValidation' => true]); +$bulk->insertOne(NS, ['_id' => 1]); + +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(1) + ["matchedCount"]=> + int(0) + ["modifiedCount"]=> + int(0) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(0) + ["insertResults"]=> + NULL + ["updateResults"]=> + NULL + ["deleteResults"]=> + NULL +} +===DONE=== \ No newline at end of file diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt new file mode 100644 index 000000000..2f620769f --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt @@ -0,0 +1,106 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() bypassDocumentValidation=false +--SKIPIF-- + + + + +--FILE-- +executeWriteCommand(DATABASE_NAME, new MongoDB\Driver\Command([ + 'create' => COLLECTION_NAME, + 'validator' => [ + '$jsonSchema' => [ + 'bsonType' => 'object', + 'required' => ['x'], + ], + ], +])); + +$bulk = new MongoDB\Driver\BulkWriteCommand(['bypassDocumentValidation' => false]); +/* Include a successful write operation to ensure that mongoc_bulkwriteresult_t + * is populated (CDRIVER-5856). */ +$bulk->insertOne(NS, ['_id' => 1, 'x' => 1]); +$bulk->insertOne(NS, ['_id' => 2]); + +try { + $manager->executeBulkWriteCommand($bulk); +} catch (MongoDB\Driver\Exception\BulkWriteCommandException $e) { + printf("%s(%d): %s\n", get_class($e), $e->getCode(), $e->getMessage()); + var_dump($e->getPartialResult()); + var_dump($e->getWriteErrors()); +} + +?> +===DONE=== + +--EXPECTF-- +MongoDB\Driver\Exception\BulkWriteCommandException(0): Bulk write failed +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(1) + ["matchedCount"]=> + int(0) + ["modifiedCount"]=> + int(0) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(0) + ["insertResults"]=> + NULL + ["updateResults"]=> + NULL + ["deleteResults"]=> + NULL +} +array(1) { + [1]=> + object(MongoDB\Driver\WriteError)#%d (%d) { + ["message"]=> + string(26) "Document failed validation" + ["code"]=> + int(121) + ["index"]=> + int(1) + ["info"]=> + object(stdClass)#%d (%d) { + ["failingDocumentId"]=> + int(2) + ["details"]=> + object(stdClass)#%d (%d) { + ["operatorName"]=> + string(11) "$jsonSchema" + ["schemaRulesNotSatisfied"]=> + array(1) { + [0]=> + object(stdClass)#%d (%d) { + ["operatorName"]=> + string(8) "required" + ["specifiedAs"]=> + object(stdClass)#%d (%d) { + ["required"]=> + array(1) { + [0]=> + string(1) "x" + } + } + ["missingProperties"]=> + array(1) { + [0]=> + string(1) "x" + } + } + } + } + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-comment-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-comment-001.phpt new file mode 100644 index 000000000..2b978ea92 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-comment-001.phpt @@ -0,0 +1,61 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() comment +--SKIPIF-- + + + + +--FILE-- +getCommand(); + + if (!isset($command->comment)) { + printf("%s does not include comment option\n", $event->getCommandName()); + + return; + } + + printf("%s included comment: %s\n", $event->getCommandName(), json_encode($command->comment)); + } + + public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void + { + } + + public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event): void + { + } +} + +$manager = create_test_manager(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(['comment' => ['foo' => 1]]); +$bulk->insertOne(NS, ['_id' => 1]); + +$manager->addSubscriber(new CommandLogger); +$manager->executeBulkWriteCommand($bulk); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +bulkWrite included comment: {"foo":1} +find does not include comment option +array(1) { + [0]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(1) + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-comment_error-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-comment_error-001.phpt new file mode 100644 index 000000000..7d59c56b6 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-comment_error-001.phpt @@ -0,0 +1,27 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() comment option bsonSerialize() exception +--FILE-- + new Comment()]); +}, Exception::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got Exception +phongo_zval_to_bson_value fails +===DONE=== \ No newline at end of file diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt new file mode 100644 index 000000000..cfe77818b --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt @@ -0,0 +1,60 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() let +--SKIPIF-- + + + + +--FILE-- + 'cherry', + 'newFlavor' => 'orange', +]; + +$bulk = new MongoDB\Driver\BulkWriteCommand(['let' => $let]); + +$bulk->insertOne(NS, ['_id' => 1, 'flavor' => 'chocolate']); +$bulk->insertOne(NS, ['_id' => 2, 'flavor' => 'strawberry']); +$bulk->insertOne(NS, ['_id' => 3, 'flavor' => 'cherry']); + +$bulk->updateMany( + NS, + ['$expr' => ['$eq' => ['$flavor', '$$targetFlavor']]], + ['$set' => ['flavor' => '$$newFlavor']], +); + +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(3) + ["matchedCount"]=> + int(1) + ["modifiedCount"]=> + int(1) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(0) + ["insertResults"]=> + NULL + ["updateResults"]=> + NULL + ["deleteResults"]=> + NULL +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-let_error-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-let_error-001.phpt new file mode 100644 index 000000000..10a514b26 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-let_error-001.phpt @@ -0,0 +1,34 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() let option invalid type +--FILE-- + $invalidValue]); + }, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; +} + +echo throws(function() { + new MongoDB\Driver\BulkWriteCommand(['let' => MongoDB\BSON\PackedArray::fromPHP([])]); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "let" option to be array or object, bool given +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "let" option to be array or object, int given +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "let" option to be array or object, string given +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "let" option to be array or object, null given +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MongoDB\BSON\PackedArray cannot be serialized as a root document +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt new file mode 100644 index 000000000..30aa7a879 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt @@ -0,0 +1,67 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() ordered=true +--SKIPIF-- + + + + +--FILE-- + true]); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 2]); + +try { + $manager->executeBulkWriteCommand($bulk); +} catch (MongoDB\Driver\Exception\BulkWriteCommandException $e) { + printf("%s(%d): %s\n", get_class($e), $e->getCode(), $e->getMessage()); + var_dump($e->getPartialResult()); + var_dump($e->getWriteErrors()); +} + +?> +===DONE=== + +--EXPECTF-- +MongoDB\Driver\Exception\BulkWriteCommandException(0): Bulk write failed +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(1) + ["matchedCount"]=> + int(0) + ["modifiedCount"]=> + int(0) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(0) + ["insertResults"]=> + NULL + ["updateResults"]=> + NULL + ["deleteResults"]=> + NULL +} +array(1) { + [1]=> + object(MongoDB\Driver\WriteError)#%d (%d) { + ["message"]=> + string(128) "E11000 duplicate key error collection: phongo.bulkwritecommand_bulkwritecommand_ctor_ordered_001 index: _id_ dup key: { _id: 1 }" + ["code"]=> + int(11000) + ["index"]=> + int(1) + ["info"]=> + object(stdClass)#%d (%d) { + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt new file mode 100644 index 000000000..24ad876d4 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt @@ -0,0 +1,67 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() ordered=false +--SKIPIF-- + + + + +--FILE-- + false]); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 2]); + +try { + $manager->executeBulkWriteCommand($bulk); +} catch (MongoDB\Driver\Exception\BulkWriteCommandException $e) { + printf("%s(%d): %s\n", get_class($e), $e->getCode(), $e->getMessage()); + var_dump($e->getPartialResult()); + var_dump($e->getWriteErrors()); +} + +?> +===DONE=== + +--EXPECTF-- +MongoDB\Driver\Exception\BulkWriteCommandException(0): Bulk write failed +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(2) + ["matchedCount"]=> + int(0) + ["modifiedCount"]=> + int(0) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(0) + ["insertResults"]=> + NULL + ["updateResults"]=> + NULL + ["deleteResults"]=> + NULL +} +array(1) { + [1]=> + object(MongoDB\Driver\WriteError)#%d (%d) { + ["message"]=> + string(128) "E11000 duplicate key error collection: phongo.bulkwritecommand_bulkwritecommand_ctor_ordered_002 index: _id_ dup key: { _id: 1 }" + ["code"]=> + int(11000) + ["index"]=> + int(1) + ["info"]=> + object(stdClass)#%d (%d) { + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt new file mode 100644 index 000000000..e21572125 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt @@ -0,0 +1,98 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() verboseResults=true +--SKIPIF-- + + + + +--FILE-- + true]); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->updateOne(NS, ['_id' => 1], ['$set' => ['x' => 1]]); +$bulk->deleteOne(NS, ['_id' => 1]); + +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(1) + ["matchedCount"]=> + int(1) + ["modifiedCount"]=> + int(1) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(1) + ["insertResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(40) "HQAAAAMwABUAAAAQaW5zZXJ0ZWRJZAABAAAAAAA=" + ["value"]=> + object(stdClass)#%d (%d) { + ["0"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "FQAAABBpbnNlcnRlZElkAAEAAAAA" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(1) + } + } + } + } + ["updateResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(80) "OgAAAAMxADIAAAASbWF0Y2hlZENvdW50AAEAAAAAAAAAEm1vZGlmaWVkQ291bnQAAQAAAAAAAAAAAA==" + ["value"]=> + object(stdClass)#%d (%d) { + ["1"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(68) "MgAAABJtYXRjaGVkQ291bnQAAQAAAAAAAAASbW9kaWZpZWRDb3VudAABAAAAAAAAAAA=" + ["value"]=> + object(stdClass)#%d (%d) { + ["matchedCount"]=> + int(1) + ["modifiedCount"]=> + int(1) + } + } + } + } + ["deleteResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(48) "IwAAAAMyABsAAAASZGVsZXRlZENvdW50AAEAAAAAAAAAAAA=" + ["value"]=> + object(stdClass)#%d (%d) { + ["2"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(36) "GwAAABJkZWxldGVkQ291bnQAAQAAAAAAAAAA" + ["value"]=> + object(stdClass)#%d (%d) { + ["deletedCount"]=> + int(1) + } + } + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt new file mode 100644 index 000000000..33644ef75 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt @@ -0,0 +1,48 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() verboseResults=false +--SKIPIF-- + + + + +--FILE-- + false]); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->updateOne(NS, ['_id' => 1], ['$set' => ['x' => 1]]); +$bulk->deleteOne(NS, ['_id' => 1]); + +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(1) + ["matchedCount"]=> + int(1) + ["modifiedCount"]=> + int(1) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(1) + ["insertResults"]=> + NULL + ["updateResults"]=> + NULL + ["deleteResults"]=> + NULL +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-debug-001.phpt b/tests/bulkwritecommand/bulkwritecommand-debug-001.phpt new file mode 100644 index 000000000..d734a67f7 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-debug-001.phpt @@ -0,0 +1,39 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand debug output +--FILE-- + true, + 'comment' => 'foo', + 'let' => ['foo' => 1], + 'ordered' => true, + 'verboseResults' => true, +]); + +var_dump($bulk); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + bool(true) + ["comment"]=> + string(3) "foo" + ["let"]=> + object(stdClass)#%d (%d) { + ["foo"]=> + int(1) + } + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(true) + ["session"]=> + NULL +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-debug-002.phpt b/tests/bulkwritecommand/bulkwritecommand-debug-002.phpt new file mode 100644 index 000000000..a29dab5a2 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-debug-002.phpt @@ -0,0 +1,53 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand debug output after execution +--SKIPIF-- + + + + +--FILE-- + $manager->startSession()], +]; + +foreach ($tests as $options) { + $bulk = new MongoDB\Driver\BulkWriteCommand(); + $bulk->insertOne(NS, ['x' => 1]); + $manager->executeBulkWriteCommand($bulk, $options); + var_dump($bulk); +} + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + NULL + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(false) + ["session"]=> + NULL +} +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + NULL + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(false) + ["session"]=> + object(MongoDB\Driver\Session)#%d (%d) { + %a + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt b/tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt new file mode 100644 index 000000000..3698a1c3e --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt @@ -0,0 +1,25 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::insertOne() returns document ID +--SKIPIF-- + +--FILE-- +insertOne(NS, ['_id' => 1])); +var_dump($bulk->insertOne(NS, [])); + + +?> +===DONE=== + +--EXPECTF-- +int(1) +object(MongoDB\BSON\ObjectId)#%d (%d) { + ["oid"]=> + string(24) "%x" +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommandresult-debug-001.phpt b/tests/bulkwritecommand/bulkwritecommandresult-debug-001.phpt new file mode 100644 index 000000000..808c9ee77 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommandresult-debug-001.phpt @@ -0,0 +1,46 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommandResult debug output with unacknowledged write concern +--SKIPIF-- + + + + +--FILE-- + 0]); + +$bulk = new MongoDB\Driver\BulkWriteCommand(['ordered' => false]); +$bulk->insertOne(NS, ['_id' => 1]); + +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(false) + ["insertedCount"]=> + int(0) + ["matchedCount"]=> + int(0) + ["modifiedCount"]=> + int(0) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(0) + ["insertResults"]=> + NULL + ["updateResults"]=> + NULL + ["deleteResults"]=> + NULL +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommandresult-isAcknowledged-001.phpt b/tests/bulkwritecommand/bulkwritecommandresult-isAcknowledged-001.phpt new file mode 100644 index 000000000..f936934fb --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommandresult-isAcknowledged-001.phpt @@ -0,0 +1,76 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommandResult::isAcknowledged() with unacknowledged write concern +--SKIPIF-- + + + + +--FILE-- + 0]); + +$bulk = new MongoDB\Driver\BulkWriteCommand(['ordered' => false]); +$bulk->insertOne(NS, ['_id' => 1]); + +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result->isAcknowledged()); + +// Additionally test that other accessor methods cannot be called +echo throws(function() use ($result) { + $result->getInsertedCount(); +}, MongoDB\Driver\Exception\LogicException::class), "\n"; + +echo throws(function() use ($result) { + $result->getMatchedCount(); +}, MongoDB\Driver\Exception\LogicException::class), "\n"; + +echo throws(function() use ($result) { + $result->getModifiedCount(); +}, MongoDB\Driver\Exception\LogicException::class), "\n"; + +echo throws(function() use ($result) { + $result->getUpsertedCount(); +}, MongoDB\Driver\Exception\LogicException::class), "\n"; + +echo throws(function() use ($result) { + $result->getDeletedCount(); +}, MongoDB\Driver\Exception\LogicException::class), "\n"; + +echo throws(function() use ($result) { + $result->getInsertResults(); +}, MongoDB\Driver\Exception\LogicException::class), "\n"; + +echo throws(function() use ($result) { + $result->getUpdateResults(); +}, MongoDB\Driver\Exception\LogicException::class), "\n"; + +echo throws(function() use ($result) { + $result->getDeleteResults(); +}, MongoDB\Driver\Exception\LogicException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +bool(false) +OK: Got MongoDB\Driver\Exception\LogicException +MongoDB\Driver\BulkWriteCommandResult::getInsertedCount() should not be called for an unacknowledged write result +OK: Got MongoDB\Driver\Exception\LogicException +MongoDB\Driver\BulkWriteCommandResult::getMatchedCount() should not be called for an unacknowledged write result +OK: Got MongoDB\Driver\Exception\LogicException +MongoDB\Driver\BulkWriteCommandResult::getModifiedCount() should not be called for an unacknowledged write result +OK: Got MongoDB\Driver\Exception\LogicException +MongoDB\Driver\BulkWriteCommandResult::getUpsertedCount() should not be called for an unacknowledged write result +OK: Got MongoDB\Driver\Exception\LogicException +MongoDB\Driver\BulkWriteCommandResult::getDeletedCount() should not be called for an unacknowledged write result +OK: Got MongoDB\Driver\Exception\LogicException +MongoDB\Driver\BulkWriteCommandResult::getInsertResults() should not be called for an unacknowledged write result +OK: Got MongoDB\Driver\Exception\LogicException +MongoDB\Driver\BulkWriteCommandResult::getUpdateResults() should not be called for an unacknowledged write result +OK: Got MongoDB\Driver\Exception\LogicException +MongoDB\Driver\BulkWriteCommandResult::getDeleteResults() should not be called for an unacknowledged write result +===DONE=== diff --git a/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt new file mode 100644 index 000000000..626f60247 --- /dev/null +++ b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt @@ -0,0 +1,179 @@ +--TEST-- +MongoDB\Driver\Manager::executeBulkWriteCommand() +--SKIPIF-- + + + + +--FILE-- + true]); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 2]); +$bulk->insertOne(NS, ['_id' => 3]); +$bulk->insertOne(NS, ['_id' => 4]); +$bulk->insertOne(NS, ['_id' => 5]); +$bulk->replaceOne(NS, ['_id' => 1], ['x' => 1]); +$bulk->updateOne(NS, ['_id' => 2], ['$set' => ['x' => 1]]); +$bulk->updateMany(NS, ['x' => ['$exists' => true]], ['$inc' => ['x' => 1]]); +$bulk->deleteOne(NS, ['_id' => ['$gt' => 4]]); +$bulk->deleteMany(NS, ['_id' => ['$gt' => 2]]); + +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(5) + ["matchedCount"]=> + int(4) + ["modifiedCount"]=> + int(4) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(3) + ["insertResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(168) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["0"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(1) + } + } + ["1"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(2) + } + } + ["2"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(3) + } + } + ["3"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(4) + } + } + ["4"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(5) + } + } + } + } + ["updateResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(220) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["5"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(68) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["matchedCount"]=> + int(1) + ["modifiedCount"]=> + int(1) + } + } + ["6"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(68) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["matchedCount"]=> + int(1) + ["modifiedCount"]=> + int(1) + } + } + ["7"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(68) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["matchedCount"]=> + int(2) + ["modifiedCount"]=> + int(2) + } + } + } + } + ["deleteResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(88) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["8"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(36) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["deletedCount"]=> + int(1) + } + } + ["9"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(36) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["deletedCount"]=> + int(2) + } + } + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/manager-executeBulkWriteCommand_error-001.phpt b/tests/bulkwritecommand/manager-executeBulkWriteCommand_error-001.phpt new file mode 100644 index 000000000..c21b1d30b --- /dev/null +++ b/tests/bulkwritecommand/manager-executeBulkWriteCommand_error-001.phpt @@ -0,0 +1,31 @@ +--TEST-- +MongoDB\Driver\Manager::executeBulkWriteCommand() prohibits session and unacknowledged write concern +--SKIPIF-- + + + + +--FILE-- +insertOne(NS, ['_id' => 1]); + +echo throws(function() use ($manager, $bulk) { + $manager->executeBulkWriteCommand($bulk, [ + 'session' => $manager->startSession(), + 'writeConcern' => new MongoDB\Driver\WriteConcern(0), + ]); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Cannot combine "session" option with an unacknowledged write concern +===DONE=== diff --git a/tests/bulkwritecommand/manager-executeBulkWriteCommand_error-002.phpt b/tests/bulkwritecommand/manager-executeBulkWriteCommand_error-002.phpt new file mode 100644 index 000000000..e5e74084c --- /dev/null +++ b/tests/bulkwritecommand/manager-executeBulkWriteCommand_error-002.phpt @@ -0,0 +1,30 @@ +--TEST-- +MongoDB\Driver\Manager::executeBulkWriteCommand() prohibits session and unacknowledged write concern (inherited) +--SKIPIF-- + + + + +--FILE-- + 0]); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->insertOne(NS, ['_id' => 1]); + +echo throws(function() use ($manager, $bulk) { + $manager->executeBulkWriteCommand($bulk, [ + 'session' => $manager->startSession(), + ]); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Cannot combine "session" option with an unacknowledged write concern +===DONE=== diff --git a/tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt b/tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt new file mode 100644 index 000000000..f5b1c7244 --- /dev/null +++ b/tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt @@ -0,0 +1,180 @@ +--TEST-- +MongoDB\Driver\Server::executeBulkWriteCommand() +--SKIPIF-- + + + + +--FILE-- +selectServer(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(['verboseResults' => true]); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 2]); +$bulk->insertOne(NS, ['_id' => 3]); +$bulk->insertOne(NS, ['_id' => 4]); +$bulk->insertOne(NS, ['_id' => 5]); +$bulk->replaceOne(NS, ['_id' => 1], ['x' => 1]); +$bulk->updateOne(NS, ['_id' => 2], ['$set' => ['x' => 1]]); +$bulk->updateMany(NS, ['x' => ['$exists' => true]], ['$inc' => ['x' => 1]]); +$bulk->deleteOne(NS, ['_id' => ['$gt' => 4]]); +$bulk->deleteMany(NS, ['_id' => ['$gt' => 2]]); + +$result = $server->executeBulkWriteCommand($bulk); + +var_dump($result); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { + ["isAcknowledged"]=> + bool(true) + ["insertedCount"]=> + int(5) + ["matchedCount"]=> + int(4) + ["modifiedCount"]=> + int(4) + ["upsertedCount"]=> + int(0) + ["deletedCount"]=> + int(3) + ["insertResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(168) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["0"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(1) + } + } + ["1"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(2) + } + } + ["2"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(3) + } + } + ["3"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(4) + } + } + ["4"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(28) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["insertedId"]=> + int(5) + } + } + } + } + ["updateResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(220) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["5"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(68) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["matchedCount"]=> + int(1) + ["modifiedCount"]=> + int(1) + } + } + ["6"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(68) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["matchedCount"]=> + int(1) + ["modifiedCount"]=> + int(1) + } + } + ["7"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(68) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["matchedCount"]=> + int(2) + ["modifiedCount"]=> + int(2) + } + } + } + } + ["deleteResults"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(88) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["8"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(36) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["deletedCount"]=> + int(1) + } + } + ["9"]=> + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(36) "%s" + ["value"]=> + object(stdClass)#%d (%d) { + ["deletedCount"]=> + int(2) + } + } + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/server-executeBulkWriteCommand_error-001.phpt b/tests/bulkwritecommand/server-executeBulkWriteCommand_error-001.phpt new file mode 100644 index 000000000..4c2ddbbca --- /dev/null +++ b/tests/bulkwritecommand/server-executeBulkWriteCommand_error-001.phpt @@ -0,0 +1,32 @@ +--TEST-- +MongoDB\Driver\Server::executeBulkWriteCommand() prohibits session and unacknowledged write concern +--SKIPIF-- + + + + +--FILE-- +selectServer(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->insertOne(NS, ['_id' => 1]); + +echo throws(function() use ($manager, $server, $bulk) { + $server->executeBulkWriteCommand($bulk, [ + 'session' => $manager->startSession(), + 'writeConcern' => new MongoDB\Driver\WriteConcern(0), + ]); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Cannot combine "session" option with an unacknowledged write concern +===DONE=== diff --git a/tests/bulkwritecommand/server-executeBulkWriteCommand_error-002.phpt b/tests/bulkwritecommand/server-executeBulkWriteCommand_error-002.phpt new file mode 100644 index 000000000..6be57bd0b --- /dev/null +++ b/tests/bulkwritecommand/server-executeBulkWriteCommand_error-002.phpt @@ -0,0 +1,31 @@ +--TEST-- +MongoDB\Driver\Server::executeBulkWriteCommand() prohibits session and unacknowledged write concern (inherited) +--SKIPIF-- + + + + +--FILE-- + 0]); +$server = $manager->selectServer(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->insertOne(NS, ['_id' => 1]); + +echo throws(function() use ($manager, $server, $bulk) { + $server->executeBulkWriteCommand($bulk, [ + 'session' => $manager->startSession(), + ]); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Cannot combine "session" option with an unacknowledged write concern +===DONE=== From e3fa20ce410c4fc50f2f8c66dcfbf563faa3597d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 17 Apr 2025 11:18:48 +0200 Subject: [PATCH 2/2] PHPC-2571 Import BulkWrite tests for Client BulkWriteCommand (#1819) --- .../bulkwritecommand-debug-003.phpt | 106 +++++++++++++++ .../bulkwritecommand-deleteOne-001.phpt | 85 ++++++++++++ .../bulkwritecommand-deleteOne-002.phpt | 55 ++++++++ .../bulkwritecommand-deleteOne-003.phpt | 33 +++++ .../bulkwritecommand-deleteOne_error-001.phpt | 27 ++++ .../bulkwritecommand-deleteOne_error-002.phpt | 27 ++++ .../bulkwritecommand-deleteOne_error-003.phpt | 55 ++++++++ .../bulkwritecommand-deleteOne_error-004.phpt | 33 +++++ .../bulkwritecommand-insertOne-001.phpt | 72 ++++++++++ .../bulkwritecommand-insertOne-002.phpt | 30 +++++ .../bulkwritecommand-insertOne-003.phpt | 54 ++++++++ .../bulkwritecommand-insertOne-004.phpt | 126 ++++++++++++++++++ .../bulkwritecommand-insertOne_error-001.phpt | 20 +++ .../bulkwritecommand-insertOne_error-002.phpt | 20 +++ .../bulkwritecommand-insertOne_error-003.phpt | 34 +++++ .../bulkwritecommand-insertone-001.phpt | 25 ---- .../bulkwritecommand-updateOne-001.phpt | 109 +++++++++++++++ .../bulkwritecommand-updateOne-002.phpt | 84 ++++++++++++ .../bulkwritecommand-updateOne-003.phpt | 65 +++++++++ .../bulkwritecommand-updateOne-004.phpt | 55 ++++++++ .../bulkwritecommand-updateOne-005.phpt | 36 +++++ .../bulkwritecommand-updateOne-006.phpt | 65 +++++++++ .../bulkwritecommand-updateOne-007.phpt | 87 ++++++++++++ .../bulkwritecommand-updateOne-008.phpt | 81 +++++++++++ .../bulkwritecommand-updateOne_error-003.phpt | 48 +++++++ .../bulkwritecommand-updateOne_error-004.phpt | 41 ++++++ .../bulkwritecommand-updateOne_error-005.phpt | 76 +++++++++++ .../bulkwritecommand-updateOne_error-006.phpt | 31 +++++ 28 files changed, 1555 insertions(+), 25 deletions(-) create mode 100644 tests/bulkwritecommand/bulkwritecommand-debug-003.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-deleteOne-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-deleteOne-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-deleteOne-003.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-deleteOne_error-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-deleteOne_error-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-deleteOne_error-003.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-deleteOne_error-004.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertOne-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertOne-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertOne-003.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertOne-004.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertOne_error-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertOne_error-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertOne_error-003.phpt delete mode 100644 tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne-002.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne-003.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne-004.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne-005.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne-006.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne-007.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne-008.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne_error-003.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne_error-004.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne_error-005.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-updateOne_error-006.phpt diff --git a/tests/bulkwritecommand/bulkwritecommand-debug-003.phpt b/tests/bulkwritecommand/bulkwritecommand-debug-003.phpt new file mode 100644 index 000000000..971f55381 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-debug-003.phpt @@ -0,0 +1,106 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand debug output before execution +--FILE-- + true], + ['ordered' => false], + ['bypassDocumentValidation' => true], + ['bypassDocumentValidation' => false], + ['comment' => ['foo' => 1]], + ['let' => ['id' => 1, 'x' => 'foo']], +]; + +foreach ($tests as $options) { + var_dump(new MongoDB\Driver\BulkWriteCommand($options)); +} + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + NULL + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(false) + ["session"]=> + NULL +} +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + NULL + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(false) + ["session"]=> + NULL +} +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + NULL + ["ordered"]=> + bool(false) + ["verboseResults"]=> + bool(false) + ["session"]=> + NULL +} +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + bool(true) + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(false) + ["session"]=> + NULL +} +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + bool(false) + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(false) + ["session"]=> + NULL +} +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + NULL + ["comment"]=> + object(stdClass)#2 (1) { + ["foo"]=> + int(1) + } + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(false) + ["session"]=> + NULL +} +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + NULL + ["let"]=> + object(stdClass)#2 (2) { + ["id"]=> + int(1) + ["x"]=> + string(3) "foo" + } + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(false) + ["session"]=> + NULL +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-deleteOne-001.phpt b/tests/bulkwritecommand/bulkwritecommand-deleteOne-001.phpt new file mode 100644 index 000000000..eaa45f502 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-deleteOne-001.phpt @@ -0,0 +1,85 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::deleteOne() should always encode __pclass for Persistable objects +--SKIPIF-- + + + + +--FILE-- +id = $id; + $this->child = $child; + } + + public function bsonSerialize(): array + { + return [ + '_id' => $this->id, + 'child' => $this->child, + ]; + } + + public function bsonUnserialize(array $data): void + { + $this->id = $data['_id']; + $this->child = $data['child']; + } +} + +$manager = create_test_manager(); + +$document = new MyClass('foo', new MyClass('bar', new MyClass('baz'))); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->insertOne(NS, $document); +$result = $manager->executeBulkWriteCommand($bulk); +printf("Inserted %d document(s)\n", $result->getInsertedCount()); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->deleteOne(NS, $document); +$result = $manager->executeBulkWriteCommand($bulk); +printf("Deleted %d document(s)\n", $result->getDeletedCount()); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +Inserted 1 document(s) +array(1) { + [0]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "foo" + ["child":"MyClass":private]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "bar" + ["child":"MyClass":private]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "baz" + ["child":"MyClass":private]=> + NULL + } + } + } +} +Deleted 1 document(s) +array(0) { +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-deleteOne-002.phpt b/tests/bulkwritecommand/bulkwritecommand-deleteOne-002.phpt new file mode 100644 index 000000000..c53ec4208 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-deleteOne-002.phpt @@ -0,0 +1,55 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::deleteOne() with hint option +--SKIPIF-- + + + + +--FILE-- +getCommandName() !== 'bulkWrite') { + return; + } + + printf("delete included hint: %s\n", json_encode($event->getCommand()->ops[0]->hint)); + } + + public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void + { + } + + public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event): void + { + } +} + +$manager = create_test_manager(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->insertOne(NS, ['x' => 1]); +$bulk->insertOne(NS, ['x' => 2]); +$manager->executeBulkWriteCommand($bulk); + +MongoDB\Driver\Monitoring\addSubscriber(new CommandLogger); + +$bulk = new MongoDB\Driver\BulkWriteCommand; +$bulk->deleteOne(NS, ['_id' => 1], ['hint' => '_id_']); +$manager->executeBulkWriteCommand($bulk); + +$bulk = new MongoDB\Driver\BulkWriteCommand; +$bulk->deleteOne(NS, ['_id' => 2], ['hint' => ['_id' => 1]]); +$manager->executeBulkWriteCommand($bulk); + +?> +===DONE=== + +--EXPECTF-- +delete included hint: "_id_" +delete included hint: {"_id":1} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-deleteOne-003.phpt b/tests/bulkwritecommand/bulkwritecommand-deleteOne-003.phpt new file mode 100644 index 000000000..172a7a6ef --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-deleteOne-003.phpt @@ -0,0 +1,33 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::deleteOne() $filter is MongoDB\BSON\Document +--SKIPIF-- + + + + +--FILE-- +insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 2]); +$manager->executeBulkWriteCommand($bulk); + +$filter = MongoDB\BSON\Document::fromJSON('{ "_id": { "$gt": 1 } }'); + +$bulk = new MongoDB\Driver\BulkWriteCommand; +$bulk->deleteOne(NS, $filter); +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result->getDeletedCount()); + +?> +===DONE=== + +--EXPECT-- +int(1) +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-001.phpt b/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-001.phpt new file mode 100644 index 000000000..aaae4bdca --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-001.phpt @@ -0,0 +1,27 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::deleteOne() with invalid options +--FILE-- +deleteOne(NS, ['x' => 1], ['collation' => 1]); +}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, ['x' => 1], ['hint' => 1]); +}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "collation" option to be array or object, int given + +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "hint" option to be string, array, or object, int given +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-002.phpt b/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-002.phpt new file mode 100644 index 000000000..f59ab761e --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-002.phpt @@ -0,0 +1,27 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::deleteOne() with BSON encoding error (invalid UTF-8 string) +--FILE-- +deleteOne(NS, ['x' => "\xc3\x28"]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, ['x' => 1], ['collation' => ['locale' => "\xc3\x28"]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n"; + +?> +===DONE=== + +--EXPECTF-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Detected invalid UTF-8 for field path "x": %s + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Detected invalid UTF-8 for field path "locale": %s +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-003.phpt b/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-003.phpt new file mode 100644 index 000000000..c6b7ba1b1 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-003.phpt @@ -0,0 +1,55 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::deleteOne() with BSON encoding error (null bytes in keys) +--FILE-- +deleteOne(NS, ["\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, ["x\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, ["\0\0\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, ['x' => 1], ['collation' => ["\0" => 1]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, ['x' => 1], ['collation' => ["x\0" => 1]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, ['x' => 1], ['collation' => ["\0\0\0" => 1]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "x". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "x". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-004.phpt b/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-004.phpt new file mode 100644 index 000000000..52abea739 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-deleteOne_error-004.phpt @@ -0,0 +1,33 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::deleteOne() prohibits PackedArray for document values +--FILE-- +deleteOne(NS, MongoDB\BSON\PackedArray::fromPHP([])); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, [], ['collation' => MongoDB\BSON\PackedArray::fromPHP([])]); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +// @TODO: ALMOST: Got MongoDB\Driver\Exception\InvalidArgumentException - expected MongoDB\Driver\Exception\UnexpectedValueException +// Expected "hint" option to yield string or document but got "array" +echo throws(function() use ($bulk) { + $bulk->deleteOne(NS, [], ['hint' => MongoDB\BSON\PackedArray::fromPHP([])]); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MongoDB\BSON\PackedArray cannot be serialized as a root document +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MongoDB\BSON\PackedArray cannot be serialized as a root document +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "hint" option to yield string or document but got "array" +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertOne-001.phpt b/tests/bulkwritecommand/bulkwritecommand-insertOne-001.phpt new file mode 100644 index 000000000..9a9a77d0d --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-insertOne-001.phpt @@ -0,0 +1,72 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::insertOne() should always encode __pclass for Persistable objects +--SKIPIF-- + + + + +--FILE-- +id = $id; + $this->child = $child; + } + + public function bsonSerialize(): array + { + return [ + '_id' => $this->id, + 'child' => $this->child, + ]; + } + + public function bsonUnserialize(array $data): void + { + $this->id = $data['_id']; + $this->child = $data['child']; + } +} + +$manager = create_test_manager(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->insertOne(NS, new MyClass('foo', new MyClass('bar', new MyClass('baz')))); +$result = $manager->executeBulkWriteCommand($bulk); +printf("Inserted %d document(s)\n", $result->getInsertedCount()); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +Inserted 1 document(s) +array(1) { + [0]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "foo" + ["child":"MyClass":private]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "bar" + ["child":"MyClass":private]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "baz" + ["child":"MyClass":private]=> + NULL + } + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertOne-002.phpt b/tests/bulkwritecommand/bulkwritecommand-insertOne-002.phpt new file mode 100644 index 000000000..b69431c74 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-insertOne-002.phpt @@ -0,0 +1,30 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::insertOne() $document is MongoDB\BSON\Document +--SKIPIF-- + + + + +--FILE-- +insertOne(NS, $document); +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($insertedId); +var_dump($result->getInsertedCount()); + +?> +===DONE=== + +--EXPECT-- +int(2) +int(1) +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertOne-003.phpt b/tests/bulkwritecommand/bulkwritecommand-insertOne-003.phpt new file mode 100644 index 000000000..f4497591a --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-insertOne-003.phpt @@ -0,0 +1,54 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::insertOne() appends ID when inserting Document instances +--SKIPIF-- + + + + +--FILE-- + 'bar']); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); + +$insertedId = $bulk->insertOne(NS, $document); + +$result = $manager->executeBulkWriteCommand($bulk); +printf("Inserted %d document(s)\n", $result->getInsertedCount()); + +var_dump($insertedId); +var_dump($document->toPHP()); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +Inserted 1 document(s) +object(MongoDB\BSON\ObjectId)#%d (%d) { + ["oid"]=> + string(24) "%x" +} +object(stdClass)#%d (%d) { + ["foo"]=> + string(3) "bar" +} +array(1) { + [0]=> + object(stdClass)#%d (%d) { + ["_id"]=> + object(MongoDB\BSON\ObjectId)#%d (%d) { + ["oid"]=> + string(24) "%x" + } + ["foo"]=> + string(3) "bar" + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertOne-004.phpt b/tests/bulkwritecommand/bulkwritecommand-insertOne-004.phpt new file mode 100644 index 000000000..9c9363600 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-insertOne-004.phpt @@ -0,0 +1,126 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::insertOne() returns "_id" of inserted document +--SKIPIF-- + + + + +--FILE-- +id = $id; + } + + public function bsonSerialize(): array + { + return ['id' => $this->id]; + } +} + +class MyPersistableId extends MySerializableId implements MongoDB\BSON\Persistable +{ + public function bsonUnserialize(array $data): void + { + $this->id = $data['id']; + } +} + +$documents = [ + ['x' => 1], + ['_id' => new MongoDB\BSON\ObjectId('590b72d606e9660190656a55')], + ['_id' => ['foo' => 1]], + ['_id' => new MySerializableId('foo')], + ['_id' => new MyPersistableId('bar')], +]; + +$manager = create_test_manager(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); + +foreach ($documents as $document) { + var_dump($bulk->insertOne(NS, $document)); +} + +$result = $manager->executeBulkWriteCommand($bulk); +printf("Inserted %d document(s)\n", $result->getInsertedCount()); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\BSON\ObjectId)#%d (%d) { + ["oid"]=> + string(24) "%x" +} +object(MongoDB\BSON\ObjectId)#%d (%d) { + ["oid"]=> + string(24) "590b72d606e9660190656a55" +} +object(stdClass)#%d (%d) { + ["foo"]=> + int(1) +} +object(stdClass)#%d (%d) { + ["id"]=> + string(3) "foo" +} +object(MyPersistableId)#%d (%d) { + ["id"]=> + string(3) "bar" +} +Inserted 5 document(s) +array(5) { + [0]=> + object(stdClass)#%d (%d) { + ["_id"]=> + object(MongoDB\BSON\ObjectId)#%d (%d) { + ["oid"]=> + string(24) "%x" + } + ["x"]=> + int(1) + } + [1]=> + object(stdClass)#%d (%d) { + ["_id"]=> + object(MongoDB\BSON\ObjectId)#%d (%d) { + ["oid"]=> + string(24) "590b72d606e9660190656a55" + } + } + [2]=> + object(stdClass)#%d (%d) { + ["_id"]=> + object(stdClass)#%d (%d) { + ["foo"]=> + int(1) + } + } + [3]=> + object(stdClass)#%d (%d) { + ["_id"]=> + object(stdClass)#%d (%d) { + ["id"]=> + string(3) "foo" + } + } + [4]=> + object(stdClass)#%d (%d) { + ["_id"]=> + object(MyPersistableId)#%d (%d) { + ["id"]=> + string(3) "bar" + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertOne_error-001.phpt b/tests/bulkwritecommand/bulkwritecommand-insertOne_error-001.phpt new file mode 100644 index 000000000..135378a88 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-insertOne_error-001.phpt @@ -0,0 +1,20 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::insertOne() prohibits PackedArray for document +--FILE-- +insertOne(NS, MongoDB\BSON\PackedArray::fromPHP([])); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MongoDB\BSON\PackedArray cannot be serialized as a root document +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertOne_error-002.phpt b/tests/bulkwritecommand/bulkwritecommand-insertOne_error-002.phpt new file mode 100644 index 000000000..04d9a9fb8 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-insertOne_error-002.phpt @@ -0,0 +1,20 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::insertOne() with BSON encoding error (invalid UTF-8 string) +--FILE-- +insertOne(NS, ['x' => "\xc3\x28"]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n"; + +?> +===DONE=== + +--EXPECTF-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Detected invalid UTF-8 for field path "x": %s +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertOne_error-003.phpt b/tests/bulkwritecommand/bulkwritecommand-insertOne_error-003.phpt new file mode 100644 index 000000000..26e8b9b41 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-insertOne_error-003.phpt @@ -0,0 +1,34 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::insertOne() with BSON encoding error (null bytes in keys) +--FILE-- +insertOne(NS, ["\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->insertOne(NS, ["x\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->insertOne(NS, ["\0\0\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "x". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt b/tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt deleted file mode 100644 index 3698a1c3e..000000000 --- a/tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -MongoDB\Driver\BulkWriteCommand::insertOne() returns document ID ---SKIPIF-- - ---FILE-- -insertOne(NS, ['_id' => 1])); -var_dump($bulk->insertOne(NS, [])); - - -?> -===DONE=== - ---EXPECTF-- -int(1) -object(MongoDB\BSON\ObjectId)#%d (%d) { - ["oid"]=> - string(24) "%x" -} -===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne-001.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne-001.phpt new file mode 100644 index 000000000..60ecbe600 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne-001.phpt @@ -0,0 +1,109 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() should always encode __pclass for Persistable objects +--SKIPIF-- + + + + +--FILE-- +id = $id; + $this->child = $child; + } + + public function bsonSerialize(): array + { + return [ + '_id' => $this->id, + 'child' => $this->child, + ]; + } + + public function bsonUnserialize(array $data): void + { + $this->id = $data['_id']; + $this->child = $data['child']; + } +} + +$manager = create_test_manager(); + +$document = new MyClass('foo', new MyClass('bar', new MyClass('baz'))); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->updateOne(NS, + ['_id' => 'foo'], + ['$set' => $document], + ['upsert' => true] +); +$result = $manager->executeBulkWriteCommand($bulk); +printf("Upserted %d document(s)\n", $result->getUpsertedCount()); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->updateOne(NS, + $document, + ['$set' => ['child' => new MyClass('yip', new MyClass('yap'))]] +); +$result = $manager->executeBulkWriteCommand($bulk); +printf("Modified %d document(s)\n", $result->getModifiedCount()); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +Upserted 1 document(s) +array(1) { + [0]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "foo" + ["child":"MyClass":private]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "bar" + ["child":"MyClass":private]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "baz" + ["child":"MyClass":private]=> + NULL + } + } + } +} +Modified 1 document(s) +array(1) { + [0]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "foo" + ["child":"MyClass":private]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "yip" + ["child":"MyClass":private]=> + object(MyClass)#%d (%d) { + ["id":"MyClass":private]=> + string(3) "yap" + ["child":"MyClass":private]=> + NULL + } + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne-002.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne-002.phpt new file mode 100644 index 000000000..9a5d147e9 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne-002.phpt @@ -0,0 +1,84 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() with arrayFilters option +--SKIPIF-- + + + + +--FILE-- +insertOne(NS, [ '_id' => 1, 'grades' => [ 95, 92, 90 ] ]); +$bulk->insertOne(NS, [ '_id' => 2, 'grades' => [ 98, 100, 102 ] ]); +$bulk->insertOne(NS, [ '_id' => 3, 'grades' => [ 95, 110, 100 ] ]); + +$manager->executeBulkWriteCommand($bulk); + +$updateBulk = new MongoDB\Driver\BulkWriteCommand(); + +$query = ['grades' => ['$gte' => 100]]; +$update = [ '$set' => [ 'grades.$[element]' => 100 ] ]; +$options = [ + 'arrayFilters' => [ [ 'element' => [ '$gte' => 100 ] ] ], + 'multi' => true +]; + +$updateBulk->updateMany(NS, $query, $update, $options); +$manager->executeBulkWriteCommand($updateBulk); + +$cursor = $manager->executeQuery(NS, new \MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); +?> +===DONE=== + +--EXPECTF-- +array(%d) { + [0]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(1) + ["grades"]=> + array(%d) { + [0]=> + int(95) + [1]=> + int(92) + [2]=> + int(90) + } + } + [1]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(2) + ["grades"]=> + array(%d) { + [0]=> + int(98) + [1]=> + int(100) + [2]=> + int(100) + } + } + [2]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(3) + ["grades"]=> + array(%d) { + [0]=> + int(95) + [1]=> + int(100) + [2]=> + int(100) + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne-003.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne-003.phpt new file mode 100644 index 000000000..1f44fee44 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne-003.phpt @@ -0,0 +1,65 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() with pipeline option +--SKIPIF-- + + + + +--FILE-- +insertOne(NS, [ '_id' => 1, 'x' => 1, 'y' => 1, 't' => [ 'u' => [ 'v' => 1 ] ] ]); +$bulk->insertOne(NS, [ '_id' => 2, 'x' => 2, 'y' => 1]); + +$manager->executeBulkWriteCommand($bulk); + +$updateBulk = new MongoDB\Driver\BulkWriteCommand(); + +$query = ['_id' => 1]; +$update = [ + [ + '$replaceRoot' => [ 'newRoot' => '$t' ], + ], + [ + '$addFields' => [ 'foo' => 1 ], + ], +]; + +$updateBulk->updateMany(NS, $query, $update); +$manager->executeBulkWriteCommand($updateBulk); + +$cursor = $manager->executeQuery(NS, new \MongoDB\Driver\Query([])); +var_dump($cursor->toArray()); +?> +===DONE=== + +--EXPECTF-- +array(%d) { + [0]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(1) + ["u"]=> + object(stdClass)#%d (%d) { + ["v"]=> + int(1) + } + ["foo"]=> + int(1) + } + [1]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(2) + ["x"]=> + int(2) + ["y"]=> + int(1) + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne-004.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne-004.phpt new file mode 100644 index 000000000..a1850f387 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne-004.phpt @@ -0,0 +1,55 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() with hint option +--SKIPIF-- + + + + +--FILE-- +getCommandName() !== 'bulkWrite') { + return; + } + + printf("update included hint: %s\n", json_encode($event->getCommand()->ops[0]->hint)); + } + + public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void + { + } + + public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event): void + { + } +} + +$manager = create_test_manager(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->insertOne(NS, ['x' => 1]); +$bulk->insertOne(NS, ['x' => 2]); +$manager->executeBulkWriteCommand($bulk); + +MongoDB\Driver\Monitoring\addSubscriber(new CommandLogger); + +$bulk = new MongoDB\Driver\BulkWriteCommand; +$bulk->updateOne(NS, ['_id' => 1], ['$set' => ['x' => 11]], ['hint' => '_id_']); +$manager->executeBulkWriteCommand($bulk); + +$bulk = new MongoDB\Driver\BulkWriteCommand; +$bulk->updateOne(NS, ['_id' => 2], ['$set' => ['x' => 22]], ['hint' => ['_id' => 1]]); +$manager->executeBulkWriteCommand($bulk); + +?> +===DONE=== + +--EXPECTF-- +update included hint: "_id_" +update included hint: {"_id":1} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne-005.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne-005.phpt new file mode 100644 index 000000000..ab01dc0d0 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne-005.phpt @@ -0,0 +1,36 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() $filter and $newObj are MongoDB\BSON\Document +--SKIPIF-- + + + + +--FILE-- +insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 2]); +$manager->executeBulkWriteCommand($bulk); + +$filter = MongoDB\BSON\Document::fromJSON('{ "_id": { "$gt": 1 } }'); +$newObj = MongoDB\BSON\Document::fromJSON('{ "$set": { "x": 1 } }'); + +$bulk = new MongoDB\Driver\BulkWriteCommand; +$bulk->updateMany(NS, $filter, $newObj); +$result = $manager->executeBulkWriteCommand($bulk); + +var_dump($result->getMatchedCount()); +var_dump($result->getModifiedCount()); + +?> +===DONE=== + +--EXPECT-- +int(1) +int(1) +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne-006.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne-006.phpt new file mode 100644 index 000000000..d572cb489 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne-006.phpt @@ -0,0 +1,65 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() PackedArray for update pipeline +--SKIPIF-- + + + + +--FILE-- +insertOne(NS, ['_id' => 1, 'x' => 1, 'y' => 1, 't' => ['u' => ['v' => 1]]]); +$bulk->insertOne(NS, ['_id' => 2, 'x' => 2, 'y' => 1]); + +$manager->executeBulkWriteCommand($bulk); + +$updateBulk = new MongoDB\Driver\BulkWriteCommand(); + +$updateBulk->updateOne( + NS, + ['_id' => 1], + MongoDB\BSON\PackedArray::fromPHP([ + ['$replaceRoot' => ['newRoot' => '$t']], + ['$addFields' => ['foo' => 1]], + ]) +); + +$manager->executeBulkWriteCommand($updateBulk); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); + +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +array(%d) { + [0]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(1) + ["u"]=> + object(stdClass)#%d (%d) { + ["v"]=> + int(1) + } + ["foo"]=> + int(1) + } + [1]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(2) + ["x"]=> + int(2) + ["y"]=> + int(1) + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne-007.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne-007.phpt new file mode 100644 index 000000000..984e1234b --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne-007.phpt @@ -0,0 +1,87 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() PackedArray for arrayFilters option +--SKIPIF-- + + + + +--FILE-- +insertOne(NS, ['_id' => 1, 'grades' => [95, 92, 90]]); +$bulk->insertOne(NS, ['_id' => 2, 'grades' => [98, 100, 102]]); +$bulk->insertOne(NS, ['_id' => 3, 'grades' => [95, 110, 100]]); + +$manager->executeBulkWriteCommand($bulk); + +$updateBulk = new MongoDB\Driver\BulkWriteCommand(); + +$updateBulk->updateMany(NS, + ['grades' => ['$gte' => 100]], + ['$set' => ['grades.$[element]' => 100]], + [ + 'arrayFilters' => MongoDB\BSON\PackedArray::fromPHP([['element' => ['$gte' => 100]]]), + 'multi' => true, + ] +); + +$manager->executeBulkWriteCommand($updateBulk); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); + +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +array(%d) { + [0]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(1) + ["grades"]=> + array(%d) { + [0]=> + int(95) + [1]=> + int(92) + [2]=> + int(90) + } + } + [1]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(2) + ["grades"]=> + array(%d) { + [0]=> + int(98) + [1]=> + int(100) + [2]=> + int(100) + } + } + [2]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(3) + ["grades"]=> + array(%d) { + [0]=> + int(95) + [1]=> + int(100) + [2]=> + int(100) + } + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne-008.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne-008.phpt new file mode 100644 index 000000000..d1c12cf73 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne-008.phpt @@ -0,0 +1,81 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() with sort option +--SKIPIF-- + + + + +--FILE-- +getCommandName() !== 'bulkWrite') { + return; + } + + printf("update included sort: %s\n", json_encode($event->getCommand()->ops[0]->sort)); + } + + public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void + { + } + + public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event): void + { + } +} + +$manager = create_test_manager(); + +$bulk = new MongoDB\Driver\BulkWriteCommand(); +$bulk->insertOne(NS, ['_id' => 1]); +$bulk->insertOne(NS, ['_id' => 2]); +$bulk->insertOne(NS, ['_id' => 3]); +$manager->executeBulkWriteCommand($bulk); + +MongoDB\Driver\Monitoring\addSubscriber(new CommandLogger); + +$bulk = new MongoDB\Driver\BulkWriteCommand; +$bulk->updateOne(NS, ['_id' => ['$gt' => 1]], ['$set' => ['x' => 11]], ['sort' => ['_id' => 1]]); +$manager->executeBulkWriteCommand($bulk); + +$bulk = new MongoDB\Driver\BulkWriteCommand; +$bulk->updateOne(NS, ['_id' => ['$gt' => 1]], ['$set' => ['x' => 22]], ['sort' => ['_id' => -1]]); +$manager->executeBulkWriteCommand($bulk); + +$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); + +var_dump($cursor->toArray()); + +?> +===DONE=== + +--EXPECTF-- +update included sort: {"_id":1} +update included sort: {"_id":-1} +array(3) { + [0]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(1) + } + [1]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(2) + ["x"]=> + int(11) + } + [2]=> + object(stdClass)#%d (%d) { + ["_id"]=> + int(3) + ["x"]=> + int(22) + } +} +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne_error-003.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne_error-003.phpt new file mode 100644 index 000000000..3a8f3f5a0 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne_error-003.phpt @@ -0,0 +1,48 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() with invalid options +--FILE-- +updateOne(NS, ['x' => 1], ['$set' => ['y' => 1]], ['collation' => 1]); +}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['$set' => ['y' => 1]], ['collation' => 1]); +}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['$set' => ['y' => 1]], ['arrayFilters' => 1]); +}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['$set' => ['y' => 1]], ['arrayFilters' => ['foo' => 'bar']]); +}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['$set' => ['y' => 1]], ['hint' => 1]); +}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "collation" option to be array or object, int given + +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "collation" option to be array or object, int given + +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "arrayFilters" option to be array or object, int given + +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "arrayFilters" option to yield array but got non-sequential keys + +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "hint" option to be string, array, or object, int given +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne_error-004.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne_error-004.phpt new file mode 100644 index 000000000..cc7a91a5a --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne_error-004.phpt @@ -0,0 +1,41 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() with BSON encoding error (invalid UTF-8 string) +--FILE-- +updateOne(NS, ['x' => "\xc3\x28"], ['x' => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['x' => "\xc3\x28"]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['$set' => ['x' => "\xc3\x28"]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['y' => 1], ['collation' => ['locale' => "\xc3\x28"]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n"; + +?> +===DONE=== + +--EXPECTF-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Detected invalid UTF-8 for field path "x": %s + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Detected invalid UTF-8 for field path "x": %s + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Detected invalid UTF-8 for field path "$set.x": %s + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Detected invalid UTF-8 for field path "locale": %s +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne_error-005.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne_error-005.phpt new file mode 100644 index 000000000..0cb3dfd8c --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne_error-005.phpt @@ -0,0 +1,76 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() with BSON encoding error (null bytes in keys) +--FILE-- +updateOne(NS, ["\0" => 1], ['x' => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ["x\0" => 1], ['x' => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ["\0\0\0" => 1], ['x' => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ["\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ["x\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ["\0\0\0" => 1]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['y' => 1], ['collation' => ["\0" => 1]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['y' => 1], ['collation' => ["x\0" => 1]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, ['x' => 1], ['y' => 1], ['collation' => ["\0\0\0" => 1]]); +}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "x". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "x". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "x". + +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +BSON keys cannot contain null bytes. Unexpected null byte after "". +===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-updateOne_error-006.phpt b/tests/bulkwritecommand/bulkwritecommand-updateOne_error-006.phpt new file mode 100644 index 000000000..20ca761ea --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-updateOne_error-006.phpt @@ -0,0 +1,31 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::updateOne() prohibits PackedArray for document values +--FILE-- +updateOne(NS, MongoDB\BSON\PackedArray::fromPHP([]), []); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, [], [], ['collation' => MongoDB\BSON\PackedArray::fromPHP([])]); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +echo throws(function() use ($bulk) { + $bulk->updateOne(NS, [], [], ['hint' => MongoDB\BSON\PackedArray::fromPHP([])]); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MongoDB\BSON\PackedArray cannot be serialized as a root document +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MongoDB\BSON\PackedArray cannot be serialized as a root document +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Expected "hint" option to yield string or document but got "array" +===DONE===