Skip to content

Commit 9b67aa5

Browse files
committed
Merge pull request #419
2 parents 63f7d31 + 887f067 commit 9b67aa5

File tree

2 files changed

+198
-41
lines changed

2 files changed

+198
-41
lines changed

src/MongoDB/BulkWrite.c

Lines changed: 165 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,112 @@ PHONGO_API zend_class_entry *php_phongo_bulkwrite_ce;
4949

5050
zend_object_handlers php_phongo_handler_bulkwrite;
5151

52+
/* Returns whether any top-level field names in the document contain a "$". */
53+
static inline bool php_phongo_bulkwrite_update_has_operators(bson_t *bupdate) /* {{{ */
54+
{
55+
bson_iter_t iter;
56+
57+
if (bson_iter_init(&iter, bupdate)) {
58+
while (bson_iter_next (&iter)) {
59+
if (strchr(bson_iter_key(&iter), '$')) {
60+
return true;
61+
}
62+
}
63+
}
64+
65+
return false;
66+
} /* }}} */
67+
68+
/* Appends a document field for the given opts document and key. Returns true on
69+
* success; otherwise, false is returned and an exception is thrown. */
70+
static bool php_phongo_bulkwrite_opts_append_document(bson_t *opts, const char *opts_key, zval *zarr, const char *zarr_key TSRMLS_DC)
71+
{
72+
zval *value = php_array_fetch(zarr, zarr_key);
73+
bson_t b = BSON_INITIALIZER;
74+
75+
if (Z_TYPE_P(value) != IS_OBJECT && Z_TYPE_P(value) != IS_ARRAY) {
76+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected \"%s\" option to be array or object, %s given", zarr_key, zend_get_type_by_const(Z_TYPE_P(value)));
77+
return false;
78+
}
79+
80+
phongo_zval_to_bson(value, PHONGO_BSON_NONE, &b, NULL TSRMLS_CC);
81+
82+
if (EG(exception)) {
83+
bson_destroy(&b);
84+
return false;
85+
}
86+
87+
if (!BSON_APPEND_DOCUMENT(opts, opts_key, &b)) {
88+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending \"%s\" option", opts_key);
89+
bson_destroy(&b);
90+
return false;
91+
}
92+
93+
bson_destroy(&b);
94+
return true;
95+
}
96+
97+
#define PHONGO_BULKWRITE_APPEND_BOOL(opt, value) \
98+
if (!BSON_APPEND_BOOL(boptions, (opt), (value))) { \
99+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending \"%s\" option", (opt)); \
100+
return false; \
101+
}
102+
103+
#define PHONGO_BULKWRITE_APPEND_INT32(opt, value) \
104+
if (!BSON_APPEND_INT32(boptions, (opt), (value))) { \
105+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error appending \"%s\" option", (opt)); \
106+
return false; \
107+
}
108+
109+
#define PHONGO_BULKWRITE_OPT_DOCUMENT(opt) \
110+
if (zoptions && php_array_existsc(zoptions, (opt))) { \
111+
if (!php_phongo_bulkwrite_opts_append_document(boptions, (opt), zoptions, (opt) TSRMLS_CC)) { \
112+
return false; \
113+
} \
114+
}
115+
116+
/* Applies options (including defaults) for an update operation. */
117+
static bool php_phongo_bulkwrite_update_apply_options(bson_t *boptions, zval *zoptions TSRMLS_DC)/* {{{ */
118+
{
119+
bool multi = false, upsert = false;
120+
121+
if (zoptions) {
122+
if (php_array_existsc(zoptions, "multi")) {
123+
multi = php_array_fetchc_bool(zoptions, "multi");
124+
}
125+
if (php_array_existsc(zoptions, "upsert")) {
126+
upsert = php_array_fetchc_bool(zoptions, "upsert");
127+
}
128+
}
129+
130+
PHONGO_BULKWRITE_APPEND_BOOL("multi", multi);
131+
PHONGO_BULKWRITE_APPEND_BOOL("upsert", upsert);
132+
PHONGO_BULKWRITE_OPT_DOCUMENT("collation");
133+
134+
return true;
135+
} /* }}} */
136+
137+
/* Applies options (including defaults) for an delete operation. */
138+
static bool php_phongo_bulkwrite_delete_apply_options(bson_t *boptions, zval *zoptions TSRMLS_DC)/* {{{ */
139+
{
140+
int32_t limit = 0;
141+
142+
if (zoptions) {
143+
if (php_array_existsc(zoptions, "limit")) {
144+
limit = php_array_fetchc_bool(zoptions, "limit") ? 1 : 0;
145+
}
146+
}
147+
148+
PHONGO_BULKWRITE_APPEND_INT32("limit", limit);
149+
PHONGO_BULKWRITE_OPT_DOCUMENT("collation");
150+
151+
return true;
152+
} /* }}} */
153+
154+
#undef PHONGO_BULKWRITE_APPEND_BOOL
155+
#undef PHONGO_BULKWRITE_APPEND_INT32
156+
#undef PHONGO_BULKWRITE_OPT_DOCUMENT
157+
52158
/* {{{ proto void BulkWrite::__construct([array $options = array()])
53159
Constructs a new BulkWrite */
54160
PHP_METHOD(BulkWrite, __construct)
@@ -135,92 +241,110 @@ PHP_METHOD(BulkWrite, insert)
135241
Adds an update operation to the BulkWrite */
136242
PHP_METHOD(BulkWrite, update)
137243
{
138-
php_phongo_bulkwrite_t *intern;
139-
zval *query;
140-
zval *newObj;
141-
zval *updateOptions = NULL;
142-
mongoc_update_flags_t flags = MONGOC_UPDATE_NONE;
143-
bson_t *bquery;
144-
bson_t *bupdate;
244+
php_phongo_bulkwrite_t *intern;
245+
zval *zquery, *zupdate, *zoptions = NULL;
246+
bson_t *bquery, *bupdate, *boptions = NULL;
247+
bson_error_t error = {0};
145248
SUPPRESS_UNUSED_WARNING(return_value_ptr) SUPPRESS_UNUSED_WARNING(return_value) SUPPRESS_UNUSED_WARNING(return_value_used)
146249

147250

148251
intern = Z_BULKWRITE_OBJ_P(getThis());
149252

150-
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "AA|a!", &query, &newObj, &updateOptions) == FAILURE) {
253+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "AA|a!", &zquery, &zupdate, &zoptions) == FAILURE) {
151254
return;
152255
}
153256

