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