From 60a54dd0b2c5681b7752ec8d26b4d2bcc77e7144 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 10 Dec 2024 14:58:11 -0500 Subject: [PATCH 01/20] PHPC-2495, PHPC-2490, PHPC-2491, PHPC-2492: BulkWriteCommand ctor and ops --- config.m4 | 1 + config.w32 | 2 +- php_phongo.c | 1 + src/MongoDB/BulkWriteCommand.c | 836 ++++++++++++++++++ src/MongoDB/BulkWriteCommand.stub.php | 30 + src/MongoDB/BulkWriteCommand_arginfo.h | 79 ++ src/phongo_classes.h | 8 + src/phongo_structs.h | 13 + .../bulkwritecommand-insertone-001.phpt | 25 + 9 files changed, 994 insertions(+), 1 deletion(-) create mode 100644 src/MongoDB/BulkWriteCommand.c create mode 100644 src/MongoDB/BulkWriteCommand.stub.php create mode 100644 src/MongoDB/BulkWriteCommand_arginfo.h create mode 100644 tests/bulkwritecommand/bulkwritecommand-insertone-001.phpt diff --git a/config.m4 b/config.m4 index e056c2661..e97edc75c 100644 --- a/config.m4 +++ b/config.m4 @@ -167,6 +167,7 @@ if test "$PHP_MONGODB" != "no"; then src/BSON/UTCDateTime.c \ src/BSON/UTCDateTimeInterface.c \ src/MongoDB/BulkWrite.c \ + src/MongoDB/BulkWriteCommand.c \ src/MongoDB/ClientEncryption.c \ src/MongoDB/Command.c \ src/MongoDB/Cursor.c \ diff --git a/config.w32 b/config.w32 index 22979021a..2137f6640 100644 --- a/config.w32 +++ b/config.w32 @@ -116,7 +116,7 @@ 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", "BulkWrite.c BulkWriteCommand.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/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); diff --git a/php_phongo.c b/php_phongo.c index f947d3942..1f760312c 100644 --- a/php_phongo.c +++ b/php_phongo.c @@ -252,6 +252,7 @@ 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_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); diff --git a/src/MongoDB/BulkWriteCommand.c b/src/MongoDB/BulkWriteCommand.c new file mode 100644 index 000000000..653fb29b3 --- /dev/null +++ b/src/MongoDB/BulkWriteCommand.c @@ -0,0 +1,836 @@ +/* + * 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; + +// 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(); + + // TODO: Consider removing initialization for zero values + 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; + intern->write_concern = NULL; + + 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"); + } + + if (php_array_existsc(zoptions, "writeConcern")) { + zval* value = php_array_fetchc_deref(zoptions, "writeConcern"); + + if (Z_TYPE_P(value) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(value), php_phongo_writeconcern_ce)) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"writeConcern\" option to be %s, %s given", ZSTR_VAL(php_phongo_writeconcern_ce->name), zend_zval_type_name(value)); + return; + } + + intern->write_concern = mongoc_write_concern_copy(phongo_write_concern_from_zval(value)); + } +} + +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, execute) +{ + php_phongo_bulkwritecommand_t* intern; + + intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + RETURN_LONG(intern->num_ops); +} + +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 (!mongoc_bulkwrite_append_insertone(intern->bw, ns, &bdocument, NULL, &error)) { + phongo_throw_exception_from_bson_error_t(&error); + goto cleanup; + } + + intern->num_ops++; + + if (!bson_out) { + phongo_throw_exception(PHONGO_ERROR_LOGIC, "php_phongo_zval_to_bson() did not return document identifier. Please file a bug report."); + goto cleanup; + } + + 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* zupdate; + zval* zoptions = NULL; + bson_t bfilter = BSON_INITIALIZER; + bson_t bupdate = 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(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_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, "upsert")) { + mongoc_bulkwrite_replaceoneopts_set_upsert(opts, php_array_fetchc_bool(zoptions, "upsert")); + } + } + + if (!mongoc_bulkwrite_append_replaceone(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_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, "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); + } + + if (intern->write_concern) { + mongoc_write_concern_destroy(intern->write_concern); + } +} + +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"); + } + + if (intern->write_concern) { + zval write_concern; + + php_phongo_write_concern_to_zval(&write_concern, intern->write_concern); + ADD_ASSOC_ZVAL_EX(&retval, "write_concern", &write_concern); + } else { + ADD_ASSOC_NULL_EX(&retval, "write_concern"); + } + +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.stub.php b/src/MongoDB/BulkWriteCommand.stub.php new file mode 100644 index 000000000..d26f9f2ca --- /dev/null +++ b/src/MongoDB/BulkWriteCommand.stub.php @@ -0,0 +1,30 @@ +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/phongo_classes.h b/src/phongo_classes.h index b0b9f01a4..e6cab1a55 100644 --- a/src/phongo_classes.h +++ b/src/phongo_classes.h @@ -26,6 +26,10 @@ 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_clientencryption_t* php_clientencryption_fetch_object(zend_object* obj) { return (php_phongo_clientencryption_t*) ((char*) obj - XtOffsetOf(php_phongo_clientencryption_t, std)); @@ -216,6 +220,7 @@ 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_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 +267,7 @@ 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_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 +314,7 @@ 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_writeconcern_ce; extern zend_class_entry* php_phongo_writeconcernerror_ce; extern zend_class_entry* php_phongo_writeerror_ce; @@ -409,6 +416,7 @@ 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_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); diff --git a/src/phongo_structs.h b/src/phongo_structs.h index 5a22cfc59..13a6123d3 100644 --- a/src/phongo_structs.h +++ b/src/phongo_structs.h @@ -38,6 +38,19 @@ 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 { mongoc_client_encryption_t* client_encryption; zval key_vault_client_manager; 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=== From d197dda2a3798b9db8719b8abdb5f8f68bc4dcfd Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 2 Jan 2025 16:47:24 -0500 Subject: [PATCH 02/20] PHPC-2494: BulkWriteCommandResult and BulkWriteCommandException It was possible to reuse WriteConcernError and WriteError with slight changes to their init functions. --- config.m4 | 2 + config.w32 | 4 +- php_phongo.c | 2 + src/MongoDB/BulkWriteCommandResult.c | 455 ++++++++++++++++++ src/MongoDB/BulkWriteCommandResult.h | 28 ++ src/MongoDB/BulkWriteCommandResult.stub.php | 40 ++ src/MongoDB/BulkWriteCommandResult_arginfo.h | 82 ++++ .../Exception/BulkWriteCommandException.c | 41 ++ .../BulkWriteCommandException.stub.php | 15 + .../BulkWriteCommandException_arginfo.h | 31 ++ src/MongoDB/WriteConcernError.c | 8 +- src/MongoDB/WriteError.c | 19 +- src/MongoDB/WriteError.h | 1 + src/phongo_classes.h | 10 + src/phongo_structs.h | 18 + 15 files changed, 748 insertions(+), 8 deletions(-) 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/Exception/BulkWriteCommandException.c create mode 100644 src/MongoDB/Exception/BulkWriteCommandException.stub.php create mode 100644 src/MongoDB/Exception/BulkWriteCommandException_arginfo.h diff --git a/config.m4 b/config.m4 index e97edc75c..c3f90dccf 100644 --- a/config.m4 +++ b/config.m4 @@ -168,6 +168,7 @@ if test "$PHP_MONGODB" != "no"; then 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 \ @@ -187,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 2137f6640..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 BulkWriteCommand.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 1f760312c..98a0b6d0e 100644 --- a/php_phongo.c +++ b/php_phongo.c @@ -253,6 +253,7 @@ PHP_MINIT_FUNCTION(mongodb) /* {{{ */ 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); @@ -278,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/BulkWriteCommandResult.c b/src/MongoDB/BulkWriteCommandResult.c new file mode 100644 index 000000000..9c61197f8 --- /dev/null +++ b/src/MongoDB/BulkWriteCommandResult.c @@ -0,0 +1,455 @@ +/* + * Copyright 2014-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_error.h" + +#include "BSON/Document.h" +#include "MongoDB/Server.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; + +static bool php_phongo_bulkwritecommandresult_get_writeconcernerrors(php_phongo_bulkwritecommandresult_t* intern, zval* return_value) +{ + bson_iter_t iter; + + array_init(return_value); + + for (bson_iter_init(&iter, intern->write_concern_errors); 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)) { + zval_ptr_dtor(&write_concern_error); + continue; + } + + add_next_index_zval(return_value, &write_concern_error); + } + + return true; +} + +static bool php_phongo_bulkwritecommandresult_get_writeerrors(php_phongo_bulkwritecommandresult_t* intern, zval* return_value) +{ + bson_iter_t iter; + + array_init(return_value); + + for (bson_iter_init(&iter, intern->write_errors); bson_iter_next(&iter);) { + bson_t bson; + uint32_t len; + const uint8_t* data; + zval write_error; + + if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { + continue; + } + + bson_iter_document(&iter, &len, &data); + + if (!bson_init_static(&bson, data, len)) { + continue; + } + + if (!phongo_writeerror_init_ex(&write_error, &bson, atoi(bson_iter_key(&iter)))) { + zval_ptr_dtor(&write_error); + continue; + } + + add_next_index_zval(return_value, &write_error); + } + + return true; +} + +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); +} + +/* Returns the last Server used to execute a command for the bulk write */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getServer) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getServer"); + + // TODO: null handling + phongo_server_init(return_value, &intern->manager, intern->server_id); +} + +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); + } +} + +/* Return any write concern errors that occurred */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteConcernErrors) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + // TODO: null handling + php_phongo_bulkwritecommandresult_get_writeconcernerrors(intern, return_value); +} + +/* Returns any write errors that occurred */ +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteErrors) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + // TODO: null handling + php_phongo_bulkwritecommandresult_get_writeerrors(intern, return_value); +} + +static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getErrorReply) +{ + php_phongo_bulkwritecommandresult_t* intern; + + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); + + PHONGO_PARSE_PARAMETERS_NONE(); + + // TODO: null handling + phongo_document_new(return_value, intern->error_reply, 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); + bson_destroy(intern->error_reply); + bson_destroy(intern->write_errors); + bson_destroy(intern->write_concern_errors); + + if (!Z_ISUNDEF(intern->manager)) { + zval_ptr_dtor(&intern->manager); + } +} + +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"); + } + + if (intern->write_errors) { + zval writeerrors; + + php_phongo_bulkwritecommandresult_get_writeerrors(intern, &writeerrors); + ADD_ASSOC_ZVAL_EX(&retval, "writeErrors", &writeerrors); + } else { + ADD_ASSOC_NULL_EX(&retval, "writeErrors"); + } + + if (intern->write_concern_errors) { + zval writeconcernerrors; + + php_phongo_bulkwritecommandresult_get_writeconcernerrors(intern, &writeconcernerrors); + ADD_ASSOC_ZVAL_EX(&retval, "writeConcernErrors", &writeconcernerrors); + } else { + ADD_ASSOC_NULL_EX(&retval, "writeConcernErrors"); + } + + if (intern->error_reply) { + zval error_reply; + + phongo_document_new(&error_reply, intern->error_reply, true); + ADD_ASSOC_ZVAL_EX(&retval, "errorReply", &error_reply); + } else { + ADD_ASSOC_NULL_EX(&retval, "errorReply"); + } + + if (intern->server_id) { + zval server; + + phongo_server_init(&server, &intern->manager, intern->server_id); + ADD_ASSOC_ZVAL_EX(&retval, "server", &server); + } else { + /* TODO: Determine if this path is only reached when a partial result is + * attached to a BulkWriteCommandException on an unacknowledged write. */ + ADD_ASSOC_NULL_EX(&retval, "server"); + } + + 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); +} + +php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* return_value, mongoc_bulkwritereturn_t* bw_ret, zval* manager) +{ + php_phongo_bulkwritecommandresult_t* bwcr; + + object_init_ex(return_value, php_phongo_bulkwritecommandresult_ce); + + bwcr = Z_BULKWRITECOMMANDRESULT_OBJ_P(return_value); + bwcr->is_acknowledged = !!bw_ret->res; + + // Copy mongoc_bulkwriteresult_t fields + if (bw_ret->res) { + bwcr->inserted_count = mongoc_bulkwriteresult_insertedcount(bw_ret->res); + bwcr->upserted_count = mongoc_bulkwriteresult_upsertedcount(bw_ret->res); + bwcr->matched_count = mongoc_bulkwriteresult_matchedcount(bw_ret->res); + bwcr->modified_count = mongoc_bulkwriteresult_modifiedcount(bw_ret->res); + bwcr->deleted_count = mongoc_bulkwriteresult_deletedcount(bw_ret->res); + +#define BSON_COPY_OR_NULL(bson) ((bson) ? bson_copy(bson) : NULL) + bwcr->insert_results = BSON_COPY_OR_NULL(mongoc_bulkwriteresult_insertresults(bw_ret->res)); + bwcr->update_results = BSON_COPY_OR_NULL(mongoc_bulkwriteresult_updateresults(bw_ret->res)); + bwcr->delete_results = BSON_COPY_OR_NULL(mongoc_bulkwriteresult_deleteresults(bw_ret->res)); +#undef BSON_COPY_OR_NULL + + bwcr->server_id = mongoc_bulkwriteresult_serverid(bw_ret->res); + } + + // Copy mongoc_bulkwriteexception_t fields + if (bw_ret->exc) { + bwcr->error_reply = bson_copy(mongoc_bulkwriteexception_errorreply(bw_ret->exc)); + bwcr->write_errors = bson_copy(mongoc_bulkwriteexception_writeerrors(bw_ret->exc)); + bwcr->write_concern_errors = bson_copy(mongoc_bulkwriteexception_writeconcernerrors(bw_ret->exc)); + } + + ZVAL_ZVAL(&bwcr->manager, manager, 1, 0); + + return bwcr; +} diff --git a/src/MongoDB/BulkWriteCommandResult.h b/src/MongoDB/BulkWriteCommandResult.h new file mode 100644 index 000000000..567e2a1fc --- /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_bulkwritereturn_t* bw_ret, zval* manager); + +#endif /* PHONGO_BULKWRITECOMMANDRESULT_H */ diff --git a/src/MongoDB/BulkWriteCommandResult.stub.php b/src/MongoDB/BulkWriteCommandResult.stub.php new file mode 100644 index 000000000..e316ee8b1 --- /dev/null +++ b/src/MongoDB/BulkWriteCommandResult.stub.php @@ -0,0 +1,40 @@ +ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NOT_SERIALIZABLE; + + return class_entry; +} diff --git a/src/MongoDB/Exception/BulkWriteCommandException.c b/src/MongoDB/Exception/BulkWriteCommandException.c new file mode 100644 index 000000000..739e458f8 --- /dev/null +++ b/src/MongoDB/Exception/BulkWriteCommandException.c @@ -0,0 +1,41 @@ +/* + * 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 "BulkWriteCommandException_arginfo.h" + +zend_class_entry* php_phongo_bulkwritecommandexception_ce; + +/* Returns the BulkWriteCommandResult from the failed write operation. */ +static PHP_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getBulkWriteCommandResult) +{ + zval* bwcr; + zval rv; + + PHONGO_PARSE_PARAMETERS_NONE(); + + bwcr = zend_read_property(php_phongo_bulkwritecommandexception_ce, Z_OBJ_P(getThis()), ZEND_STRL("bulkWriteCommandResult"), 0, &rv); + + RETURN_ZVAL(bwcr, 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); +} diff --git a/src/MongoDB/Exception/BulkWriteCommandException.stub.php b/src/MongoDB/Exception/BulkWriteCommandException.stub.php new file mode 100644 index 000000000..9d0053462 --- /dev/null +++ b/src/MongoDB/Exception/BulkWriteCommandException.stub.php @@ -0,0 +1,15 @@ +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; diff --git a/src/MongoDB/WriteError.c b/src/MongoDB/WriteError.c index 5db082d20..5befa1d2e 100644 --- a/src/MongoDB/WriteError.c +++ b/src/MongoDB/WriteError.c @@ -144,6 +144,11 @@ void php_phongo_writeerror_init_ce(INIT_FUNC_ARGS) } zend_bool phongo_writeerror_init(zval* return_value, bson_t* bson) +{ + return phongo_writeerror_init_ex(return_value, bson, 0); +} + +zend_bool phongo_writeerror_init_ex(zval* return_value, bson_t* bson, int32_t index) { bson_iter_t iter; php_phongo_writeerror_t* intern; @@ -152,20 +157,24 @@ 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; @@ -179,7 +188,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 will 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..3cb641bf5 100644 --- a/src/MongoDB/WriteError.h +++ b/src/MongoDB/WriteError.h @@ -22,5 +22,6 @@ #include zend_bool phongo_writeerror_init(zval* return_value, bson_t* bson); +zend_bool phongo_writeerror_init_ex(zval* return_value, bson_t* bson, int32_t index); #endif /* PHONGO_WRITEERROR_H */ diff --git a/src/phongo_classes.h b/src/phongo_classes.h index e6cab1a55..1bdb0fafb 100644 --- a/src/phongo_classes.h +++ b/src/phongo_classes.h @@ -30,6 +30,10 @@ static inline php_phongo_bulkwritecommand_t* php_bulkwritecommand_fetch_object(z { 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)); @@ -221,6 +225,7 @@ static inline php_phongo_topologyopeningevent_t* php_topologyopeningevent_fetch_ #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))) @@ -268,6 +273,7 @@ static inline php_phongo_topologyopeningevent_t* php_topologyopeningevent_fetch_ #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)) @@ -315,6 +321,7 @@ 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; @@ -335,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; @@ -417,6 +425,7 @@ 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); @@ -438,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_structs.h b/src/phongo_structs.h index 13a6123d3..d0988872e 100644 --- a/src/phongo_structs.h +++ b/src/phongo_structs.h @@ -51,6 +51,24 @@ typedef struct { 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; + uint32_t server_id; + zend_object std; +} php_phongo_bulkwritecommandresult_t; + typedef struct { mongoc_client_encryption_t* client_encryption; zval key_vault_client_manager; From d0af8f90d265afc86803044ac15f14a64352f34b Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 2 Jan 2025 16:49:24 -0500 Subject: [PATCH 03/20] Include missing header in WriteResult.h This was not necessary for compilation, but it makes the header internally consistent. --- src/MongoDB/WriteResult.h | 2 ++ 1 file changed, 2 insertions(+) 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 */ From 61ffb2d14f1185210d396021d63f14508bedb8f0 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 2 Jan 2025 16:52:05 -0500 Subject: [PATCH 04/20] PHPC-2493: Manager and Server::executeBulkWriteCommand() --- src/MongoDB/BulkWriteCommand.c | 39 +++++++++---- src/MongoDB/BulkWriteCommand.h | 26 +++++++++ src/MongoDB/BulkWriteCommand.stub.php | 2 - src/MongoDB/BulkWriteCommand_arginfo.h | 8 +-- 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/phongo_execute.c | 79 ++++++++++++++++++++++++++ src/phongo_execute.h | 3 + 12 files changed, 217 insertions(+), 22 deletions(-) create mode 100644 src/MongoDB/BulkWriteCommand.h diff --git a/src/MongoDB/BulkWriteCommand.c b/src/MongoDB/BulkWriteCommand.c index 653fb29b3..022149534 100644 --- a/src/MongoDB/BulkWriteCommand.c +++ b/src/MongoDB/BulkWriteCommand.c @@ -34,6 +34,34 @@ 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); + + if (intern->write_concern) { + mongoc_bulkwriteopts_set_writeconcern(opts, intern->write_concern); + } + + 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) @@ -381,17 +409,6 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, deleteOne) mongoc_bulkwrite_deleteoneopts_destroy(opts); } -static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, execute) -{ - php_phongo_bulkwritecommand_t* intern; - - intern = Z_BULKWRITECOMMAND_OBJ_P(getThis()); - - PHONGO_PARSE_PARAMETERS_NONE(); - - RETURN_LONG(intern->num_ops); -} - static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, insertOne) { php_phongo_bulkwritecommand_t* intern; 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 index d26f9f2ca..c8554917f 100644 --- a/src/MongoDB/BulkWriteCommand.stub.php +++ b/src/MongoDB/BulkWriteCommand.stub.php @@ -25,6 +25,4 @@ final public function replaceOne(string $namespace, array|object $filter, array| final public function updateOne(string $namespace, array|object $filter, array|object $update, ?array $options = null): void {} final public function updateMany(string $namespace, array|object $filter, array|object $update, ?array $options = null): void {} - - final public function execute(Manager|Server $managerOrServer): mixed {} } diff --git a/src/MongoDB/BulkWriteCommand_arginfo.h b/src/MongoDB/BulkWriteCommand_arginfo.h index 6663b5f34..1dde4d410 100644 --- a/src/MongoDB/BulkWriteCommand_arginfo.h +++ b/src/MongoDB/BulkWriteCommand_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e2198c12dd76c9056a16ee6cb86b8af059fdcd6f */ + * 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") @@ -37,10 +37,6 @@ ZEND_END_ARG_INFO() #define arginfo_class_MongoDB_Driver_BulkWriteCommand_updateMany arginfo_class_MongoDB_Driver_BulkWriteCommand_updateOne -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommand_execute, 0, 1, IS_MIXED, 0) - ZEND_ARG_OBJ_TYPE_MASK(0, managerOrServer, MongoDB\\Driver\\Manager|MongoDB\\Driver\\Server, 0, NULL) -ZEND_END_ARG_INFO() - static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, __construct); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, count); @@ -50,7 +46,6 @@ 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 ZEND_METHOD(MongoDB_Driver_BulkWriteCommand, execute); static const zend_function_entry class_MongoDB_Driver_BulkWriteCommand_methods[] = { @@ -62,7 +57,6 @@ static const zend_function_entry class_MongoDB_Driver_BulkWriteCommand_methods[] 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_ME(MongoDB_Driver_BulkWriteCommand, execute, arginfo_class_MongoDB_Driver_BulkWriteCommand_execute, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_FE_END }; diff --git a/src/MongoDB/Manager.c b/src/MongoDB/Manager.c index f24a383fe..8d08f6a4e 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..e0bef5d7f 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..57ac1d94e 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: ae0c35849b6ad73edf96a3ecdbe42b6940af0e6a */ 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, 1) + 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..58e007c61 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..3c2dbbf64 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..70104ef69 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: 36c3e152527ee774d3dfbae26d24fea7bfa3c6f3 */ 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, 1) + 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/phongo_execute.c b/src/phongo_execute.c index 848b69edd..1850d769f 100644 --- a/src/phongo_execute.c +++ b/src/phongo_execute.c @@ -27,6 +27,8 @@ #include "phongo_execute.h" #include "phongo_util.h" +#include "MongoDB/BulkWriteCommand.h" +#include "MongoDB/BulkWriteCommandResult.h" #include "MongoDB/Cursor.h" #include "MongoDB/ReadPreference.h" #include "MongoDB/Session.h" @@ -333,6 +335,83 @@ 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 }; + php_phongo_bulkwritecommandresult_t* bwcr; + zval* zsession = 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; + } + + mongoc_bulkwrite_set_client(bw, client); + + bw_opts = phongo_bwc_assemble_opts(bwc); + mongoc_bulkwriteopts_set_serverid(bw_opts, server_id); + + if (zsession) { + mongoc_bulkwrite_set_session(bw, Z_SESSION_OBJ_P(zsession)->client_session); + /* 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); + } + + bw_ret = mongoc_bulkwrite_execute(bw, bw_opts); + + bwcr = phongo_bulkwritecommandresult_init(return_value, &bw_ret, manager); + + if (bw_ret.exc) { + success = false; + bson_error_t error = { 0 }; + + // Check if there is a top-level error + if (mongoc_bulkwriteexception_error(bw_ret.exc, &error)) { + phongo_throw_exception_from_bson_error_t_and_reply(&error, mongoc_bulkwriteexception_errorreply(bw_ret.exc)); + } + + /* Unlike mongoc_bulk_operation_execute, mongoc_bulkwrite_execute may + * report COMMAND_INVALID_ARG alongside a partial result (CDRIVER-5842). + * If there is no result, we can throw InvalidArgumentException without + * proxying it behind a BulkWriteException. */ + if (!bw_ret.res && error.domain == MONGOC_ERROR_COMMAND && error.code == MONGOC_ERROR_COMMAND_INVALID_ARG) { + // TODO: Do we care about other mongoc_bulkwriteexception_t fields? + 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 { + // TODO: Determine appropriate message w/o top-level error + zend_throw_exception(php_phongo_bulkwritecommandexception_ce, "Bulk write failed", 0); + } + + /* Ensure error labels are added to the final BulkWriteCommandException. If a + * previous exception was also thrown, error labels will already have + * been added by phongo_throw_exception_from_bson_error_t_and_reply. */ + phongo_exception_add_error_labels(mongoc_bulkwriteexception_errorreply(bw_ret.exc)); + phongo_add_exception_prop(ZEND_STRL("bulkWriteCommandResult"), return_value); + } + +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); From 965118ce2a6eb34ea58676ab267be0f606907d76 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 16 Jan 2025 13:50:30 -0500 Subject: [PATCH 05/20] PHPC-2494: Handle null error fields in BulkWriteCommandResult Always return arrays for writeErrors and writeConcernErrors --- src/MongoDB/BulkWriteCommandResult.c | 42 +++++++++++--------- src/MongoDB/BulkWriteCommandResult.stub.php | 4 +- src/MongoDB/BulkWriteCommandResult_arginfo.h | 8 ++-- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/MongoDB/BulkWriteCommandResult.c b/src/MongoDB/BulkWriteCommandResult.c index 9c61197f8..e060e3b5d 100644 --- a/src/MongoDB/BulkWriteCommandResult.c +++ b/src/MongoDB/BulkWriteCommandResult.c @@ -45,6 +45,10 @@ static bool php_phongo_bulkwritecommandresult_get_writeconcernerrors(php_phongo_ array_init(return_value); + if (!intern->write_concern_errors) { + return true; + } + for (bson_iter_init(&iter, intern->write_concern_errors); bson_iter_next(&iter);) { bson_t bson; uint32_t len; @@ -78,6 +82,10 @@ static bool php_phongo_bulkwritecommandresult_get_writeerrors(php_phongo_bulkwri array_init(return_value); + if (!intern->write_errors) { + return true; + } + for (bson_iter_init(&iter, intern->write_errors); bson_iter_next(&iter);) { bson_t bson; uint32_t len; @@ -246,7 +254,6 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteConcernErrors) PHONGO_PARSE_PARAMETERS_NONE(); - // TODO: null handling php_phongo_bulkwritecommandresult_get_writeconcernerrors(intern, return_value); } @@ -259,7 +266,6 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteErrors) PHONGO_PARSE_PARAMETERS_NONE(); - // TODO: null handling php_phongo_bulkwritecommandresult_get_writeerrors(intern, return_value); } @@ -271,8 +277,9 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getErrorReply) PHONGO_PARSE_PARAMETERS_NONE(); - // TODO: null handling - phongo_document_new(return_value, intern->error_reply, true); + if (intern->error_reply) { + phongo_document_new(return_value, intern->error_reply, true); + } } /* Returns whether the write operation was acknowledged (based on the write @@ -364,22 +371,18 @@ static HashTable* php_phongo_bulkwritecommandresult_get_debug_info(zend_object* ADD_ASSOC_NULL_EX(&retval, "deleteResults"); } - if (intern->write_errors) { + { zval writeerrors; php_phongo_bulkwritecommandresult_get_writeerrors(intern, &writeerrors); ADD_ASSOC_ZVAL_EX(&retval, "writeErrors", &writeerrors); - } else { - ADD_ASSOC_NULL_EX(&retval, "writeErrors"); } - if (intern->write_concern_errors) { + { zval writeconcernerrors; php_phongo_bulkwritecommandresult_get_writeconcernerrors(intern, &writeconcernerrors); ADD_ASSOC_ZVAL_EX(&retval, "writeConcernErrors", &writeconcernerrors); - } else { - ADD_ASSOC_NULL_EX(&retval, "writeConcernErrors"); } if (intern->error_reply) { @@ -416,6 +419,11 @@ void php_phongo_bulkwritecommandresult_init_ce(INIT_FUNC_ARGS) 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_empty0(bson) ? NULL : bson_copy(bson); +} + php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* return_value, mongoc_bulkwritereturn_t* bw_ret, zval* manager) { php_phongo_bulkwritecommandresult_t* bwcr; @@ -433,20 +441,18 @@ php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* re bwcr->modified_count = mongoc_bulkwriteresult_modifiedcount(bw_ret->res); bwcr->deleted_count = mongoc_bulkwriteresult_deletedcount(bw_ret->res); -#define BSON_COPY_OR_NULL(bson) ((bson) ? bson_copy(bson) : NULL) - bwcr->insert_results = BSON_COPY_OR_NULL(mongoc_bulkwriteresult_insertresults(bw_ret->res)); - bwcr->update_results = BSON_COPY_OR_NULL(mongoc_bulkwriteresult_updateresults(bw_ret->res)); - bwcr->delete_results = BSON_COPY_OR_NULL(mongoc_bulkwriteresult_deleteresults(bw_ret->res)); -#undef BSON_COPY_OR_NULL + bwcr->insert_results = _bson_copy_or_null(mongoc_bulkwriteresult_insertresults(bw_ret->res)); + bwcr->update_results = _bson_copy_or_null(mongoc_bulkwriteresult_updateresults(bw_ret->res)); + bwcr->delete_results = _bson_copy_or_null(mongoc_bulkwriteresult_deleteresults(bw_ret->res)); bwcr->server_id = mongoc_bulkwriteresult_serverid(bw_ret->res); } // Copy mongoc_bulkwriteexception_t fields if (bw_ret->exc) { - bwcr->error_reply = bson_copy(mongoc_bulkwriteexception_errorreply(bw_ret->exc)); - bwcr->write_errors = bson_copy(mongoc_bulkwriteexception_writeerrors(bw_ret->exc)); - bwcr->write_concern_errors = bson_copy(mongoc_bulkwriteexception_writeconcernerrors(bw_ret->exc)); + bwcr->error_reply = _bson_copy_or_null(mongoc_bulkwriteexception_errorreply(bw_ret->exc)); + bwcr->write_errors = _bson_copy_or_null(mongoc_bulkwriteexception_writeerrors(bw_ret->exc)); + bwcr->write_concern_errors = _bson_copy_or_null(mongoc_bulkwriteexception_writeconcernerrors(bw_ret->exc)); } ZVAL_ZVAL(&bwcr->manager, manager, 1, 0); diff --git a/src/MongoDB/BulkWriteCommandResult.stub.php b/src/MongoDB/BulkWriteCommandResult.stub.php index e316ee8b1..ca5a82f67 100644 --- a/src/MongoDB/BulkWriteCommandResult.stub.php +++ b/src/MongoDB/BulkWriteCommandResult.stub.php @@ -30,9 +30,9 @@ final public function getUpdateResults(): ?\MongoDB\BSON\Document {} final public function getDeleteResults(): ?\MongoDB\BSON\Document {} - final public function getWriteErrors(): ?\MongoDB\BSON\Document {} + final public function getWriteErrors(): array {} - final public function getWriteConcernErrors(): ?\MongoDB\BSON\PackedArray {} + final public function getWriteConcernErrors(): array {} final public function getErrorReply(): ?\MongoDB\BSON\Document {} diff --git a/src/MongoDB/BulkWriteCommandResult_arginfo.h b/src/MongoDB/BulkWriteCommandResult_arginfo.h index 6fa406da2..1856afbe8 100644 --- a/src/MongoDB/BulkWriteCommandResult_arginfo.h +++ b/src/MongoDB/BulkWriteCommandResult_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d69e0d25638cc7f95b04d3cf0e4749838c6ddca8 */ + * Stub hash: d14704cc61cbe0ed62906e92a9036fbbd5e228d8 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -25,11 +25,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getDeleteResults arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertResults -#define arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteErrors arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertResults - -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteConcernErrors, 0, 0, MongoDB\\BSON\\PackedArray, 1) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteErrors, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() +#define arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteConcernErrors arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteErrors + #define arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getErrorReply arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertResults ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult_isAcknowledged, 0, 0, _IS_BOOL, 0) From d3276c19c2f4c179daefe14864ffa4965b9e890b Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 16 Jan 2025 13:51:33 -0500 Subject: [PATCH 06/20] PHPC-2494: Return writeErrors as an associative array --- src/MongoDB/BulkWriteCommandResult.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/MongoDB/BulkWriteCommandResult.c b/src/MongoDB/BulkWriteCommandResult.c index e060e3b5d..9b42eaf91 100644 --- a/src/MongoDB/BulkWriteCommandResult.c +++ b/src/MongoDB/BulkWriteCommandResult.c @@ -19,6 +19,7 @@ #include #include +#include #include "php_array_api.h" @@ -91,6 +92,7 @@ static bool php_phongo_bulkwritecommandresult_get_writeerrors(php_phongo_bulkwri uint32_t len; const uint8_t* data; zval write_error; + zend_ulong index; if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { continue; @@ -102,12 +104,14 @@ static bool php_phongo_bulkwritecommandresult_get_writeerrors(php_phongo_bulkwri continue; } - if (!phongo_writeerror_init_ex(&write_error, &bson, atoi(bson_iter_key(&iter)))) { + index = (zend_ulong) ZEND_STRTOUL(bson_iter_key(&iter), NULL, 10); + + if (!phongo_writeerror_init_ex(&write_error, &bson, (int32_t) index)) { zval_ptr_dtor(&write_error); continue; } - add_next_index_zval(return_value, &write_error); + add_index_zval(return_value, index, &write_error); } return true; From 9b50625eb9ec32f254060b325eb1db778c56eace Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 16 Jan 2025 14:34:27 -0500 Subject: [PATCH 07/20] PHPC-2493: Revise error handling for mongoc_bulkwrite_execute --- src/phongo_execute.c | 45 ++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/phongo_execute.c b/src/phongo_execute.c index 1850d769f..f5cb54533 100644 --- a/src/phongo_execute.c +++ b/src/phongo_execute.c @@ -368,21 +368,42 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ bwcr = phongo_bulkwritecommandresult_init(return_value, &bw_ret, manager); + /* 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 fails 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 BulkWriteCommandResult. + * + * - InvalidArgumentException may be thrown directly for a basic top-level + * error (assuming BulkWriteCommandResult would also be irrelevant). + * Otherwise, BulkWriteCommandException is thrown with an attached + * BulkWriteCommandResult that collects any error reply, write errors, and + * write concern errors, along with a possible partial write result. + */ if (bw_ret.exc) { success = false; bson_error_t error = { 0 }; + const bson_t *error_reply = mongoc_bulkwriteexception_errorreply(bw_ret.exc); - // Check if there is a top-level error + // Consult any top-level error to throw the first exception if (mongoc_bulkwriteexception_error(bw_ret.exc, &error)) { - phongo_throw_exception_from_bson_error_t_and_reply(&error, mongoc_bulkwriteexception_errorreply(bw_ret.exc)); + phongo_throw_exception_from_bson_error_t_and_reply(&error, error_reply); } /* Unlike mongoc_bulk_operation_execute, mongoc_bulkwrite_execute may - * report COMMAND_INVALID_ARG alongside a partial result (CDRIVER-5842). - * If there is no result, we can throw InvalidArgumentException without - * proxying it behind a BulkWriteException. */ - if (!bw_ret.res && error.domain == MONGOC_ERROR_COMMAND && error.code == MONGOC_ERROR_COMMAND_INVALID_ARG) { - // TODO: Do we care about other mongoc_bulkwriteexception_t fields? + * 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 && !error_reply) { goto cleanup; } @@ -393,14 +414,14 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ zend_throw_exception(php_phongo_bulkwritecommandexception_ce, message, 0); efree(message); } else { - // TODO: Determine appropriate message w/o top-level error zend_throw_exception(php_phongo_bulkwritecommandexception_ce, "Bulk write failed", 0); } - /* Ensure error labels are added to the final BulkWriteCommandException. If a - * previous exception was also thrown, error labels will already have - * been added by phongo_throw_exception_from_bson_error_t_and_reply. */ - phongo_exception_add_error_labels(mongoc_bulkwriteexception_errorreply(bw_ret.exc)); + /* 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); + phongo_add_exception_prop(ZEND_STRL("bulkWriteCommandResult"), return_value); } From 1af9111fd3b5cfd5ddb568dedf4fbbda53d90df0 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 17 Jan 2025 15:50:04 -0500 Subject: [PATCH 08/20] Consistent WriteError and WriteConcernError parsing for bulk write results --- src/MongoDB/BulkWriteCommandResult.c | 99 ++++++++++++++-------------- src/MongoDB/WriteConcernError.c | 14 ++-- src/MongoDB/WriteConcernError.h | 2 +- src/MongoDB/WriteError.c | 14 +++- src/MongoDB/WriteError.h | 4 +- src/MongoDB/WriteResult.c | 21 ++++-- 6 files changed, 89 insertions(+), 65 deletions(-) diff --git a/src/MongoDB/BulkWriteCommandResult.c b/src/MongoDB/BulkWriteCommandResult.c index 9b42eaf91..a3e534cf1 100644 --- a/src/MongoDB/BulkWriteCommandResult.c +++ b/src/MongoDB/BulkWriteCommandResult.c @@ -40,78 +40,81 @@ zend_class_entry* php_phongo_bulkwritecommandresult_ce; -static bool php_phongo_bulkwritecommandresult_get_writeconcernerrors(php_phongo_bulkwritecommandresult_t* intern, zval* return_value) +/* 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_bulkwritecommandresult_get_writeconcernerrors(php_phongo_bulkwritecommandresult_t* intern, zval* return_value) { bson_iter_t iter; array_init(return_value); - if (!intern->write_concern_errors) { - return true; - } + if (intern->write_concern_errors && bson_iter_init(&iter, intern->write_concern_errors)) { + while (bson_iter_next(&iter)) { + bson_t bson; + uint32_t len; + const uint8_t* data; + zval write_concern_error; - for (bson_iter_init(&iter, intern->write_concern_errors); 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; + } - if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { - continue; - } + bson_iter_document(&iter, &len, &data); - bson_iter_document(&iter, &len, &data); + if (!bson_init_static(&bson, data, len)) { + continue; + } - 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; + } - if (!phongo_writeconcernerror_init(&write_concern_error, &bson)) { - zval_ptr_dtor(&write_concern_error); - continue; + add_next_index_zval(return_value, &write_concern_error); } - - add_next_index_zval(return_value, &write_concern_error); } return true; } -static bool php_phongo_bulkwritecommandresult_get_writeerrors(php_phongo_bulkwritecommandresult_t* intern, zval* return_value) +/* 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_bulkwritecommandresult_get_writeerrors(php_phongo_bulkwritecommandresult_t* intern, zval* return_value) { bson_iter_t iter; array_init(return_value); - if (!intern->write_errors) { - return true; - } + if (intern->write_errors && bson_iter_init(&iter, intern->write_errors)) { + while (bson_iter_next(&iter)) { + bson_t bson; + uint32_t len; + const uint8_t* data; + zval write_error; + zend_ulong index; - for (bson_iter_init(&iter, intern->write_errors); 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; + } - if (!BSON_ITER_HOLDS_DOCUMENT(&iter)) { - continue; - } + bson_iter_document(&iter, &len, &data); - bson_iter_document(&iter, &len, &data); + if (!bson_init_static(&bson, data, len)) { + continue; + } - if (!bson_init_static(&bson, data, len)) { - continue; - } + index = (zend_ulong) ZEND_STRTOUL(bson_iter_key(&iter), NULL, 10); - 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; + } - if (!phongo_writeerror_init_ex(&write_error, &bson, (int32_t) index)) { - zval_ptr_dtor(&write_error); - continue; + add_index_zval(return_value, index, &write_error); } - - add_index_zval(return_value, index, &write_error); } return true; @@ -258,7 +261,7 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteConcernErrors) PHONGO_PARSE_PARAMETERS_NONE(); - php_phongo_bulkwritecommandresult_get_writeconcernerrors(intern, return_value); + phongo_bulkwritecommandresult_get_writeconcernerrors(intern, return_value); } /* Returns any write errors that occurred */ @@ -270,7 +273,7 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteErrors) PHONGO_PARSE_PARAMETERS_NONE(); - php_phongo_bulkwritecommandresult_get_writeerrors(intern, return_value); + phongo_bulkwritecommandresult_get_writeerrors(intern, return_value); } static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getErrorReply) @@ -378,14 +381,14 @@ static HashTable* php_phongo_bulkwritecommandresult_get_debug_info(zend_object* { zval writeerrors; - php_phongo_bulkwritecommandresult_get_writeerrors(intern, &writeerrors); + phongo_bulkwritecommandresult_get_writeerrors(intern, &writeerrors); ADD_ASSOC_ZVAL_EX(&retval, "writeErrors", &writeerrors); } { zval writeconcernerrors; - php_phongo_bulkwritecommandresult_get_writeconcernerrors(intern, &writeconcernerrors); + phongo_bulkwritecommandresult_get_writeconcernerrors(intern, &writeconcernerrors); ADD_ASSOC_ZVAL_EX(&retval, "writeConcernErrors", &writeconcernerrors); } diff --git a/src/MongoDB/WriteConcernError.c b/src/MongoDB/WriteConcernError.c index 9624d5b0e..76f395b87 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; @@ -150,10 +156,10 @@ zend_bool phongo_writeconcernerror_init(zval* return_value, bson_t* bson) // 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); + uint32_t len; + const char* message = bson_iter_utf8(&iter, &len); - intern->message = estrndup(err_msg, errmsg_len); + intern->message = estrndup(message, len); } // Additionally check for field name used by mongoc_bulkwriteexception_t 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 5befa1d2e..46d2e580a 100644 --- a/src/MongoDB/WriteError.c +++ b/src/MongoDB/WriteError.c @@ -143,12 +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); } -zend_bool phongo_writeerror_init_ex(zval* return_value, bson_t* bson, int32_t index) +/* 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; @@ -181,6 +188,7 @@ zend_bool phongo_writeerror_init_ex(zval* return_value, bson_t* bson, int32_t in 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); @@ -189,7 +197,7 @@ zend_bool phongo_writeerror_init_ex(zval* return_value, bson_t* bson, int32_t in } /* If the WriteError is initialized from mongoc_bulkwriteexception_t, an - * index will already have been specified. */ + * 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 3cb641bf5..69347a1e5 100644 --- a/src/MongoDB/WriteError.h +++ b/src/MongoDB/WriteError.h @@ -21,7 +21,7 @@ #include -zend_bool phongo_writeerror_init(zval* return_value, bson_t* bson); -zend_bool phongo_writeerror_init_ex(zval* return_value, bson_t* bson, int32_t index); +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); } From d8716e8a4555fcd2e92f8dd148ce37403b72991b Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 14 Jan 2025 09:21:10 -0500 Subject: [PATCH 09/20] BulkWriteCommand tests --- ...and-ctor-bypassDocumentValidation-001.phpt | 67 ++++++ ...and-ctor-bypassDocumentValidation-002.phpt | 114 ++++++++++ .../bulkwritecommand-ctor-comment-001.phpt | 61 ++++++ ...lkwritecommand-ctor-comment_error-001.phpt | 27 +++ .../bulkwritecommand-ctor-let-001.phpt | 71 ++++++ .../bulkwritecommand-ctor-let_error-001.phpt | 34 +++ .../bulkwritecommand-ctor-ordered-001.phpt | 75 +++++++ .../bulkwritecommand-ctor-ordered-002.phpt | 75 +++++++ ...kwritecommand-ctor-verboseresults-001.phpt | 109 ++++++++++ ...kwritecommand-ctor-verboseresults-002.phpt | 59 +++++ .../manager-executeBulkWriteCommand-001.phpt | 203 ++++++++++++++++++ 11 files changed, 895 insertions(+) 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/manager-executeBulkWriteCommand-001.phpt diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt new file mode 100644 index 000000000..7aa4204da --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt @@ -0,0 +1,67 @@ +--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 + ["writeErrors"]=> + array(0) { + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL + ["server"]=> + object(MongoDB\Driver\Server)#%d (%d) {%A + } +} +===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..8f231839d --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt @@ -0,0 +1,114 @@ +--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->getBulkWriteCommandResult()); +} + +?> +===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 + ["writeErrors"]=> + 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" + } + } + } + } + } + } + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL + ["server"]=> + object(MongoDB\Driver\Server)#%d (%d) {%A + } +} +===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..ed4367173 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt @@ -0,0 +1,71 @@ +--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 + ["writeErrors"]=> + array(0) { + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL + ["server"]=> + object(MongoDB\Driver\Server)#%d (%d) {%A + } +} +===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..137a2a3db --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt @@ -0,0 +1,75 @@ +--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->getBulkWriteCommandResult()); +} + +?> +===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 + ["writeErrors"]=> + 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) { + } + } + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL + ["server"]=> + object(MongoDB\Driver\Server)#%d (%d) {%A + } +} +===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..a2e4f30bd --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt @@ -0,0 +1,75 @@ +--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->getBulkWriteCommandResult()); +} + +?> +===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 + ["writeErrors"]=> + 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) { + } + } + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL + ["server"]=> + object(MongoDB\Driver\Server)#%d (%d) {%A + } +} +===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..b4f178cc9 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt @@ -0,0 +1,109 @@ +--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) + } + } + } + } + ["writeErrors"]=> + array(0) { + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL + ["server"]=> + object(MongoDB\Driver\Server)#%d (%d) {%A + } +} +===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..09acc3371 --- /dev/null +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt @@ -0,0 +1,59 @@ +--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 + ["writeErrors"]=> + array(0) { + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL + ["server"]=> + object(MongoDB\Driver\Server)#%d (%d) {%A + } +} +===DONE=== diff --git a/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt new file mode 100644 index 000000000..114a7cebc --- /dev/null +++ b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt @@ -0,0 +1,203 @@ +--TEST-- +MongoDB\Driver\BulkWriteCommand::__construct() +--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($bulk); +var_dump($result); + +?> +===DONE=== + +--EXPECTF-- +object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { + ["bypassDocumentValidation"]=> + NULL + ["ordered"]=> + bool(true) + ["verboseResults"]=> + bool(true) + ["session"]=> + NULL + ["write_concern"]=> + NULL +} +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) + } + } + } + } + ["writeErrors"]=> + array(0) { + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL + ["server"]=> + object(MongoDB\Driver\Server)#%d (%d) {%A + } +} +===DONE=== From 5c78086b7890291e61702b9e606feda7c54e533a Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 5 Feb 2025 16:51:03 -0500 Subject: [PATCH 10/20] clang-format --- src/MongoDB/BulkWriteCommand.c | 14 +++++++------- src/MongoDB/BulkWriteCommandResult.c | 20 ++++++++++---------- src/MongoDB/Manager.c | 12 ++++++------ src/MongoDB/Server.c | 8 ++++---- src/MongoDB/WriteConcernError.c | 4 ++-- src/MongoDB/WriteError.c | 4 ++-- src/phongo_execute.c | 14 +++++++------- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/MongoDB/BulkWriteCommand.c b/src/MongoDB/BulkWriteCommand.c index 022149534..48b8889d2 100644 --- a/src/MongoDB/BulkWriteCommand.c +++ b/src/MongoDB/BulkWriteCommand.c @@ -132,13 +132,13 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, __construct) PHONGO_PARSE_PARAMETERS_END(); // TODO: Consider removing initialization for zero values - 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; + 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; intern->write_concern = NULL; if (!zoptions) { diff --git a/src/MongoDB/BulkWriteCommandResult.c b/src/MongoDB/BulkWriteCommandResult.c index a3e534cf1..7a4685c18 100644 --- a/src/MongoDB/BulkWriteCommandResult.c +++ b/src/MongoDB/BulkWriteCommandResult.c @@ -32,10 +32,10 @@ #include "MongoDB/WriteError.h" #include "BulkWriteCommandResult_arginfo.h" -#define PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED(method) \ - if (!intern->is_acknowledged) { \ +#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; \ + return; \ } zend_class_entry* php_phongo_bulkwritecommandresult_ce; @@ -338,7 +338,7 @@ static zend_object* php_phongo_bulkwritecommandresult_create_object(zend_class_e static HashTable* php_phongo_bulkwritecommandresult_get_debug_info(zend_object* object, int* is_temp) { php_phongo_bulkwritecommandresult_t* intern; - zval retval = ZVAL_STATIC_INIT; + zval retval = ZVAL_STATIC_INIT; intern = Z_OBJ_BULKWRITECOMMANDRESULT(object); *is_temp = 1; @@ -426,7 +426,7 @@ void php_phongo_bulkwritecommandresult_init_ce(INIT_FUNC_ARGS) php_phongo_handler_bulkwritecommandresult.offset = XtOffsetOf(php_phongo_bulkwritecommandresult_t, std); } -static inline bson_t* _bson_copy_or_null (const bson_t* bson) +static inline bson_t* _bson_copy_or_null(const bson_t* bson) { return bson_empty0(bson) ? NULL : bson_copy(bson); } @@ -437,16 +437,16 @@ php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* re object_init_ex(return_value, php_phongo_bulkwritecommandresult_ce); - bwcr = Z_BULKWRITECOMMANDRESULT_OBJ_P(return_value); + bwcr = Z_BULKWRITECOMMANDRESULT_OBJ_P(return_value); bwcr->is_acknowledged = !!bw_ret->res; // Copy mongoc_bulkwriteresult_t fields if (bw_ret->res) { bwcr->inserted_count = mongoc_bulkwriteresult_insertedcount(bw_ret->res); bwcr->upserted_count = mongoc_bulkwriteresult_upsertedcount(bw_ret->res); - bwcr->matched_count = mongoc_bulkwriteresult_matchedcount(bw_ret->res); + bwcr->matched_count = mongoc_bulkwriteresult_matchedcount(bw_ret->res); bwcr->modified_count = mongoc_bulkwriteresult_modifiedcount(bw_ret->res); - bwcr->deleted_count = mongoc_bulkwriteresult_deletedcount(bw_ret->res); + bwcr->deleted_count = mongoc_bulkwriteresult_deletedcount(bw_ret->res); bwcr->insert_results = _bson_copy_or_null(mongoc_bulkwriteresult_insertresults(bw_ret->res)); bwcr->update_results = _bson_copy_or_null(mongoc_bulkwriteresult_updateresults(bw_ret->res)); @@ -457,8 +457,8 @@ php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* re // Copy mongoc_bulkwriteexception_t fields if (bw_ret->exc) { - bwcr->error_reply = _bson_copy_or_null(mongoc_bulkwriteexception_errorreply(bw_ret->exc)); - bwcr->write_errors = _bson_copy_or_null(mongoc_bulkwriteexception_writeerrors(bw_ret->exc)); + bwcr->error_reply = _bson_copy_or_null(mongoc_bulkwriteexception_errorreply(bw_ret->exc)); + bwcr->write_errors = _bson_copy_or_null(mongoc_bulkwriteexception_writeerrors(bw_ret->exc)); bwcr->write_concern_errors = _bson_copy_or_null(mongoc_bulkwriteexception_writeconcernerrors(bw_ret->exc)); } diff --git a/src/MongoDB/Manager.c b/src/MongoDB/Manager.c index 8d08f6a4e..86d693dd5 100644 --- a/src/MongoDB/Manager.c +++ b/src/MongoDB/Manager.c @@ -522,12 +522,12 @@ static PHP_METHOD(MongoDB_Driver_Manager, executeBulkWrite) /* 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_manager_t* intern; + zval* zbwc; php_phongo_bulkwritecommand_t* bwc; - zval* zoptions = NULL; - uint32_t server_id = 0; - zval* zsession = NULL; + 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) @@ -536,7 +536,7 @@ static PHP_METHOD(MongoDB_Driver_Manager, executeBulkWriteCommand) PHONGO_PARSE_PARAMETERS_END(); intern = Z_MANAGER_OBJ_P(getThis()); - bwc = Z_BULKWRITECOMMAND_OBJ_P(zbwc); + bwc = Z_BULKWRITECOMMAND_OBJ_P(zbwc); if (!phongo_parse_session(zoptions, intern->client, NULL, &zsession)) { /* Exception should already have been thrown */ diff --git a/src/MongoDB/Server.c b/src/MongoDB/Server.c index 58e007c61..24c53a12a 100644 --- a/src/MongoDB/Server.c +++ b/src/MongoDB/Server.c @@ -197,10 +197,10 @@ static PHP_METHOD(MongoDB_Driver_Server, executeBulkWrite) /* 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_server_t* intern; + zval* zbwc; php_phongo_bulkwritecommand_t* bwc; - zval* zoptions = NULL; + zval* zoptions = NULL; PHONGO_PARSE_PARAMETERS_START(1, 2) Z_PARAM_OBJECT_OF_CLASS(zbwc, php_phongo_bulkwritecommand_ce) @@ -209,7 +209,7 @@ static PHP_METHOD(MongoDB_Driver_Server, executeBulkWriteCommand) PHONGO_PARSE_PARAMETERS_END(); intern = Z_SERVER_OBJ_P(getThis()); - bwc = Z_BULKWRITECOMMAND_OBJ_P(zbwc); + 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. */ diff --git a/src/MongoDB/WriteConcernError.c b/src/MongoDB/WriteConcernError.c index 76f395b87..edf81dd45 100644 --- a/src/MongoDB/WriteConcernError.c +++ b/src/MongoDB/WriteConcernError.c @@ -155,7 +155,7 @@ bool phongo_writeconcernerror_init(zval* return_value, const bson_t* bson) // 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))) { + (bson_iter_init_find(&iter, bson, "message") && BSON_ITER_HOLDS_UTF8(&iter))) { uint32_t len; const char* message = bson_iter_utf8(&iter, &len); @@ -164,7 +164,7 @@ bool phongo_writeconcernerror_init(zval* return_value, const bson_t* bson) // 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))) { + (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/WriteError.c b/src/MongoDB/WriteError.c index 46d2e580a..148a90c0d 100644 --- a/src/MongoDB/WriteError.c +++ b/src/MongoDB/WriteError.c @@ -172,7 +172,7 @@ bool phongo_writeerror_init_ex(zval* return_value, const bson_t* bson, int32_t i // 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))) { + (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); @@ -181,7 +181,7 @@ bool phongo_writeerror_init_ex(zval* return_value, const bson_t* bson, int32_t i // 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))) { + (bson_iter_init_find(&iter, bson, "details") && BSON_ITER_HOLDS_DOCUMENT(&iter))) { uint32_t len; const uint8_t* data = NULL; diff --git a/src/phongo_execute.c b/src/phongo_execute.c index f5cb54533..77f138f59 100644 --- a/src/phongo_execute.c +++ b/src/phongo_execute.c @@ -337,13 +337,13 @@ bool phongo_execute_bulk_write(zval* manager, const char* namespace, php_phongo_ 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_client_t* client = NULL; + mongoc_bulkwrite_t* bw = bwc->bw; mongoc_bulkwriteopts_t* bw_opts = NULL; - mongoc_bulkwritereturn_t bw_ret = { 0 }; + mongoc_bulkwritereturn_t bw_ret = { 0 }; php_phongo_bulkwritecommandresult_t* bwcr; zval* zsession = NULL; - bool success = true; + bool success = true; client = Z_MANAGER_OBJ_P(manager)->client; @@ -389,9 +389,9 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ * write concern errors, along with a possible partial write result. */ if (bw_ret.exc) { - success = false; - bson_error_t error = { 0 }; - const bson_t *error_reply = mongoc_bulkwriteexception_errorreply(bw_ret.exc); + success = false; + bson_error_t error = { 0 }; + const bson_t* error_reply = mongoc_bulkwriteexception_errorreply(bw_ret.exc); // Consult any top-level error to throw the first exception if (mongoc_bulkwriteexception_error(bw_ret.exc, &error)) { From 6da193876671c932acbcf586d361df724bef86c0 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 10 Feb 2025 16:47:21 -0500 Subject: [PATCH 11/20] 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. --- src/MongoDB/BulkWriteCommandResult.c | 29 ------------------- src/MongoDB/BulkWriteCommandResult.stub.php | 2 -- src/MongoDB/BulkWriteCommandResult_arginfo.h | 7 +---- src/phongo_structs.h | 1 - ...and-ctor-bypassDocumentValidation-001.phpt | 3 -- ...and-ctor-bypassDocumentValidation-002.phpt | 3 -- .../bulkwritecommand-ctor-let-001.phpt | 3 -- .../bulkwritecommand-ctor-ordered-001.phpt | 3 -- .../bulkwritecommand-ctor-ordered-002.phpt | 3 -- ...kwritecommand-ctor-verboseresults-001.phpt | 3 -- ...kwritecommand-ctor-verboseresults-002.phpt | 3 -- .../manager-executeBulkWriteCommand-001.phpt | 3 -- 12 files changed, 1 insertion(+), 62 deletions(-) diff --git a/src/MongoDB/BulkWriteCommandResult.c b/src/MongoDB/BulkWriteCommandResult.c index 7a4685c18..b8ffdaf48 100644 --- a/src/MongoDB/BulkWriteCommandResult.c +++ b/src/MongoDB/BulkWriteCommandResult.c @@ -27,7 +27,6 @@ #include "phongo_error.h" #include "BSON/Document.h" -#include "MongoDB/Server.h" #include "MongoDB/WriteConcernError.h" #include "MongoDB/WriteError.h" #include "BulkWriteCommandResult_arginfo.h" @@ -192,21 +191,6 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getUpsertedCount) RETURN_LONG(intern->upserted_count); } -/* Returns the last Server used to execute a command for the bulk write */ -static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getServer) -{ - php_phongo_bulkwritecommandresult_t* intern; - - intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); - - PHONGO_PARSE_PARAMETERS_NONE(); - - PHONGO_BULKWRITECOMMANDRESULT_CHECK_ACKNOWLEDGED("getServer"); - - // TODO: null handling - phongo_server_init(return_value, &intern->manager, intern->server_id); -} - static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getInsertResults) { php_phongo_bulkwritecommandresult_t* intern; @@ -401,17 +385,6 @@ static HashTable* php_phongo_bulkwritecommandresult_get_debug_info(zend_object* ADD_ASSOC_NULL_EX(&retval, "errorReply"); } - if (intern->server_id) { - zval server; - - phongo_server_init(&server, &intern->manager, intern->server_id); - ADD_ASSOC_ZVAL_EX(&retval, "server", &server); - } else { - /* TODO: Determine if this path is only reached when a partial result is - * attached to a BulkWriteCommandException on an unacknowledged write. */ - ADD_ASSOC_NULL_EX(&retval, "server"); - } - return Z_ARRVAL(retval); } @@ -451,8 +424,6 @@ php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* re bwcr->insert_results = _bson_copy_or_null(mongoc_bulkwriteresult_insertresults(bw_ret->res)); bwcr->update_results = _bson_copy_or_null(mongoc_bulkwriteresult_updateresults(bw_ret->res)); bwcr->delete_results = _bson_copy_or_null(mongoc_bulkwriteresult_deleteresults(bw_ret->res)); - - bwcr->server_id = mongoc_bulkwriteresult_serverid(bw_ret->res); } // Copy mongoc_bulkwriteexception_t fields diff --git a/src/MongoDB/BulkWriteCommandResult.stub.php b/src/MongoDB/BulkWriteCommandResult.stub.php index ca5a82f67..bcca4816f 100644 --- a/src/MongoDB/BulkWriteCommandResult.stub.php +++ b/src/MongoDB/BulkWriteCommandResult.stub.php @@ -22,8 +22,6 @@ final public function getUpsertedCount(): int {} final public function getDeletedCount(): int {} - final public function getServer(): Server {} - final public function getInsertResults(): ?\MongoDB\BSON\Document {} final public function getUpdateResults(): ?\MongoDB\BSON\Document {} diff --git a/src/MongoDB/BulkWriteCommandResult_arginfo.h b/src/MongoDB/BulkWriteCommandResult_arginfo.h index 1856afbe8..0e80889d5 100644 --- a/src/MongoDB/BulkWriteCommandResult_arginfo.h +++ b/src/MongoDB/BulkWriteCommandResult_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d14704cc61cbe0ed62906e92a9036fbbd5e228d8 */ + * Stub hash: a4cbc5665d7b1d99f5f4c6425d6d2eb3829bb001 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -15,9 +15,6 @@ ZEND_END_ARG_INFO() #define arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getDeletedCount arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertedCount -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getServer, 0, 0, MongoDB\\Driver\\Server, 0) -ZEND_END_ARG_INFO() - ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertResults, 0, 0, MongoDB\\BSON\\Document, 1) ZEND_END_ARG_INFO() @@ -42,7 +39,6 @@ static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getMatchedCount); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getModifiedCount); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getUpsertedCount); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getDeletedCount); -static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getServer); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getInsertResults); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getUpdateResults); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getDeleteResults); @@ -59,7 +55,6 @@ static const zend_function_entry class_MongoDB_Driver_BulkWriteCommandResult_met ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getModifiedCount, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getModifiedCount, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getUpsertedCount, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getUpsertedCount, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getDeletedCount, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getDeletedCount, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) - ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getServer, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getServer, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getInsertResults, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertResults, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getUpdateResults, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getUpdateResults, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getDeleteResults, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getDeleteResults, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) diff --git a/src/phongo_structs.h b/src/phongo_structs.h index d0988872e..7b083e612 100644 --- a/src/phongo_structs.h +++ b/src/phongo_structs.h @@ -65,7 +65,6 @@ typedef struct { bson_t* write_concern_errors; bson_t* error_reply; zval manager; - uint32_t server_id; zend_object std; } php_phongo_bulkwritecommandresult_t; diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt index 7aa4204da..e1280e162 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt @@ -60,8 +60,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } ["errorReply"]=> NULL - ["server"]=> - object(MongoDB\Driver\Server)#%d (%d) {%A - } } ===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 index 8f231839d..abcad96f5 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt @@ -107,8 +107,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } ["errorReply"]=> NULL - ["server"]=> - object(MongoDB\Driver\Server)#%d (%d) {%A - } } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt index ed4367173..1c3565097 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt @@ -64,8 +64,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } ["errorReply"]=> NULL - ["server"]=> - object(MongoDB\Driver\Server)#%d (%d) {%A - } } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt index 137a2a3db..43dfb8c8a 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt @@ -68,8 +68,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } ["errorReply"]=> NULL - ["server"]=> - object(MongoDB\Driver\Server)#%d (%d) {%A - } } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt index a2e4f30bd..49b084f7c 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt @@ -68,8 +68,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } ["errorReply"]=> NULL - ["server"]=> - object(MongoDB\Driver\Server)#%d (%d) {%A - } } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt index b4f178cc9..df6f45649 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt @@ -102,8 +102,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } ["errorReply"]=> NULL - ["server"]=> - object(MongoDB\Driver\Server)#%d (%d) {%A - } } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt index 09acc3371..2b48ae4cc 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt @@ -52,8 +52,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } ["errorReply"]=> NULL - ["server"]=> - object(MongoDB\Driver\Server)#%d (%d) {%A - } } ===DONE=== diff --git a/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt index 114a7cebc..98cb480b4 100644 --- a/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt +++ b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt @@ -196,8 +196,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } ["errorReply"]=> NULL - ["server"]=> - object(MongoDB\Driver\Server)#%d (%d) {%A - } } ===DONE=== From 6dd6f5a85d69f2ee53067fb621464db89fda9b73 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 18 Feb 2025 10:30:41 -0500 Subject: [PATCH 12/20] Check for _id extraction before appending insert --- src/MongoDB/BulkWriteCommand.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MongoDB/BulkWriteCommand.c b/src/MongoDB/BulkWriteCommand.c index 48b8889d2..23ee668ed 100644 --- a/src/MongoDB/BulkWriteCommand.c +++ b/src/MongoDB/BulkWriteCommand.c @@ -437,6 +437,11 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, insertOne) 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; @@ -444,11 +449,6 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, insertOne) intern->num_ops++; - if (!bson_out) { - phongo_throw_exception(PHONGO_ERROR_LOGIC, "php_phongo_zval_to_bson() did not return document identifier. Please file a bug report."); - goto cleanup; - } - phongo_bwc_extract_id(bson_out, &return_value); cleanup: From dce281df0cf8df6a1986902f6dbd9dc7869ad218 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 18 Feb 2025 16:43:43 -0500 Subject: [PATCH 13/20] Regenerate BulkWriteCommandException arginfo --- src/MongoDB/Exception/BulkWriteCommandException_arginfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MongoDB/Exception/BulkWriteCommandException_arginfo.h b/src/MongoDB/Exception/BulkWriteCommandException_arginfo.h index 4f3bc7a11..24ccf0941 100644 --- a/src/MongoDB/Exception/BulkWriteCommandException_arginfo.h +++ b/src/MongoDB/Exception/BulkWriteCommandException_arginfo.h @@ -20,10 +20,10 @@ static zend_class_entry *register_class_MongoDB_Driver_Exception_BulkWriteComman INIT_NS_CLASS_ENTRY(ce, "MongoDB\\Driver\\Exception", "BulkWriteCommandException", class_MongoDB_Driver_Exception_BulkWriteCommandException_methods); class_entry = zend_register_internal_class_ex(&ce, class_entry_MongoDB_Driver_Exception_ServerException); + zend_string *property_bulkWriteCommandResult_class_MongoDB_Driver_BulkWriteCommandResult = zend_string_init("MongoDB\\Driver\\BulkWriteCommandResult", sizeof("MongoDB\\Driver\\BulkWriteCommandResult")-1, 1); zval property_bulkWriteCommandResult_default_value; ZVAL_UNDEF(&property_bulkWriteCommandResult_default_value); zend_string *property_bulkWriteCommandResult_name = zend_string_init("bulkWriteCommandResult", sizeof("bulkWriteCommandResult") - 1, 1); - zend_string *property_bulkWriteCommandResult_class_MongoDB_Driver_BulkWriteCommandResult = zend_string_init("MongoDB\\Driver\\BulkWriteCommandResult", sizeof("MongoDB\\Driver\\BulkWriteCommandResult")-1, 1); zend_declare_typed_property(class_entry, property_bulkWriteCommandResult_name, &property_bulkWriteCommandResult_default_value, ZEND_ACC_PROTECTED, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_bulkWriteCommandResult_class_MongoDB_Driver_BulkWriteCommandResult, 0, 0)); zend_string_release(property_bulkWriteCommandResult_name); From 560a17911d18ec19666e3e6a3dc8344394c20d2a Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 28 Feb 2025 12:38:18 -0500 Subject: [PATCH 14/20] PHPC-2493: Relocate writeConcern option to executeBulkWriteCommand() --- src/MongoDB/BulkWriteCommand.c | 44 +--- src/phongo_execute.c | 27 ++- .../bulkwritecommand-debug-001.phpt | 39 ++++ .../bulkwritecommand-debug-002.phpt | 53 +++++ .../manager-executeBulkWriteCommand-001.phpt | 15 +- ...ger-executeBulkWriteCommand_error-001.phpt | 31 +++ ...ger-executeBulkWriteCommand_error-002.phpt | 30 +++ .../server-executeBulkWriteCommand-001.phpt | 188 ++++++++++++++++++ ...ver-executeBulkWriteCommand_error-001.phpt | 32 +++ ...ver-executeBulkWriteCommand_error-002.phpt | 31 +++ 10 files changed, 436 insertions(+), 54 deletions(-) create mode 100644 tests/bulkwritecommand/bulkwritecommand-debug-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommand-debug-002.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/src/MongoDB/BulkWriteCommand.c b/src/MongoDB/BulkWriteCommand.c index 23ee668ed..43a02843e 100644 --- a/src/MongoDB/BulkWriteCommand.c +++ b/src/MongoDB/BulkWriteCommand.c @@ -55,10 +55,6 @@ mongoc_bulkwriteopts_t* phongo_bwc_assemble_opts(php_phongo_bulkwritecommand_t* mongoc_bulkwriteopts_set_ordered(opts, intern->ordered); mongoc_bulkwriteopts_set_verboseresults(opts, intern->verbose); - if (intern->write_concern) { - mongoc_bulkwriteopts_set_writeconcern(opts, intern->write_concern); - } - return opts; } @@ -131,15 +127,13 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, __construct) Z_PARAM_ARRAY_OR_NULL(zoptions) PHONGO_PARSE_PARAMETERS_END(); - // TODO: Consider removing initialization for zero values - 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; - intern->write_concern = NULL; + 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; @@ -183,17 +177,6 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, __construct) if (php_array_existsc(zoptions, "verboseResults")) { intern->verbose = php_array_fetchc_bool(zoptions, "verboseResults"); } - - if (php_array_existsc(zoptions, "writeConcern")) { - zval* value = php_array_fetchc_deref(zoptions, "writeConcern"); - - if (Z_TYPE_P(value) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(value), php_phongo_writeconcern_ce)) { - phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Expected \"writeConcern\" option to be %s, %s given", ZSTR_VAL(php_phongo_writeconcern_ce->name), zend_zval_type_name(value)); - return; - } - - intern->write_concern = mongoc_write_concern_copy(phongo_write_concern_from_zval(value)); - } } static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, count) @@ -763,10 +746,6 @@ static void php_phongo_bulkwritecommand_free_object(zend_object* object) if (!Z_ISUNDEF(intern->session)) { zval_ptr_dtor(&intern->session); } - - if (intern->write_concern) { - mongoc_write_concern_destroy(intern->write_concern); - } } static zend_object* php_phongo_bulkwritecommand_create_object(zend_class_entry* class_type) @@ -828,15 +807,6 @@ static HashTable* php_phongo_bulkwritecommand_get_debug_info(zend_object* object ADD_ASSOC_NULL_EX(&retval, "session"); } - if (intern->write_concern) { - zval write_concern; - - php_phongo_write_concern_to_zval(&write_concern, intern->write_concern); - ADD_ASSOC_ZVAL_EX(&retval, "write_concern", &write_concern); - } else { - ADD_ASSOC_NULL_EX(&retval, "write_concern"); - } - done: return Z_ARRVAL(retval); } diff --git a/src/phongo_execute.c b/src/phongo_execute.c index 77f138f59..5b53c2851 100644 --- a/src/phongo_execute.c +++ b/src/phongo_execute.c @@ -342,8 +342,10 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ mongoc_bulkwriteopts_t* bw_opts = NULL; mongoc_bulkwritereturn_t bw_ret = { 0 }; php_phongo_bulkwritecommandresult_t* bwcr; - zval* zsession = NULL; - bool success = true; + zval* zsession = NULL; + zval* zwriteConcern = NULL; + const mongoc_write_concern_t* write_concern = NULL; + bool success = true; client = Z_MANAGER_OBJ_P(manager)->client; @@ -352,16 +354,35 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ 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) { - mongoc_bulkwrite_set_session(bw, Z_SESSION_OBJ_P(zsession)->client_session); /* 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); 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/manager-executeBulkWriteCommand-001.phpt b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt index 98cb480b4..3923ee6ec 100644 --- a/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt +++ b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt @@ -1,5 +1,5 @@ --TEST-- -MongoDB\Driver\BulkWriteCommand::__construct() +MongoDB\Driver\Manager::executeBulkWriteCommand() --SKIPIF-- @@ -26,25 +26,12 @@ $bulk->deleteMany(NS, ['_id' => ['$gt' => 2]]); $result = $manager->executeBulkWriteCommand($bulk); -var_dump($bulk); var_dump($result); ?> ===DONE=== --EXPECTF-- -object(MongoDB\Driver\BulkWriteCommand)#%d (%d) { - ["bypassDocumentValidation"]=> - NULL - ["ordered"]=> - bool(true) - ["verboseResults"]=> - bool(true) - ["session"]=> - NULL - ["write_concern"]=> - NULL -} object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { ["isAcknowledged"]=> bool(true) 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..e2c6ed68c --- /dev/null +++ b/tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt @@ -0,0 +1,188 @@ +--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) + } + } + } + } + ["writeErrors"]=> + array(0) { + } + ["writeConcernErrors"]=> + array(0) { + } + ["errorReply"]=> + NULL +} +===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 aa92cc5b074d12123f806e8225a3ee09641372be Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 3 Mar 2025 17:05:16 -0500 Subject: [PATCH 15/20] Parse sort option for replaceOne and updateOne --- src/MongoDB/BulkWriteCommand.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/MongoDB/BulkWriteCommand.c b/src/MongoDB/BulkWriteCommand.c index 43a02843e..47ac1b3e3 100644 --- a/src/MongoDB/BulkWriteCommand.c +++ b/src/MongoDB/BulkWriteCommand.c @@ -507,6 +507,18 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, replaceOne) 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")); } @@ -703,6 +715,18 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, updateOne) 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")); } From 82defbfe4dd8dc744f465a72bd6e5cfe2e840f9d Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 4 Mar 2025 13:21:37 -0500 Subject: [PATCH 16/20] BulkWriteCommand::replaceOne() need not accept root-level arrays --- src/MongoDB/BulkWriteCommand.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/MongoDB/BulkWriteCommand.c b/src/MongoDB/BulkWriteCommand.c index 47ac1b3e3..22fc35778 100644 --- a/src/MongoDB/BulkWriteCommand.c +++ b/src/MongoDB/BulkWriteCommand.c @@ -445,19 +445,19 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, replaceOne) 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_replaceoneopts_t* opts = NULL; - bson_error_t error = { 0 }; + 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(zupdate) + Z_PARAM_ARRAY_OR_OBJECT(zreplacement) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_OR_NULL(zoptions) PHONGO_PARSE_PARAMETERS_END(); @@ -473,8 +473,7 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, replaceOne) goto cleanup; } - // Explicitly allow MongoDB\BSON\PackedArray for update pipelines - php_phongo_zval_to_bson(zupdate, PHONGO_BSON_ALLOW_ROOT_ARRAY, &bupdate, NULL); + php_phongo_zval_to_bson(zreplacement, PHONGO_BSON_NONE, &breplacement, NULL); if (EG(exception)) { goto cleanup; @@ -524,7 +523,7 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, replaceOne) } } - if (!mongoc_bulkwrite_append_replaceone(intern->bw, ns, &bfilter, &bupdate, opts, &error)) { + if (!mongoc_bulkwrite_append_replaceone(intern->bw, ns, &bfilter, &breplacement, opts, &error)) { phongo_throw_exception_from_bson_error_t(&error); goto cleanup; } @@ -533,7 +532,7 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommand, replaceOne) cleanup: bson_destroy(&bfilter); - bson_destroy(&bupdate); + bson_destroy(&breplacement); mongoc_bulkwrite_replaceoneopts_destroy(opts); } From 0b922bc024426a07c200a9d2d826aeaf1ee7da61 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 7 Mar 2025 14:48:14 -0500 Subject: [PATCH 17/20] 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). --- src/MongoDB/BulkWriteCommandResult.c | 12 +++++++----- ...itecommand-ctor-bypassDocumentValidation-002.phpt | 8 +++++++- .../bulkwritecommand-ctor-ordered-001.phpt | 8 +++++++- .../bulkwritecommand-ctor-ordered-002.phpt | 8 +++++++- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/MongoDB/BulkWriteCommandResult.c b/src/MongoDB/BulkWriteCommandResult.c index b8ffdaf48..2e1a4ddb6 100644 --- a/src/MongoDB/BulkWriteCommandResult.c +++ b/src/MongoDB/BulkWriteCommandResult.c @@ -401,7 +401,7 @@ void php_phongo_bulkwritecommandresult_init_ce(INIT_FUNC_ARGS) static inline bson_t* _bson_copy_or_null(const bson_t* bson) { - return bson_empty0(bson) ? NULL : bson_copy(bson); + return bson ? bson_copy(bson) : NULL; } php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* return_value, mongoc_bulkwritereturn_t* bw_ret, zval* manager) @@ -421,16 +421,18 @@ php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* re bwcr->modified_count = mongoc_bulkwriteresult_modifiedcount(bw_ret->res); bwcr->deleted_count = mongoc_bulkwriteresult_deletedcount(bw_ret->res); + // Result documents will null if verboseResults=false bwcr->insert_results = _bson_copy_or_null(mongoc_bulkwriteresult_insertresults(bw_ret->res)); bwcr->update_results = _bson_copy_or_null(mongoc_bulkwriteresult_updateresults(bw_ret->res)); bwcr->delete_results = _bson_copy_or_null(mongoc_bulkwriteresult_deleteresults(bw_ret->res)); } - // Copy mongoc_bulkwriteexception_t fields + /* If any error(s) occurred, mongoc_bulkwriteexception_t will be non-null. + * Copy its fields into the result object. */ if (bw_ret->exc) { - bwcr->error_reply = _bson_copy_or_null(mongoc_bulkwriteexception_errorreply(bw_ret->exc)); - bwcr->write_errors = _bson_copy_or_null(mongoc_bulkwriteexception_writeerrors(bw_ret->exc)); - bwcr->write_concern_errors = _bson_copy_or_null(mongoc_bulkwriteexception_writeconcernerrors(bw_ret->exc)); + bwcr->error_reply = bson_copy(mongoc_bulkwriteexception_errorreply(bw_ret->exc)); + bwcr->write_errors = bson_copy(mongoc_bulkwriteexception_writeerrors(bw_ret->exc)); + bwcr->write_concern_errors = bson_copy(mongoc_bulkwriteexception_writeconcernerrors(bw_ret->exc)); } ZVAL_ZVAL(&bwcr->manager, manager, 1, 0); diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt index abcad96f5..3bc0596fa 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt @@ -106,6 +106,12 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { array(0) { } ["errorReply"]=> - NULL + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(8) "BQAAAAA=" + ["value"]=> + object(stdClass)#%d (%d) { + } + } } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt index 43dfb8c8a..9ec209d88 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt @@ -67,6 +67,12 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { array(0) { } ["errorReply"]=> - NULL + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(8) "BQAAAAA=" + ["value"]=> + object(stdClass)#%d (%d) { + } + } } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt index 49b084f7c..585c2d2a2 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt @@ -67,6 +67,12 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { array(0) { } ["errorReply"]=> - NULL + object(MongoDB\BSON\Document)#%d (%d) { + ["data"]=> + string(8) "BQAAAAA=" + ["value"]=> + object(stdClass)#%d (%d) { + } + } } ===DONE=== From 3928264e49b367f65546f43127d6b28d1ef95e10 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 11 Mar 2025 17:06:18 -0400 Subject: [PATCH 18/20] 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. --- src/MongoDB/BulkWriteCommandResult.c | 185 ++---------------- src/MongoDB/BulkWriteCommandResult.h | 2 +- src/MongoDB/BulkWriteCommandResult.stub.php | 6 - src/MongoDB/BulkWriteCommandResult_arginfo.h | 15 +- .../Exception/BulkWriteCommandException.c | 160 ++++++++++++++- .../Exception/BulkWriteCommandException.h | 26 +++ .../BulkWriteCommandException.stub.php | 18 +- .../BulkWriteCommandException_arginfo.h | 54 ++++- src/MongoDB/Manager.stub.php | 2 +- src/MongoDB/Manager_arginfo.h | 4 +- src/MongoDB/Server.stub.php | 2 +- src/MongoDB/Server_arginfo.h | 4 +- src/phongo_execute.c | 37 ++-- ...and-ctor-bypassDocumentValidation-001.phpt | 8 - ...and-ctor-bypassDocumentValidation-002.phpt | 75 +++---- .../bulkwritecommand-ctor-let-001.phpt | 8 - .../bulkwritecommand-ctor-ordered-001.phpt | 37 ++-- .../bulkwritecommand-ctor-ordered-002.phpt | 37 ++-- ...kwritecommand-ctor-verboseresults-001.phpt | 8 - ...kwritecommand-ctor-verboseresults-002.phpt | 8 - .../bulkwritecommandresult-debug-001.phpt | 46 +++++ ...writecommandresult-isAcknowledged-001.phpt | 76 +++++++ .../manager-executeBulkWriteCommand-001.phpt | 8 - .../server-executeBulkWriteCommand-001.phpt | 8 - 24 files changed, 461 insertions(+), 373 deletions(-) create mode 100644 src/MongoDB/Exception/BulkWriteCommandException.h create mode 100644 tests/bulkwritecommand/bulkwritecommandresult-debug-001.phpt create mode 100644 tests/bulkwritecommand/bulkwritecommandresult-isAcknowledged-001.phpt diff --git a/src/MongoDB/BulkWriteCommandResult.c b/src/MongoDB/BulkWriteCommandResult.c index 2e1a4ddb6..5b1f7813e 100644 --- a/src/MongoDB/BulkWriteCommandResult.c +++ b/src/MongoDB/BulkWriteCommandResult.c @@ -39,86 +39,6 @@ zend_class_entry* php_phongo_bulkwritecommandresult_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_bulkwritecommandresult_get_writeconcernerrors(php_phongo_bulkwritecommandresult_t* intern, zval* return_value) -{ - bson_iter_t iter; - - array_init(return_value); - - if (intern->write_concern_errors && bson_iter_init(&iter, intern->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_bulkwritecommandresult_get_writeerrors(php_phongo_bulkwritecommandresult_t* intern, zval* return_value) -{ - bson_iter_t iter; - - array_init(return_value); - - if (intern->write_errors && bson_iter_init(&iter, intern->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; -} - PHONGO_DISABLED_CONSTRUCTOR(MongoDB_Driver_BulkWriteCommandResult) /* Returns the number of documents that were inserted */ @@ -236,43 +156,6 @@ static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getDeleteResults) } } -/* Return any write concern errors that occurred */ -static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteConcernErrors) -{ - php_phongo_bulkwritecommandresult_t* intern; - - intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); - - PHONGO_PARSE_PARAMETERS_NONE(); - - phongo_bulkwritecommandresult_get_writeconcernerrors(intern, return_value); -} - -/* Returns any write errors that occurred */ -static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteErrors) -{ - php_phongo_bulkwritecommandresult_t* intern; - - intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); - - PHONGO_PARSE_PARAMETERS_NONE(); - - phongo_bulkwritecommandresult_get_writeerrors(intern, return_value); -} - -static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, getErrorReply) -{ - php_phongo_bulkwritecommandresult_t* intern; - - intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(getThis()); - - PHONGO_PARSE_PARAMETERS_NONE(); - - if (intern->error_reply) { - phongo_document_new(return_value, intern->error_reply, true); - } -} - /* Returns whether the write operation was acknowledged (based on the write concern). */ static PHP_METHOD(MongoDB_Driver_BulkWriteCommandResult, isAcknowledged) @@ -298,13 +181,6 @@ static void php_phongo_bulkwritecommandresult_free_object(zend_object* object) bson_destroy(intern->insert_results); bson_destroy(intern->update_results); bson_destroy(intern->delete_results); - bson_destroy(intern->error_reply); - bson_destroy(intern->write_errors); - bson_destroy(intern->write_concern_errors); - - if (!Z_ISUNDEF(intern->manager)) { - zval_ptr_dtor(&intern->manager); - } } static zend_object* php_phongo_bulkwritecommandresult_create_object(zend_class_entry* class_type) @@ -362,29 +238,6 @@ static HashTable* php_phongo_bulkwritecommandresult_get_debug_info(zend_object* ADD_ASSOC_NULL_EX(&retval, "deleteResults"); } - { - zval writeerrors; - - phongo_bulkwritecommandresult_get_writeerrors(intern, &writeerrors); - ADD_ASSOC_ZVAL_EX(&retval, "writeErrors", &writeerrors); - } - - { - zval writeconcernerrors; - - phongo_bulkwritecommandresult_get_writeconcernerrors(intern, &writeconcernerrors); - ADD_ASSOC_ZVAL_EX(&retval, "writeConcernErrors", &writeconcernerrors); - } - - if (intern->error_reply) { - zval error_reply; - - phongo_document_new(&error_reply, intern->error_reply, true); - ADD_ASSOC_ZVAL_EX(&retval, "errorReply", &error_reply); - } else { - ADD_ASSOC_NULL_EX(&retval, "errorReply"); - } - return Z_ARRVAL(retval); } @@ -404,38 +257,28 @@ 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_bulkwritereturn_t* bw_ret, zval* manager) +php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* return_value, mongoc_bulkwriteresult_t* bw_res) { - php_phongo_bulkwritecommandresult_t* bwcr; + php_phongo_bulkwritecommandresult_t* intern; object_init_ex(return_value, php_phongo_bulkwritecommandresult_ce); - bwcr = Z_BULKWRITECOMMANDRESULT_OBJ_P(return_value); - bwcr->is_acknowledged = !!bw_ret->res; + intern = Z_BULKWRITECOMMANDRESULT_OBJ_P(return_value); + intern->is_acknowledged = (bw_res != NULL); // Copy mongoc_bulkwriteresult_t fields - if (bw_ret->res) { - bwcr->inserted_count = mongoc_bulkwriteresult_insertedcount(bw_ret->res); - bwcr->upserted_count = mongoc_bulkwriteresult_upsertedcount(bw_ret->res); - bwcr->matched_count = mongoc_bulkwriteresult_matchedcount(bw_ret->res); - bwcr->modified_count = mongoc_bulkwriteresult_modifiedcount(bw_ret->res); - bwcr->deleted_count = mongoc_bulkwriteresult_deletedcount(bw_ret->res); + 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 - bwcr->insert_results = _bson_copy_or_null(mongoc_bulkwriteresult_insertresults(bw_ret->res)); - bwcr->update_results = _bson_copy_or_null(mongoc_bulkwriteresult_updateresults(bw_ret->res)); - bwcr->delete_results = _bson_copy_or_null(mongoc_bulkwriteresult_deleteresults(bw_ret->res)); - } - - /* If any error(s) occurred, mongoc_bulkwriteexception_t will be non-null. - * Copy its fields into the result object. */ - if (bw_ret->exc) { - bwcr->error_reply = bson_copy(mongoc_bulkwriteexception_errorreply(bw_ret->exc)); - bwcr->write_errors = bson_copy(mongoc_bulkwriteexception_writeerrors(bw_ret->exc)); - bwcr->write_concern_errors = bson_copy(mongoc_bulkwriteexception_writeconcernerrors(bw_ret->exc)); + 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)); } - ZVAL_ZVAL(&bwcr->manager, manager, 1, 0); - - return bwcr; + return intern; } diff --git a/src/MongoDB/BulkWriteCommandResult.h b/src/MongoDB/BulkWriteCommandResult.h index 567e2a1fc..a69adb7c1 100644 --- a/src/MongoDB/BulkWriteCommandResult.h +++ b/src/MongoDB/BulkWriteCommandResult.h @@ -23,6 +23,6 @@ #include "phongo_structs.h" -php_phongo_bulkwritecommandresult_t* phongo_bulkwritecommandresult_init(zval* return_value, mongoc_bulkwritereturn_t* bw_ret, zval* manager); +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 index bcca4816f..a6f60e7f2 100644 --- a/src/MongoDB/BulkWriteCommandResult.stub.php +++ b/src/MongoDB/BulkWriteCommandResult.stub.php @@ -28,11 +28,5 @@ final public function getUpdateResults(): ?\MongoDB\BSON\Document {} final public function getDeleteResults(): ?\MongoDB\BSON\Document {} - final public function getWriteErrors(): array {} - - final public function getWriteConcernErrors(): array {} - - final public function getErrorReply(): ?\MongoDB\BSON\Document {} - final public function isAcknowledged(): bool {} } diff --git a/src/MongoDB/BulkWriteCommandResult_arginfo.h b/src/MongoDB/BulkWriteCommandResult_arginfo.h index 0e80889d5..c375c82f0 100644 --- a/src/MongoDB/BulkWriteCommandResult_arginfo.h +++ b/src/MongoDB/BulkWriteCommandResult_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a4cbc5665d7b1d99f5f4c6425d6d2eb3829bb001 */ + * Stub hash: 042b10edec437daefeda4f0fc883dd25e240439f */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -22,13 +22,6 @@ ZEND_END_ARG_INFO() #define arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getDeleteResults arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertResults -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteErrors, 0, 0, IS_ARRAY, 0) -ZEND_END_ARG_INFO() - -#define arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteConcernErrors arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteErrors - -#define arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getErrorReply arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertResults - ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_BulkWriteCommandResult_isAcknowledged, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -42,9 +35,6 @@ static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getDeletedCount); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getInsertResults); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getUpdateResults); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getDeleteResults); -static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteErrors); -static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getWriteConcernErrors); -static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, getErrorReply); static ZEND_METHOD(MongoDB_Driver_BulkWriteCommandResult, isAcknowledged); @@ -58,9 +48,6 @@ static const zend_function_entry class_MongoDB_Driver_BulkWriteCommandResult_met ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getInsertResults, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getInsertResults, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getUpdateResults, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getUpdateResults, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getDeleteResults, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getDeleteResults, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) - ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getWriteErrors, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteErrors, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) - ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getWriteConcernErrors, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getWriteConcernErrors, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) - ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, getErrorReply, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_getErrorReply, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(MongoDB_Driver_BulkWriteCommandResult, isAcknowledged, arginfo_class_MongoDB_Driver_BulkWriteCommandResult_isAcknowledged, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_FE_END }; diff --git a/src/MongoDB/Exception/BulkWriteCommandException.c b/src/MongoDB/Exception/BulkWriteCommandException.c index 739e458f8..986b70263 100644 --- a/src/MongoDB/Exception/BulkWriteCommandException.c +++ b/src/MongoDB/Exception/BulkWriteCommandException.c @@ -18,24 +18,174 @@ #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 BulkWriteCommandResult from the failed write operation. */ -static PHP_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getBulkWriteCommandResult) +/* 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) { - zval* bwcr; + 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(); - bwcr = zend_read_property(php_phongo_bulkwritecommandexception_ce, Z_OBJ_P(getThis()), ZEND_STRL("bulkWriteCommandResult"), 0, &rv); + zval rv; + zval* result = zend_read_property(php_phongo_bulkwritecommandexception_ce, Z_OBJ_P(getThis()), ZEND_STRL("writeErrors"), 0, &rv); - RETURN_ZVAL(bwcr, 1, 0); + 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 index 9d0053462..8c8b46e0d 100644 --- a/src/MongoDB/Exception/BulkWriteCommandException.stub.php +++ b/src/MongoDB/Exception/BulkWriteCommandException.stub.php @@ -7,9 +7,21 @@ namespace MongoDB\Driver\Exception; -class BulkWriteCommandException extends ServerException +final class BulkWriteCommandException extends ServerException { - protected \MongoDB\Driver\BulkWriteCommandResult $bulkWriteCommandResult; + private ?\MongoDB\BSON\Document $errorReply = null; - final public function getBulkWriteCommandResult(): \MongoDB\Driver\BulkWriteCommandResult {} + private ?\MongoDB\Driver\BulkWriteCommandResult $partialResult = null; + + private array $writeErrors = []; + + private array $writeConcernErrors = []; + + final public function getErrorReply(): ?\MongoDB\BSON\Document {} + + final public function getPartialResult(): ?\MongoDB\Driver\BulkWriteCommandResult {} + + final public function getWriteErrors(): array {} + + final public function getWriteConcernErrors(): array {} } diff --git a/src/MongoDB/Exception/BulkWriteCommandException_arginfo.h b/src/MongoDB/Exception/BulkWriteCommandException_arginfo.h index 24ccf0941..e5e30b12a 100644 --- a/src/MongoDB/Exception/BulkWriteCommandException_arginfo.h +++ b/src/MongoDB/Exception/BulkWriteCommandException_arginfo.h @@ -1,15 +1,29 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e56ba0ccf0a1c5ef736ef9778f05368b27f6febf */ + * Stub hash: 16a2478ef423f897f914ad7b2ed3dbbdc4036e60 */ -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getBulkWriteCommandResult, 0, 0, MongoDB\\Driver\\BulkWriteCommandResult, 0) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getErrorReply, 0, 0, MongoDB\\BSON\\Document, 1) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getPartialResult, 0, 0, MongoDB\\Driver\\BulkWriteCommandResult, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getWriteErrors, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getWriteConcernErrors arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getWriteErrors -static ZEND_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getBulkWriteCommandResult); + +static ZEND_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getErrorReply); +static ZEND_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getPartialResult); +static ZEND_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getWriteErrors); +static ZEND_METHOD(MongoDB_Driver_Exception_BulkWriteCommandException, getWriteConcernErrors); static const zend_function_entry class_MongoDB_Driver_Exception_BulkWriteCommandException_methods[] = { - ZEND_ME(MongoDB_Driver_Exception_BulkWriteCommandException, getBulkWriteCommandResult, arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getBulkWriteCommandResult, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_Exception_BulkWriteCommandException, getErrorReply, arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getErrorReply, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_Exception_BulkWriteCommandException, getPartialResult, arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getPartialResult, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_Exception_BulkWriteCommandException, getWriteErrors, arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getWriteErrors, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + ZEND_ME(MongoDB_Driver_Exception_BulkWriteCommandException, getWriteConcernErrors, arginfo_class_MongoDB_Driver_Exception_BulkWriteCommandException_getWriteConcernErrors, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_FE_END }; @@ -19,13 +33,33 @@ static zend_class_entry *register_class_MongoDB_Driver_Exception_BulkWriteComman INIT_NS_CLASS_ENTRY(ce, "MongoDB\\Driver\\Exception", "BulkWriteCommandException", class_MongoDB_Driver_Exception_BulkWriteCommandException_methods); class_entry = zend_register_internal_class_ex(&ce, class_entry_MongoDB_Driver_Exception_ServerException); + class_entry->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); - zend_string *property_bulkWriteCommandResult_class_MongoDB_Driver_BulkWriteCommandResult = zend_string_init("MongoDB\\Driver\\BulkWriteCommandResult", sizeof("MongoDB\\Driver\\BulkWriteCommandResult")-1, 1); - zval property_bulkWriteCommandResult_default_value; - ZVAL_UNDEF(&property_bulkWriteCommandResult_default_value); - zend_string *property_bulkWriteCommandResult_name = zend_string_init("bulkWriteCommandResult", sizeof("bulkWriteCommandResult") - 1, 1); - zend_declare_typed_property(class_entry, property_bulkWriteCommandResult_name, &property_bulkWriteCommandResult_default_value, ZEND_ACC_PROTECTED, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_bulkWriteCommandResult_class_MongoDB_Driver_BulkWriteCommandResult, 0, 0)); - zend_string_release(property_bulkWriteCommandResult_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.stub.php b/src/MongoDB/Manager.stub.php index e0bef5d7f..300bbdb16 100644 --- a/src/MongoDB/Manager.stub.php +++ b/src/MongoDB/Manager.stub.php @@ -18,7 +18,7 @@ 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 executeBulkWriteCommand(BulkWriteCommand $bulkWriteCommand, ?array $options = null): BulkWriteCommandResult {} final public function executeCommand(string $db, Command $command, array|null $options = null): CursorInterface {} diff --git a/src/MongoDB/Manager_arginfo.h b/src/MongoDB/Manager_arginfo.h index 57ac1d94e..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: ae0c35849b6ad73edf96a3ecdbe42b6940af0e6a */ + * 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,7 +21,7 @@ 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, 1) +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() diff --git a/src/MongoDB/Server.stub.php b/src/MongoDB/Server.stub.php index 3c2dbbf64..3aee834e2 100644 --- a/src/MongoDB/Server.stub.php +++ b/src/MongoDB/Server.stub.php @@ -74,7 +74,7 @@ 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 executeBulkWriteCommand(BulkWriteCommand $bulkWriteCommand, ?array $options = null): BulkWriteCommandResult {} final public function executeCommand(string $db, Command $command, array|null $options = null): CursorInterface {} diff --git a/src/MongoDB/Server_arginfo.h b/src/MongoDB/Server_arginfo.h index 70104ef69..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: 36c3e152527ee774d3dfbae26d24fea7bfa3c6f3 */ + * Stub hash: ed6e371d10ddbcfd3ffed6aeb48758d0e88d717f */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_MongoDB_Driver_Server___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -10,7 +10,7 @@ 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, 1) +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() diff --git a/src/phongo_execute.c b/src/phongo_execute.c index 5b53c2851..b122d149c 100644 --- a/src/phongo_execute.c +++ b/src/phongo_execute.c @@ -30,6 +30,7 @@ #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" @@ -337,15 +338,14 @@ bool phongo_execute_bulk_write(zval* manager, const char* namespace, php_phongo_ 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 }; - php_phongo_bulkwritecommandresult_t* bwcr; - zval* zsession = NULL; - zval* zwriteConcern = NULL; - const mongoc_write_concern_t* write_concern = NULL; - bool success = true; + 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; @@ -387,27 +387,25 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ bw_ret = mongoc_bulkwrite_execute(bw, bw_opts); - bwcr = phongo_bulkwritecommandresult_init(return_value, &bw_ret, manager); + 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 fails due to write or write concern + * 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 BulkWriteCommandResult. + * otherwise accessible to the user through BulkWriteCommandException. * * - InvalidArgumentException may be thrown directly for a basic top-level - * error (assuming BulkWriteCommandResult would also be irrelevant). - * Otherwise, BulkWriteCommandException is thrown with an attached - * BulkWriteCommandResult that collects any error reply, write errors, and - * write concern errors, along with a possible partial write result. + * error if there is no partial write result or error reply. Otherwise, + * BulkWriteCommandException is thrown. */ if (bw_ret.exc) { success = false; @@ -438,12 +436,15 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ zend_throw_exception(php_phongo_bulkwritecommandexception_ce, "Bulk write failed", 0); } + /* 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); - - phongo_add_exception_prop(ZEND_STRL("bulkWriteCommandResult"), return_value); } cleanup: diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt index e1280e162..171781e28 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-001.phpt @@ -52,13 +52,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { NULL ["deleteResults"]=> NULL - ["writeErrors"]=> - array(0) { - } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - 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 index 3bc0596fa..2f620769f 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-bypassDocumentValidation-002.phpt @@ -32,7 +32,8 @@ 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->getBulkWriteCommandResult()); + var_dump($e->getPartialResult()); + var_dump($e->getWriteErrors()); } ?> @@ -59,59 +60,47 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { NULL ["deleteResults"]=> NULL - ["writeErrors"]=> - array(1) { - [1]=> - object(MongoDB\Driver\WriteError)#%d (%d) { - ["message"]=> - string(26) "Document failed validation" - ["code"]=> - int(121) - ["index"]=> - int(1) - ["info"]=> +} +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) { - ["failingDocumentId"]=> - int(2) - ["details"]=> - object(stdClass)#%d (%d) { - ["operatorName"]=> - string(11) "$jsonSchema" - ["schemaRulesNotSatisfied"]=> - array(1) { - [0]=> + ["operatorName"]=> + string(11) "$jsonSchema" + ["schemaRulesNotSatisfied"]=> + array(1) { + [0]=> + object(stdClass)#%d (%d) { + ["operatorName"]=> + string(8) "required" + ["specifiedAs"]=> object(stdClass)#%d (%d) { - ["operatorName"]=> - string(8) "required" - ["specifiedAs"]=> - object(stdClass)#%d (%d) { - ["required"]=> - array(1) { - [0]=> - string(1) "x" - } - } - ["missingProperties"]=> + ["required"]=> array(1) { [0]=> string(1) "x" } } + ["missingProperties"]=> + array(1) { + [0]=> + string(1) "x" + } } } } } } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - object(MongoDB\BSON\Document)#%d (%d) { - ["data"]=> - string(8) "BQAAAAA=" - ["value"]=> - object(stdClass)#%d (%d) { - } - } } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt index 1c3565097..cfe77818b 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-let-001.phpt @@ -56,13 +56,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { NULL ["deleteResults"]=> NULL - ["writeErrors"]=> - array(0) { - } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - NULL } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt index 9ec209d88..30aa7a879 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-001.phpt @@ -21,7 +21,8 @@ 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->getBulkWriteCommandResult()); + var_dump($e->getPartialResult()); + var_dump($e->getWriteErrors()); } ?> @@ -48,29 +49,17 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { NULL ["deleteResults"]=> NULL - ["writeErrors"]=> - 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) { - } - } - } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - object(MongoDB\BSON\Document)#%d (%d) { - ["data"]=> - string(8) "BQAAAAA=" - ["value"]=> +} +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) { } } diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt index 585c2d2a2..24ad876d4 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-ordered-002.phpt @@ -21,7 +21,8 @@ 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->getBulkWriteCommandResult()); + var_dump($e->getPartialResult()); + var_dump($e->getWriteErrors()); } ?> @@ -48,29 +49,17 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { NULL ["deleteResults"]=> NULL - ["writeErrors"]=> - 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) { - } - } - } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - object(MongoDB\BSON\Document)#%d (%d) { - ["data"]=> - string(8) "BQAAAAA=" - ["value"]=> +} +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) { } } diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt index df6f45649..e21572125 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-001.phpt @@ -94,13 +94,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } } } - ["writeErrors"]=> - array(0) { - } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - NULL } ===DONE=== diff --git a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt index 2b48ae4cc..33644ef75 100644 --- a/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt +++ b/tests/bulkwritecommand/bulkwritecommand-ctor-verboseresults-002.phpt @@ -44,13 +44,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { NULL ["deleteResults"]=> NULL - ["writeErrors"]=> - array(0) { - } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - NULL } ===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 index 3923ee6ec..626f60247 100644 --- a/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt +++ b/tests/bulkwritecommand/manager-executeBulkWriteCommand-001.phpt @@ -175,13 +175,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } } } - ["writeErrors"]=> - array(0) { - } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - NULL } ===DONE=== diff --git a/tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt b/tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt index e2c6ed68c..f5b1c7244 100644 --- a/tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt +++ b/tests/bulkwritecommand/server-executeBulkWriteCommand-001.phpt @@ -176,13 +176,5 @@ object(MongoDB\Driver\BulkWriteCommandResult)#%d (%d) { } } } - ["writeErrors"]=> - array(0) { - } - ["writeConcernErrors"]=> - array(0) { - } - ["errorReply"]=> - NULL } ===DONE=== From dc7135f7efb2fc61a0cfbc176ddce2b642c21b5d Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 14 Mar 2025 14:04:10 -0400 Subject: [PATCH 19/20] 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. --- src/phongo_error.c | 2 +- src/phongo_execute.c | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) 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 b122d149c..dfc44925c 100644 --- a/src/phongo_execute.c +++ b/src/phongo_execute.c @@ -408,12 +408,20 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ * BulkWriteCommandException is thrown. */ if (bw_ret.exc) { - success = false; - bson_error_t error = { 0 }; - const bson_t* error_reply = mongoc_bulkwriteexception_errorreply(bw_ret.exc); - - // Consult any top-level error to throw the first exception - if (mongoc_bulkwriteexception_error(bw_ret.exc, &error)) { + 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); } @@ -433,7 +441,7 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ zend_throw_exception(php_phongo_bulkwritecommandexception_ce, message, 0); efree(message); } else { - zend_throw_exception(php_phongo_bulkwritecommandexception_ce, "Bulk write failed", 0); + zend_throw_exception(php_phongo_bulkwritecommandexception_ce, has_top_level_error ? error.message : "Bulk write failed", error.code); } /* Initialize BulkWriteCommandException properties. Although a From 6dd4fff50a9235167f0c0f3683fd42d694437958 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 2 Apr 2025 14:45:11 -0400 Subject: [PATCH 20/20] 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. --- src/phongo_execute.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phongo_execute.c b/src/phongo_execute.c index dfc44925c..03aeca794 100644 --- a/src/phongo_execute.c +++ b/src/phongo_execute.c @@ -430,7 +430,7 @@ bool phongo_execute_bulkwritecommand(zval* manager, php_phongo_bulkwritecommand_ * (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 && !error_reply) { + if (EG(exception) && EG(exception)->ce == php_phongo_invalidargumentexception_ce && !bw_ret.res && bson_empty(error_reply)) { goto cleanup; }