154-
155257
bquery = bson_new();
156258
bupdate = bson_new();
259+
boptions = bson_new();
260+
261+
phongo_zval_to_bson(zquery, PHONGO_BSON_NONE, bquery, NULL TSRMLS_CC);
157262

158-
phongo_zval_to_bson(query, PHONGO_BSON_NONE, bquery, NULL TSRMLS_CC);
159-
phongo_zval_to_bson(newObj, PHONGO_BSON_NONE, bupdate, NULL TSRMLS_CC);
263+
if (EG(exception)) {
264+
goto cleanup;
265+
}
266+
267+
phongo_zval_to_bson(zupdate, PHONGO_BSON_NONE, bupdate, NULL TSRMLS_CC);
268+
269+
if (EG(exception)) {
270+
goto cleanup;
271+
}
160272

161-
if (updateOptions) {
162-
flags |= php_array_fetchc_bool(updateOptions, "multi") ? MONGOC_UPDATE_MULTI_UPDATE : 0;
163-
flags |= php_array_fetchc_bool(updateOptions, "upsert") ? MONGOC_UPDATE_UPSERT : 0;
273+
if (!php_phongo_bulkwrite_update_apply_options(boptions, zoptions TSRMLS_CC)) {
274+
goto cleanup;
164275
}
165276

166-
if (flags & MONGOC_UPDATE_MULTI_UPDATE) {
167-
mongoc_bulk_operation_update(intern->bulk, bquery, bupdate, !!(flags & MONGOC_UPDATE_UPSERT));
277+
if (php_phongo_bulkwrite_update_has_operators(bupdate)) {
278+
if (!mongoc_bulk_operation_update_with_opts(intern->bulk, bquery, bupdate, boptions, &error)) {
279+
phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC);
280+
goto cleanup;
281+
}
168282
} else {
169-
bson_iter_t iter;
170-
zend_bool replaced = 0;
171-
172-
if (bson_iter_init(&iter, bupdate)) {
173-
while (bson_iter_next (&iter)) {
174-
if (!strchr (bson_iter_key (&iter), '$')) {
175-
mongoc_bulk_operation_replace_one(intern->bulk, bquery, bupdate, !!(flags & MONGOC_UPDATE_UPSERT));
176-
replaced = 1;
177-
break;
178-
}
179-
}
283+
if (!bson_validate(bupdate, BSON_VALIDATE_DOT_KEYS|BSON_VALIDATE_DOLLAR_KEYS, NULL)) {
284+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Replacement document may not contain \"$\" or \".\" in keys");
285+
goto cleanup;
286+
}
287+
288+
if (zoptions && php_array_existsc(zoptions, "multi") && php_array_fetchc_bool(zoptions, "multi")) {
289+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Replacement document conflicts with true \"multi\" option");
290+
goto cleanup;
180291
}
181292

182-
if (!replaced) {
183-
mongoc_bulk_operation_update_one(intern->bulk, bquery, bupdate, !!(flags & MONGOC_UPDATE_UPSERT));
293+
if (!mongoc_bulk_operation_replace_one_with_opts(intern->bulk, bquery, bupdate, boptions, &error)) {
294+
phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC);
295+
goto cleanup;
184296
}
185297
}
186298

187299
intern->num_ops++;
188300

301+
cleanup:
189302
bson_clear(&bquery);
190303
bson_clear(&bupdate);
304+
bson_clear(&boptions);
191305
}
192306
/* }}} */
193307

194308
/* {{{ proto void BulkWrite::delete(array|object $query[, array $deleteOptions = array()])
195309
Adds a delete operation to the BulkWrite */
196310
PHP_METHOD(BulkWrite, delete)
197311
{
198-
php_phongo_bulkwrite_t *intern;
199-
zval *query;
200-
zval *deleteOptions = NULL;
201-
bson_t *bson;
312+
php_phongo_bulkwrite_t *intern;
313+
zval *zquery, *zoptions = NULL;
314+
bson_t *bquery, *boptions = NULL;
315+
bson_error_t error = {0};
202316
SUPPRESS_UNUSED_WARNING(return_value_ptr) SUPPRESS_UNUSED_WARNING(return_value) SUPPRESS_UNUSED_WARNING(return_value_used)
203317

204318

205319
intern = Z_BULKWRITE_OBJ_P(getThis());
206320

207-
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "A|a!", &query, &deleteOptions) == FAILURE) {
321+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "A|a!", &zquery, &zoptions) == FAILURE) {
208322
return;
209323
}
210324

325+
bquery = bson_new();
326+
boptions = bson_new();
327+
328+
phongo_zval_to_bson(zquery, PHONGO_BSON_NONE, bquery, NULL TSRMLS_CC);
211329

212-
bson = bson_new();
213-
phongo_zval_to_bson(query, PHONGO_BSON_NONE, bson, NULL TSRMLS_CC);
330+
if (EG(exception)) {
331+
goto cleanup;
332+
}
214333

215-
if (deleteOptions && php_array_fetchc_bool(deleteOptions, "limit")) {
216-
mongoc_bulk_operation_remove_one(intern->bulk, bson);
217-
} else {
218-
mongoc_bulk_operation_remove(intern->bulk, bson);
334+
if (!php_phongo_bulkwrite_delete_apply_options(boptions, zoptions TSRMLS_CC)) {
335+
goto cleanup;
336+
}
337+
338+
if (!mongoc_bulk_operation_remove_with_opts(intern->bulk, bquery, boptions, &error)) {
339+
phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC);
340+
goto cleanup;
219341
}
220342

221343
intern->num_ops++;
222344

223-
bson_clear(&bson);
345+
cleanup:
346+
bson_clear(&bquery);
347+
bson_clear(&boptions);
224348
}
225349
/* }}} */
226350

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
MongoDB\Driver\BulkWrite::update() with invalid options
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/../utils/tools.php';
7+
8+
$bulk = new MongoDB\Driver\BulkWrite;
9+
10+
echo throws(function() use ($bulk) {
11+
$bulk->update(['x' => 1], ['x.y' => 1]);
12+
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n\n";
13+
14+
echo throws(function() use ($bulk) {
15+
$bulk->update(['x' => 1], ['y' => 1], ['multi' => true]);
16+
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n\n";
17+
18+
echo throws(function() use ($bulk) {
19+
$bulk->update([], [], ['collation' => 1]);
20+
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";
21+
?>
22+
===DONE===
23+
<?php exit(0); ?>
24+
--EXPECT--
25+
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
26+
Replacement document may not contain "$" or "." in keys
27+
28+
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
29+
Replacement document conflicts with true "multi" option
30+
31+
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
32+
Expected "collation" option to be array or object, integer given
33+
===DONE===

0 commit comments

Comments
 (0)